alphax/app/core/trade_math.py
2026-05-31 19:00:46 +08:00

113 lines
3.3 KiB
Python

"""Side-aware execution math shared by paper and live trading adapters."""
from __future__ import annotations
def safe_float(value, default: float = 0.0) -> float:
try:
if value is None or value == "":
return default
return float(value)
except Exception:
return default
def normalize_side(side: str | None) -> str:
return "short" if str(side or "").strip().lower() == "short" else "long"
def open_price(side: str, current_price: float, slippage_pct: float = 0.0) -> float:
price = safe_float(current_price)
slip = max(0.0, safe_float(slippage_pct))
if price <= 0:
return 0.0
if normalize_side(side) == "short":
return round(price * (1 - slip / 100), 12)
return round(price * (1 + slip / 100), 12)
def close_price(side: str, current_price: float, slippage_pct: float = 0.0) -> float:
price = safe_float(current_price)
slip = max(0.0, safe_float(slippage_pct))
if price <= 0:
return 0.0
if normalize_side(side) == "short":
return round(price * (1 + slip / 100), 12)
return round(price * (1 - slip / 100), 12)
def pnl_pct(side: str, entry_price: float, current_price: float) -> float:
entry = safe_float(entry_price)
price = safe_float(current_price)
if entry <= 0 or price <= 0:
return 0.0
if normalize_side(side) == "short":
return round((entry / price - 1) * 100, 4)
return round((price / entry - 1) * 100, 4)
def stop_loss_distance_pct(side: str, entry_price: float, stop_loss: float) -> float:
entry = safe_float(entry_price)
stop = safe_float(stop_loss)
if entry <= 0 or stop <= 0:
return 0.0
if normalize_side(side) == "short":
return max(0.0, (stop / entry - 1) * 100)
return max(0.0, (1 - stop / entry) * 100)
def should_stop_loss(side: str, current_price: float, stop_loss: float) -> bool:
price = safe_float(current_price)
stop = safe_float(stop_loss)
if price <= 0 or stop <= 0:
return False
if normalize_side(side) == "short":
return price >= stop
return price <= stop
def should_take_profit(side: str, current_price: float, target: float) -> bool:
price = safe_float(current_price)
tp = safe_float(target)
if price <= 0 or tp <= 0:
return False
if normalize_side(side) == "short":
return price <= tp
return price >= tp
def should_hit_trailing_stop(side: str, current_price: float, trailing_stop: float) -> bool:
price = safe_float(current_price)
stop = safe_float(trailing_stop)
if price <= 0 or stop <= 0:
return False
if normalize_side(side) == "short":
return price >= stop
return price <= stop
def tighter_stop(side: str, current_stop: float, candidate_stop: float) -> float:
current = safe_float(current_stop)
candidate = safe_float(candidate_stop)
if candidate <= 0:
return current
if current <= 0:
return candidate
if normalize_side(side) == "short":
return min(current, candidate)
return max(current, candidate)
__all__ = [
"close_price",
"normalize_side",
"open_price",
"pnl_pct",
"safe_float",
"should_hit_trailing_stop",
"should_stop_loss",
"should_take_profit",
"stop_loss_distance_pct",
"tighter_stop",
]