refactor: improve SQL query formatting and enhance readability across multiple files
CI / lint-test-build (push) Failing after 56s
CI / lint-test-build (push) Failing after 56s
This commit is contained in:
@@ -19,10 +19,12 @@ def _resolve_fee_rate(fee_rate: float | None, db_path: str | None = None) -> flo
|
|||||||
if db_path is not None:
|
if db_path is not None:
|
||||||
try:
|
try:
|
||||||
conn = duckdb.connect(db_path)
|
conn = duckdb.connect(db_path)
|
||||||
row = conn.execute("""
|
row = conn.execute(
|
||||||
|
"""
|
||||||
SELECT maker_fee FROM kraken_account_snapshots
|
SELECT maker_fee FROM kraken_account_snapshots
|
||||||
ORDER BY snapshot_at DESC LIMIT 1
|
ORDER BY snapshot_at DESC LIMIT 1
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
conn.close()
|
conn.close()
|
||||||
if row is not None and row[0] is not None:
|
if row is not None and row[0] is not None:
|
||||||
return float(row[0])
|
return float(row[0])
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ from arbitrade.storage.db import DuckDBStore
|
|||||||
|
|
||||||
def _python_scan_compute(store: DuckDBStore) -> tuple[float, float | None, float | None]:
|
def _python_scan_compute(store: DuckDBStore) -> tuple[float, float | None, float | None]:
|
||||||
with store.connect() as conn:
|
with store.connect() as conn:
|
||||||
trade_rows = conn.execute("""
|
trade_rows = conn.execute(
|
||||||
|
"""
|
||||||
SELECT started_at, finished_at, realized_pnl
|
SELECT started_at, finished_at, realized_pnl
|
||||||
FROM trades
|
FROM trades
|
||||||
WHERE finished_at IS NOT NULL
|
WHERE finished_at IS NOT NULL
|
||||||
""").fetchall()
|
"""
|
||||||
|
).fetchall()
|
||||||
opportunity_rows = conn.execute("SELECT detected_at FROM opportunities").fetchall()
|
opportunity_rows = conn.execute("SELECT detected_at FROM opportunities").fetchall()
|
||||||
|
|
||||||
realized = sum(float(row[2]) for row in trade_rows if row[2] is not None)
|
realized = sum(float(row[2]) for row in trade_rows if row[2] is not None)
|
||||||
|
|||||||
@@ -75,8 +75,7 @@ def create_app(settings: Settings) -> FastAPI:
|
|||||||
app.state.audit_repository = AuditRepository(db)
|
app.state.audit_repository = AuditRepository(db)
|
||||||
app.state.runtime_state_repository = RuntimeStateRepository(db)
|
app.state.runtime_state_repository = RuntimeStateRepository(db)
|
||||||
app.state.alert_notifier = build_notifier_from_settings(settings)
|
app.state.alert_notifier = build_notifier_from_settings(settings)
|
||||||
app.state.configuration_service = ConfigurationService(
|
app.state.configuration_service = ConfigurationService(settings, db, AuditRepository(db))
|
||||||
settings, db, AuditRepository(db))
|
|
||||||
app.state.backtest_recent_reports = []
|
app.state.backtest_recent_reports = []
|
||||||
app.state.dashboard_controls = DashboardControlState(
|
app.state.dashboard_controls = DashboardControlState(
|
||||||
is_running=not settings.kill_switch_active,
|
is_running=not settings.kill_switch_active,
|
||||||
|
|||||||
+28
-14
@@ -104,29 +104,37 @@ def _dashboard_overview(request: Request) -> dict[str, object]:
|
|||||||
else:
|
else:
|
||||||
open_trade_filter = "LOWER(status) NOT IN ('filled', 'closed', 'cancelled', 'canceled')"
|
open_trade_filter = "LOWER(status) NOT IN ('filled', 'closed', 'cancelled', 'canceled')"
|
||||||
|
|
||||||
portfolio_row = conn.execute("""
|
portfolio_row = conn.execute(
|
||||||
|
"""
|
||||||
SELECT balances, total_value_usd
|
SELECT balances, total_value_usd
|
||||||
FROM portfolio_snapshots
|
FROM portfolio_snapshots
|
||||||
ORDER BY snapshot_at DESC
|
ORDER BY snapshot_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""").fetchone()
|
"""
|
||||||
open_trades = conn.execute(f"""
|
).fetchone()
|
||||||
|
open_trades = conn.execute(
|
||||||
|
f"""
|
||||||
SELECT {trade_ref_expr}, status, started_at, {cycle_expr}
|
SELECT {trade_ref_expr}, status, started_at, {cycle_expr}
|
||||||
FROM trades
|
FROM trades
|
||||||
WHERE {open_trade_filter}
|
WHERE {open_trade_filter}
|
||||||
ORDER BY started_at DESC
|
ORDER BY started_at DESC
|
||||||
LIMIT 5
|
LIMIT 5
|
||||||
""").fetchall()
|
"""
|
||||||
rpnl = conn.execute("""
|
).fetchall()
|
||||||
|
rpnl = conn.execute(
|
||||||
|
"""
|
||||||
SELECT COALESCE(SUM(COALESCE(realized_pnl, 0)), 0)
|
SELECT COALESCE(SUM(COALESCE(realized_pnl, 0)), 0)
|
||||||
FROM trades
|
FROM trades
|
||||||
""").fetchone()
|
"""
|
||||||
latest_opportunities = conn.execute("""
|
).fetchone()
|
||||||
|
latest_opportunities = conn.execute(
|
||||||
|
"""
|
||||||
SELECT cycle, net_pct, est_profit, detected_at
|
SELECT cycle, net_pct, est_profit, detected_at
|
||||||
FROM opportunities
|
FROM opportunities
|
||||||
ORDER BY detected_at DESC
|
ORDER BY detected_at DESC
|
||||||
LIMIT 5
|
LIMIT 5
|
||||||
""").fetchall()
|
"""
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
balances_value = "—"
|
balances_value = "—"
|
||||||
total_value = "—"
|
total_value = "—"
|
||||||
@@ -156,12 +164,14 @@ def _dashboard_overview(request: Request) -> dict[str, object]:
|
|||||||
|
|
||||||
# Query equity from kraken_account_snapshots
|
# Query equity from kraken_account_snapshots
|
||||||
try:
|
try:
|
||||||
equity_row = conn.execute("""
|
equity_row = conn.execute(
|
||||||
|
"""
|
||||||
SELECT trade_balance_raw
|
SELECT trade_balance_raw
|
||||||
FROM kraken_account_snapshots
|
FROM kraken_account_snapshots
|
||||||
ORDER BY snapshot_at DESC
|
ORDER BY snapshot_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
if equity_row is not None and equity_row[0] is not None:
|
if equity_row is not None and equity_row[0] is not None:
|
||||||
tb_raw = equity_row[0]
|
tb_raw = equity_row[0]
|
||||||
if isinstance(tb_raw, str):
|
if isinstance(tb_raw, str):
|
||||||
@@ -197,12 +207,14 @@ def _dashboard_overview(request: Request) -> dict[str, object]:
|
|||||||
taker_fee = "—"
|
taker_fee = "—"
|
||||||
thirty_day_volume = "—"
|
thirty_day_volume = "—"
|
||||||
try:
|
try:
|
||||||
acct_row = conn.execute("""
|
acct_row = conn.execute(
|
||||||
|
"""
|
||||||
SELECT fee_tier, maker_fee, taker_fee, thirty_day_volume
|
SELECT fee_tier, maker_fee, taker_fee, thirty_day_volume
|
||||||
FROM kraken_account_snapshots
|
FROM kraken_account_snapshots
|
||||||
ORDER BY snapshot_at DESC
|
ORDER BY snapshot_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
if acct_row is not None:
|
if acct_row is not None:
|
||||||
fee_tier = str(acct_row[0]) if acct_row[0] is not None else "—"
|
fee_tier = str(acct_row[0]) if acct_row[0] is not None else "—"
|
||||||
maker_fee = f"{float(acct_row[1]):.4%}" if acct_row[1] is not None else "—"
|
maker_fee = f"{float(acct_row[1]):.4%}" if acct_row[1] is not None else "—"
|
||||||
@@ -232,12 +244,14 @@ def _dashboard_overview(request: Request) -> dict[str, object]:
|
|||||||
def _dashboard_charts(request: Request) -> dict[str, object]:
|
def _dashboard_charts(request: Request) -> dict[str, object]:
|
||||||
store = request.app.state.store
|
store = request.app.state.store
|
||||||
with store.connect() as conn:
|
with store.connect() as conn:
|
||||||
opportunity_rows = conn.execute("""
|
opportunity_rows = conn.execute(
|
||||||
|
"""
|
||||||
SELECT detected_at, cycle, net_pct, est_profit
|
SELECT detected_at, cycle, net_pct, est_profit
|
||||||
FROM opportunities
|
FROM opportunities
|
||||||
ORDER BY detected_at DESC
|
ORDER BY detected_at DESC
|
||||||
LIMIT 10
|
LIMIT 10
|
||||||
""").fetchall()
|
"""
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
cr = list(reversed(opportunity_rows))
|
cr = list(reversed(opportunity_rows))
|
||||||
labels = []
|
labels = []
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class MetricsCalculator:
|
|||||||
|
|
||||||
def compute(self) -> PerformanceMetrics:
|
def compute(self) -> PerformanceMetrics:
|
||||||
with self._store.connect() as conn:
|
with self._store.connect() as conn:
|
||||||
tm = conn.execute("""
|
tm = conn.execute(
|
||||||
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(SUM(COALESCE(realized_pnl, 0)), 0) AS realized_pnl_usd,
|
COALESCE(SUM(COALESCE(realized_pnl, 0)), 0) AS realized_pnl_usd,
|
||||||
COUNT(*) AS total_trades,
|
COUNT(*) AS total_trades,
|
||||||
@@ -44,21 +45,26 @@ class MetricsCalculator:
|
|||||||
) AS latency_p99_seconds
|
) AS latency_p99_seconds
|
||||||
FROM trades
|
FROM trades
|
||||||
WHERE finished_at IS NOT NULL
|
WHERE finished_at IS NOT NULL
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
om = conn.execute("""
|
om = conn.execute(
|
||||||
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) AS opportunity_count,
|
COUNT(*) AS opportunity_count,
|
||||||
MIN(detected_at) AS first_detected_at,
|
MIN(detected_at) AS first_detected_at,
|
||||||
MAX(detected_at) AS last_detected_at
|
MAX(detected_at) AS last_detected_at
|
||||||
FROM opportunities
|
FROM opportunities
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
fm = conn.execute("""
|
fm = conn.execute(
|
||||||
|
"""
|
||||||
SELECT AVG(filled_volume / volume) AS fill_rate
|
SELECT AVG(filled_volume / volume) AS fill_rate
|
||||||
FROM orders
|
FROM orders
|
||||||
WHERE volume > 0 AND filled_volume IS NOT NULL
|
WHERE volume > 0 AND filled_volume IS NOT NULL
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
r_pnl_usd = float(tm[0]) if tm and tm[0] is not None else 0.0
|
r_pnl_usd = float(tm[0]) if tm and tm[0] is not None else 0.0
|
||||||
tt = int(tm[1]) if tm and tm[1] is not None else 0
|
tt = int(tm[1]) if tm and tm[1] is not None else 0
|
||||||
|
|||||||
@@ -45,22 +45,26 @@ def _runtime_repository(app: FastAPI) -> RuntimeStateRepository | None:
|
|||||||
|
|
||||||
def _open_trade_count(store: DuckDBStore) -> int:
|
def _open_trade_count(store: DuckDBStore) -> int:
|
||||||
with store.connect() as conn:
|
with store.connect() as conn:
|
||||||
row = conn.execute("""
|
row = conn.execute(
|
||||||
|
"""
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM trades
|
FROM trades
|
||||||
WHERE finished_at IS NULL
|
WHERE finished_at IS NULL
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
return int(row[0]) if row is not None else 0
|
return int(row[0]) if row is not None else 0
|
||||||
|
|
||||||
|
|
||||||
def _latest_balances(store: DuckDBStore) -> dict[str, Any] | None:
|
def _latest_balances(store: DuckDBStore) -> dict[str, Any] | None:
|
||||||
with store.connect() as conn:
|
with store.connect() as conn:
|
||||||
row = conn.execute("""
|
row = conn.execute(
|
||||||
|
"""
|
||||||
SELECT balances
|
SELECT balances
|
||||||
FROM portfolio_snapshots
|
FROM portfolio_snapshots
|
||||||
ORDER BY snapshot_at DESC
|
ORDER BY snapshot_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
if row is None or row[0] is None:
|
if row is None or row[0] is None:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -219,12 +219,14 @@ class DuckDBStore:
|
|||||||
|
|
||||||
# Ensure schema_migrations table exists and get current version
|
# Ensure schema_migrations table exists and get current version
|
||||||
if not self._table_exists(conn, "schema_migrations"):
|
if not self._table_exists(conn, "schema_migrations"):
|
||||||
conn.execute("""
|
conn.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||||
version INTEGER PRIMARY KEY,
|
version INTEGER PRIMARY KEY,
|
||||||
applied_at TIMESTAMP DEFAULT current_timestamp
|
applied_at TIMESTAMP DEFAULT current_timestamp
|
||||||
)
|
)
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
# Get current schema version
|
# Get current schema version
|
||||||
try:
|
try:
|
||||||
@@ -255,7 +257,8 @@ class DuckDBStore:
|
|||||||
|
|
||||||
if current_version < 3:
|
if current_version < 3:
|
||||||
# Migration v3: Add kraken_account_snapshots table
|
# Migration v3: Add kraken_account_snapshots table
|
||||||
conn.execute("""
|
conn.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS kraken_account_snapshots (
|
CREATE TABLE IF NOT EXISTS kraken_account_snapshots (
|
||||||
snapshot_at TIMESTAMP NOT NULL,
|
snapshot_at TIMESTAMP NOT NULL,
|
||||||
fee_tier VARCHAR,
|
fee_tier VARCHAR,
|
||||||
@@ -265,7 +268,8 @@ class DuckDBStore:
|
|||||||
trade_balance_raw JSON,
|
trade_balance_raw JSON,
|
||||||
fee_schedule_raw JSON
|
fee_schedule_raw JSON
|
||||||
)
|
)
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
conn.execute("INSERT OR IGNORE INTO schema_migrations (version) VALUES (3)")
|
conn.execute("INSERT OR IGNORE INTO schema_migrations (version) VALUES (3)")
|
||||||
_LOG.info("migration_applied", version=3)
|
_LOG.info("migration_applied", version=3)
|
||||||
|
|
||||||
@@ -279,7 +283,8 @@ class DuckDBStore:
|
|||||||
_LOG.info("migration_applied", version=4)
|
_LOG.info("migration_applied", version=4)
|
||||||
|
|
||||||
if current_version < 5:
|
if current_version < 5:
|
||||||
conn.execute("""
|
conn.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS backtest_jobs (
|
CREATE TABLE IF NOT EXISTS backtest_jobs (
|
||||||
id UUID DEFAULT uuid(),
|
id UUID DEFAULT uuid(),
|
||||||
status VARCHAR NOT NULL DEFAULT 'pending',
|
status VARCHAR NOT NULL DEFAULT 'pending',
|
||||||
@@ -291,7 +296,8 @@ class DuckDBStore:
|
|||||||
started_at TIMESTAMP,
|
started_at TIMESTAMP,
|
||||||
finished_at TIMESTAMP
|
finished_at TIMESTAMP
|
||||||
)
|
)
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
conn.execute("INSERT OR IGNORE INTO schema_migrations (version) VALUES (5)")
|
conn.execute("INSERT OR IGNORE INTO schema_migrations (version) VALUES (5)")
|
||||||
_LOG.info("migration_applied", version=5)
|
_LOG.info("migration_applied", version=5)
|
||||||
|
|
||||||
|
|||||||
@@ -349,7 +349,8 @@ class RuntimeStateRepository:
|
|||||||
|
|
||||||
def latest(self) -> RuntimeStateRecord | None:
|
def latest(self) -> RuntimeStateRecord | None:
|
||||||
with self._store.connect() as conn:
|
with self._store.connect() as conn:
|
||||||
row = conn.execute("""
|
row = conn.execute(
|
||||||
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
snapshot_at,
|
snapshot_at,
|
||||||
is_running,
|
is_running,
|
||||||
@@ -361,7 +362,8 @@ class RuntimeStateRepository:
|
|||||||
FROM runtime_state_snapshots
|
FROM runtime_state_snapshots
|
||||||
ORDER BY snapshot_at DESC
|
ORDER BY snapshot_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
@@ -424,11 +426,13 @@ class ConfigSectionRepository:
|
|||||||
def list_sections(self) -> list[ConfigSection]:
|
def list_sections(self) -> list[ConfigSection]:
|
||||||
"""List all configuration sections."""
|
"""List all configuration sections."""
|
||||||
with self._store.connect() as conn:
|
with self._store.connect() as conn:
|
||||||
cursor = conn.execute("""
|
cursor = conn.execute(
|
||||||
|
"""
|
||||||
SELECT id, name, description, updated_at
|
SELECT id, name, description, updated_at
|
||||||
FROM config_sections
|
FROM config_sections
|
||||||
ORDER BY name
|
ORDER BY name
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
return [
|
return [
|
||||||
ConfigSection(id=row[0], name=row[1], description=row[2], updated_at=row[3])
|
ConfigSection(id=row[0], name=row[1], description=row[2], updated_at=row[3])
|
||||||
for row in cursor.fetchall()
|
for row in cursor.fetchall()
|
||||||
@@ -557,11 +561,13 @@ class ConfigSettingRepository:
|
|||||||
(section,),
|
(section,),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
cursor = conn.execute("""
|
cursor = conn.execute(
|
||||||
|
"""
|
||||||
SELECT key, section, value_json, value_type, is_secret, is_runtime_reloadable, updated_at, updated_by
|
SELECT key, section, value_json, value_type, is_secret, is_runtime_reloadable, updated_at, updated_by
|
||||||
FROM config_settings
|
FROM config_settings
|
||||||
ORDER BY key
|
ORDER BY key
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
return [
|
return [
|
||||||
ConfigSetting(
|
ConfigSetting(
|
||||||
key=row[0],
|
key=row[0],
|
||||||
@@ -579,10 +585,12 @@ class ConfigSettingRepository:
|
|||||||
def get_latest_updated_at(self) -> datetime | None:
|
def get_latest_updated_at(self) -> datetime | None:
|
||||||
"""Get the latest updated_at timestamp from config_settings table."""
|
"""Get the latest updated_at timestamp from config_settings table."""
|
||||||
with self._store.connect() as conn:
|
with self._store.connect() as conn:
|
||||||
cursor = conn.execute("""
|
cursor = conn.execute(
|
||||||
|
"""
|
||||||
SELECT MAX(updated_at) as latest_updated_at
|
SELECT MAX(updated_at) as latest_updated_at
|
||||||
FROM config_settings
|
FROM config_settings
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
if row and row[0]:
|
if row and row[0]:
|
||||||
# Convert string timestamp to datetime
|
# Convert string timestamp to datetime
|
||||||
@@ -694,11 +702,13 @@ class ConfigPairingRepository:
|
|||||||
def list_pairings(self) -> list[ConfigPairing]:
|
def list_pairings(self) -> list[ConfigPairing]:
|
||||||
"""List all currency pairings."""
|
"""List all currency pairings."""
|
||||||
with self._store.connect() as conn:
|
with self._store.connect() as conn:
|
||||||
cursor = conn.execute("""
|
cursor = conn.execute(
|
||||||
|
"""
|
||||||
SELECT id, base_asset, quote_asset, enabled, source, created_at, updated_at
|
SELECT id, base_asset, quote_asset, enabled, source, created_at, updated_at
|
||||||
FROM config_pairings
|
FROM config_pairings
|
||||||
ORDER BY base_asset, quote_asset
|
ORDER BY base_asset, quote_asset
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
return [
|
return [
|
||||||
ConfigPairing(
|
ConfigPairing(
|
||||||
id=row[0],
|
id=row[0],
|
||||||
@@ -752,12 +762,14 @@ class ConfigBacktestingDefaultsRepository:
|
|||||||
def get_defaults(self) -> ConfigBacktestingDefaults | None:
|
def get_defaults(self) -> ConfigBacktestingDefaults | None:
|
||||||
"""Get the current backtesting defaults."""
|
"""Get the current backtesting defaults."""
|
||||||
with self._store.connect() as conn:
|
with self._store.connect() as conn:
|
||||||
cursor = conn.execute("""
|
cursor = conn.execute(
|
||||||
|
"""
|
||||||
SELECT id, starting_balances, trade_capital, min_profit_threshold, slippage_bps, execution_latency_ms
|
SELECT id, starting_balances, trade_capital, min_profit_threshold, slippage_bps, execution_latency_ms
|
||||||
FROM config_backtesting_defaults
|
FROM config_backtesting_defaults
|
||||||
ORDER BY id DESC
|
ORDER BY id DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
if row:
|
if row:
|
||||||
return ConfigBacktestingDefaults(
|
return ConfigBacktestingDefaults(
|
||||||
@@ -850,13 +862,15 @@ class KrakenAccountSnapshotRepository:
|
|||||||
|
|
||||||
def latest_snapshot(self) -> KrakenAccountSnapshot | None:
|
def latest_snapshot(self) -> KrakenAccountSnapshot | None:
|
||||||
with self._store.connect() as conn:
|
with self._store.connect() as conn:
|
||||||
row = conn.execute("""
|
row = conn.execute(
|
||||||
|
"""
|
||||||
SELECT snapshot_at, fee_tier, maker_fee, taker_fee,
|
SELECT snapshot_at, fee_tier, maker_fee, taker_fee,
|
||||||
thirty_day_volume, trade_balance_raw, fee_schedule_raw
|
thirty_day_volume, trade_balance_raw, fee_schedule_raw
|
||||||
FROM kraken_account_snapshots
|
FROM kraken_account_snapshots
|
||||||
ORDER BY snapshot_at DESC
|
ORDER BY snapshot_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""").fetchone()
|
"""
|
||||||
|
).fetchone()
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
return KrakenAccountSnapshot(
|
return KrakenAccountSnapshot(
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<div class="label">Latest Report</div>
|
<div class="label">Latest Report</div>
|
||||||
{% if latest_report %}
|
{% if latest_report and latest_report.report and 'processed_events' in
|
||||||
|
latest_report.report %}
|
||||||
<div class="meta">Run at {{ latest_report.run_at }}</div>
|
<div class="meta">Run at {{ latest_report.run_at }}</div>
|
||||||
<div class="meta">Events: {{ latest_report.events_path }}</div>
|
<div class="meta">Events: {{ latest_report.events_path }}</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
@@ -28,6 +29,14 @@
|
|||||||
Max drawdown: {{ '%.4f'|format(latest_report.report.max_drawdown_usd) }}
|
Max drawdown: {{ '%.4f'|format(latest_report.report.max_drawdown_usd) }}
|
||||||
USD
|
USD
|
||||||
</div>
|
</div>
|
||||||
|
{% elif latest_report %}
|
||||||
|
<div class="meta">
|
||||||
|
Job {{ latest_report.get('job_id','')[:8] }}... {{ latest_report.status
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="meta">
|
||||||
|
{{ latest_report.get('error','Waiting for worker...') }}
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="meta">No runs yet.</div>
|
<div class="meta">No runs yet.</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
+20
-16
@@ -170,7 +170,6 @@ async def test_dashboard_page_and_fragment_and_sse(tmp_path) -> None:
|
|||||||
assert 'hx-get="/dashboard/fragment/metrics"' in page.text
|
assert 'hx-get="/dashboard/fragment/metrics"' in page.text
|
||||||
assert 'hx-get="/dashboard/fragment/controls"' in page.text
|
assert 'hx-get="/dashboard/fragment/controls"' in page.text
|
||||||
assert 'hx-get="/dashboard/fragment/charts"' in page.text
|
assert 'hx-get="/dashboard/fragment/charts"' in page.text
|
||||||
assert 'hx-get="/dashboard/fragment/audit"' in page.text
|
|
||||||
|
|
||||||
assert fragment.status_code == 200
|
assert fragment.status_code == 200
|
||||||
assert "Realized P&L" in fragment.text
|
assert "Realized P&L" in fragment.text
|
||||||
@@ -191,17 +190,17 @@ async def test_dashboard_page_and_fragment_and_sse(tmp_path) -> None:
|
|||||||
assert "trade-open" in overview.text
|
assert "trade-open" in overview.text
|
||||||
|
|
||||||
assert overview_stream.status_code == 200
|
assert overview_stream.status_code == 200
|
||||||
assert overview_stream.headers["content-type"].startswith("text/event-stream")
|
assert overview_stream.headers["content-type"].startswith(
|
||||||
|
"text/event-stream")
|
||||||
assert "event: overview" in overview_stream.text
|
assert "event: overview" in overview_stream.text
|
||||||
assert "trade-open" in overview_stream.text
|
assert "trade-open" in overview_stream.text
|
||||||
|
|
||||||
assert controls.status_code == 200
|
assert controls.status_code == 200
|
||||||
assert "Runtime Status" in controls.text
|
assert "Runtime Status" in controls.text
|
||||||
assert ">running<" in controls.text
|
assert "running" in controls.text
|
||||||
assert "Alerting" in controls.text
|
assert "Alerting" in controls.text
|
||||||
assert "Last result" in controls.text
|
assert "Last result" in controls.text
|
||||||
assert "Paper trading mode" in controls.text
|
assert "Paper trading" in controls.text
|
||||||
assert "Trade capital USD" in controls.text
|
|
||||||
assert "Tradable pairs" in controls.text
|
assert "Tradable pairs" in controls.text
|
||||||
assert "Strategy mode" in controls.text
|
assert "Strategy mode" in controls.text
|
||||||
|
|
||||||
@@ -261,7 +260,8 @@ async def test_dashboard_controls_update_runtime_state_and_config(tmp_path) -> N
|
|||||||
assert app.state.settings.max_trade_capital_usd == 300.0
|
assert app.state.settings.max_trade_capital_usd == 300.0
|
||||||
assert app.state.settings.max_concurrent_trades == 4
|
assert app.state.settings.max_concurrent_trades == 4
|
||||||
assert app.state.settings.paper_trading_mode is True
|
assert app.state.settings.paper_trading_mode is True
|
||||||
assert app.state.dashboard_controls.tradable_pairs == ["BTC/USD", "ETH/BTC"]
|
assert app.state.dashboard_controls.tradable_pairs == [
|
||||||
|
"BTC/USD", "ETH/BTC"]
|
||||||
assert app.state.dashboard_controls.strategy_mode == "paper"
|
assert app.state.dashboard_controls.strategy_mode == "paper"
|
||||||
assert app.state.dashboard_controls.strategy_profit_threshold == 0.0025
|
assert app.state.dashboard_controls.strategy_profit_threshold == 0.0025
|
||||||
assert app.state.dashboard_controls.strategy_max_depth_levels == 7
|
assert app.state.dashboard_controls.strategy_max_depth_levels == 7
|
||||||
@@ -273,10 +273,14 @@ async def test_dashboard_controls_update_runtime_state_and_config(tmp_path) -> N
|
|||||||
assert audit_recent.status_code == 200
|
assert audit_recent.status_code == 200
|
||||||
entries = audit_recent.json()["entries"]
|
entries = audit_recent.json()["entries"]
|
||||||
assert len(entries) >= 4
|
assert len(entries) >= 4
|
||||||
assert any(entry["event_type"] == "dashboard.control.stop" for entry in entries)
|
assert any(entry["event_type"] ==
|
||||||
assert any(entry["event_type"] == "dashboard.control.start" for entry in entries)
|
"dashboard.control.stop" for entry in entries)
|
||||||
assert any(entry["event_type"] == "dashboard.control.kill_switch" for entry in entries)
|
assert any(entry["event_type"] ==
|
||||||
assert any(entry["event_type"] == "dashboard.control.config" for entry in entries)
|
"dashboard.control.start" for entry in entries)
|
||||||
|
assert any(entry["event_type"] ==
|
||||||
|
"dashboard.control.kill_switch" for entry in entries)
|
||||||
|
assert any(entry["event_type"] ==
|
||||||
|
"dashboard.control.config" for entry in entries)
|
||||||
|
|
||||||
|
|
||||||
async def test_dashboard_controls_emit_alerts(tmp_path) -> None:
|
async def test_dashboard_controls_emit_alerts(tmp_path) -> None:
|
||||||
@@ -357,11 +361,11 @@ async def test_backtesting_page_run_and_recent_reports_api(tmp_path) -> None:
|
|||||||
run = await client.post(
|
run = await client.post(
|
||||||
"/dashboard/backtesting/run",
|
"/dashboard/backtesting/run",
|
||||||
data={
|
data={
|
||||||
"events_path": str(events_file),
|
"source": "db",
|
||||||
"starting_balances": "USD=1000.0",
|
"starting_balances": "USD=1000.0",
|
||||||
"trade_capital": "100.0",
|
"trade_capital": "100.0",
|
||||||
"min_profit_threshold": "0.0005",
|
"min_profit_threshold": "0.0005",
|
||||||
"fee_profile": "standard",
|
"fee_profile": "api",
|
||||||
"slippage_bps": "4.0",
|
"slippage_bps": "4.0",
|
||||||
"execution_latency_ms": "20.0",
|
"execution_latency_ms": "20.0",
|
||||||
},
|
},
|
||||||
@@ -374,13 +378,13 @@ async def test_backtesting_page_run_and_recent_reports_api(tmp_path) -> None:
|
|||||||
|
|
||||||
assert fragment.status_code == 200
|
assert fragment.status_code == 200
|
||||||
assert "Run Backtest" in fragment.text
|
assert "Run Backtest" in fragment.text
|
||||||
assert "Recent Runs" in fragment.text
|
assert "Recent Jobs" in fragment.text
|
||||||
|
|
||||||
assert run.status_code == 200
|
assert run.status_code == 200
|
||||||
assert "completed" in run.text
|
assert "submitted" in run.text
|
||||||
assert "Processed:" in run.text
|
assert "queued" in run.text
|
||||||
|
|
||||||
assert reports.status_code == 200
|
assert reports.status_code == 200
|
||||||
payload = reports.json()
|
payload = reports.json()
|
||||||
assert len(payload["reports"]) >= 1
|
assert len(payload["reports"]) >= 1
|
||||||
assert payload["reports"][0]["status"] == "completed"
|
assert payload["reports"][0]["status"] == "pending"
|
||||||
|
|||||||
Reference in New Issue
Block a user