Backtesting, paper-trading and live-execution infrastructure for Taiwan index
futures (TAIFEX: TX / MTX / TMF), single-stock futures and TXO options.
Python + FastAPI backend, React front end, shioaji broker adapter.
This is a curated export of a private production system that has been running
since March 2026. Strategy implementations, factor books, research reports and
account configuration are withheld; everything else — the engine, the simulator,
the execution and risk layers, the data pipeline and the dashboard — is here in
full. The single strategy in src/strategies/ is a reference implementation
written for this repository. It documents the interface. It is not traded.
The War Room against the mock broker: per-strategy allocation and running P&L on the left, entry and exit markers on the price chart, kill-switch controls in the header. Equity, fills and drawdowns shown are simulated.
The system's central design rule is that no signal logic lives in the engine. A strategy is a triple of policies:
PositionEngine(
entry_policy=..., # when to open, which direction, how many lots
add_policy=..., # when to pyramid, when to de-lever in place
stop_policy=..., # initial stop, trailing stop, signal exit
config=EngineConfig(max_loss=...),
)The engine executes decisions; it never forms them. That boundary is what makes
publishing the engine possible without publishing what it trades, and it is
worth more than any individual strategy. See
src/strategies/short_term/trend_following/demo_ema_cross.py
for a working example and
tests/unit/strategies/test_demo_ema_cross.py
for what the contract guarantees.
Intra-bar stop checking. A backtest that evaluates stops only at bar close
reports fills that could not have happened. src/bar_simulator/ reconstructs a
plausible intra-bar price path from OHLC and checks stop and entry conditions
along it, so a bar whose low pierced the stop counts as stopped out even if it
closed green. This costs accuracy nothing and removes a systematic optimism that
no amount of walk-forward validation would have caught.
Split ATR by purpose. Stop distances are sized from daily ATR; pyramid add-triggers use hourly ATR. Sizing a stop from the signal timeframe's ATR measures microstructure noise, not risk — at 1-minute resolution the two are not the same quantity, and a stop set from 1m ATR is a stop set to be hit.
Anti-martingale pyramiding. Position size at level k is
Size_0 × γ^k with γ < 1, so total exposure converges and maximum loss stays
bounded by the initial stop distance no matter how many levels get added. The
configuration derives from an account-level risk setting rather than per-strategy
parameters, which keeps a strategy from quietly changing the account's risk
profile through its own parameter schema.
One cost model everywhere. src/costs.py is the only place a fill becomes
money, and it is shared by backtest, paper and live. TAIFEX commission is charged
round-trip and tax is a rate on notional — so trading cost scales with the index
rather than sitting at a fixed per-lot figure. Backtests that hardcode a per-fill
cost drift away from live results as the index moves; this one cannot.
Sessions are a first-class concept. TAIFEX runs a night session
(15:00→05:00+1d) and a day session (08:45→13:45). VWAP, ATR calibration and
opening-range windows all reset on those boundaries, and every strategy declaring
StopArchitecture.INTRADAY must be flat before the bell. Session arithmetic
lives in src/data/session_utils.py and is never hardcoded at a call site —
the night session crossing midnight breaks naive date comparisons in ways that
are extremely quiet in a backtest.
Streaming indicators with declared parameter specs. Every indicator in
src/indicators/ exposes a PARAM_SPEC (type, bounds, default) and updates
incrementally rather than recomputing over a window. Strategies compose their
parameter schema from those specs, so the optimizer's search space and the
runtime's validation come from one definition instead of three that drift.
| Path | What lives there |
|---|---|
src/core/ |
PositionEngine, policy ABCs, position sizing, shared types |
src/strategies/ |
Strategy sandbox: registry, parameter loading, one reference strategy |
src/indicators/ |
25+ streaming indicators with declared parameter specs |
src/simulator/ |
Backtester, walk-forward, portfolio promotion, metrics |
src/bar_simulator/ |
Intra-bar price-path reconstruction and stop/entry checking |
src/data/ |
Ingestion, session utilities, gap detection and repair, contract rolling |
src/execution/ |
Live and paper engines, portfolio orchestration, kill-switch |
src/oms/, src/reconciliation/ |
Order management, broker-vs-engine position reconciliation |
src/risk/ |
Pre-trade checks, VaR, risk monitor |
src/broker_gateway/ |
shioaji adapter, live bar store |
src/api/, src/runtime/ |
FastAPI routes, WebSocket feeds, process orchestration, telemetry |
src/mcp_server/ |
MCP tool surface for running backtests from an agent |
frontend/ |
React "War Room" dashboard |
uv sync --group dev
uv run pytest tests/unitThe dev server binds ports separate from production and refuses to start on the production ports:
./scripts/run-dev.shLive trading requires broker credentials that are not in this repository. The paper and backtest paths run without them.
This repository is generated from the private one by a one-way export, so its commit history starts at the first export rather than at the start of the project. Nothing flows back the other way. The tradeoff is deliberate: a scrubbed 1,200-commit history would be one missed commit away from publishing what it was meant to withhold, and a green test suite is better evidence that the code is real than a commit count is.
MIT — see LICENSE.
Nothing here is investment advice, and none of it is a strategy you can trade. It is the machinery that runs one.
