Oracle3
Open-source trading engine and MCP server for prediction markets. Fee-aware no-arbitrage trading across related contracts, live on Kalshi, Polymarket and Solana or on paper.
Strategy backtesting dashboard for risk and return analysis
FinancePython · TypeScriptGitHub @TJA0308OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Full-stack backtesting workbench: Python engine, FastAPI, Next.js dashboard
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A full-stack backtesting workbench that tests simple, explainable trading rules against buy-and-hold, and shows every assumption behind the result.
The dashboard runs an example AAPL backtest as soon as it opens. The API runs on Render's free tier, which sleeps when idle. If it has been asleep, the first request can take up to a minute to wake it, and the dashboard tells you while it waits.Read the full README ↗A full-stack backtesting workbench that tests simple, explainable trading rules against buy-and-hold, and shows every assumption behind the result.
[!NOTE] The dashboard runs an example AAPL backtest as soon as it opens. The API runs on Render's free tier, which sleeps when idle. If it has been asleep, the first request can take up to a minute to wake it, and the dashboard tells you while it waits.
Contents: At a glance · Architecture · Engineering decisions · Strategies · API · Run it locally · Testing and CI · Project structure · Limitations
| What it does | Loads market data, runs one of three rule-based strategies, simulates a long-only portfolio with fees and slippage, and compares it with buy-and-hold |
| Stack | Python engine (pandas, NumPy) → FastAPI + SQLite → Next.js / TypeScript dashboard, deployed on Render and Vercel |
| Correctness | Signals trade one bar after they are observed (no look-ahead), and too-short windows are refused rather than reported as a 0% return |
| Tests | Python regression tests with a 90% coverage gate, frontend unit tests, desktop + phone browser tests, and a 72-scenario deterministic benchmark, all run in CI |
| Performance | Execution loop over NumPy arrays: 50,000 bars in ~0.17 s, down from ~8.8 s with iterrows() |
| Ops | Docker image checked in CI, typed OpenAPI contract, Dependabot updates, one-command make check |
A notebook can compute a backtest return in a few lines, and it can hide exactly the details that decide whether the number means anything: when a signal becomes tradable, how costs are charged, what happens to cash and shares, and whether the comparison with buy-and-hold is fair.
AlphaNexus makes that pipeline inspectable. Data loading, indicators, signals, portfolio simulation, metrics, the API, and the dashboard live in separate layers that are small enough to explain and test one at a time. It is a research and education tool, not a trading system, and it makes no claim that these strategies beat the market. With the default settings, the example on the dashboard shows a strategy losing to buy-and-hold, and the page says so.
flowchart LR
U([Browser]) --> W["Next.js dashboard<br/>(Vercel)"]
W -- "POST /backtests" --> A["FastAPI<br/>(Render, native Python)"]
A --> E["alphanexus engine<br/>pandas + NumPy"]
E --> Y[("yfinance<br/>market data")]
A --> S[("SQLite<br/>run history")]
E -.-> C["15-minute<br/>in-memory cache"]
sequenceDiagram
participant D as Dashboard
participant A as FastAPI
participant M as Market data
participant E as Engine
participant S as SQLite
D->>D: Validate the form (dates, windows, thresholds)
D->>A: POST /backtests
A->>A: Pydantic validation (422 on bad input)
A->>M: fetch_prices (cached for 15 min)
M-->>A: OHLCV (502 if the provider fails)
A->>E: generate_signals → run_backtest
E->>E: Indicators → target position → lag one bar → simulate cash and shares
E-->>A: Equity curve, trades, metrics (400 if the window is too short)
A->>S: save_run (skipped for ?save=false)
A-->>D: Typed JSON response
D->>D: Render metrics, charts, ledger, history
flowchart LR
A[OHLCV data] --> B[Normalize columns]
B --> C[Calculate indicators]
C --> D[Target position 0 or 1]
D --> E[Lag signal one bar]
E --> F[Simulate cash and shares]
F --> G[Calculate metrics]
The engine is a plain Python package with no dependency on the web layer, so the tests and the benchmark call it directly without starting a server or a browser. More detail is in docs/architecture.md.
Click a decision to see the reasoning.
An indicator computed from bar t's close cannot also trade at that same close, because you only know the close once the bar is over. Target positions are shifted one bar before trades are generated, so information seen at t is acted on at t+1. An earlier version got this wrong; fixing it changed the results, and a regression test now holds the behavior in place.
If the date range is shorter than a strategy's warm-up (for example, 50 bars for a 50-bar SMA, plus one for the lag), the indicators never produce a value and the strategy can never trade. The API used to return a confident 0.00% return for that. It now returns a 400 that states how many bars were available and how many are needed. This has to be decided from the window length: an all-zero signal is also exactly what a flat market produces, so the output alone can't tell "no data" from "no trades".
Each entry is sized from the cash the previous exit left, so bar t depends on every trade before it, and the simulation can't be a single vectorized expression. The cost was never the loop itself; it was DataFrame.iterrows(), which builds a pandas Series for every bar. Looping over NumPy arrays produces identical output (checked frame-for-frame against the old engine) and takes 50,000 hourly bars from ~8.8 s to ~0.17 s.
yfinance returns seven hourly bars per US trading session. The last one covers only half an hour, but it is still a bar. So the hourly annualization factor is 252 × 7, not the 252 × 6.5 that the session length suggests. I checked the bar count against a month of AAPL and MSFT data; the old factor understated hourly Sharpe by about 3.6%.
A malformed request is a 422 (schema) or a 400 (engine rule, such as the fast SMA not being shorter than the slow one). A provider exception, malformed dataset, or empty download is a 502. An empty download is ambiguous: yfinance can swallow an upstream error, so it does not prove the ticker is invalid. The dashboard turns FastAPI's two error shapes (a string or a list of field errors) into one readable message.
The API accepts one ticker per run. Backtest requests are limited to 20 per client address per minute per API process; excess requests return 429 with Retry-After. Shared history retains the newest 1,000 saved runs. See deployment settings for proxy configuration and scaling limits.
Pydantic models describe every response, so the OpenAPI page documents the full contract, and frontend/lib/types.ts mirrors it. A test fails if an endpoint is added without a response model.
Network timing and upstream data revisions make live downloads useless for regression timing. The benchmark runs 8 synthetic OHLCV fixtures (trending, mean-reverting, volatile, shock-and-recovery, and so on) × 3 date windows × 3 strategies = 72 scenarios, and CI fails if the p95 engine time goes over 100 ms. See benchmarks/README.md.
The portfolio holds cash or one long position, so position state, fees, realized PnL, and every trade record can be checked by hand. Short selling or leverage would need margin, borrow costs, and liquidation rules, which makes it a different engine rather than another dropdown option.
| Strategy | Enter long when | Exit to cash when |
|---|---|---|
| SMA crossover | The fast SMA rises above the slow SMA | The fast SMA falls below the slow SMA |
| RSI mean reversion | RSI falls below the oversold threshold | RSI rises above the overbought threshold |
| Bollinger breakout | The close rises above the upper band | The close falls below the center line |
Every strategy outputs a target position of 1 (long) or 0 (cash); the engine turns changes in that target into trades. Metrics reported: total return, buy-and-hold return, excess return (deliberately not called alpha, since there is no beta or risk adjustment), max drawdown, Sharpe ratio, round trips, win rate, and ending equity.
Interactive docs: alphanexus-api.onrender.com/docs. You can send real requests from that page.
| Method | Path | Description |
|---|---|---|
GET | /health | Service health |
GET | /strategies | Supported strategies |
GET | /backtests?limit=20 | Recent saved runs, newest first (limit 1–100) |
POST | /backtests | Run and save a complete experiment; ?save=false skips saving |
GET | /backtests/{id} | Reopen/export a complete saved experiment |
POST | /backtests/{id}/replay | Verify saved inputs reproduce the original result without downloads |
New manual runs save their settings, input prices, original results and engine provenance together. History offers Open and Clone settings; saved results offer Verify replay and Export experiment JSON. A failed save is explicitly shown. Legacy runs remain summary-only. See saved experiments for the schema, retention, deployment requirements and full-stack test workflow.
curl -X POST "https://alphanexus-api.onrender.com/backtests?save=false" \
-H "Content-Type: application/json" \
-d '{
"ticker": "AAPL",
"start": "2024-01-01",
"end": "2024-12-31",
"interval": "1d",
"strategy": "sma_crossover",
"starting_cash": 10000,
"fee_bps": 5,
"slippage_bps": 5,
"fast_window": 17,
"slow_window": 50
}'
{
"ticker": "AAPL",
"strategy": "sma_crossover",
"metrics": {
"total_return": 0.2884,
"benchmark_return": 0.3585,
"excess_return_vs_benchmark": -0.0702,
"max_drawdown": -0.1175,
"sharpe_ratio": 1.5399,
"trade_count": 2,
"win_rate": 1.0,
"ending_equity": 12883.53
},
"equity_curve": [
{ "date": "2024-01-02T00:00:00", "close": 185.64, "portfolio_value": 10000.0,
"benchmark_value": 10000.0, "drawdown": 0.0, "signal": 0, "trade_signal": 0 }
],
"trades": [
{ "date": "2024-05-10T00:00:00", "close": 183.05, "trade_signal": 1, "shares": 54.5753,
"cash": 0.0, "portfolio_value": 9990.01, "realized_pnl": 0.0 }
]
}
Requires Python 3.11+ and Node.js 22.
python -m venv .venv && source .venv/bin/activate
make install # Python dev requirements + frontend packages
make dev-api # API on http://127.0.0.1:8000
In a second terminal:
echo "NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000" > frontend/.env.local
make dev-web # dashboard on http://127.0.0.1:3000
python -m venv .venv; .venv\Scripts\Activate.ps1
pip install -r requirements-dev.txt
uvicorn api.main:app --reload
In a second terminal:
cd frontend
Set-Content .env.local "NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000"
npm install
npm run dev
docker build -t alphanexus-api .
docker run -p 8000:8000 alphanexus-api
[!TIP] Without
.env.local, the local dashboard talks to the deployed API. That's handy for UI work, but your local backend changes won't show up.
| Check | Command | What it covers |
|---|---|---|
| Lint (Python) | make lint | ruff: pyflakes, pycodestyle, import order, bugbear, pyupgrade |
| Tests + coverage | make coverage | Domain and API regression tests; fails below 90% coverage |
| Benchmark | make benchmark | 72 deterministic scenarios; fails if p95 > 100 ms |
| Frontend | make frontend-check | ESLint, tsc, Vitest tests, production build |
| Browser (E2E) | make e2e | Playwright drives the production build on desktop and phone viewports; API responses come from fixtures generated by the real engine |
| Everything | make check | All of the above, the same as CI minus the container check |
save=falsesave=false), every chart actually drawing a series, switching strategy, the trade ledger and history tabs, a 422 shown as a message rather than a crash, the cold-start notice, disabled Run on bad dates, and no sideways scrolling on a phone. The drawdown-chart test fails if the original <Area>-in-<LineChart> bug is reintroduced.Market data is stubbed in every test, so the suite never touches the network.
CI runs on every pull request: Python lint, tests with coverage (the per-file table appears on the run's summary page), the benchmark gate, the frontend checks, the browser tests, and a Docker build that must pass a /health check. All four jobs are required before anything can merge into main. Dependabot opens weekly dependency updates; patch and minor ones merge automatically once CI passes, and major ones wait for review.
alphanexus/ Python engine (no web dependencies)
data.py yfinance loading, normalization, 15-minute cache
indicators.py SMA, RSI (Wilder), Bollinger Bands
strategies.py Indicator → target position, one-bar lag, warm-up rules
backtest.py Cash, shares, fees, slippage, realized PnL
metrics.py Return, drawdown, Sharpe, win rate
storage.py SQLite run history
api/main.py FastAPI routes, request and response models
frontend/
app/ Next.js page, styles, icon
components/ Controls, metric cards, charts, tables, run history
lib/ API client, types, formatting, validation, status (+ Vitest)
benchmarks/ Synthetic fixtures, scenario matrix, timing runner
tests/ pytest suite
docs/ Architecture, deployment, demo media
Makefile One-command dev, test, and CI checks
Dockerfile API image (checked in CI)
On the live dashboard, set AAPL, SMA Crossover, interval 1h, 2025-07-01 → 2026-07-01, SMA windows 17 / 61, $10,000 cash, 5 bps fee, and 5 bps slippage. Values can shift slightly if the data provider revises its history.
signal_date, reference close, actual fill_price, fee, shares, cash_before, and ending cash. The ledger labels these separately; CSV exports preserve full precision.NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000 and restart the frontend.See implementation notes and remaining work.
Next up: asset metadata validation, durable hosted storage and ownership, then run comparison and chronological holdout evaluation. See build status.
MIT © Tejasv Agarwal
[!WARNING] For research and education only. This is not financial advice and does not predict future returns.