- pyproject.toml: add mypy overrides for pandas/tabulate/matplotlib stubs,
disable strict checking for vendored MyTT library
- config.py: use cast() for dict[str, Any] .get() returns
- beichi.py: widen _calc_bi_force param to BI | XD, import XD
- backtest/cli.py: split combo/single strategy into separate typed variables
- backtest/combo.py: add bool_array() helper for numpy return types
- chanlun/analyser.py: type ignore for pandas row access, fix dict type arg
- unified.py: change fields param from object to Any
- ex/mac_client.py: add type args to list literals
- cli/cmd_offline.py: wrap int market as Market enum before API call
- cli/cmd_chanlun.py: fix dict type arg
- offline/write_*.py: explicit int() cast for struct.unpack returns
- MyTT.py: fix line-too-long comments, UP038 isinstance syntax
- tests: fix E712 (==False → ~mask), E741 (noqa), F841, import sorting
- ruff format applied across codebase
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- screen() now calls run_combination() internally, eliminating duplicated
signal extraction/combination logic
- _run_combo_screen creates one CombinationRunner before the size loop,
so signal cache is reused across 2-factor and 3-factor screens
- Add MAJORITY(2)=AND note to screen() docstring
Performance dict outputs 'sharpe' but _print_table looked up 'sharpe_ratio',
so perf.get('sharpe_ratio', 0) always returned the default 0 regardless of
actual Sharpe value.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previous formula was: max(absolute_drawdown) / initial_capital, which
exceeds 100% when the portfolio grows then drops (e.g. from 600k to 300k
on a 100k initial = 300% drawdown, which is nonsensical).
Fixed to use drawdown_pct (drawdown / peak) which is always in [0, 1].
This correctly measures the maximum percentage drop from the highest
equity peak, matching the standard financial definition.
Also added regression test: test_max_drawdown_never_exceeds_100_pct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause: _generate_signals() iterated all bars calling strategy.next()
but never updated _position_size or _cash on the strategy. Strategies
that check self.position['size'] before buy/sell always saw 0, producing
only BUY signals with no SELL — exhausting cash and producing drawdowns
exceeding 100%.
Fix: add _update_strategy_position() that estimates position changes
after each bar's signals using close price. This gives the strategy an
accurate view of its holdings so it can correctly alternate buy/sell.
Regression tests added:
- test_position_aware_buy_sell_alternation: verifies BUY/SELL alternation
- test_position_aware_no_duplicate_buys: no suspicious tiny duplicate buys
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add dsl_strategy decorator in dsl.py (P1 skeleton implementation)
- Update __init__.py to export BacktestEngine, Strategy, and related types
- All 106 backtest unit tests pass
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Implement BacktestEngine orchestrator with 4-step pipeline:
1. Signal generation (Strategy)
2. Order simulation (OrderSimulator)
3. Portfolio tracking (PortfolioTracker)
4. Performance analysis (PerformanceAnalyzer)
- Support both strategy class and instance initialization
- Add PnL calculation for sell trades
- Add JSON serialization with numpy/timestamp support
- Include comprehensive test coverage (12 tests, all passing)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Implement PerformanceAnalyzer class with compute() method
- Calculate 19 performance metrics: total_return, annual_return, max_drawdown,
max_dd_duration, sharpe, sortino, calmar, trade statistics, and volatility
- Handle edge cases: empty data, no negative returns (sortino=999), no drawdown (calmar=999)
- Add 20 comprehensive unit tests covering all metrics
- Type annotations use NDArray pattern for mypy strict compliance
- All tests pass, mypy and ruff checks clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Pre-allocate numpy arrays for performance (cash, position, avg_price)
- apply_trades() processes buys/sells with commission and slippage
- equity_curve returns DataFrame with drawdown calculation
- positions returns DataFrame with market value and unrealized PnL
- 12 unit tests covering all scenarios
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Implement OrderSimulator class for order matching simulation
- Support 5 execution modes: next_open, next_close, this_close, worst, best
- Support 3 position modes: full, fixed, percent
- Support 2 reject policies: reduce (partial fill), skip (reject)
- Implement fee model: commission (min 5 CNY), stamp tax (0.1% sell only), slippage
- Add future_leak_warning flag for this_close mode
- Handle both int and datetime column types in DataFrame
- Add comprehensive test suite with 24 test cases covering all modes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add _SeriesAccessor for relative indexed data access ([0] current, [-1] previous)
- Add StrategyDataProxy for efficient DataFrame column access via numpy arrays
- Add crossover() function for golden cross detection (fast line crosses above slow line)
- Add Strategy abstract base class with:
- init() for indicator registration via self.I()
- next() for signal generation via buy()/sell()
- Internal engine hooks (_bind_data, _call_init, _set_bar_index, etc.)
- All code is mypy strict compliant with full type annotations
- 25 unit tests covering all components
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add Signal dataclass for trading signals with optional price/stop_loss/take_profit
- Add Trade dataclass for executed trades with commission/slippage/pnl/rejected
- Add Position dataclass for position snapshots (long/short/flat)
- Add BacktestResult dataclass with to_dict()/to_json()/summary() methods
- Add comprehensive unit tests (13 test cases, 100% pass)
- All code passes mypy strict, ruff lint+format checks
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>