feat: add backtesting functionality with UI and API endpoints
CI / lint-test-build (push) Successful in 2m31s
CI / lint-test-build (push) Successful in 2m31s
- Introduced backtesting page and fragment in the dashboard for running backtests and viewing recent reports. - Implemented backtest run logic with configuration options including event path, starting balances, trade capital, and fee profiles. - Added recent backtest reports storage and retrieval. - Created a new strategy module for statistical arbitrage experiments with validation on configuration parameters. - Updated settings to include parameters for the statistical arbitrage strategy. - Enhanced dashboard controls to support the new strategy mode. - Added unit tests for backtesting functionality and strategy validation. - Updated templates for backtesting UI integration.
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
<div id="backtesting-shell" class="panel">
|
||||
<div
|
||||
class="grid"
|
||||
style="grid-template-columns: repeat(auto-fit, minmax(280px, 1fr))"
|
||||
>
|
||||
<article class="card">
|
||||
<div class="label">Run Status</div>
|
||||
<div class="value">{{ status }}</div>
|
||||
<div class="meta">{{ message }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Latest Report</div>
|
||||
{% if latest_report %}
|
||||
<div class="meta">Run at {{ latest_report.run_at }}</div>
|
||||
<div class="meta">Events: {{ latest_report.events_path }}</div>
|
||||
<div class="meta">
|
||||
Processed: {{ latest_report.report.processed_events }}
|
||||
</div>
|
||||
<div class="meta">
|
||||
Opportunities: {{ latest_report.report.opportunities_seen }}
|
||||
</div>
|
||||
<div class="meta">Trades: {{ latest_report.report.trades_executed }}</div>
|
||||
<div class="meta">
|
||||
Realized P&L: {{
|
||||
'%.4f'|format(latest_report.report.realized_pnl_usd) }} USD
|
||||
</div>
|
||||
<div class="meta">
|
||||
Max drawdown: {{ '%.4f'|format(latest_report.report.max_drawdown_usd) }}
|
||||
USD
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="meta">No runs yet.</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<article class="card" style="margin-top: 16px">
|
||||
<div class="label">Run Backtest</div>
|
||||
<form
|
||||
class="form-grid"
|
||||
hx-post="{{ run_endpoint }}"
|
||||
hx-target="#backtesting-shell"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<label class="field">
|
||||
<span>Replay events path (JSONL)</span>
|
||||
<input
|
||||
name="events_path"
|
||||
type="text"
|
||||
value="{{ events_path }}"
|
||||
placeholder="data/replay.jsonl"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Starting balances</span>
|
||||
<input
|
||||
name="starting_balances"
|
||||
type="text"
|
||||
value="{{ starting_balances }}"
|
||||
placeholder="USD=1000.0,BTC=0.0"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Trade capital</span>
|
||||
<input
|
||||
name="trade_capital"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ trade_capital }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Min profit threshold</span>
|
||||
<input
|
||||
name="min_profit_threshold"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.0001"
|
||||
value="{{ min_profit_threshold }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Fee profile</span>
|
||||
<select name="fee_profile">
|
||||
{% set sel = "selected" if fee_profile == "standard" else "" %}
|
||||
<option value="standard" {{ sel }}>standard</option>
|
||||
{% set sel = "selected" if fee_profile == "maker_heavy" else "" %}
|
||||
<option value="maker_heavy" {{ sel }}>maker_heavy</option>
|
||||
{% set sel = "selected" if fee_profile == "taker_heavy" else "" %}
|
||||
<option value="taker_heavy" {{ sel }}>taker_heavy</option>
|
||||
{% set sel = "selected" if fee_profile == "custom" else "" %}
|
||||
<option value="custom" {{ sel }}>custom</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Custom fee rate (if fee profile = custom)</span>
|
||||
<input
|
||||
name="custom_fee_rate"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.0001"
|
||||
value="{{ custom_fee_rate }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Slippage (bps)</span>
|
||||
<input
|
||||
name="slippage_bps"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.1"
|
||||
value="{{ slippage_bps }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Execution latency (ms)</span>
|
||||
<input
|
||||
name="execution_latency_ms"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.1"
|
||||
value="{{ execution_latency_ms }}"
|
||||
/>
|
||||
</label>
|
||||
<button type="submit" class="button">Run backtest</button>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<article class="card" style="margin-top: 16px">
|
||||
<div class="label">Recent Runs</div>
|
||||
{% if recent_reports %} {% for item in recent_reports %}
|
||||
<div class="meta">
|
||||
{{ item.run_at }} | {{ item.events_path }} | trades={{
|
||||
item.report.trades_executed }} | pnl={{
|
||||
'%.4f'|format(item.report.realized_pnl_usd) }} USD
|
||||
</div>
|
||||
{% endfor %} {% else %}
|
||||
<div class="meta">No recent reports yet.</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
</div>
|
||||
@@ -131,6 +131,12 @@
|
||||
<option value="paper" {{ sel }}>paper</option>
|
||||
{% set sel = "selected" if strategy_mode == "live" else "" %}
|
||||
<option value="live" {{ sel }}>live</option>
|
||||
{% if strategy_stat_arb_enabled %} {% set sel = "selected" if
|
||||
strategy_mode == "stat_arb_experiment" else "" %}
|
||||
<option value="stat_arb_experiment" {{ sel }}>
|
||||
stat_arb_experiment
|
||||
</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
|
||||
Reference in New Issue
Block a user