auto_trade_sys/scripts/aggregate_trade_stats.py
薇薇安 861f1dc548 feat(stats): 增加全局交易统计功能
在后端新增了全局交易统计功能,包括按交易对和按小时的聚合统计,支持生成软黑名单和时段过滤建议。前端组件更新以展示基于最近N天交易统计的过滤结果,旨在提升交易策略的灵活性和风险控制能力。
2026-02-27 13:29:49 +08:00

165 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
定时任务:将最近 N 天的交易数据聚合到 trade_stats_daily / trade_stats_time_bucket。
优先从 binance_trades 读取(需先跑 sync_binance_orders.py无数据时用 trades 表。
用法:
python scripts/aggregate_trade_stats.py # 默认 7 天、默认账号
python scripts/aggregate_trade_stats.py -d 30 # 最近 30 天
python scripts/aggregate_trade_stats.py -a 2 # 指定账号
"""
import argparse
import sys
from pathlib import Path
proj = Path(__file__).resolve().parent.parent
if (proj / "backend").exists():
sys.path.insert(0, str(proj / "backend"))
sys.path.insert(0, str(proj))
def main():
parser = argparse.ArgumentParser(description="聚合交易统计到 trade_stats_* 表")
parser.add_argument("-a", "--account", type=int, default=None, help="账号 ID不传则用默认")
parser.add_argument("-d", "--days", type=int, default=7, help="聚合最近 N 天,默认 7")
args = parser.parse_args()
if args.days <= 0:
print("days 须 > 0")
sys.exit(1)
try:
from datetime import datetime, timezone
from database.models import TradeStats, GlobalStrategyConfig, TradingConfig
# 1) 先按账号聚合到 trade_stats_* 表(供仪表盘等使用)
TradeStats.aggregate_recent_days(days=args.days, account_id=args.account)
print(f"已聚合最近 {args.days} 天统计 (account_id={args.account or 'default'})")
# 2) 再基于 trade_stats_* 做一次【全局】评估:按交易对 / 按小时生成软黑名单与时段过滤建议
days = max(1, int(args.days))
symbol_rows = TradeStats.get_global_symbol_stats(days=days)
hour_rows = TradeStats.get_global_hourly_stats(days=days)
# 规则参数(后续可改成全局配置,这里先写死一版直观规则):
min_trades_for_symbol = 5 # 至少 N 笔才纳入评估,避免样本太少
min_trades_for_hour = 10 # 小时至少 N 笔才纳入评估
blacklist_pnl_threshold = 0.0 # 净盈亏 < 0 视为表现不佳
good_hour_pnl_threshold = 0.0 # 净盈亏 > 0 视为表现较好
symbol_blacklist = []
symbol_whitelist = []
for r in symbol_rows or []:
try:
sym = (r.get("symbol") or "").strip()
tc = int(r.get("trade_count") or 0)
net_pnl = float(r.get("net_pnl") or 0.0)
win = int(r.get("win_count") or 0)
loss = int(r.get("loss_count") or 0)
except Exception:
continue
if not sym or tc < min_trades_for_symbol:
continue
total = max(1, win + loss)
win_rate = 100.0 * win / total
# 软黑名单:近期净亏 + 有一定笔数 -> 降权(不一刀切禁止)
if net_pnl < blacklist_pnl_threshold:
symbol_blacklist.append(
{
"symbol": sym,
"trade_count": tc,
"net_pnl": round(net_pnl, 4),
"win_rate_pct": round(win_rate, 1),
"mode": "reduced", # 软黑名单:仅降权+提门槛
}
)
# 白名单:近期净盈且胜率尚可
elif net_pnl > 0 and win_rate >= 50.0:
symbol_whitelist.append(
{
"symbol": sym,
"trade_count": tc,
"net_pnl": round(net_pnl, 4),
"win_rate_pct": round(win_rate, 1),
}
)
hour_filters = []
for r in hour_rows or []:
try:
h = int(r.get("hour"))
tc = int(r.get("trade_count") or 0)
net_pnl = float(r.get("net_pnl") or 0.0)
except Exception:
continue
if h < 0 or h > 23:
continue
if tc < min_trades_for_hour:
bucket = "unknown"
elif net_pnl > good_hour_pnl_threshold:
bucket = "good"
elif net_pnl < -good_hour_pnl_threshold:
bucket = "bad"
else:
bucket = "neutral"
# 约定bad 时段默认 0.5 仓位系数 + 信号门槛+1good 时段略微放宽(可视需求调整)
if bucket == "bad":
position_factor = 0.5
signal_boost = 1
elif bucket == "good":
position_factor = 1.1
signal_boost = 0
else:
position_factor = 1.0
signal_boost = 0
hour_filters.append(
{
"hour": h,
"bucket": bucket,
"trade_count": tc,
"net_pnl": round(net_pnl, 4),
"position_factor": position_factor,
"signal_boost": signal_boost,
}
)
now_iso = datetime.now(timezone.utc).isoformat()
symbol_payload = {
"generated_at": now_iso,
"days": days,
"min_trades": min_trades_for_symbol,
"blacklist": sorted(symbol_blacklist, key=lambda x: (x["net_pnl"], -x["trade_count"])),
"whitelist": sorted(symbol_whitelist, key=lambda x: (-x["net_pnl"], -x["trade_count"])),
}
hour_payload = {
"generated_at": now_iso,
"days": days,
"min_trades": min_trades_for_hour,
"hours": sorted(hour_filters, key=lambda x: x["hour"]),
}
# 写入全局策略配置json供交易进程 & 前端全局配置页读取
GlobalStrategyConfig.set(
"STATS_SYMBOL_FILTERS",
symbol_payload,
"json",
"stats_filters",
description="基于最近 N 天交易统计自动生成的 symbol 白/黑名单(软降权),请勿手动修改。",
)
GlobalStrategyConfig.set(
"STATS_HOUR_FILTERS",
hour_payload,
"json",
"stats_filters",
description="基于最近 N 天交易统计自动生成的按小时时段过滤建议,请勿手动修改。",
)
print(f"已更新全局统计过滤配置:{len(symbol_blacklist)} 个黑名单 symbol{len(symbol_whitelist)} 个白名单 symbol。")
except Exception as e:
print(f"聚合失败: {e}")
sys.exit(1)
if __name__ == "__main__":
main()