Performance#

Every completed TradeTide.Backtester returns a structured BacktestResult. Its metrics field uses decimal fractions (0.12 is 12%) and includes return, annualized return, volatility, maximum drawdown and its duration, Sharpe, Sortino, and Calmar ratios, win rate, profit factor, exposure, and trade counts.

Equity and drawdown chart#

BacktestResult.plot_equity_drawdown() uses compact green/red equity candles: each candle opens at the previous observation’s equity and closes at the current one. The drawdown panel shares its time axis and uses the same net-of-costs equity curve.

backtester.run()
result = BacktestResult.from_portfolio(backtester.portfolio)
result.plot_equity_drawdown(max_candles=500)

Debug logging#

Logging is opt-in and uses standard Python logging. DEBUG mode reports market validation summaries, order lifecycle events, individual trade costs, and final performance metrics. enable_debug_logging also enables native diagnostics for objects created after it is called.

import logging
from TradeTide import configure_logging, enable_debug_logging

configure_logging(logging.DEBUG)  # Python-side diagnostics
enable_debug_logging()            # Python and native diagnostics

Execution costs#

Recorded market bid/ask prices already contain their observed spread. Use extra_spread_pips only to model additional execution friction. Commission and slippage are charged on both entry and exit; financing is charged once per completed overnight period. Costs are applied to the returned net equity curve and trade metrics without changing the raw native record.

from TradeTide import ExecutionCosts

costs = ExecutionCosts(
    commission_per_lot=3.50,
    slippage_pips=0.2,
    extra_spread_pips=0.1,
    financing_per_lot_per_night=0.05,
)
# Pass execution_costs=costs to Backtester(...).
backtester.run()
result = BacktestResult.from_portfolio(backtester.portfolio, costs)
print(result.metrics.to_dict())

Chronological validation#

chronological_split never shuffles data, so future observations cannot leak into training. WalkForwardSplitter produces deterministic expanding or rolling windows and evaluates only the corresponding out-of-sample period. Supply factories so every fold receives fresh strategy, exit, and sizing state.

from TradeTide import WalkForwardSplitter

splitter = WalkForwardSplitter(train_size=2_000, test_size=500, expanding=True)
report = splitter.run(
    market,
    strategy_factory=lambda training_market: make_strategy(training_market),
    exit_strategy_factory=make_exit_strategy,
    capital_management_factory=make_capital_management,
)

for result in report.results:
    print(result.metrics.total_return)