auto_trade_sys/scripts/aggregate_trade_stats.py
薇薇安 78007040f1 feat(blacklist): Enhance blacklist logic and UI representation
Updated the trading system to differentiate between soft and hard blacklists based on recent performance metrics. The frontend now displays the blacklist status with clear visual indicators. This change aims to improve risk management and user awareness of trading conditions.
2026-02-27 20:08:07 +08:00

175 lines
7.2 KiB
Python
Raw Permalink 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 视为表现不佳(软黑名单)
hard_blacklist_pnl_threshold = -3.0 # 净亏绝对值较大时考虑升级为硬黑名单
hard_blacklist_min_trades = 10 # 硬黑名单至少需要的样本数
hard_blacklist_max_win_rate = 10.0 # 胜率≤10% 且净亏较大时视为严重拖累
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:
mode = "reduced"
if (
net_pnl <= hard_blacklist_pnl_threshold
and tc >= hard_blacklist_min_trades
and win_rate <= hard_blacklist_max_win_rate
):
mode = "hard"
symbol_blacklist.append(
{
"symbol": sym,
"trade_count": tc,
"net_pnl": round(net_pnl, 4),
"win_rate_pct": round(win_rate, 1),
"mode": mode, # reduced=软黑名单(降权+提门槛hard=硬黑名单(自动不再开仓)
}
)
# 白名单:近期净盈且胜率尚可
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()