lowkey_backtest/trade.py

52 lines
1.5 KiB
Python
Raw Normal View History

from __future__ import annotations
from dataclasses import dataclass
import pandas as pd
from market_costs import okx_fee, estimate_slippage_rate
from intrabar import entry_slippage_row
@dataclass
class TradeState:
cash: float = 1000.0
qty: float = 0.0
entry_px: float | None = None
max_px: float | None = None
stop_loss_frac: float = 0.02
fee_bps: float = 10.0
slippage_bps: float = 2.0
def enter_long(state: TradeState, price: float) -> dict:
if state.qty > 0:
return {}
px = entry_slippage_row(price, 0.0, state.slippage_bps)
qty = state.cash / px
fee = okx_fee(state.fee_bps, state.cash)
state.qty = max(qty - fee / px, 0.0)
state.cash = 0.0
state.entry_px = px
state.max_px = px
return {"side": "BUY", "price": px, "qty": state.qty, "fee": fee}
def maybe_trailing_stop(state: TradeState, price: float) -> float:
if state.qty <= 0:
return float("inf")
state.max_px = max(state.max_px or price, price)
trail_px = state.max_px * (1.0 - state.stop_loss_frac)
return trail_px
def exit_long(state: TradeState, price: float) -> dict:
if state.qty <= 0:
return {}
notional = state.qty * price
slip = estimate_slippage_rate(state.slippage_bps, notional)
fee = okx_fee(state.fee_bps, notional)
cash_back = notional - slip - fee
event = {"side": "SELL", "price": price, "qty": state.qty, "fee": fee, "slippage": slip}
state.cash = cash_back
state.qty = 0.0
state.entry_px = None
state.max_px = None
return event