a1abe159fa
Add deploy/manage.sh bootstrap for git.bz121.com/dekun/crypto_okx and point docs at this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
121 lines
3.6 KiB
Python
121 lines
3.6 KiB
Python
"""交易结果展示与入库时的语义归一化."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
_WIN_EPS = 1e-9
|
|
|
|
|
|
def classify_exit_by_levels(
|
|
direction,
|
|
trigger_price,
|
|
stop_loss,
|
|
take_profit,
|
|
exit_price,
|
|
) -> Optional[str]:
|
|
"""根据成交价相对止盈/止损位归类;无法可靠归类时返回 None.
|
|
|
|
交易所条件止盈常按标记价触发、市价成交,成交价可能偏离计划止盈数个 tick.
|
|
因此先用窄带,失败后再用宽带;仍失败则看是否落在入场→止盈/止损的「盈利/亏损侧」。
|
|
"""
|
|
try:
|
|
tp = float(take_profit)
|
|
sl = float(stop_loss)
|
|
ex = float(exit_price)
|
|
trig = float(trigger_price)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
d = (direction or "").strip().lower()
|
|
if d not in ("long", "short"):
|
|
return None
|
|
band = max(abs(trig) * 0.0008, abs(tp - sl) * 0.003, 1e-12)
|
|
# 宽带:覆盖 BTC 等高价币种条件单滑点(实测 Gate 止盈成交可偏出窄带 ~100U)
|
|
band_loose = max(abs(trig) * 0.003, abs(tp - sl) * 0.05, band * 4.0, 1e-12)
|
|
|
|
def _is_tp(b: float) -> bool:
|
|
return ex >= tp - b if d == "long" else ex <= tp + b
|
|
|
|
def _is_sl(b: float) -> bool:
|
|
return ex <= sl + b if d == "long" else ex >= sl - b
|
|
|
|
if _is_tp(band):
|
|
return "止盈"
|
|
if _is_sl(band):
|
|
return "止损"
|
|
if _is_tp(band_loose):
|
|
return "止盈"
|
|
if _is_sl(band_loose):
|
|
return "止损"
|
|
|
|
# 盈利侧且更靠近止盈 → 止盈; 亏损侧且更靠近止损 → 止损
|
|
if d == "long":
|
|
if ex > trig and abs(ex - tp) <= abs(ex - trig):
|
|
return "止盈"
|
|
if ex < trig and abs(ex - sl) <= abs(ex - trig):
|
|
return "止损"
|
|
else:
|
|
if ex < trig and abs(ex - tp) <= abs(ex - trig):
|
|
return "止盈"
|
|
if ex > trig and abs(ex - sl) <= abs(ex - trig):
|
|
return "止损"
|
|
return None
|
|
|
|
|
|
def normalize_display_result(result):
|
|
"""展示用:外部平仓一律视为手动平仓."""
|
|
res = (result or "").strip()
|
|
if res == "外部平仓" or res.startswith("外部平仓"):
|
|
return "手动平仓"
|
|
return res
|
|
|
|
|
|
def is_winning_pnl(pnl_amount) -> bool:
|
|
"""胜率统计:盈亏为正即计为盈利单."""
|
|
try:
|
|
return float(pnl_amount or 0) > _WIN_EPS
|
|
except (TypeError, ValueError):
|
|
return False
|
|
|
|
|
|
def sql_effective_pnl_expr() -> str:
|
|
"""与 to_effective_trade_dict / hub_trades_lib 一致的盈亏 SQL 表达式."""
|
|
return "COALESCE(reviewed_pnl_amount, exchange_realized_pnl, pnl_amount, 0)"
|
|
|
|
|
|
def count_winning_trades(trades) -> int:
|
|
return sum(1 for r in trades or [] if is_winning_pnl(r.get("effective_pnl_amount")))
|
|
|
|
|
|
MISS_TRADE_RESULT = "错过"
|
|
|
|
|
|
def is_miss_trade_result(result) -> bool:
|
|
return (result or "").strip() == MISS_TRADE_RESULT
|
|
|
|
|
|
def filter_trade_records_excluding_miss(records):
|
|
"""列表/统计:不展示,不计入「错过」类交易记录."""
|
|
return [
|
|
r
|
|
for r in (records or [])
|
|
if not is_miss_trade_result(r.get("effective_result") or r.get("result"))
|
|
]
|
|
|
|
|
|
def normalize_result_with_pnl(result, pnl_amount):
|
|
"""
|
|
非手动平仓且实际盈利时,不应记为「止损」.
|
|
程序触发的止损类平仓若盈亏为正,归类为「移动止盈」.
|
|
"""
|
|
res = normalize_display_result(result)
|
|
if res == "手动平仓":
|
|
return res
|
|
if res == "止损":
|
|
try:
|
|
if float(pnl_amount or 0) > 0:
|
|
return "移动止盈"
|
|
except (TypeError, ValueError):
|
|
pass
|
|
return res
|