feat: 内照明心交易日历与交易所口径成交额/手续费统计
新增按 08:00 切日的月历(盈亏、笔数、犯病日高亮与点击筛选);平仓时从交易所 fill 写入双边成交额与手续费,统计表与明细同步展示。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -202,6 +202,12 @@ from history_window_lib import (
|
||||
utc_window_to_utc_sql_strings,
|
||||
)
|
||||
from trade_result_lib import count_winning_trades, normalize_result_with_pnl
|
||||
from trade_exchange_stats_lib import (
|
||||
attach_exchange_stats_to_trade,
|
||||
filter_position_lifecycle_fills,
|
||||
sum_binance_commission_income,
|
||||
trade_ids_from_fills,
|
||||
)
|
||||
|
||||
def load_env_file(path):
|
||||
if not os.path.exists(path):
|
||||
@@ -1499,6 +1505,8 @@ def init_db():
|
||||
("exchange_opened_at", "ALTER TABLE trade_records ADD COLUMN exchange_opened_at TEXT"),
|
||||
("exchange_closed_at", "ALTER TABLE trade_records ADD COLUMN exchange_closed_at TEXT"),
|
||||
("exchange_sync_key", "ALTER TABLE trade_records ADD COLUMN exchange_sync_key TEXT"),
|
||||
("exchange_turnover_usdt", "ALTER TABLE trade_records ADD COLUMN exchange_turnover_usdt REAL"),
|
||||
("exchange_commission_usdt", "ALTER TABLE trade_records ADD COLUMN exchange_commission_usdt REAL"),
|
||||
):
|
||||
try:
|
||||
c.execute(ddl)
|
||||
@@ -2644,6 +2652,8 @@ def insert_trade_record(
|
||||
key_signal_type=None,
|
||||
entry_reason=None,
|
||||
trend_plan_id=None,
|
||||
exchange_symbol=None,
|
||||
attach_exchange_stats=True,
|
||||
):
|
||||
hold_minutes = calc_hold_minutes(hold_seconds)
|
||||
open_ts = opened_at or app_now_str()
|
||||
@@ -2670,7 +2680,20 @@ def insert_trade_record(
|
||||
trend_plan_id,
|
||||
)
|
||||
)
|
||||
return int(cur.lastrowid or 0)
|
||||
tid = int(cur.lastrowid or 0)
|
||||
if attach_exchange_stats and tid:
|
||||
ex_sym = (exchange_symbol or "").strip() or normalize_exchange_symbol(symbol)
|
||||
_attach_binance_trade_exchange_stats(
|
||||
conn,
|
||||
tid,
|
||||
exchange_symbol=ex_sym,
|
||||
direction=direction,
|
||||
opened_at_str=open_ts,
|
||||
closed_at_str=close_ts,
|
||||
opened_at_ms=open_ts_ms,
|
||||
closed_at_ms=close_ts_ms,
|
||||
)
|
||||
return tid
|
||||
|
||||
|
||||
def calc_duration_text(open_str, close_str):
|
||||
@@ -4265,6 +4288,88 @@ def fetch_closing_fills_for_record(exchange_symbol, direction, opened_at_str, cl
|
||||
return _cluster_closing_trades_near_close(all_side_candidates[-5:], int(closed_ms))
|
||||
|
||||
|
||||
def fetch_all_position_fills_for_record(
|
||||
exchange_symbol,
|
||||
direction,
|
||||
opened_at_str,
|
||||
closed_at_str=None,
|
||||
opened_at_ms=None,
|
||||
closed_at_ms=None,
|
||||
):
|
||||
"""持仓生命周期内全部 fill(开+平),用于双边成交额与手续费。"""
|
||||
if not (BINANCE_API_KEY and BINANCE_API_SECRET):
|
||||
return []
|
||||
ensure_markets_loaded()
|
||||
since_ms = _to_ms_with_fallback(opened_at_ms, opened_at_str)
|
||||
closed_ms = _to_ms_with_fallback(closed_at_ms, closed_at_str) if (closed_at_str or closed_at_ms is not None) else None
|
||||
try:
|
||||
trades = exchange.fetch_my_trades(exchange_symbol, since=since_ms, limit=200)
|
||||
except Exception:
|
||||
trades = []
|
||||
return filter_position_lifecycle_fills(
|
||||
trades or [],
|
||||
direction,
|
||||
since_ms,
|
||||
closed_ms,
|
||||
hedge_mode=(BINANCE_POSITION_MODE == "hedge"),
|
||||
)
|
||||
|
||||
|
||||
def _attach_binance_trade_exchange_stats(
|
||||
conn,
|
||||
trade_id,
|
||||
*,
|
||||
exchange_symbol,
|
||||
direction,
|
||||
opened_at_str,
|
||||
closed_at_str,
|
||||
opened_at_ms=None,
|
||||
closed_at_ms=None,
|
||||
):
|
||||
if not (BINANCE_API_KEY and BINANCE_API_SECRET):
|
||||
return
|
||||
open_ms = _to_ms_with_fallback(opened_at_ms, opened_at_str)
|
||||
close_ms = _to_ms_with_fallback(closed_at_ms, closed_at_str)
|
||||
contract_size = 1.0
|
||||
try:
|
||||
ensure_markets_loaded()
|
||||
contract_size = float(exchange.market(exchange_symbol).get("contractSize") or 1)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _fetch():
|
||||
return fetch_all_position_fills_for_record(
|
||||
exchange_symbol,
|
||||
direction,
|
||||
opened_at_str,
|
||||
closed_at_str,
|
||||
opened_at_ms=open_ms,
|
||||
closed_at_ms=close_ms,
|
||||
)
|
||||
|
||||
income_comm = None
|
||||
if open_ms and close_ms:
|
||||
fills_preview = _fetch()
|
||||
trade_ids = trade_ids_from_fills(fills_preview)
|
||||
buffer_ms = 3 * 60 * 1000 if trade_ids else 5 * 60 * 1000
|
||||
entries = _fetch_binance_income_entries(
|
||||
exchange_symbol,
|
||||
max(0, int(open_ms) - buffer_ms),
|
||||
int(close_ms) + buffer_ms,
|
||||
)
|
||||
income_comm = sum_binance_commission_income(entries, trade_ids or None)
|
||||
try:
|
||||
attach_exchange_stats_to_trade(
|
||||
conn,
|
||||
trade_id,
|
||||
fetch_fills=_fetch,
|
||||
contract_size=contract_size,
|
||||
income_commission=income_comm,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def calc_weighted_exit_price(trades):
|
||||
if not trades:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user