52 lines
1.5 KiB
Python
52 lines
1.5 KiB
Python
|
|
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
|