feat: add pair fees configuration panel and related functionality
- Introduced a new HTML template for configuring pair fees, allowing users to add, edit, and delete fees for trading pairs. - Implemented a responsive fee table displaying existing fees with options for editing and deleting. - Added a form for adding new fees, including fields for base asset, quote asset, market type, and fee rates. - Removed outdated templates related to backtesting, dashboard, health, and various partials to streamline the codebase. - Ensured the new fee configuration panel integrates seamlessly with existing endpoints and uses htmx for dynamic updates.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %} {% block title %}{{ title }}{% endblock %} {% block
|
||||
main_class %}shell{% endblock %} {% block content %}
|
||||
<section class="hero">
|
||||
<div>
|
||||
<h1 class="title">Configuration</h1>
|
||||
<p class="subtitle">
|
||||
Runtime settings, alerts, exchange, risk, and strategy.
|
||||
</p>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<a class="button secondary" href="/dashboard">Dashboard</a>
|
||||
<a class="button secondary" href="/dashboard/backtesting">Backtesting</a>
|
||||
<a class="button secondary" href="/health">Health</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="fees-shell"
|
||||
hx-get="{{ config_fees_fragment }}"
|
||||
hx-target="this"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/config_fees.html" %}
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="config-shell"
|
||||
hx-get="/dashboard/fragment/config"
|
||||
hx-target="this"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/config.html" %}
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
@@ -18,6 +18,7 @@ head_scripts %}
|
||||
>Refresh metrics</a
|
||||
>
|
||||
<a class="button secondary" href="/health">Health</a>
|
||||
<a class="button secondary" href="/dashboard/config">Config</a>
|
||||
<a class="button secondary" href="/dashboard/backtesting">Backtesting</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,614 @@
|
||||
<div id="config-panel" class="panel" style="margin-top: 16px">
|
||||
<form
|
||||
class="form-grid"
|
||||
hx-post="{{ config_endpoint }}"
|
||||
hx-target="#config-panel"
|
||||
hx-swap="outerHTML"
|
||||
style="
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 20px;
|
||||
"
|
||||
>
|
||||
<!-- Runtime -->
|
||||
<div class="card">
|
||||
<div class="label">Runtime</div>
|
||||
<label class="field">
|
||||
<span>App env</span>
|
||||
<input type="text" value="{{ app_env }}" disabled />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>App host</span>
|
||||
<input name="app_host" type="text" value="{{ app_host }}" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>App port</span>
|
||||
<input
|
||||
name="app_port"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
value="{{ app_port }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Log level</span>
|
||||
<select name="log_level">
|
||||
{% for lvl in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] %} {%
|
||||
set sel = "selected" if log_level == lvl else "" %}
|
||||
<option value="{{ lvl }}" {{ sel }}>{{ lvl }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
<input name="log_json" type="checkbox" {{ log_json }} />
|
||||
<span>JSON logs</span>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="paper_trading_mode"
|
||||
type="checkbox"
|
||||
{{
|
||||
paper_trading_mode
|
||||
}}
|
||||
/>
|
||||
<span>Paper trading mode</span>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Trade capital USD</span>
|
||||
<input
|
||||
name="trade_capital_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ trade_capital_usd_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max trade capital USD</span>
|
||||
<input
|
||||
name="max_trade_capital_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ max_trade_capital_usd_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max concurrent trades</span>
|
||||
<input
|
||||
name="max_concurrent_trades"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
value="{{ max_concurrent_trades_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max exposure per asset USD</span>
|
||||
<input
|
||||
name="max_exposure_per_asset_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ max_exposure_per_asset_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Quote balance asset</span>
|
||||
<input
|
||||
name="quote_balance_asset"
|
||||
type="text"
|
||||
value="{{ quote_balance_asset }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Min order size USD</span>
|
||||
<input
|
||||
name="min_order_size_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ min_order_size_usd_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Tradable pairs (comma-separated)</span>
|
||||
<input
|
||||
name="tradable_pairs"
|
||||
type="text"
|
||||
placeholder="BTC/USD, ETH/BTC"
|
||||
value="{{ tradable_pairs_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Strategy mode</span>
|
||||
<select name="strategy_mode">
|
||||
{% set sel = "selected" if strategy_mode == "incremental" else "" %}
|
||||
<option value="incremental" {{ sel }}>incremental</option>
|
||||
{% set sel = "selected" if strategy_mode == "paper" else "" %}
|
||||
<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">
|
||||
<span>Strategy profit threshold</span>
|
||||
<input
|
||||
name="strategy_profit_threshold"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.0001"
|
||||
value="{{ strategy_profit_threshold }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max depth levels</span>
|
||||
<input
|
||||
name="strategy_max_depth_levels"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
value="{{ strategy_max_depth_levels }}"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div class="card">
|
||||
<div class="label">Alerting</div>
|
||||
<label class="field checkbox">
|
||||
<input name="alerts_enabled" type="checkbox" {{ alerts_enabled }} />
|
||||
<span>Alerts enabled</span>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Min severity</span>
|
||||
<select name="alert_min_severity">
|
||||
{% for sev in ["info", "warning", "error", "critical"] %} {% set sel =
|
||||
"selected" if alert_min_severity == sev else "" %}
|
||||
<option value="{{ sev }}" {{ sel }}>{{ sev }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Dedup seconds</span>
|
||||
<input
|
||||
name="alert_dedup_seconds"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value="{{ alert_dedup_seconds }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="alert_on_trade_events"
|
||||
type="checkbox"
|
||||
{{
|
||||
alert_on_trade_events
|
||||
}}
|
||||
/>
|
||||
<span>Trade events</span>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="alert_on_error_events"
|
||||
type="checkbox"
|
||||
{{
|
||||
alert_on_error_events
|
||||
}}
|
||||
/>
|
||||
<span>Error events</span>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="alert_on_threshold_events"
|
||||
type="checkbox"
|
||||
{{
|
||||
alert_on_threshold_events
|
||||
}}
|
||||
/>
|
||||
<span>Threshold events</span>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="alert_on_system_events"
|
||||
type="checkbox"
|
||||
{{
|
||||
alert_on_system_events
|
||||
}}
|
||||
/>
|
||||
<span>System events</span>
|
||||
</label>
|
||||
<hr
|
||||
style="
|
||||
border: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
margin: 12px 0;
|
||||
"
|
||||
/>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="telegram_alerts_enabled"
|
||||
type="checkbox"
|
||||
{{
|
||||
telegram_alerts_enabled
|
||||
}}
|
||||
/>
|
||||
<span>Telegram</span>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Telegram bot token</span>
|
||||
<input
|
||||
name="telegram_bot_token"
|
||||
type="password"
|
||||
value="{{ telegram_bot_token }}"
|
||||
placeholder="Bot token"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Telegram chat ID</span>
|
||||
<input
|
||||
name="telegram_chat_id"
|
||||
type="text"
|
||||
value="{{ telegram_chat_id }}"
|
||||
placeholder="Chat ID"
|
||||
/>
|
||||
</label>
|
||||
<hr
|
||||
style="
|
||||
border: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
margin: 12px 0;
|
||||
"
|
||||
/>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="discord_alerts_enabled"
|
||||
type="checkbox"
|
||||
{{
|
||||
discord_alerts_enabled
|
||||
}}
|
||||
/>
|
||||
<span>Discord</span>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Discord webhook URL</span>
|
||||
<input
|
||||
name="discord_webhook_url"
|
||||
type="password"
|
||||
value="{{ discord_webhook_url }}"
|
||||
placeholder="Webhook URL"
|
||||
/>
|
||||
</label>
|
||||
<hr
|
||||
style="
|
||||
border: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
margin: 12px 0;
|
||||
"
|
||||
/>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="email_alerts_enabled"
|
||||
type="checkbox"
|
||||
{{
|
||||
email_alerts_enabled
|
||||
}}
|
||||
/>
|
||||
<span>Email</span>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>SMTP host</span>
|
||||
<input
|
||||
name="email_smtp_host"
|
||||
type="text"
|
||||
value="{{ email_smtp_host }}"
|
||||
placeholder="smtp.example.com"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>SMTP port</span>
|
||||
<input
|
||||
name="email_smtp_port"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
value="{{ email_smtp_port }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>SMTP username</span>
|
||||
<input
|
||||
name="email_smtp_username"
|
||||
type="text"
|
||||
value="{{ email_smtp_username }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>SMTP password</span>
|
||||
<input
|
||||
name="email_smtp_password"
|
||||
type="password"
|
||||
value="{{ email_smtp_password }}"
|
||||
placeholder="Leave blank to keep existing"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>From address</span>
|
||||
<input
|
||||
name="email_alert_from"
|
||||
type="text"
|
||||
value="{{ email_alert_from }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>To address</span>
|
||||
<input name="email_alert_to" type="text" value="{{ email_alert_to }}" />
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="email_smtp_use_tls"
|
||||
type="checkbox"
|
||||
{{
|
||||
email_smtp_use_tls
|
||||
}}
|
||||
/>
|
||||
<span>Use TLS</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Kraken -->
|
||||
<div class="card">
|
||||
<div class="label">Kraken Exchange</div>
|
||||
<label class="field">
|
||||
<span>REST URL</span>
|
||||
<input
|
||||
name="kraken_rest_url"
|
||||
type="text"
|
||||
value="{{ kraken_rest_url }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>WebSocket URL</span>
|
||||
<input name="kraken_ws_url" type="text" value="{{ kraken_ws_url }}" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Private rate limit (s)</span>
|
||||
<input
|
||||
name="kraken_private_rate_limit_seconds"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ kraken_private_rate_limit_seconds }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>HTTP timeout (s)</span>
|
||||
<input
|
||||
name="kraken_http_timeout_seconds"
|
||||
type="number"
|
||||
min="1"
|
||||
step="0.5"
|
||||
value="{{ kraken_http_timeout_seconds }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Retry attempts</span>
|
||||
<input
|
||||
name="kraken_retry_attempts"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value="{{ kraken_retry_attempts }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Retry base delay (s)</span>
|
||||
<input
|
||||
name="kraken_retry_base_delay_seconds"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ kraken_retry_base_delay_seconds }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>API key</span>
|
||||
<input
|
||||
name="kraken_api_key"
|
||||
type="text"
|
||||
value="{{ kraken_api_key }}"
|
||||
placeholder="API key"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>API secret</span>
|
||||
<input
|
||||
name="kraken_api_secret"
|
||||
type="password"
|
||||
value="{{ kraken_api_secret }}"
|
||||
placeholder="API secret"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Key permissions</span>
|
||||
<input
|
||||
name="kraken_api_key_permissions"
|
||||
type="text"
|
||||
value="{{ kraken_api_key_permissions }}"
|
||||
placeholder="query,trade"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Heartbeat timeout (s)</span>
|
||||
<input
|
||||
name="ws_heartbeat_timeout_seconds"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
value="{{ ws_heartbeat_timeout_seconds }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max staleness (s)</span>
|
||||
<input
|
||||
name="ws_max_staleness_seconds"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.5"
|
||||
value="{{ ws_max_staleness_seconds }}"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Risk -->
|
||||
<div class="card">
|
||||
<div class="label">Risk Limits</div>
|
||||
<label class="field">
|
||||
<span>Daily loss limit USD</span>
|
||||
<input
|
||||
name="daily_loss_limit_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ daily_loss_limit_value }}"
|
||||
placeholder="None"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Cumulative loss limit USD</span>
|
||||
<input
|
||||
name="cumulative_loss_limit_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ cumulative_loss_limit_value }}"
|
||||
placeholder="None"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max source latency (ms)</span>
|
||||
<input
|
||||
name="max_source_latency_ms"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value="{{ max_source_latency_value }}"
|
||||
placeholder="None"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max apply latency (ms)</span>
|
||||
<input
|
||||
name="max_apply_latency_ms"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value="{{ max_apply_latency_value }}"
|
||||
placeholder="None"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max consecutive failures</span>
|
||||
<input
|
||||
name="max_consecutive_failures"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
value="{{ max_consecutive_failures_value }}"
|
||||
placeholder="None"
|
||||
/>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="kill_switch_active"
|
||||
type="checkbox"
|
||||
{{
|
||||
kill_switch_active
|
||||
}}
|
||||
/>
|
||||
<span>Kill switch active</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Strategy Stat-Arb -->
|
||||
<div class="card">
|
||||
<div class="label">Stat-Arb Strategy</div>
|
||||
<label class="field checkbox">
|
||||
<input
|
||||
name="strategy_enable_stat_arb_experiment"
|
||||
type="checkbox"
|
||||
{%
|
||||
if
|
||||
strategy_stat_arb_enabled
|
||||
%}checked{%
|
||||
endif
|
||||
%}
|
||||
/>
|
||||
<span>Enable stat-arb experiment</span>
|
||||
</label>
|
||||
{% if strategy_stat_arb_enabled %}
|
||||
<label class="field">
|
||||
<span>Lookback window</span>
|
||||
<input
|
||||
name="strategy_stat_arb_lookback_window"
|
||||
type="number"
|
||||
min="2"
|
||||
step="1"
|
||||
value="{{ strategy_stat_arb_lookback_window }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Entry z-score</span>
|
||||
<input
|
||||
name="strategy_stat_arb_entry_zscore"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.1"
|
||||
value="{{ strategy_stat_arb_entry_zscore }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Exit z-score</span>
|
||||
<input
|
||||
name="strategy_stat_arb_exit_zscore"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.1"
|
||||
value="{{ strategy_stat_arb_exit_zscore }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max holding seconds</span>
|
||||
<input
|
||||
name="strategy_stat_arb_max_holding_seconds"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
value="{{ strategy_stat_arb_max_holding_seconds }}"
|
||||
/>
|
||||
</label>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<div
|
||||
class="card"
|
||||
style="display: flex; align-items: center; justify-content: center"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="button"
|
||||
style="padding: 14px 32px; font-size: 1.1rem"
|
||||
>
|
||||
Save configuration
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,214 @@
|
||||
<div id="config-fees-panel" class="panel" style="margin-top: 16px">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
<h2 style="margin: 0; font-size: 1.3rem">Pair Fees</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="button"
|
||||
onclick="
|
||||
document.getElementById('fee-form-section').style.display =
|
||||
document.getElementById('fee-form-section').style.display === 'none'
|
||||
? 'block'
|
||||
: 'none'
|
||||
"
|
||||
>
|
||||
Add / Edit Fee
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<div
|
||||
style="
|
||||
background: rgba(186, 61, 79, 0.2);
|
||||
border: 1px solid #ba3d4f;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Fee Table -->
|
||||
{% if pair_fees %}
|
||||
<div style="overflow-x: auto">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem">
|
||||
<thead>
|
||||
<tr style="border-bottom: 1px solid rgba(255, 255, 255, 0.14)">
|
||||
<th style="text-align: left; padding: 10px; color: #9fb2d0">Pair</th>
|
||||
<th style="text-align: left; padding: 10px; color: #9fb2d0">
|
||||
Market Type
|
||||
</th>
|
||||
<th style="text-align: left; padding: 10px; color: #9fb2d0">
|
||||
Maker Rate
|
||||
</th>
|
||||
<th style="text-align: left; padding: 10px; color: #9fb2d0">
|
||||
Taker Rate
|
||||
</th>
|
||||
<th style="text-align: left; padding: 10px; color: #9fb2d0">
|
||||
Updated
|
||||
</th>
|
||||
<th style="text-align: left; padding: 10px; color: #9fb2d0">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fee in pair_fees %}
|
||||
<tr style="border-bottom: 1px solid rgba(255, 255, 255, 0.06)">
|
||||
<td style="padding: 10px">{{ fee.pair_display }}</td>
|
||||
<td style="padding: 10px">{{ fee.market_type }}</td>
|
||||
<td style="padding: 10px">{{ "%.4f"|format(fee.maker_fee_rate) }}</td>
|
||||
<td style="padding: 10px">{{ "%.4f"|format(fee.taker_fee_rate) }}</td>
|
||||
<td style="padding: 10px; color: #7f95b7">
|
||||
{{ fee.updated_at or "—" }}
|
||||
</td>
|
||||
<td style="padding: 10px">
|
||||
<form
|
||||
hx-post="{{ config_fees_endpoint }}"
|
||||
hx-target="#config-fees-panel"
|
||||
hx-swap="outerHTML"
|
||||
style="display: inline"
|
||||
>
|
||||
<input type="hidden" name="action" value="update" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="base_asset"
|
||||
value="{{ fee.base_asset }}"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="quote_asset"
|
||||
value="{{ fee.quote_asset }}"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="market_type"
|
||||
value="{{ fee.market_type }}"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="maker_fee_rate"
|
||||
value="{{ fee.maker_fee_rate }}"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="taker_fee_rate"
|
||||
value="{{ fee.taker_fee_rate }}"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="button secondary"
|
||||
style="padding: 4px 10px; font-size: 0.8rem"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</form>
|
||||
<form
|
||||
hx-post="{{ config_fees_endpoint }}"
|
||||
hx-target="#config-fees-panel"
|
||||
hx-swap="outerHTML"
|
||||
style="display: inline"
|
||||
>
|
||||
<input type="hidden" name="action" value="delete" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="base_asset"
|
||||
value="{{ fee.base_asset }}"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="quote_asset"
|
||||
value="{{ fee.quote_asset }}"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="market_type"
|
||||
value="{{ fee.market_type }}"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="button danger"
|
||||
style="padding: 4px 10px; font-size: 0.8rem"
|
||||
onclick="return confirm('Delete this fee?');"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="color: #7f95b7; padding: 20px; text-align: center">
|
||||
No pair fees configured. Click "Add / Edit Fee" to create one.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Add/Edit Form -->
|
||||
<div id="fee-form-section" style="display: none; margin-top: 20px">
|
||||
<div class="card">
|
||||
<div class="label">Add / Edit Fee</div>
|
||||
<form
|
||||
class="form-grid"
|
||||
hx-post="{{ config_fees_endpoint }}"
|
||||
hx-target="#config-fees-panel"
|
||||
hx-swap="outerHTML"
|
||||
style="
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
"
|
||||
>
|
||||
<input type="hidden" name="action" value="add" />
|
||||
<label class="field">
|
||||
<span>Base Asset</span>
|
||||
<input name="base_asset" type="text" placeholder="BTC" required />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Quote Asset</span>
|
||||
<input name="quote_asset" type="text" placeholder="USD" required />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Market Type</span>
|
||||
<select name="market_type">
|
||||
<option value="crypto_crypto">crypto_crypto</option>
|
||||
<option value="crypto_fiat">crypto_fiat</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Maker Fee Rate</span>
|
||||
<input
|
||||
name="maker_fee_rate"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.0001"
|
||||
placeholder="0.0016"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Taker Fee Rate</span>
|
||||
<input
|
||||
name="taker_fee_rate"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.0001"
|
||||
placeholder="0.0026"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span> </span>
|
||||
<button type="submit" class="button">Save Fee</button>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,24 +0,0 @@
|
||||
{% extends "base.html" %} {% block title %}{{ title }}{% endblock %} {% block
|
||||
content %}
|
||||
<section class="hero">
|
||||
<div>
|
||||
<h1 class="title">Backtesting</h1>
|
||||
<p class="subtitle">
|
||||
Replay controls, run status, and recent summary reports.
|
||||
</p>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<a class="button secondary" href="{{ dashboard_endpoint }}">Dashboard</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="backtesting-shell"
|
||||
hx-get="{{ panel_endpoint }}"
|
||||
hx-target="this"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/backtesting_panel.html" %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -1,148 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{% block title %}{{ title or "Arbitrade" }}{% endblock %}</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||
{% block head_scripts %}{% endblock %}
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background: #0b1220;
|
||||
color: #e5eefb;
|
||||
}
|
||||
.shell {
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
.hero {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
align-items: end;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
color: #9fb2d0;
|
||||
}
|
||||
.panel {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
}
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.label {
|
||||
color: #9fb2d0;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.value {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.meta {
|
||||
margin-top: 18px;
|
||||
color: #7f95b7;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.toolbar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.toolbar form {
|
||||
margin: 0;
|
||||
}
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
background: #2d6cdf;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font: inherit;
|
||||
}
|
||||
.button.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
.button.danger {
|
||||
background: #ba3d4f;
|
||||
}
|
||||
.form-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
color: #9fb2d0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.field input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: #e5eefb;
|
||||
font: inherit;
|
||||
}
|
||||
.field.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.field.checkbox input {
|
||||
width: auto;
|
||||
}
|
||||
.control-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.chart-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.chart-canvas {
|
||||
width: 100%;
|
||||
min-height: 260px;
|
||||
}
|
||||
</style>
|
||||
{% block extra_style %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<main class="{% block main_class %}shell{% endblock %}">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,180 +0,0 @@
|
||||
{% extends "base.html" %} {% block title %}{{ title }}{% endblock %} {% block
|
||||
head_scripts %}
|
||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
|
||||
{% endblock %} {% block main_class %}shell{% endblock %} {% block content %}
|
||||
<section class="hero">
|
||||
<div>
|
||||
<h1 class="title">Arbitrade Dashboard</h1>
|
||||
<p class="subtitle">Live execution, P&L, and system state.</p>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<a
|
||||
class="button"
|
||||
href="{{ metrics_endpoint }}"
|
||||
hx-get="{{ metrics_endpoint }}"
|
||||
hx-target="#metrics-panel"
|
||||
hx-swap="outerHTML"
|
||||
>Refresh metrics</a
|
||||
>
|
||||
<a class="button secondary" href="/health">Health</a>
|
||||
<a class="button secondary" href="/dashboard/backtesting">Backtesting</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="metrics-shell"
|
||||
hx-get="{{ metrics_endpoint }}"
|
||||
hx-target="this"
|
||||
hx-trigger="load, every 15s"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/metrics.html" %}
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="overview-shell"
|
||||
hx-get="{{ overview_endpoint }}"
|
||||
hx-target="this"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/overview.html" %}
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="controls-shell"
|
||||
hx-get="{{ controls_endpoint }}"
|
||||
hx-target="this"
|
||||
hx-trigger="load, every 20s"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/controls.html" %}
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="charts-shell"
|
||||
hx-get="{{ charts_endpoint }}"
|
||||
hx-target="this"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/charts.html" %}
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="audit-shell"
|
||||
hx-get="{{ audit_endpoint }}"
|
||||
hx-target="this"
|
||||
hx-trigger="load, every 20s"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{% include "partials/audit.html" %}
|
||||
</section>
|
||||
{% endblock %} {% block scripts %}
|
||||
<script>
|
||||
window.arbitradeRenderCharts = (payload) => {
|
||||
const chartHost = document.getElementById("opportunity-chart");
|
||||
if (!chartHost || typeof Chart === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = Chart.getChart(chartHost);
|
||||
if (existing) {
|
||||
existing.destroy();
|
||||
}
|
||||
|
||||
const data = JSON.parse(payload);
|
||||
if (!data.has_chart_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
new Chart(chartHost, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: data.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Net %",
|
||||
data: data.net_pct_values,
|
||||
borderColor: "#2d6cdf",
|
||||
backgroundColor: "rgba(45, 108, 223, 0.18)",
|
||||
tension: 0.3,
|
||||
fill: true,
|
||||
yAxisID: "y",
|
||||
},
|
||||
{
|
||||
label: "Est profit USD",
|
||||
data: data.est_profit_values,
|
||||
borderColor: "#52c41a",
|
||||
backgroundColor: "rgba(82, 196, 26, 0.12)",
|
||||
tension: 0.3,
|
||||
fill: false,
|
||||
yAxisID: "y1",
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
mode: "index",
|
||||
intersect: false,
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: "#e5eefb",
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: "#9fb2d0",
|
||||
maxRotation: 0,
|
||||
autoSkip: true,
|
||||
},
|
||||
grid: {
|
||||
color: "rgba(255, 255, 255, 0.06)",
|
||||
},
|
||||
},
|
||||
y: {
|
||||
position: "left",
|
||||
ticks: {
|
||||
color: "#9fb2d0",
|
||||
},
|
||||
grid: {
|
||||
color: "rgba(255, 255, 255, 0.06)",
|
||||
},
|
||||
},
|
||||
y1: {
|
||||
position: "right",
|
||||
ticks: {
|
||||
color: "#9fb2d0",
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const stream = new EventSource("{{ stream_endpoint }}");
|
||||
stream.addEventListener("metrics", (event) => {
|
||||
const panel = document.getElementById("metrics-panel");
|
||||
if (panel) {
|
||||
panel.outerHTML = JSON.parse(event.data);
|
||||
}
|
||||
});
|
||||
const overviewStream = new EventSource("{{ overview_stream_endpoint }}");
|
||||
overviewStream.addEventListener("overview", (event) => {
|
||||
const panel = document.getElementById("overview-panel");
|
||||
if (panel) {
|
||||
panel.outerHTML = JSON.parse(event.data);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,14 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="card">
|
||||
<h1>Arbitrade Bootstrap Complete</h1>
|
||||
<p><span class="badge">Status: {{ status }}</span></p>
|
||||
<p>UTC: {{ time }}</p>
|
||||
<p>
|
||||
Health JSON:
|
||||
<a href="/health" hx-get="/health" hx-target="#health-json" hx-swap="innerHTML">refresh</a>
|
||||
</p>
|
||||
<pre id="health-json">{"status":"ok","service":"arbitrade"}</pre>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -1,37 +0,0 @@
|
||||
<div id="audit-panel" class="panel" style="margin-top: 16px">
|
||||
<div class="label">Audit Trail</div>
|
||||
<div class="meta">Generated {{ generated_at }}</div>
|
||||
|
||||
<div style="overflow-x: auto; margin-top: 12px">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 8px">Time</th>
|
||||
<th style="text-align: left; padding: 8px">Actor</th>
|
||||
<th style="text-align: left; padding: 8px">Event</th>
|
||||
<th style="text-align: left; padding: 8px">Decision</th>
|
||||
<th style="text-align: left; padding: 8px">Payload</th>
|
||||
<th style="text-align: left; padding: 8px">Correlation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if entries %}
|
||||
{% for entry in entries %}
|
||||
<tr>
|
||||
<td style="padding: 8px; color: #9fb2d0">{{ entry.occurred_at }}</td>
|
||||
<td style="padding: 8px">{{ entry.actor }}</td>
|
||||
<td style="padding: 8px">{{ entry.event_type }}</td>
|
||||
<td style="padding: 8px">{{ entry.decision }}</td>
|
||||
<td style="padding: 8px; color: #9fb2d0">{{ entry.payload }}</td>
|
||||
<td style="padding: 8px; color: #9fb2d0">{{ entry.correlation_id }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" style="padding: 8px; color: #9fb2d0">No audit entries yet.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,142 +0,0 @@
|
||||
<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>
|
||||
@@ -1,37 +0,0 @@
|
||||
<div
|
||||
id="charts-panel"
|
||||
class="panel"
|
||||
style="margin-top: 16px"
|
||||
x-data="{ expanded: true }"
|
||||
>
|
||||
<div class="chart-head">
|
||||
<div>
|
||||
<div class="label">Opportunity Trend</div>
|
||||
<div class="meta">Recent opportunities from DuckDB. Updated {{ generated_at }}</div>
|
||||
</div>
|
||||
<button type="button" class="button secondary" x-on:click="expanded = !expanded">
|
||||
<span x-text="expanded ? 'Hide chart' : 'Show chart'"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div x-show="expanded" x-transition style="margin-top: 16px">
|
||||
<div class="card" style="padding: 12px">
|
||||
{% if has_chart_data %}
|
||||
<canvas id="opportunity-chart" class="chart-canvas"></canvas>
|
||||
<script>
|
||||
window.arbitradeRenderCharts(
|
||||
{{ {
|
||||
"has_chart_data": has_chart_data,
|
||||
"labels": labels,
|
||||
"net_pct_values": net_pct_values,
|
||||
"est_profit_values": est_profit_values,
|
||||
"cycles": cycles,
|
||||
} | tojson }}
|
||||
);
|
||||
</script>
|
||||
{% else %}
|
||||
<div class="meta">No opportunity data yet.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,171 +0,0 @@
|
||||
<div id="controls-panel" class="panel" style="margin-top: 16px">
|
||||
<div class="grid">
|
||||
<article class="card">
|
||||
<div class="label">Runtime Status</div>
|
||||
<div class="value">{{ execution_status }}</div>
|
||||
<div class="meta">Updated {{ updated_at }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Kill Switch</div>
|
||||
<div class="value">{{ kill_switch_status }}</div>
|
||||
<div class="meta">Reason {{ kill_switch_reason }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Config Snapshot</div>
|
||||
<div class="meta">Paper trading: {{ paper_trading_mode }}</div>
|
||||
<div class="meta">Trade capital: {{ trade_capital_usd }}</div>
|
||||
<div class="meta">Max trade capital: {{ max_trade_capital_usd }}</div>
|
||||
<div class="meta">Max concurrent trades: {{ max_concurrent_trades }}</div>
|
||||
<div class="meta">Tradable pairs: {{ tradable_pairs_display }}</div>
|
||||
<div class="meta">Strategy mode: {{ strategy_mode }}</div>
|
||||
<div class="meta">Profit threshold: {{ strategy_profit_threshold }}</div>
|
||||
<div class="meta">Max depth levels: {{ strategy_max_depth_levels }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Alerting</div>
|
||||
<div class="meta">Status: {{ alerts_enabled }}</div>
|
||||
<div class="meta">Channels: {{ alerts_channels }}</div>
|
||||
<div class="meta">Min severity: {{ alerts_min_severity }}</div>
|
||||
<div class="meta">Dedup window: {{ alerts_dedup_seconds }}s</div>
|
||||
<div class="meta">Last result: {{ alerts_last_result }}</div>
|
||||
<div class="meta">Last attempted: {{ alerts_last_attempted_at }}</div>
|
||||
<div class="meta">Last success: {{ alerts_last_success_at }}</div>
|
||||
<div class="meta">Last event: {{ alerts_last_event_title }}</div>
|
||||
<div class="meta">Last error: {{ alerts_last_error }}</div>
|
||||
{% if alerts_last_channel_results %} {% for item in
|
||||
alerts_last_channel_results %}
|
||||
<div class="meta">{{ item }}</div>
|
||||
{% endfor %} {% endif %}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid"
|
||||
style="
|
||||
margin-top: 16px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
"
|
||||
>
|
||||
<article class="card">
|
||||
<div class="label">Execution Controls</div>
|
||||
<div class="control-actions">
|
||||
<form
|
||||
hx-post="{{ start_endpoint }}"
|
||||
hx-target="#controls-panel"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit" class="button">Start</button>
|
||||
</form>
|
||||
<form
|
||||
hx-post="{{ stop_endpoint }}"
|
||||
hx-target="#controls-panel"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit" class="button secondary">Stop</button>
|
||||
</form>
|
||||
<form
|
||||
hx-post="{{ kill_switch_endpoint }}"
|
||||
hx-target="#controls-panel"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<input type="hidden" name="reason" value="manual" />
|
||||
<button type="submit" class="button danger">
|
||||
Trigger Kill Switch
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Edit Config</div>
|
||||
<form
|
||||
class="form-grid"
|
||||
hx-post="{{ config_endpoint }}"
|
||||
hx-target="#controls-panel"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<label class="field">
|
||||
<span>Trade capital USD</span>
|
||||
<input
|
||||
name="trade_capital_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ trade_capital_usd_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max trade capital USD</span>
|
||||
<input
|
||||
name="max_trade_capital_usd"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value="{{ max_trade_capital_usd_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max concurrent trades</span>
|
||||
<input
|
||||
name="max_concurrent_trades"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
value="{{ max_concurrent_trades_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Tradable pairs</span>
|
||||
<input
|
||||
name="tradable_pairs"
|
||||
type="text"
|
||||
placeholder="BTC/USD, ETH/BTC"
|
||||
value="{{ tradable_pairs_value }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Strategy mode</span>
|
||||
<select name="strategy_mode">
|
||||
{% set sel = "selected" if strategy_mode == "incremental" else "" %}
|
||||
<option value="incremental" {{ sel }}>incremental</option>
|
||||
{% set sel = "selected" if strategy_mode == "paper" else "" %}
|
||||
<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">
|
||||
<span>Strategy profit threshold</span>
|
||||
<input
|
||||
name="strategy_profit_threshold"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.0001"
|
||||
value="{{ strategy_profit_threshold }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Max depth levels</span>
|
||||
<input
|
||||
name="strategy_max_depth_levels"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
value="{{ strategy_max_depth_levels }}"
|
||||
/>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
{% set check = "checked" if paper_trading_mode == "enabled" else "" %}
|
||||
<input name="paper_trading_mode" type="checkbox" {{ check }} />
|
||||
<span>Paper trading mode</span>
|
||||
</label>
|
||||
<button type="submit" class="button">Save config</button>
|
||||
</form>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
<div id="metrics-panel" class="panel">
|
||||
<div class="grid">
|
||||
<article class="card">
|
||||
<div class="label">Realized P&L</div>
|
||||
<div class="value">{{ realized_pnl }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Win Rate</div>
|
||||
<div class="value">{{ win_rate }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Avg Trade Duration</div>
|
||||
<div class="value">{{ avg_trade_duration }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Opportunities / Min</div>
|
||||
<div class="value">{{ opportunities_per_minute }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Fill Rate</div>
|
||||
<div class="value">{{ fill_rate }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Latency p50 / p95 / p99</div>
|
||||
<div class="value">
|
||||
{{ latency_p50 }} | {{ latency_p95 }} | {{ latency_p99 }}
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="meta">Updated {{ generated_at }}</div>
|
||||
</div>
|
||||
@@ -1,67 +0,0 @@
|
||||
<div id="overview-panel" class="panel" style="margin-top: 16px">
|
||||
<div class="grid">
|
||||
<article class="card">
|
||||
<div class="label">Status</div>
|
||||
<div class="value">{{ status }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Balances</div>
|
||||
<div class="value">{{ balances }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Open Trades</div>
|
||||
<div class="value">{{ open_trade_count }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Realized P&L</div>
|
||||
<div class="value">{{ realized_pnl_total }}</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid"
|
||||
style="
|
||||
margin-top: 16px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
"
|
||||
>
|
||||
<article class="card">
|
||||
<div class="label">Open Trades</div>
|
||||
<ul>
|
||||
{% for trade in open_trades %}
|
||||
<li>
|
||||
{{ trade.trade_ref }} - {{ trade.status }} - {{ trade.cycle }} - {{
|
||||
trade.started_at }}
|
||||
</li>
|
||||
{% else %}
|
||||
<li>No open trades.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Balances Snapshot</div>
|
||||
<div
|
||||
class="value"
|
||||
style="font-size: 1rem; font-weight: 500; word-break: break-word"
|
||||
>
|
||||
{{ balances }}
|
||||
</div>
|
||||
<div class="meta">Total value {{ total_value }}</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="label">Opportunity Feed</div>
|
||||
<ul>
|
||||
{% for opp in opportunities %}
|
||||
<li>
|
||||
{{ opp.cycle }} - {{ opp.net_pct }} - {{ opp.est_profit }} - {{
|
||||
opp.detected_at }}
|
||||
</li>
|
||||
{% else %}
|
||||
<li>No opportunities.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="meta">Updated {{ generated_at }}</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user