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