feat: enhance fee management with API integration and audit trail support
This commit is contained in:
@@ -6,10 +6,31 @@ from collections.abc import Mapping
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
import duckdb
|
||||
|
||||
from arbitrade.backtesting.replay import BacktestConfig, BacktestReplayEngine, load_replay_events
|
||||
from arbitrade.detection.graph import CurrencyGraph, TriangularCycle
|
||||
|
||||
|
||||
def _resolve_fee_rate(fee_rate: float | None, db_path: str | None = None) -> float:
|
||||
"""Resolve fee rate from arg or DB snapshot. Falls back to 0.0026."""
|
||||
if fee_rate is not None:
|
||||
return fee_rate
|
||||
if db_path is not None:
|
||||
try:
|
||||
conn = duckdb.connect(db_path)
|
||||
row = conn.execute("""
|
||||
SELECT maker_fee FROM kraken_account_snapshots
|
||||
ORDER BY snapshot_at DESC LIMIT 1
|
||||
""").fetchone()
|
||||
conn.close()
|
||||
if row is not None and row[0] is not None:
|
||||
return float(row[0])
|
||||
except Exception:
|
||||
pass
|
||||
return 0.0026 # ultimate fallback
|
||||
|
||||
|
||||
def _build_graph() -> tuple[dict[str, list[TriangularCycle]], list[str]]:
|
||||
graph = CurrencyGraph()
|
||||
graph.add_pair("USD", "BTC", "BTC/USD")
|
||||
@@ -30,19 +51,23 @@ def _parse_balances(raw: str) -> Mapping[str, float]:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Run a deterministic replay backtest.")
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run a deterministic replay backtest.")
|
||||
parser.add_argument("--events", type=Path, required=True)
|
||||
parser.add_argument("--starting-balances", type=str, default="USD=1000.0")
|
||||
parser.add_argument("--trade-capital", type=float, default=100.0)
|
||||
parser.add_argument("--fee-rate", type=float, default=0.0026)
|
||||
parser.add_argument("--fee-rate", type=float, default=None)
|
||||
parser.add_argument("--slippage-bps", type=float, default=4.0)
|
||||
parser.add_argument("--execution-latency-ms", type=float, default=20.0)
|
||||
parser.add_argument("--db-path", type=str, default=None,
|
||||
help="DuckDB path for fee lookup")
|
||||
args = parser.parse_args()
|
||||
|
||||
cycles_by_pair, available_pairs = _build_graph()
|
||||
events = load_replay_events(args.events)
|
||||
fee_rate = _resolve_fee_rate(args.fee_rate, args.db_path)
|
||||
config = BacktestConfig(
|
||||
fee_rate=args.fee_rate,
|
||||
fee_rate=fee_rate,
|
||||
trade_capital=args.trade_capital,
|
||||
slippage_bps=args.slippage_bps,
|
||||
execution_latency_ms=args.execution_latency_ms,
|
||||
@@ -55,15 +80,18 @@ def main() -> int:
|
||||
started_at=events[0].occurred_at if events else datetime.now(UTC),
|
||||
)
|
||||
report = asyncio.run(
|
||||
engine.run(events, starting_balances=_parse_balances(args.starting_balances))
|
||||
engine.run(events, starting_balances=_parse_balances(
|
||||
args.starting_balances))
|
||||
)
|
||||
|
||||
print("Backtest report:")
|
||||
print(f"- processed_events: {report.processed_events}")
|
||||
print(f"- opportunities_seen: {report.opportunities_seen}")
|
||||
print(f"- trades_executed: {report.trades_executed}")
|
||||
print(f"- win_rate: {report.win_rate if report.win_rate is not None else 'n/a'}")
|
||||
print(f"- fill_rate: {report.fill_rate if report.fill_rate is not None else 'n/a'}")
|
||||
print(
|
||||
f"- win_rate: {report.win_rate if report.win_rate is not None else 'n/a'}")
|
||||
print(
|
||||
f"- fill_rate: {report.fill_rate if report.fill_rate is not None else 'n/a'}")
|
||||
print(f"- realized_pnl_usd: {report.realized_pnl_usd:.4f}")
|
||||
print(f"- max_drawdown_usd: {report.max_drawdown_usd:.4f}")
|
||||
print(f"- miss_reasons: {dict(report.miss_reasons)}")
|
||||
|
||||
Reference in New Issue
Block a user