feat: VBT trade log — show asset, entry/exit prices, Hyperliquid fees
Backend (vbt_runner.py):
- Add asset (BTC/ETH) to each trade record
- Compute per-trade fee using HL taker rate (0.05%)
entry_fee = size * entry_px * fee_rate
exit_fee = size * exit_px * fee_rate
- Add pnl_gross (before fees) and pnl_net (after fees)
- Add fee_rate field for transparency
Server (server.py):
- Normalize old backtest trades: add missing asset, fee,
pnl_net, pnl_gross fields
- Holyliquid default fee rate: 0.05% taker
Frontend (vbt.html):
- Trade log table now shows:
Time | Side + Asset | Size | Entry | Exit | Fee | PnL (net) | Duration
- Asset shown as inline badge in Side column
- Fee column with explicit USD amount
- PnL now explicitly labeled 'net' (after fees)
- Fallback to old 'pnl' field for legacy backtest files
This commit is contained in:
+29
-6
@@ -442,18 +442,41 @@ class VBTBacktestRunner:
|
||||
return coin_map.get(strategy, ["BTC"])
|
||||
|
||||
def _extract_metrics(self, pf, stats, strategy, interval, n_bars) -> dict:
|
||||
# Extract trade records from VectorBT portfolio
|
||||
# Determine which coin this strategy trades
|
||||
main_coin = self._get_coins(strategy)[0]
|
||||
asset = main_coin if main_coin else "BTC"
|
||||
|
||||
trades = []
|
||||
try:
|
||||
trade_records = pf.trades.records_readable
|
||||
for _, t in trade_records.iterrows():
|
||||
side = "BUY" if str(t.get("Direction", "")) == "Long" else "SELL"
|
||||
entry_px = round(float(t.get("Avg Entry Price", 0)), 2)
|
||||
exit_px = round(float(t.get("Avg Exit Price", 0)), 2)
|
||||
size = round(float(t.get("Size", 0)), 6)
|
||||
notional = size * entry_px
|
||||
|
||||
# VectorBT's PnL already accounts for fees when fees= is set on Portfolio
|
||||
pnl_vbt = round(float(t.get("PnL", 0)), 4)
|
||||
fee_rate = self._fee_rate # applied per side by VBT
|
||||
entry_fee = round(notional * fee_rate, 6)
|
||||
exit_fee = round(size * exit_px * fee_rate, 6)
|
||||
total_fee = round(entry_fee + exit_fee, 6)
|
||||
|
||||
# Gross PnL = net + fees
|
||||
gross_pnl = round(pnl_vbt + total_fee, 4)
|
||||
|
||||
trades.append({
|
||||
"time": str(t.get("Exit Timestamp", t.get("Entry Timestamp", "")))[:19],
|
||||
"side": "BUY" if str(t.get("Direction", "")) == "Long" else "SELL",
|
||||
"size": round(float(t.get("Size", 0)), 6),
|
||||
"entry_px": round(float(t.get("Avg Entry Price", 0)), 2),
|
||||
"exit_px": round(float(t.get("Avg Exit Price", 0)), 2),
|
||||
"pnl": round(float(t.get("PnL", 0)), 4),
|
||||
"side": side,
|
||||
"asset": asset,
|
||||
"size": size,
|
||||
"entry_px": entry_px,
|
||||
"exit_px": exit_px,
|
||||
"pnl_gross": gross_pnl,
|
||||
"pnl_net": pnl_vbt,
|
||||
"fee": total_fee,
|
||||
"fee_rate": f"{fee_rate*100:.3f}%",
|
||||
"return_pct": round(float(t.get("Return", 0)) * 100, 3),
|
||||
"duration": str(t.get("Duration", "")),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user