Extending
Three common extension points. Each touches one layer.
Add a strategy
-
Subclass
Strategyinsrc/strategies/:class MyStrategy(Strategy):TIMEFRAME = "5Min"PARAM_RANGES = { # min/max/step/default/type per tunable param"lookback": {"type": "int", "min": 5, "max": 50, "step": 5, "default": 20},"risk_per_trade": {"type": "float", "min": 0.01, "max": 0.05, "step": 0.01, "default": 0.02},"stop_loss": {"type": "float", "min": 0.01, "max": 0.05, "step": 0.01, "default": 0.02},"take_profit": {"type": "float", "min": 0.02, "max": 0.10, "step": 0.02, "default": 0.04},}def calculate_required_lookback(self): return self.config["lookback"] + 1def initialize(self): ...def process_data(self, df): ... # add indicator columnsdef generate_signals(self, df): ... # -> {timestamp: signal} -
Register it in
main.STRATEGIES. It now works inbacktest,live, andoptimize— sizing, fills, execution, and metrics come for free because they only depend on the base interface.
Use the pure indicators; don't reach for a compiled TA library.
Add a scanner
- Subclass
ScannerStrategyinsrc/scanners/— implementprocess_dataandgenerate_signals_df(emitSCANNER_BUY/SCANNER_SELL/SCANNER_HOLDplus asignal_strength). - Register it in
SymbolScanner.SCANNERS. Keep it TA-Lib-free.
Add a broker
- Implement
Broker(and optionallyMarketDataProvider) for the venue in a newsrc/brokers/<vendor>/package, mapping the SDK to the domain types. - Construct it in
main.build_data_and_broker().
Nothing in engine/, execution/, strategies/, scanners/, or the optimizer
changes — they only ever knew the interface. Prove it the same way the suite does:
run against your adapter, or against FakeBroker first.
Add an optimization objective
Any key in the metrics dict (sharpe_ratio, total_return, calmar_ratio, ...)
is a valid --objective. To add a new one, compute it in
analytics.performance.compute_backtest_metrics and it's immediately selectable.