From 1498532e05c620a3a9a156d31a69d481b706ce3a Mon Sep 17 00:00:00 2001 From: Lxy Date: Sat, 20 Jun 2026 22:12:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=BA=A4=E6=98=93?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E5=AF=BC=E5=85=A5=E5=8F=8A=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 24 + app/__pycache__/main.cpython-311.pyc | Bin 8159 -> 8272 bytes app/analysis_db.py | 1 + app/analysis_models.py | 48 + app/api/trade_review.py | 424 +++++ app/main.py | 3 +- app/services/trade_parser.py | 609 ++++++ app/static/futures_analysis.html | 246 +++ app/static/futures_analysis.js | 711 +++++++ data/buffer.db | Bin 3354624 -> 3354624 bytes data/futures_analysis.db | Bin 8151040 -> 8204288 bytes .../desktop/prototype_desktop_light-v2.html | 241 +++ .../desktop/prototype_desktop_light-v3.html | 459 +++++ .../mobile/prototype_tablet_android-v2.html | 338 ++++ .../prototype_windows_desktop.html | 381 ++++ data/trading_calculate.py | 534 ++++++ .../计划/2026-06-16_周一交易计划.html | 1695 +++++++++++++++++ ...交易计划_执行逻辑说明_v2 (1).md | 156 ++ logs.bat | 25 + 19 files changed, 5894 insertions(+), 1 deletion(-) create mode 100644 .env create mode 100644 app/api/trade_review.py create mode 100644 app/services/trade_parser.py create mode 100644 data/new_ui/desktop/prototype_desktop_light-v2.html create mode 100644 data/new_ui/desktop/prototype_desktop_light-v3.html create mode 100644 data/new_ui/mobile/prototype_tablet_android-v2.html create mode 100644 data/new_ui/windows_desktop/prototype_windows_desktop.html create mode 100644 data/trading_calculate.py create mode 100644 data/计划/2026-06-16_周一交易计划.html create mode 100644 data/计划/每日收盘交易计划_执行逻辑说明_v2 (1).md create mode 100644 logs.bat diff --git a/.env b/.env new file mode 100644 index 0000000..26f03c0 --- /dev/null +++ b/.env @@ -0,0 +1,24 @@ +# 期货智析缓冲平台 - 环境配置 + +# 数据库配置 +BUFFER_DB_PATH=/app/data/buffer.db + +# 服务配置 +BUFFER_HOST=0.0.0.0 +BUFFER_PORT=8600 + +# 缓存配置(秒) +CACHE_TTL=300 + +# 日志级别 (DEBUG/INFO/WARNING/ERROR/CRITICAL) +BUFFER_LOG_LEVEL=INFO + +# 并发采集数 +MAX_WORKERS=2 + +# Docker端口映射 +HOST_PORT=9600 +CONTAINER_PORT=8600 + +# 数据目录 +DATA_DIR=E:\docker_workspace\futures_datas diff --git a/app/__pycache__/main.cpython-311.pyc b/app/__pycache__/main.cpython-311.pyc index 0e8c451ec6cea8bab161a69b065d77f6b632604b..27e7430826f082e5d523e61ab44ee9bbec628e6a 100644 GIT binary patch delta 1394 zcmbVMO>7%Q6rNdo?Zk=W)J`1#HH~A_*p2O^DQ!t>HwFo*5*1KXA?hB?#ygGIS?`9~ z-JM3@WWf^%srfrNy`34z3+CweedCD2}qxN+iwkP1TLJ-Z3Q=EP{{?Y!@O z-`lruX7|xXuNu4)3TAC7-szH6RovCHo*;D`8c}Ch;j`J> zUf{rd!YQ(1rzGAOkBF~m?$FA=Gjvw_kzWF#XzTdCM2+8=Af&d&);cHp;SDL~DR!Rj zuiH|SQdT|la-%2@G9V~gLej*GGRwrXT@8d!=T+q5+g`* z1WAn`hewe45oBQmNgp6jYL&Bc`JY>0eJN*?8|;``+Lo|SJOP_2>D5{l znqO@_+x<=UQ9366^gTc2PTE{iS)H?M8hf)_^>hFH5zx4k%Fa$nRcvKbu^3;#G$}M` z(VI#qVn_)i1%lVK2D3^HrO{-3QA~tVG$V4M!j(9X^5X*;<{V^6qf}Bi`65CY^n%P# zHg%@fZN=KQcn&aY9L&Q0;CQUuFO0C7;}^yKNP#Yk$C16I7m+cK4;k!yYIuiEn3$p!jv>qr!hF{Ejvtz6$9K zO!*3$Wzn7eW_nm4-iapV%MiaQ_M&;n`d##s{4fc^?v$d{8>Vqp+ZrUl0^lvs%!<2n zGobr=u37GvK2*Q2$rRmg^4Ed?9!%>Hn77GK(L`_QY;U5Bm$wYnDY0hp0>tvv^*CJ5 zQ;whha67!*G;OXzWLNwin}-L_#@Fa`;(Yw$^aMH4Zn^F>L-4LY^&V^NYy4ga$k8 zPH9F%ckqOoj?K918{9c|m1CRw@7}N3^*eABO>Vdz?ifSw_QzNm3UzdLjt6dm41Y}4 zo_H#Cp>B;Xh;cO=690hFxY5MKb2`$*){QrF&)n~v?>qP0+tWFiJDhbt zal4xZ82(T3T*t0ED1MiA`(MO}pc2)sb?1AMJ^9{bFA+q6l3WC-H~^id1Z*I8hKHMd z4G`?(5i%_I+XO+N(Z@s(p24(7a)9eZ?i~b?Q$4AkQrqa@bMNB@;K2ODiP6~p5FZs! z@i(OZMEvgz`@@w;%H{FpMvf$E@UBY`lB0BVf2m;bb$8A6RPvbb)NP*Pe+|6T<7Vk zdhE(BN)hQowX!>a5>@mP(8VhHDn;ZO8dc-#BHj%P!7nIkv&mUF{cLWoJkr=mh?7q_ zFO|2Q%oz>?$!bvwg}AB|l@-OLtP^!Inl2vlbQ3>Mcmm;WB>IpDl-t=fHREPFL)DT- z8SCN96T1}vO0>zAVqQB_7Yapng<%HPgUC&FWksWEs;HRjChG&A*#xHRXfW&_>zI~Z zrDj3VS)%;I^HC&;@`sag(vJPd2GCS!&6=^6)l&s!jj|=~Ym1QqzR>ol?=muKxW}2H z(UeIwnl9RkW$yA0fnwTwKlU$$vy`=o*CCybDX*a!;J>`DkJk$Ld!H=bg!l&cc)ff6 zHc0ICKaAG$fMK;N*;LxlSF>w&{vo93`ej3B$j0?cp1kVH8;!B0GF87zSM`I7a6AUhn}K<3piNGQuy17G0C5 z^`i;!r=f%^hRXyRnSTilZ(fC^CFR%EEQ6O((u?d0x*Th?$d-_|j0P{vYR&3tt)$Wv zGfG9utVa9I@RArFxpIz5QI@j=ePuA+a<<*-?rZqd9L$w1NPBxbxSs0 diff --git a/app/analysis_db.py b/app/analysis_db.py index 50b2af6..a4218e3 100644 --- a/app/analysis_db.py +++ b/app/analysis_db.py @@ -40,6 +40,7 @@ def init_analysis_db(): FuturesAnalysis, WatchedSymbol, AIModelConfig, AnalysisSettings, AIAnalysisCache, ReviewDate, SymbolRanking, TradingPlan, SymbolScoreV2, TradingPlanV2, SectorHeat, ReviewPlanV2, + TradeRecord, TradeImportBatch, ) AnalysisBase.metadata.create_all(bind=analysis_engine) diff --git a/app/analysis_models.py b/app/analysis_models.py index e3609ab..ca5f155 100644 --- a/app/analysis_models.py +++ b/app/analysis_models.py @@ -275,3 +275,51 @@ class ReviewPlanV2(AnalysisBase): def __repr__(self): return f"" + + +# ==================== 交易复盘模型 ==================== + +class TradeRecord(AnalysisBase): + """交易记录表 - 从期货结算单导入的逐笔交易明细""" + __tablename__ = "trade_records" + + id = Column(Integer, primary_key=True, autoincrement=True) + trade_type = Column(String(8), nullable=False, comment="类型: 期货/期权") + symbol = Column(String(32), nullable=False, index=True, comment="合约代码 AG2606") + variety = Column(String(16), nullable=False, index=True, comment="品种代码 AG") + symbol_name = Column(String(64), nullable=True, comment="品种名称 沪银") + direction = Column(String(8), nullable=False, comment="买卖方向: 买/卖") + offset = Column(String(8), nullable=True, comment="开平标志: 开/平") + price = Column(Float, nullable=True, comment="成交价/权利金单价") + volume = Column(Float, nullable=True, comment="手数/成交量") + amount = Column(Float, nullable=True, comment="成交额/权利金") + close_pnl = Column(Float, nullable=True, default=0.0, comment="平仓盈亏") + commission = Column(Float, nullable=True, default=0.0, comment="手续费") + trade_date = Column(String(16), nullable=True, index=True, comment="成交日期 YYYY-MM-DD") + trade_time = Column(String(32), nullable=True, comment="成交时间 HH:MM:SS") + import_batch = Column(String(64), nullable=False, index=True, comment="导入批次号 UUID") + source_file = Column(String(128), nullable=True, comment="来源文件名") + created_at = Column(DateTime, nullable=False, default=datetime.now) + + __table_args__ = ( + Index('ix_trade_records_date_variety', 'trade_date', 'variety'), + ) + + def __repr__(self): + return f"" + + +class TradeImportBatch(AnalysisBase): + """交易导入批次表 - 记录每次导入的元信息""" + __tablename__ = "trade_import_batches" + + id = Column(Integer, primary_key=True, autoincrement=True) + batch_id = Column(String(64), nullable=False, unique=True, index=True, comment="批次号 UUID") + source_file = Column(String(128), nullable=False, comment="来源文件名") + futures_count = Column(Integer, nullable=False, default=0, comment="期货交易记录数") + options_count = Column(Integer, nullable=False, default=0, comment="期权交易记录数") + trade_dates = Column(String(256), nullable=True, comment="涉及交易日期范围") + created_at = Column(DateTime, nullable=False, default=datetime.now) + + def __repr__(self): + return f"" diff --git a/app/api/trade_review.py b/app/api/trade_review.py new file mode 100644 index 0000000..a87a0e8 --- /dev/null +++ b/app/api/trade_review.py @@ -0,0 +1,424 @@ +""" +交易复盘接口 - 提供交易记录导入、查询、汇总、统计等功能 +""" +import logging +from typing import Optional + +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query +from pydantic import BaseModel +from sqlalchemy.orm import Session + +from app.analysis_db import get_analysis_db +from app.analysis_models import TradeRecord, TradeImportBatch +from app.services.trade_parser import ( + parse_settlement_file, + save_to_db, + calc_daily_summary, + calc_variety_summary, + calc_overall_statistics, + get_trade_pairs, +) + +logger = logging.getLogger(__name__) +router = APIRouter(prefix="/trade-review", tags=["交易复盘"]) + + +# ==================== 文件导入 ==================== + +@router.post("/import") +async def import_settlement( + file: UploadFile = File(...), + db: Session = Depends(get_analysis_db), +): + """上传并解析期货结算单文件,导入交易记录""" + if not file.filename.endswith(('.xls', '.xlsx')): + raise HTTPException(status_code=400, detail="仅支持 .xls/.xlsx 格式的结算单文件") + + try: + content = await file.read() + df_futures, df_options = parse_settlement_file(content, file.filename) + + if df_futures.empty and df_options.empty: + return {"success": False, "message": "文件中未找到有效的交易记录(请确认是期货公司导出的结算单)"} + + result = save_to_db(db, df_futures, df_options, file.filename) + + return { + "success": True, + "message": f"导入成功:期货 {result['futures_count']} 条,期权 {result['options_count']} 条", + "data": result, + } + except Exception as e: + logger.exception("导入结算单失败") + raise HTTPException(status_code=500, detail=f"导入失败: {str(e)}") + + +# ==================== 交易记录查询 ==================== + +@router.get("/records") +def get_records( + start_date: Optional[str] = Query(None, description="开始日期 YYYY-MM-DD"), + end_date: Optional[str] = Query(None, description="结束日期 YYYY-MM-DD"), + symbol: Optional[str] = Query(None, description="合约代码"), + variety: Optional[str] = Query(None, description="品种代码"), + trade_type: Optional[str] = Query(None, description="类型: 期货/期权"), + page: int = Query(1, ge=1), + page_size: int = Query(50, ge=1, le=200), + db: Session = Depends(get_analysis_db), +): + """查询交易记录(支持筛选和分页)""" + query = db.query(TradeRecord) + + if start_date: + query = query.filter(TradeRecord.trade_date >= start_date) + if end_date: + query = query.filter(TradeRecord.trade_date <= end_date) + if symbol: + query = query.filter(TradeRecord.symbol == symbol) + if variety: + query = query.filter(TradeRecord.variety == variety) + if trade_type: + query = query.filter(TradeRecord.trade_type == trade_type) + + total = query.count() + records = query.order_by( + TradeRecord.trade_date.desc(), + TradeRecord.trade_time.desc(), + ).offset((page - 1) * page_size).limit(page_size).all() + + return { + "success": True, + "data": { + "total": total, + "page": page, + "page_size": page_size, + "records": [{ + "id": r.id, + "trade_type": r.trade_type, + "symbol": r.symbol, + "variety": r.variety, + "symbol_name": r.symbol_name, + "direction": r.direction, + "offset": r.offset, + "price": r.price, + "volume": r.volume, + "amount": r.amount, + "close_pnl": r.close_pnl, + "commission": r.commission, + "trade_date": r.trade_date, + "trade_time": r.trade_time, + "import_batch": r.import_batch, + "source_file": r.source_file, + } for r in records], + }, + } + + +# ==================== 批次管理 ==================== + +@router.get("/batches") +def get_batches( + db: Session = Depends(get_analysis_db), +): + """查询导入批次列表""" + batches = db.query(TradeImportBatch).order_by( + TradeImportBatch.created_at.desc() + ).all() + + return { + "success": True, + "data": [{ + "id": b.id, + "batch_id": b.batch_id, + "source_file": b.source_file, + "futures_count": b.futures_count, + "options_count": b.options_count, + "trade_dates": b.trade_dates, + "created_at": b.created_at.strftime('%Y-%m-%d %H:%M:%S') if b.created_at else '', + } for b in batches], + } + + +@router.delete("/records/{batch_id}") +def delete_batch_records( + batch_id: str, + db: Session = Depends(get_analysis_db), +): + """按批次删除交易记录""" + count = db.query(TradeRecord).filter(TradeRecord.import_batch == batch_id).count() + if count == 0: + raise HTTPException(status_code=404, detail="未找到该批次的交易记录") + + db.query(TradeRecord).filter(TradeRecord.import_batch == batch_id).delete() + db.query(TradeImportBatch).filter(TradeImportBatch.batch_id == batch_id).delete() + db.commit() + + return {"success": True, "message": f"已删除 {count} 条交易记录"} + + +# ==================== 汇总统计 ==================== + +@router.get("/latest-trade-date") +def get_latest_trade_date( + db: Session = Depends(get_analysis_db), +): + """获取最后一个有交易记录的交易日""" + from datetime import date + latest = db.query(TradeRecord.trade_date).filter( + TradeRecord.trade_date.isnot(None), + TradeRecord.trade_date != '', + ).distinct().order_by(TradeRecord.trade_date.desc()).first() + + if latest: + return {"success": True, "data": {"trade_date": latest[0]}} + else: + return {"success": True, "data": {"trade_date": date.today().strftime('%Y-%m-%d')}} + + +@router.delete("/records-by-date/{trade_date}") +def delete_records_by_date( + trade_date: str, + db: Session = Depends(get_analysis_db), +): + """删除指定交易日的所有交易记录""" + count = db.query(TradeRecord).filter(TradeRecord.trade_date == trade_date).count() + if count == 0: + raise HTTPException(status_code=404, detail=f"未找到 {trade_date} 的交易记录") + + db.query(TradeRecord).filter(TradeRecord.trade_date == trade_date).delete() + db.commit() + + return {"success": True, "message": f"已删除 {trade_date} 的 {count} 条交易记录"} + + +@router.get("/daily-summary") +def get_daily_summary( + start_date: Optional[str] = Query(None), + end_date: Optional[str] = Query(None), + db: Session = Depends(get_analysis_db), +): + """获取每日交易盈亏汇总""" + data = calc_daily_summary(db, start_date, end_date) + return {"success": True, "data": data} + + +@router.get("/variety-summary") +def get_variety_summary( + start_date: Optional[str] = Query(None), + end_date: Optional[str] = Query(None), + db: Session = Depends(get_analysis_db), +): + """获取品种交易盈亏汇总""" + data = calc_variety_summary(db, start_date, end_date) + return {"success": True, "data": data} + + +@router.get("/statistics") +def get_statistics( + start_date: Optional[str] = Query(None), + end_date: Optional[str] = Query(None), + db: Session = Depends(get_analysis_db), +): + """获取整体交易统计""" + data = calc_overall_statistics(db, start_date, end_date) + return {"success": True, "data": data} + + +@router.get("/trade-pairs") +def get_trade_pairs_api( + start_date: Optional[str] = Query(None), + end_date: Optional[str] = Query(None), + symbol: Optional[str] = Query(None), + db: Session = Depends(get_analysis_db), +): + """获取开平仓配对后的逐笔交易""" + pairs = get_trade_pairs(db, start_date, end_date) + if symbol: + pairs = [p for p in pairs if p["symbol"] == symbol] + return {"success": True, "data": pairs} + + +# ==================== K线 + 交易标记 ==================== + + +@router.get("/kline-with-trades/{symbol}") +def get_kline_with_trades( + symbol: str, + period: str = Query("daily", description="K线周期: daily/60min/15min/5min"), + start_date: Optional[str] = Query(None), + end_date: Optional[str] = Query(None), + db: Session = Depends(get_analysis_db), +): + """获取K线数据 + 该品种的交易标记(买卖点)。symbol 可以是完整合约代码(AG2608)或品种代码(AG)""" + import json + import re + from app.database import SessionLocal as MainSessionLocal + from app.models import MarketData + + # 判断是品种代码还是完整合约代码 + variety_code = symbol.upper() + if re.match(r'^[A-Za-z]+\d', symbol): + # 完整合约代码如 AG2608 + variety_code = re.match(r'^([A-Za-z]+)', symbol).group(1).upper() + kline_symbol = symbol + else: + # 品种代码如 AG,从配置中查找当前合约 + kline_symbol = _resolve_symbol_from_config(variety_code) + + main_db = MainSessionLocal() + try: + market_data = main_db.query(MarketData).filter( + MarketData.symbol == kline_symbol, + MarketData.period == period, + ).first() + + if not market_data or not market_data.candles_json: + # 尝试模糊匹配:查找以该品种代码开头的合约 + market_data = main_db.query(MarketData).filter( + MarketData.symbol.like(f'{variety_code}%'), + MarketData.period == period, + ).first() + + if not market_data or not market_data.candles_json: + return {"success": False, "message": f"未找到 {kline_symbol} 的 {period} K线数据"} + + candles = json.loads(market_data.candles_json) + kline_symbol = market_data.symbol + finally: + main_db.close() + + # 获取该品种的交易记录 + query = db.query(TradeRecord).filter(TradeRecord.variety == variety_code) + if start_date: + query = query.filter(TradeRecord.trade_date >= start_date) + if end_date: + query = query.filter(TradeRecord.trade_date <= end_date) + trades = query.order_by(TradeRecord.trade_date, TradeRecord.trade_time).all() + + trade_markers = [] + for t in trades: + trade_markers.append({ + "date": t.trade_date, + "time": t.trade_time, + "symbol": t.symbol, + "direction": t.direction, + "offset": t.offset, + "price": t.price, + "volume": t.volume, + "close_pnl": t.close_pnl, + "commission": t.commission, + }) + + return { + "success": True, + "data": { + "symbol": kline_symbol, + "period": period, + "candles": candles, + "trade_markers": trade_markers, + }, + } + + +def _resolve_symbol_from_config(variety_code: str) -> str: + """从品种配置中查找品种代码对应的当前合约""" + import json as _json + from pathlib import Path + config_path = Path(__file__).resolve().parent.parent.parent / "config" / "symbols_config.json" + if not config_path.exists(): + return variety_code + with open(config_path, "r", encoding="utf-8") as f: + config = _json.load(f) + for name, contract in config.get("futures", {}).items(): + if contract.upper().startswith(variety_code.upper()): + return contract + return variety_code + + +# ==================== AI 逐笔交易分析 ==================== + +class AnalyzeTradeRequest(BaseModel): + symbol: str + open_date: str + open_time: Optional[str] = None + close_date: Optional[str] = None + close_time: Optional[str] = None + direction: str # 多/空 + open_price: Optional[float] = None + close_price: Optional[float] = None + + +@router.post("/analyze-trade") +async def analyze_trade( + req: AnalyzeTradeRequest, + db: Session = Depends(get_analysis_db), +): + """AI 分析单笔交易(结合多周期K线数据)""" + from app.services.ai_analysis import AIAnalysisService + + # 收集多周期K线数据 + from app.database import SessionLocal as MainSessionLocal + from app.models import MarketData + import json + + periods = ["daily", "60min", "15min", "5min"] + kline_context = {} + + main_db = MainSessionLocal() + try: + for period in periods: + market_data = main_db.query(MarketData).filter( + MarketData.symbol == req.symbol, + MarketData.period == period, + ).first() + if market_data and market_data.candles_json: + candles = json.loads(market_data.candles_json) + kline_context[period] = candles[-50:] if len(candles) > 50 else candles + finally: + main_db.close() + + if not kline_context: + return {"success": False, "message": f"未找到 {req.symbol} 的K线数据,无法分析"} + + # 构建分析提示 + prompt = f"""请分析以下期货交易交易的优缺点: + +交易信息: +- 品种:{req.symbol} +- 方向:{req.direction} +- 开仓时间:{req.open_date} {req.open_time or ''} +- 开仓价格:{req.open_price or '未知'} +- 平仓时间:{req.close_date or '未知'} {req.close_time or ''} +- 平仓价格:{req.close_price or '未知'} + +各周期K线数据(格式:[日期, 开盘, 收盘, 最低, 最高, 成交量]): +""" + for period, candles in kline_context.items(): + prompt += f"\n{period} 周期(最近{len(candles)}根K线):\n" + for c in candles[-10:]: + prompt += f" {c[0]}: 开{c[1]} 收{c[2]} 低{c[3]} 高{c[4]} 量{c[5]}\n" + + prompt += """ +请从以下维度分析这笔交易: +1. 入场时机分析:入场时各周期的趋势如何,入场点是否合理 +2. 出场时机分析:出场时的市场状态,是否过早/过晚出场 +3. 持仓期间分析:持仓期间市场走势,是否有更好的操作机会 +4. 综合评价:这笔交易的主要优点和不足之处 +5. 改进建议:类似行情下如何优化操作 +""" + + try: + # 使用 AIAnalysisService 的模型配置和调用能力 + service = AIAnalysisService.__new__(AIAnalysisService) + model = service.get_active_model() + if not model: + return {"success": False, "message": "未配置AI模型或模型未激活,请先在AI配置页面设置"} + + response = service.call_ai_model(prompt, model) + if not response: + return {"success": False, "message": "AI模型返回空响应,请稍后重试"} + + return {"success": True, "data": {"analysis": response, "symbol": req.symbol}} + except Exception as e: + logger.exception("AI分析交易失败") + return {"success": False, "message": f"AI分析失败: {str(e)}"} diff --git a/app/main.py b/app/main.py index 0380f0b..5f08a14 100644 --- a/app/main.py +++ b/app/main.py @@ -13,7 +13,7 @@ from fastapi.responses import FileResponse, RedirectResponse from app.database import engine, Base from app.user_models import Base as UserBase from app.config import HOST, PORT, LOG_LEVEL -from app.api import data, tasks, config, futures_analysis, ai_config, auth, review_plan +from app.api import data, tasks, config, futures_analysis, ai_config, auth, review_plan, trade_review from app.services.scheduler import start_scheduler, stop_scheduler # 配置日志 @@ -121,6 +121,7 @@ app.include_router(config.router, prefix="/api/v1") app.include_router(futures_analysis.router, prefix="/api/v1") app.include_router(ai_config.router, prefix="/api/v1") app.include_router(review_plan.router, prefix="/api/v1") +app.include_router(trade_review.router, prefix="/api/v1") @app.get("/futures-analysis") diff --git a/app/services/trade_parser.py b/app/services/trade_parser.py new file mode 100644 index 0000000..9b51d77 --- /dev/null +++ b/app/services/trade_parser.py @@ -0,0 +1,609 @@ +""" +交易结算单解析服务 +复用 data/trading_calculate.py 的解析逻辑,支持从 .xls 结算单文件中提取交易记录并存入数据库 +""" +import re +import uuid +import json +from datetime import datetime +from collections import defaultdict +from pathlib import Path + +import xlrd +import pandas as pd +from sqlalchemy.orm import Session as DBSession + +from app.analysis_models import TradeRecord, TradeImportBatch + +# 品种代码 -> 中文名 映射(反向映射:从合约代码前缀查中文名) +_VARIETY_NAME_MAP = {} + + +def _load_variety_name_map(): + """从 symbols_config.json 加载品种名称映射""" + global _VARIETY_NAME_MAP + if _VARIETY_NAME_MAP: + return + config_path = Path(__file__).resolve().parent.parent.parent / "config" / "symbols_config.json" + if config_path.exists(): + with open(config_path, "r", encoding="utf-8") as f: + config = json.load(f) + # 正向: {"沪银": "AG2608"} -> 反向: {"AG": "沪银"} + for name, contract in config.get("futures", {}).items(): + variety = extract_variety(contract) + if variety and variety not in _VARIETY_NAME_MAP: + _VARIETY_NAME_MAP[variety] = name + + +def extract_variety(contract): + """提取品种代码(所有前导字母)""" + if pd.isna(contract) or not contract: + return '' + contract = str(contract).strip() + match = re.match(r'^([A-Za-z]+)', contract) + if match: + return match.group(1).upper() + return contract[:2].upper() + + +def safe_float(value, default=0.0): + """安全转换为float""" + if pd.isna(value) or value is None or value == '': + return default + try: + return float(value) + except (ValueError, TypeError): + return default + + +def find_header(sheet, keywords): + """ + 动态识别表头行 + :param sheet: xlrd sheet对象 + :param keywords: 关键词列表 + :return: 表头行索引 + """ + for row_idx in range(min(20, sheet.nrows)): + row_values = [str(sheet.cell_value(row_idx, col_idx)).strip() + for col_idx in range(sheet.ncols)] + row_text = ' '.join(row_values) + has_keyword = any(kw in row_text for kw in keywords) + has_aux = any(aux in row_text for aux in ["买/卖", "成交价", "权利金"]) + if has_keyword and has_aux: + return row_idx + return None + + +def read_sheet(xls_file_path, sheet_name, trade_type): + """ + 读取单个Sheet的数据 + :param xls_file_path: Excel文件路径(str 或 bytes) + :param sheet_name: Sheet名称 + :param trade_type: 交易类型(期货/期权) + :return: DataFrame + """ + try: + wb = xlrd.open_workbook(xls_file_path) + if sheet_name not in wb.sheet_names(): + return pd.DataFrame() + + sheet = wb.sheet_by_name(sheet_name) + if sheet.nrows == 0: + return pd.DataFrame() + + if trade_type == '期货': + keywords = ["合约"] + else: + keywords = ["品种合约"] + + header_row = find_header(sheet, keywords) + if header_row is None: + return pd.DataFrame() + + headers = [str(sheet.cell_value(header_row, col_idx)).strip() + for col_idx in range(sheet.ncols)] + + data = [] + for row_idx in range(header_row + 1, sheet.nrows): + row_data = {} + first_cell = str(sheet.cell_value(row_idx, 0)).strip() + if first_cell == '合计' or first_cell == '': + continue + for col_idx, header in enumerate(headers): + if col_idx < sheet.ncols: + value = sheet.cell_value(row_idx, col_idx) + if str(value).strip() == '--': + value = None + row_data[header] = value + row_data['类型'] = trade_type + data.append(row_data) + + return pd.DataFrame(data) + except Exception: + return pd.DataFrame() + + +def parse_settlement_file(file_content: bytes, filename: str) -> tuple[pd.DataFrame, pd.DataFrame]: + """ + 解析结算单文件,返回期货和期权交易DataFrame + :param file_content: 文件二进制内容 + :param filename: 文件名 + :return: (df_futures, df_options) + """ + import tempfile + # xlrd 需要文件路径,写入临时文件 + with tempfile.NamedTemporaryFile(suffix='.xls', delete=False) as tmp: + tmp.write(file_content) + tmp_path = tmp.name + + try: + df_futures = read_sheet(tmp_path, '成交明细', '期货') + df_options = read_sheet(tmp_path, '期权成交明细', '期权') + return df_futures, df_options + finally: + Path(tmp_path).unlink(missing_ok=True) + + +def _normalize_date(date_val) -> str: + """将各种日期格式统一为 YYYY-MM-DD""" + if pd.isna(date_val) or date_val is None or date_val == '': + return '' + s = str(date_val).strip() + # 已经是 YYYY-MM-DD + if re.match(r'^\d{4}-\d{2}-\d{2}$', s): + return s + # YYYY/MM/DD + if re.match(r'^\d{4}/\d{2}/\d{2}$', s): + return s.replace('/', '-') + # YYYYMMDD + if re.match(r'^\d{8}$', s): + return f"{s[:4]}-{s[4:6]}-{s[6:8]}" + # xlrd 日期数字 (Excel serial date) + try: + f = float(s) + if 30000 < f < 60000: # 合理的 Excel 日期数字范围 + dt = xlrd.xldate_as_datetime(f, 0) + return dt.strftime('%Y-%m-%d') + except (ValueError, TypeError): + pass + return s + + +def _normalize_time(time_val) -> str: + """将各种时间格式统一为 HH:MM:SS""" + if pd.isna(time_val) or time_val is None or time_val == '': + return '' + s = str(time_val).strip() + # 已经是 HH:MM:SS + if re.match(r'^\d{1,2}:\d{2}:\d{2}$', s): + return s + # HH:MM + if re.match(r'^\d{1,2}:\d{2}$', s): + return s + ':00' + # xlrd 时间数字 + try: + f = float(s) + if 0 <= f < 1: + hours = int(f * 24) + minutes = int((f * 24 - hours) * 60) + seconds = int(((f * 24 - hours) * 60 - minutes) * 60) + return f"{hours:02d}:{minutes:02d}:{seconds:02d}" + except (ValueError, TypeError): + pass + return s + + +def save_to_db( + db: DBSession, + df_futures: pd.DataFrame, + df_options: pd.DataFrame, + filename: str, +) -> dict: + """ + 将解析后的交易记录保存到数据库 + :return: {"batch_id": str, "futures_count": int, "options_count": int, "trade_dates": str} + """ + _load_variety_name_map() + batch_id = str(uuid.uuid4()) + all_dates = set() + futures_count = 0 + options_count = 0 + + records = [] + + # 处理期货记录 + if not df_futures.empty: + for _, row in df_futures.iterrows(): + contract = str(row.get('合约', '')).strip() + if not contract or contract == '合计': + continue + variety = extract_variety(contract) + trade_date = _normalize_date(row.get('实际成交日期', '')) + trade_time = _normalize_time(row.get('成交时间', '')) + bs_flag = str(row.get('买/卖', '')).strip() + oc_flag = str(row.get('开/平', '')).strip() + + rec = TradeRecord( + trade_type='期货', + symbol=contract, + variety=variety, + symbol_name=_VARIETY_NAME_MAP.get(variety, ''), + direction='买' if '买' in bs_flag else '卖', + offset='开' if '开' in oc_flag else ('平' if '平' in oc_flag else ''), + price=safe_float(row.get('成交价')), + volume=safe_float(row.get('手数')), + amount=safe_float(row.get('成交额')), + close_pnl=safe_float(row.get('平仓盈亏')), + commission=safe_float(row.get('手续费')), + trade_date=trade_date, + trade_time=trade_time, + import_batch=batch_id, + source_file=filename, + ) + records.append(rec) + futures_count += 1 + if trade_date: + all_dates.add(trade_date) + + # 处理期权记录 + if not df_options.empty: + for _, row in df_options.iterrows(): + contract = str(row.get('品种合约', '')).strip() + if not contract or contract == '合计': + continue + variety = extract_variety(contract) + trade_date = _normalize_date(row.get('成交日期', '')) + trade_time = _normalize_time(row.get('成交时间', '')) + bs_flag = str(row.get('买/卖', '')).strip() + + rec = TradeRecord( + trade_type='期权', + symbol=contract, + variety=variety, + symbol_name=_VARIETY_NAME_MAP.get(variety, ''), + direction='买' if '买' in bs_flag else '卖', + offset='', + price=safe_float(row.get('权利金单价')), + volume=safe_float(row.get('成交量')), + amount=safe_float(row.get('权利金')), + close_pnl=0.0, + commission=safe_float(row.get('手续费')), + trade_date=trade_date, + trade_time=trade_time, + import_batch=batch_id, + source_file=filename, + ) + records.append(rec) + options_count += 1 + if trade_date: + all_dates.add(trade_date) + + if records: + db.add_all(records) + + # 日期范围描述 + sorted_dates = sorted(all_dates) + trade_dates_str = '' + if sorted_dates: + if len(sorted_dates) == 1: + trade_dates_str = sorted_dates[0] + else: + trade_dates_str = f"{sorted_dates[0]} ~ {sorted_dates[-1]}" + + # 保存批次记录 + batch = TradeImportBatch( + batch_id=batch_id, + source_file=filename, + futures_count=futures_count, + options_count=options_count, + trade_dates=trade_dates_str, + ) + db.add(batch) + db.commit() + + return { + "batch_id": batch_id, + "futures_count": futures_count, + "options_count": options_count, + "trade_dates": trade_dates_str, + } + + +def calc_daily_summary(db: DBSession, start_date: str = None, end_date: str = None) -> list[dict]: + """按日期汇总交易盈亏""" + query = db.query(TradeRecord) + if start_date: + query = query.filter(TradeRecord.trade_date >= start_date) + if end_date: + query = query.filter(TradeRecord.trade_date <= end_date) + query = query.order_by(TradeRecord.trade_date) + + records = query.all() + daily = defaultdict(lambda: { + "trade_date": "", + "total_trades": 0, + "total_pnl": 0.0, + "total_commission": 0.0, + "win_count": 0, + "loss_count": 0, + "max_win": 0.0, + "max_loss": 0.0, + "buy_volume": 0.0, + "sell_volume": 0.0, + "varieties": set(), + }) + + for r in records: + d = r.trade_date or '未知' + day = daily[d] + day["trade_date"] = d + day["total_trades"] += 1 + pnl = (r.close_pnl or 0) - (r.commission or 0) + day["total_pnl"] += pnl + day["total_commission"] += (r.commission or 0) + if pnl > 0: + day["win_count"] += 1 + day["max_win"] = max(day["max_win"], pnl) + elif pnl < 0: + day["loss_count"] += 1 + day["max_loss"] = min(day["max_loss"], pnl) + if r.direction == '买': + day["buy_volume"] += (r.volume or 0) + else: + day["sell_volume"] += (r.volume or 0) + day["varieties"].add(r.variety) + + result = [] + for d in sorted(daily.keys()): + day = daily[d] + total = day["win_count"] + day["loss_count"] + result.append({ + "trade_date": day["trade_date"], + "total_trades": day["total_trades"], + "total_pnl": round(day["total_pnl"], 2), + "total_commission": round(day["total_commission"], 2), + "win_count": day["win_count"], + "loss_count": day["loss_count"], + "win_rate": round(day["win_count"] / total * 100, 1) if total > 0 else 0, + "max_win": round(day["max_win"], 2), + "max_loss": round(day["max_loss"], 2), + "buy_volume": day["buy_volume"], + "sell_volume": day["sell_volume"], + "variety_count": len(day["varieties"]), + }) + return result + + +def calc_variety_summary(db: DBSession, start_date: str = None, end_date: str = None) -> list[dict]: + """按品种汇总交易盈亏""" + query = db.query(TradeRecord) + if start_date: + query = query.filter(TradeRecord.trade_date >= start_date) + if end_date: + query = query.filter(TradeRecord.trade_date <= end_date) + + records = query.all() + varieties = defaultdict(lambda: { + "variety": "", + "symbol_name": "", + "total_trades": 0, + "total_pnl": 0.0, + "total_commission": 0.0, + "total_close_pnl": 0.0, + "total_volume": 0.0, + "total_amount": 0.0, + "buy_count": 0, + "sell_count": 0, + "win_count": 0, + "loss_count": 0, + }) + + for r in records: + v = varieties[r.variety] + v["variety"] = r.variety + v["symbol_name"] = r.symbol_name or v["symbol_name"] + v["total_trades"] += 1 + v["total_close_pnl"] += (r.close_pnl or 0) + v["total_commission"] += (r.commission or 0) + v["total_pnl"] += (r.close_pnl or 0) - (r.commission or 0) + v["total_volume"] += (r.volume or 0) + v["total_amount"] += (r.amount or 0) + if r.direction == '买': + v["buy_count"] += 1 + else: + v["sell_count"] += 1 + pnl = (r.close_pnl or 0) - (r.commission or 0) + if pnl > 0: + v["win_count"] += 1 + elif pnl < 0: + v["loss_count"] += 1 + + result = [] + for variety in sorted(varieties.keys()): + v = varieties[variety] + total = v["win_count"] + v["loss_count"] + result.append({ + "variety": v["variety"], + "symbol_name": v["symbol_name"], + "total_trades": v["total_trades"], + "total_pnl": round(v["total_pnl"], 2), + "total_close_pnl": round(v["total_close_pnl"], 2), + "total_commission": round(v["total_commission"], 2), + "total_volume": v["total_volume"], + "total_amount": round(v["total_amount"], 2), + "buy_count": v["buy_count"], + "sell_count": v["sell_count"], + "win_count": v["win_count"], + "loss_count": v["loss_count"], + "win_rate": round(v["win_count"] / total * 100, 1) if total > 0 else 0, + }) + return result + + +def calc_overall_statistics(db: DBSession, start_date: str = None, end_date: str = None) -> dict: + """计算整体交易统计""" + daily = calc_daily_summary(db, start_date, end_date) + if not daily: + return { + "total_trades": 0, + "total_pnl": 0, + "total_commission": 0, + "win_rate": 0, + "profit_loss_ratio": 0, + "max_single_win": 0, + "max_single_loss": 0, + "max_consecutive_wins": 0, + "max_consecutive_losses": 0, + "trading_days": 0, + "avg_daily_pnl": 0, + "max_daily_pnl": 0, + "min_daily_pnl": 0, + } + + total_trades = sum(d["total_trades"] for d in daily) + total_pnl = sum(d["total_pnl"] for d in daily) + total_commission = sum(d["total_commission"] for d in daily) + total_wins = sum(d["win_count"] for d in daily) + total_losses = sum(d["loss_count"] for d in daily) + total_decided = total_wins + total_losses + + # 最大连续盈/亏 + max_consec_wins = 0 + max_consec_losses = 0 + cur_wins = 0 + cur_losses = 0 + for d in daily: + if d["total_pnl"] > 0: + cur_wins += 1 + cur_losses = 0 + max_consec_wins = max(max_consec_wins, cur_wins) + elif d["total_pnl"] < 0: + cur_losses += 1 + cur_wins = 0 + max_consec_losses = max(max_consec_losses, cur_losses) + else: + cur_wins = 0 + cur_losses = 0 + + # 平均日盈亏 + pnl_values = [d["total_pnl"] for d in daily] + + # 盈亏比 - 用逐笔数据计算 + query = db.query(TradeRecord) + if start_date: + query = query.filter(TradeRecord.trade_date >= start_date) + if end_date: + query = query.filter(TradeRecord.trade_date <= end_date) + all_records = query.all() + + wins_pnl = [] + losses_pnl = [] + for r in all_records: + pnl = (r.close_pnl or 0) - (r.commission or 0) + if pnl > 0: + wins_pnl.append(pnl) + elif pnl < 0: + losses_pnl.append(pnl) + + avg_win_val = sum(wins_pnl) / len(wins_pnl) if wins_pnl else 0 + avg_loss_val = abs(sum(losses_pnl) / len(losses_pnl)) if losses_pnl else 0 + profit_loss_ratio = round(avg_win_val / avg_loss_val, 2) if avg_loss_val > 0 else 0 + + return { + "total_trades": total_trades, + "total_pnl": round(total_pnl, 2), + "total_commission": round(total_commission, 2), + "win_rate": round(total_wins / total_decided * 100, 1) if total_decided > 0 else 0, + "profit_loss_ratio": profit_loss_ratio, + "max_single_win": round(max(wins_pnl), 2) if wins_pnl else 0, + "max_single_loss": round(min(losses_pnl), 2) if losses_pnl else 0, + "max_consecutive_wins": max_consec_wins, + "max_consecutive_losses": max_consec_losses, + "trading_days": len(daily), + "avg_daily_pnl": round(sum(pnl_values) / len(pnl_values), 2) if pnl_values else 0, + "max_daily_pnl": round(max(pnl_values), 2) if pnl_values else 0, + "min_daily_pnl": round(min(pnl_values), 2) if pnl_values else 0, + } + + +def get_trade_pairs(db: DBSession, start_date: str = None, end_date: str = None) -> list[dict]: + """ + 将开平仓配对,生成逐笔交易对 + 按合约+品种分组,按时间排序,买开/卖开 与 卖平/买平 配对 + """ + query = db.query(TradeRecord) + if start_date: + query = query.filter(TradeRecord.trade_date >= start_date) + if end_date: + query = query.filter(TradeRecord.trade_date <= end_date) + query = query.order_by(TradeRecord.symbol, TradeRecord.trade_date, TradeRecord.trade_time) + + records = query.all() + + # 按合约分组 + by_symbol = defaultdict(list) + for r in records: + by_symbol[r.symbol].append(r) + + pairs = [] + pair_id = 0 + + for symbol, recs in by_symbol.items(): + open_positions = [] # 未平仓记录 + + for r in recs: + if r.offset == '开': + open_positions.append(r) + elif r.offset == '平' and open_positions: + # 配对:取最早的一条开仓 + open_rec = open_positions.pop(0) + pair_id += 1 + pnl = (r.close_pnl or 0) - (open_rec.commission or 0) - (r.commission or 0) + direction = '多' if open_rec.direction == '买' else '空' + pairs.append({ + "id": pair_id, + "symbol": symbol, + "variety": r.variety, + "symbol_name": r.symbol_name, + "direction": direction, + "open_date": open_rec.trade_date, + "open_time": open_rec.trade_time, + "open_price": open_rec.price, + "close_date": r.trade_date, + "close_time": r.trade_time, + "close_price": r.price, + "volume": open_rec.volume, + "close_pnl": round((r.close_pnl or 0), 2), + "commission": round((open_rec.commission or 0) + (r.commission or 0), 2), + "net_pnl": round(pnl, 2), + "open_batch": open_rec.import_batch, + "close_batch": r.import_batch, + }) + elif r.offset == '' or r.offset is None: + # 期权等无开平标记的,按买卖交替配对 + if open_positions: + open_rec = open_positions.pop(0) + pair_id += 1 + pairs.append({ + "id": pair_id, + "symbol": symbol, + "variety": r.variety, + "symbol_name": r.symbol_name, + "direction": '多' if open_rec.direction == '买' else '空', + "open_date": open_rec.trade_date, + "open_time": open_rec.trade_time, + "open_price": open_rec.price, + "close_date": r.trade_date, + "close_time": r.trade_time, + "close_price": r.price, + "volume": open_rec.volume, + "close_pnl": 0, + "commission": round((open_rec.commission or 0) + (r.commission or 0), 2), + "net_pnl": 0, + "open_batch": open_rec.import_batch, + "close_batch": r.import_batch, + }) + else: + open_positions.append(r) + + return pairs diff --git a/app/static/futures_analysis.html b/app/static/futures_analysis.html index 0fa493a..a588ea2 100644 --- a/app/static/futures_analysis.html +++ b/app/static/futures_analysis.html @@ -488,6 +488,96 @@ .rv-spinner { width: 40px; height: 40px; border: 3px solid #F5F5F7; border-top-color: var(--color-brand); border-radius: 50%; animation: rv-spin 1s linear infinite; margin: 0 auto 16px; } @keyframes rv-spin { to { transform: rotate(360deg); } } + /* ============================================ + 交易复盘页面样式 + ============================================ */ + #trade-review-view { background: var(--bg-page); border-radius: 20px; padding: 24px 0; min-height: 600px; } + .tr-toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 12px; } + .tr-toolbar-left, .tr-toolbar-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } + .tr-btn { padding: 10px 20px; border-radius: 12px; font-size: 14px; font-weight: 600; cursor: pointer; border: none; display: flex; align-items: center; gap: 8px; transition: all 0.2s; font-family: inherit; } + .tr-btn-primary { background: var(--color-brand); color: #fff; } + .tr-btn-primary:hover { background: #0066d6; box-shadow: 0 4px 10px rgba(0,122,255,0.3); } + .tr-btn-secondary { background: #fff; color: var(--text-secondary); box-shadow: var(--shadow-sm); border: 1px solid rgba(0,0,0,0.05); } + .tr-btn-secondary:hover { background: #F5F5F7; } + .tr-btn-danger { background: rgba(255,59,48,0.1); color: var(--color-up); border: 1px solid rgba(255,59,48,0.2); } + .tr-btn-danger:hover { background: rgba(255,59,48,0.2); } + .tr-date-input { background: #fff; border: 1px solid rgba(0,0,0,0.05); border-radius: 12px; padding: 0 14px; height: 40px; font-size: 13px; color: var(--text-primary); box-shadow: var(--shadow-sm); outline: none; font-family: inherit; } + .tr-date-input:focus { border-color: var(--color-brand); box-shadow: 0 0 0 3px rgba(0,122,255,0.15); } + .tr-select { background: #fff; border: 1px solid rgba(0,0,0,0.05); border-radius: 12px; padding: 0 14px; height: 40px; font-size: 13px; color: var(--text-primary); box-shadow: var(--shadow-sm); outline: none; font-family: inherit; cursor: pointer; } + + .tr-sub-nav { display: flex; gap: 8px; margin-bottom: 20px; overflow-x: auto; padding-bottom: 4px; } + .tr-sub-nav-item { padding: 8px 18px; border-radius: 9999px; font-size: 13px; background: #fff; color: var(--text-secondary); cursor: pointer; white-space: nowrap; transition: all 0.2s; text-decoration: none; box-shadow: var(--shadow-sm); font-weight: 500; } + .tr-sub-nav-item:hover { background: var(--color-brand); color: #fff; } + .tr-sub-nav-item.active { background: var(--color-brand); color: #fff; box-shadow: 0 4px 10px rgba(0,122,255,0.2); } + + .tr-stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 20px; } + .tr-stat-card { background: var(--bg-card); border-radius: 16px; padding: 18px; text-align: center; box-shadow: var(--shadow-sm); transition: transform 0.2s; } + .tr-stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); } + .tr-stat-label { font-size: 11px; color: var(--text-tertiary); margin-bottom: 6px; text-transform: uppercase; font-weight: 600; } + .tr-stat-value { font-size: 22px; font-weight: 700; } + .tr-stat-value.profit { color: var(--color-down); } + .tr-stat-value.loss { color: var(--color-up); } + .tr-stat-sub { font-size: 12px; color: var(--text-tertiary); margin-top: 4px; } + + .tr-sub-page { display: none; } + .tr-sub-page.active { display: block; } + + .tr-table { width: 100%; border-collapse: collapse; font-size: 13px; } + .tr-table th { background: #F5F5F7; padding: 10px 12px; text-align: left; font-weight: 600; color: var(--text-tertiary); font-size: 12px; white-space: nowrap; } + .tr-table td { padding: 9px 12px; border-bottom: 1px solid rgba(0,0,0,0.04); color: var(--text-primary); white-space: nowrap; } + .tr-table tbody tr { transition: background 0.15s; } + .tr-table tbody tr:hover { background: rgba(0,122,255,0.03); } + .tr-pnl-positive { color: var(--color-down); font-weight: 600; } + .tr-pnl-negative { color: var(--color-up); font-weight: 600; } + .tr-dir-buy { color: var(--color-down); font-weight: 500; } + .tr-dir-sell { color: var(--color-up); font-weight: 500; } + + .tr-pagination { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 16px; } + .tr-pagination button { padding: 6px 14px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.08); background: #fff; font-size: 12px; cursor: pointer; transition: all 0.2s; } + .tr-pagination button:hover { background: var(--color-brand); color: #fff; border-color: var(--color-brand); } + .tr-pagination button:disabled { opacity: 0.4; cursor: default; } + .tr-pagination span { font-size: 12px; color: var(--text-tertiary); } + + .tr-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 300px; gap: 12px; } + .tr-empty-icon { font-size: 56px; opacity: 0.3; } + .tr-empty-text { font-size: 15px; color: var(--text-tertiary); } + + .tr-kline-controls { display: flex; gap: 10px; margin-bottom: 16px; align-items: center; } + .tr-kline-chart { width: 100%; height: 500px; background: var(--bg-card); border-radius: 16px; box-shadow: var(--shadow-sm); } + .tr-chart-container { width: 100%; height: 300px; margin-bottom: 16px; background: var(--bg-card); border-radius: 16px; box-shadow: var(--shadow-sm); } + + .tr-pairs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; } + .tr-pair-card { background: var(--bg-card); border-radius: 16px; padding: 18px; box-shadow: var(--shadow-sm); transition: all 0.2s; position: relative; overflow: hidden; } + .tr-pair-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); } + .tr-pair-card::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; } + .tr-pair-card.profit::before { background: var(--color-down); } + .tr-pair-card.loss::before { background: var(--color-up); } + .tr-pair-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } + .tr-pair-symbol { font-size: 15px; font-weight: 700; color: var(--text-primary); } + .tr-pair-pnl { font-size: 16px; font-weight: 700; padding: 4px 12px; border-radius: 8px; } + .tr-pair-pnl.profit { color: var(--color-down); background: rgba(52,199,89,0.1); } + .tr-pair-pnl.loss { color: var(--color-up); background: rgba(255,59,48,0.1); } + .tr-pair-info { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; font-size: 12px; } + .tr-pair-info-item { display: flex; justify-content: space-between; padding: 4px 8px; background: #F5F5F7; border-radius: 6px; } + .tr-pair-info-label { color: var(--text-tertiary); } + .tr-pair-info-value { font-weight: 600; color: var(--text-primary); } + .tr-pair-actions { margin-top: 12px; display: flex; gap: 8px; } + .tr-pair-actions button { flex: 1; padding: 8px; border-radius: 8px; border: none; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.2s; font-family: inherit; } + .tr-pair-btn-analyze { background: rgba(175,82,222,0.1); color: var(--color-ai); } + .tr-pair-btn-analyze:hover { background: rgba(175,82,222,0.2); } + + .tr-batch-item { display: flex; justify-content: space-between; align-items: center; padding: 14px 16px; background: #F5F5F7; border-radius: 12px; margin-bottom: 10px; } + .tr-batch-info { flex: 1; } + .tr-batch-file { font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; } + .tr-batch-meta { font-size: 12px; color: var(--text-tertiary); } + .tr-batch-delete { padding: 6px 14px; border-radius: 8px; border: none; background: rgba(255,59,48,0.1); color: var(--color-up); font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.2s; } + .tr-batch-delete:hover { background: rgba(255,59,48,0.2); } + + @media (max-width: 768px) { + .tr-stats-grid { grid-template-columns: repeat(2, 1fr); } + .tr-pairs-grid { grid-template-columns: 1fr; } + } + @media (max-width: 768px) { .rv-stats-grid { grid-template-columns: 1fr; } .rv-green-grid { grid-template-columns: 1fr; } @@ -506,6 +596,7 @@ 市场概览 风险预警 复盘计划 + 交易复盘
LIVE @@ -868,6 +959,161 @@
请选择日期或点击"复盘与计划"按钮生成分析报告
+ + +
+ +
+
+ + + + ~ + + +
+
+ + +
+
+ + + + + +
+ + +
+
+ + + + + + + + + +
日期时间类型合约品种买卖开平价格手数成交额平仓盈亏手续费文件
+
+
+ +
+ + +
+
+ + + +
+
+ +
+ + +
+
+
+ + + + + + + + + +
日期交易笔数净盈亏手续费盈利笔亏损笔胜率最大盈利最大亏损品种数
+
+ +
+ + +
+
+ + + + + + + + + +
品种名称交易笔数净盈亏平仓盈亏手续费总手数买入卖出胜率
+
+ +
+ + +
+
+ +
+
+ + +
+
+
+ 导入批次管理 + +
+
+
+
+
+
+ + +
+
+
+ AI 交易分析 + +
+
+
+
+
+
diff --git a/app/static/futures_analysis.js b/app/static/futures_analysis.js index ed8a589..f92ece7 100644 --- a/app/static/futures_analysis.js +++ b/app/static/futures_analysis.js @@ -136,6 +136,8 @@ function initEventListeners() { if (page === 'review') { showReviewView(); + } else if (page === 'trade-review') { + showTradeReviewView(); } else if (page === 'watched') { showWatchedView(); } else { @@ -154,6 +156,7 @@ function showListView() { document.getElementById('detail-view').classList.remove('active'); document.getElementById('review-view').classList.remove('active'); document.getElementById('watched-view').classList.remove('active'); + document.getElementById('trade-review-view').classList.remove('active'); if (klineChart) { klineChart.dispose(); klineChart = null; @@ -165,6 +168,7 @@ function showWatchedView() { document.getElementById('detail-view').classList.remove('active'); document.getElementById('review-view').classList.remove('active'); document.getElementById('watched-view').classList.add('active'); + document.getElementById('trade-review-view').classList.remove('active'); if (klineChart) { klineChart.dispose(); klineChart = null; @@ -177,6 +181,8 @@ function showReviewView() { document.getElementById('list-view').classList.remove('active'); document.getElementById('detail-view').classList.remove('active'); document.getElementById('review-view').classList.add('active'); + document.getElementById('watched-view').classList.remove('active'); + document.getElementById('trade-review-view').classList.remove('active'); if (klineChart) { klineChart.dispose(); klineChart = null; @@ -186,6 +192,20 @@ function showReviewView() { initReviewPlan(); } +function showTradeReviewView() { + document.getElementById('list-view').classList.remove('active'); + document.getElementById('detail-view').classList.remove('active'); + document.getElementById('review-view').classList.remove('active'); + document.getElementById('watched-view').classList.remove('active'); + document.getElementById('trade-review-view').classList.add('active'); + if (klineChart) { + klineChart.dispose(); + klineChart = null; + } + + initTradeReview(); +} + async function showDetailView(symbol) { currentSymbol = symbol; document.getElementById('list-view').classList.remove('active'); @@ -607,6 +627,697 @@ function renderFuturesGrid(data) { `}).join(''); } +// ==================== 交易复盘功能 ==================== + +const TR_API_BASE = '/api/v1/trade-review'; +let trCurrentPage = 1; +let trKlineChart = null; +let trInitialized = false; + +function initTradeReview() { + if (trInitialized) return; + trInitialized = true; + + // 导入按钮 + document.getElementById('tr-btn-import').addEventListener('click', () => { + document.getElementById('tr-file-input').click(); + }); + document.getElementById('tr-file-input').addEventListener('change', trHandleFileImport); + + // 查询按钮 + document.getElementById('tr-btn-query').addEventListener('click', () => { + trCurrentPage = 1; + trLoadAllData(); + }); + + // 删除当日按钮 + document.getElementById('tr-btn-delete-date').addEventListener('click', trDeleteByDate); + + // 批次管理 + document.getElementById('tr-btn-batches').addEventListener('click', trShowBatchModal); + + // 子导航切换 + document.querySelectorAll('.tr-sub-nav-item').forEach(item => { + item.addEventListener('click', function(e) { + e.preventDefault(); + document.querySelectorAll('.tr-sub-nav-item').forEach(i => i.classList.remove('active')); + this.classList.add('active'); + const sub = this.dataset.sub; + document.querySelectorAll('.tr-sub-page').forEach(p => p.classList.remove('active')); + document.getElementById('tr-sub-' + sub).classList.add('active'); + // 延迟加载 + if (sub === 'kline') trLoadKlineSymbolOptions(); + }); + }); + + // K线加载 + document.getElementById('tr-btn-load-kline').addEventListener('click', trLoadKlineWithTrades); + + // 初始加载:先获取最后交易日,设为默认日期,再加载数据 + trInitDefaultDate(); +} + +async function trInitDefaultDate() { + try { + const res = await fetch(`${TR_API_BASE}/latest-trade-date`); + const json = await res.json(); + if (json.success && json.data.trade_date) { + const dateStr = json.data.trade_date; + document.getElementById('tr-start-date').value = dateStr; + document.getElementById('tr-end-date').value = dateStr; + } + } catch (e) { + console.error('获取最后交易日失败', e); + } + trLoadAllData(); +} + +function trGetDateRange() { + const start = document.getElementById('tr-start-date').value || null; + const end = document.getElementById('tr-end-date').value || null; + return { start, end }; +} + +function trBuildParams(extra = {}) { + const { start, end } = trGetDateRange(); + const params = new URLSearchParams(); + if (start) params.set('start_date', start); + if (end) params.set('end_date', end); + for (const [k, v] of Object.entries(extra)) { + if (v) params.set(k, v); + } + return params.toString(); +} + +async function trLoadAllData() { + trLoadStatistics(); + trLoadRecords(); +} + +async function trLoadStatistics() { + try { + const res = await fetch(`${TR_API_BASE}/statistics?${trBuildParams()}`); + const json = await res.json(); + if (json.success) trRenderStatistics(json.data); + } catch (e) { + console.error('加载统计失败', e); + } +} + +function trRenderStatistics(stats) { + const grid = document.getElementById('tr-stats-grid'); + const pnlClass = stats.total_pnl >= 0 ? 'profit' : 'loss'; + grid.innerHTML = ` +
+
总盈亏
+
${stats.total_pnl >= 0 ? '+' : ''}${stats.total_pnl.toFixed(2)}
+
手续费: ${stats.total_commission.toFixed(2)}
+
+
+
胜率
+
${stats.win_rate}%
+
盈亏比: ${stats.profit_loss_ratio}
+
+
+
交易统计
+
${stats.total_trades}
+
${stats.trading_days} 个交易日
+
+
+
日均盈亏
+
${stats.avg_daily_pnl >= 0 ? '+' : ''}${stats.avg_daily_pnl}
+
最大连盈 ${stats.max_consecutive_wins} / 连亏 ${stats.max_consecutive_losses}
+
+ `; +} + +async function trLoadRecords() { + try { + const params = trBuildParams({ page: trCurrentPage, page_size: 50 }); + const res = await fetch(`${TR_API_BASE}/records?${params}`); + const json = await res.json(); + if (json.success) trRenderRecords(json.data); + } catch (e) { + console.error('加载记录失败', e); + } +} + +function trRenderRecords(data) { + const tbody = document.getElementById('tr-records-body'); + const emptyEl = document.getElementById('tr-records-empty'); + const tableWrap = document.getElementById('tr-records-table-wrap'); + const pagination = document.getElementById('tr-records-pagination'); + + if (!data.records || data.records.length === 0) { + tableWrap.style.display = 'none'; + pagination.style.display = 'none'; + emptyEl.style.display = 'flex'; + return; + } + + tableWrap.style.display = ''; + emptyEl.style.display = 'none'; + + tbody.innerHTML = data.records.map(r => { + const pnlClass = r.close_pnl > 0 ? 'tr-pnl-positive' : (r.close_pnl < 0 ? 'tr-pnl-negative' : ''); + const dirClass = r.direction === '买' ? 'tr-dir-buy' : 'tr-dir-sell'; + return ` + ${r.trade_date || '-'} + ${r.trade_time || '-'} + ${r.trade_type} + ${r.symbol} + ${r.symbol_name || r.variety} + ${r.direction} + ${r.offset || '-'} + ${r.price != null ? r.price.toFixed(2) : '-'} + ${r.volume || '-'} + ${r.amount != null ? r.amount.toFixed(2) : '-'} + ${r.close_pnl != null ? r.close_pnl.toFixed(2) : '-'} + ${r.commission != null ? r.commission.toFixed(2) : '-'} + ${(r.source_file || '').substring(0, 15)} + `; + }).join(''); + + // 分页 + const totalPages = Math.ceil(data.total / data.page_size); + pagination.style.display = totalPages > 1 ? 'flex' : 'none'; + pagination.innerHTML = ` + + ${trCurrentPage} / ${totalPages} (共 ${data.total} 条) + + `; +} + +async function trHandleFileImport(e) { + const file = e.target.files[0]; + if (!file) return; + e.target.value = ''; + + const btn = document.getElementById('tr-btn-import'); + btn.disabled = true; + btn.querySelector('span:last-child').textContent = '导入中...'; + + try { + const formData = new FormData(); + formData.append('file', file); + const res = await fetch(`${TR_API_BASE}/import`, { method: 'POST', body: formData }); + const json = await res.json(); + if (json.success) { + alert(json.message); + trCurrentPage = 1; + trLoadAllData(); + } else { + alert(json.message || '导入失败'); + } + } catch (err) { + alert('导入失败: ' + err.message); + } finally { + btn.disabled = false; + btn.querySelector('span:last-child').textContent = '导入结算单'; + } +} + +// ==================== 批次管理 ==================== + +async function trShowBatchModal() { + const modal = document.getElementById('tr-batch-modal'); + modal.classList.add('show'); + try { + const res = await fetch(`${TR_API_BASE}/batches`); + const json = await res.json(); + if (json.success) trRenderBatchList(json.data); + } catch (e) { + console.error('加载批次失败', e); + } +} + +function trRenderBatchList(batches) { + const list = document.getElementById('tr-batch-list'); + if (!batches || batches.length === 0) { + list.innerHTML = '
暂无导入记录
'; + return; + } + list.innerHTML = batches.map(b => ` +
+
+
${b.source_file}
+
期货 ${b.futures_count} 条 | 期权 ${b.options_count} 条 | ${b.trade_dates || '未知日期'} | ${b.created_at}
+
+ +
+ `).join(''); +} + +async function trDeleteBatch(batchId) { + if (!confirm('确认删除该批次的所有交易记录?')) return; + try { + const res = await fetch(`${TR_API_BASE}/records/${batchId}`, { method: 'DELETE' }); + const json = await res.json(); + if (json.success) { + alert(json.message); + trShowBatchModal(); + trCurrentPage = 1; + trLoadAllData(); + } + } catch (e) { + alert('删除失败: ' + e.message); + } +} + +function trCloseBatchModal() { + document.getElementById('tr-batch-modal').classList.remove('show'); +} + +async function trDeleteByDate() { + const startDate = document.getElementById('tr-start-date').value; + const endDate = document.getElementById('tr-end-date').value; + if (!startDate || !endDate) { + alert('请先选择要删除的日期范围'); + return; + } + if (startDate !== endDate) { + alert('删除功能仅支持单个日期,请确保开始日期和结束日期相同'); + return; + } + const tradeDate = startDate; + + // 第一次确认:查询该日期的记录数 + try { + const queryRes = await fetch(`${TR_API_BASE}/records?start_date=${tradeDate}&end_date=${tradeDate}&page=1&page_size=1`); + const queryJson = await queryRes.json(); + const recordCount = queryJson.success ? queryJson.data.total : 0; + + if (recordCount === 0) { + alert(`${tradeDate} 没有交易记录,无需删除`); + return; + } + + // 第一次确认弹窗 + const firstConfirm = confirm( + `⚠️ 警告:即将删除交易数据\n\n` + + `交易日:${tradeDate}\n` + + `记录数量:${recordCount} 条\n\n` + + `此操作将永久删除该日期的所有交易记录,不可恢复!\n\n` + + `点击"确定"继续,点击"取消"返回` + ); + + if (!firstConfirm) return; + + // 第二次确认弹窗(更强烈的警告) + const secondConfirm = confirm( + `🔴 最终确认\n\n` + + `您确定要删除 ${tradeDate} 的 ${recordCount} 条交易记录吗?\n\n` + + `删除后无法恢复,请确认!` + ); + + if (!secondConfirm) return; + + // 执行删除 + const res = await fetch(`${TR_API_BASE}/records-by-date/${tradeDate}`, { method: 'DELETE' }); + const json = await res.json(); + if (json.success) { + alert(`✅ ${json.message}`); + trCurrentPage = 1; + trLoadAllData(); + } else { + alert(json.message || '删除失败'); + } + } catch (e) { + alert('删除失败: ' + e.message); + } +} + +// ==================== K线标注 ==================== + +async function trLoadKlineSymbolOptions() { + try { + // 从品种汇总获取已交易的品种代码 + const res = await fetch(`${TR_API_BASE}/variety-summary?${trBuildParams()}`); + const json = await res.json(); + if (!json.success) return; + const select = document.getElementById('tr-kline-symbol'); + const current = select.value; + select.innerHTML = ''; + // 用已交易的品种代码,K线API会按variety匹配交易记录 + json.data.forEach(v => { + const opt = document.createElement('option'); + opt.value = v.variety; + opt.textContent = `${v.symbol_name || v.variety} (${v.variety})`; + select.appendChild(opt); + }); + if (current) select.value = current; + } catch (e) { + console.error('加载品种选项失败', e); + } +} + +async function trLoadKlineWithTrades() { + const symbol = document.getElementById('tr-kline-symbol').value; + const period = document.getElementById('tr-kline-period').value; + if (!symbol) { alert('请选择品种'); return; } + + const chartDom = document.getElementById('tr-kline-chart'); + const emptyEl = document.getElementById('tr-kline-empty'); + + try { + const params = trBuildParams({ period }); + const res = await fetch(`${TR_API_BASE}/kline-with-trades/${symbol}?${params}`); + const json = await res.json(); + + if (!json.success || !json.data.candles || json.data.candles.length === 0) { + chartDom.style.display = 'none'; + emptyEl.style.display = 'flex'; + if (trKlineChart) { trKlineChart.dispose(); trKlineChart = null; } + return; + } + + chartDom.style.display = ''; + emptyEl.style.display = 'none'; + trRenderKlineWithMarkers(json.data); + } catch (e) { + console.error('加载K线失败', e); + alert('加载K线数据失败'); + } +} + +function trRenderKlineWithMarkers(data) { + if (trKlineChart) trKlineChart.dispose(); + const chartDom = document.getElementById('tr-kline-chart'); + trKlineChart = echarts.init(chartDom, 'dark'); + + const candles = data.candles; + const dates = candles.map(c => c[0]); + const values = candles.map(c => [parseFloat(c[1]), parseFloat(c[2]), parseFloat(c[3]), parseFloat(c[4])]); + const volumes = candles.map(c => [parseInt(c[5]), parseFloat(c[2]) >= parseFloat(c[1]) ? 1 : -1]); + + // 构建买卖标记 + const buyMarkers = []; + const sellMarkers = []; + const markers = data.trade_markers || []; + + markers.forEach(m => { + const dateIdx = dates.indexOf(m.date); + if (dateIdx === -1) return; + const candle = candles[dateIdx]; + const price = parseFloat(m.price); + const low = parseFloat(candle[3]); + const high = parseFloat(candle[4]); + // 确保标记价格在K线范围内 + const markerPrice = Math.max(low, Math.min(high, price)); + + if (m.direction === '买') { + buyMarkers.push({ + name: `买${m.offset || ''}`, + coord: [dateIdx, markerPrice], + value: `买${m.offset || ''} ${price}`, + itemStyle: { color: '#34C759' }, + }); + } else { + sellMarkers.push({ + name: `卖${m.offset || ''}`, + coord: [dateIdx, markerPrice], + value: `卖${m.offset || ''} ${price}`, + itemStyle: { color: '#FF3B30' }, + }); + } + }); + + const option = { + backgroundColor: 'transparent', + animation: false, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'cross' }, + backgroundColor: 'rgba(10, 15, 25, 0.95)', + borderColor: 'rgba(56, 189, 248, 0.2)', + textStyle: { color: '#e2e8f0', fontSize: 12 }, + }, + legend: { data: ['K线'], top: 10, textStyle: { color: '#94a3b8' } }, + grid: [ + { left: 70, right: 20, top: 40, height: '55%' }, + { left: 70, right: 20, top: '62%', height: '14%' }, + ], + xAxis: [ + { type: 'category', data: dates, boundaryGap: true, axisLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }, axisLabel: { color: '#64748b', fontSize: 10 }, splitLine: { show: false } }, + { type: 'category', gridIndex: 1, data: dates, axisLine: { show: false }, axisLabel: { show: false }, splitLine: { show: false } }, + ], + yAxis: [ + { scale: true, splitArea: { show: false }, axisLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }, axisLabel: { color: '#64748b' } }, + { scale: true, gridIndex: 1, splitNumber: 2, axisLabel: { show: false }, axisLine: { show: false }, splitLine: { show: false } }, + ], + dataZoom: [ + { type: 'inside', xAxisIndex: [0, 1], start: candles.length > 60 ? 60 : 0, end: 100 }, + { show: true, xAxisIndex: [0, 1], type: 'slider', bottom: 10, height: 16, start: candles.length > 60 ? 60 : 0, end: 100 }, + ], + series: [ + { + name: 'K线', + type: 'candlestick', + data: values, + itemStyle: { + color: '#34C759', color0: '#FF3B30', + borderColor: '#34C759', borderColor0: '#FF3B30', + }, + markPoint: { + symbol: 'pin', + symbolSize: 40, + data: [ + ...buyMarkers.map(m => ({ + ...m, + symbol: 'triangle', + symbolSize: 14, + symbolRotate: 0, + label: { show: true, formatter: 'B', fontSize: 9, color: '#fff' }, + })), + ...sellMarkers.map(m => ({ + ...m, + symbol: 'triangle', + symbolSize: 14, + symbolRotate: 180, + label: { show: true, formatter: 'S', fontSize: 9, color: '#fff' }, + })), + ], + }, + }, + { + name: '成交量', + type: 'bar', + xAxisIndex: 1, + yAxisIndex: 1, + data: volumes.map(v => ({ + value: v[0], + itemStyle: { color: v[1] > 0 ? 'rgba(52,199,89,0.5)' : 'rgba(255,59,48,0.5)' }, + })), + }, + ], + }; + + trKlineChart.setOption(option); +} + +// ==================== 每日分析 ==================== + +async function trLoadDailySummary() { + try { + const res = await fetch(`${TR_API_BASE}/daily-summary?${trBuildParams()}`); + const json = await res.json(); + if (json.success) trRenderDailySummary(json.data); + } catch (e) { + console.error('加载每日汇总失败', e); + } +} + +function trRenderDailySummary(data) { + const tbody = document.getElementById('tr-daily-body'); + const emptyEl = document.getElementById('tr-daily-empty'); + const chartDom = document.getElementById('tr-daily-chart'); + + if (!data || data.length === 0) { + tbody.innerHTML = ''; + emptyEl.style.display = 'flex'; + chartDom.style.display = 'none'; + return; + } + emptyEl.style.display = 'none'; + chartDom.style.display = ''; + + tbody.innerHTML = data.map(d => { + const pnlClass = d.total_pnl > 0 ? 'tr-pnl-positive' : (d.total_pnl < 0 ? 'tr-pnl-negative' : ''); + return ` + ${d.trade_date} + ${d.total_trades} + ${d.total_pnl >= 0 ? '+' : ''}${d.total_pnl} + ${d.total_commission} + ${d.win_count} + ${d.loss_count} + ${d.win_rate}% + ${d.max_win > 0 ? '+' + d.max_win : '-'} + ${d.max_loss < 0 ? d.max_loss : '-'} + ${d.variety_count} + `; + }).join(''); + + // 渲染柱状图 + trRenderDailyChart(data); +} + +function trRenderDailyChart(data) { + const chartDom = document.getElementById('tr-daily-chart'); + const chart = echarts.init(chartDom); + chart.setOption({ + backgroundColor: 'transparent', + tooltip: { trigger: 'axis' }, + grid: { left: 60, right: 20, top: 20, bottom: 40 }, + xAxis: { type: 'category', data: data.map(d => d.trade_date), axisLabel: { fontSize: 10, rotate: 30 } }, + yAxis: { type: 'value', axisLabel: { fontSize: 11 } }, + series: [{ + type: 'bar', + data: data.map(d => ({ + value: d.total_pnl, + itemStyle: { color: d.total_pnl >= 0 ? '#34C759' : '#FF3B30' }, + })), + }], + }); +} + +// ==================== 品种汇总 ==================== + +async function trLoadVarietySummary() { + try { + const res = await fetch(`${TR_API_BASE}/variety-summary?${trBuildParams()}`); + const json = await res.json(); + if (json.success) trRenderVarietySummary(json.data); + } catch (e) { + console.error('加载品种汇总失败', e); + } +} + +function trRenderVarietySummary(data) { + const tbody = document.getElementById('tr-variety-body'); + const emptyEl = document.getElementById('tr-variety-empty'); + + if (!data || data.length === 0) { + tbody.innerHTML = ''; + emptyEl.style.display = 'flex'; + return; + } + emptyEl.style.display = 'none'; + + tbody.innerHTML = data.map(v => { + const pnlClass = v.total_pnl > 0 ? 'tr-pnl-positive' : (v.total_pnl < 0 ? 'tr-pnl-negative' : ''); + return ` + ${v.variety} + ${v.symbol_name || '-'} + ${v.total_trades} + ${v.total_pnl >= 0 ? '+' : ''}${v.total_pnl} + ${v.total_close_pnl} + ${v.total_commission} + ${v.total_volume} + ${v.buy_count} + ${v.sell_count} + ${v.win_rate}% + `; + }).join(''); +} + +// ==================== 逐笔分析 ==================== + +async function trLoadTradePairs() { + try { + const res = await fetch(`${TR_API_BASE}/trade-pairs?${trBuildParams()}`); + const json = await res.json(); + if (json.success) trRenderTradePairs(json.data); + } catch (e) { + console.error('加载逐笔数据失败', e); + } +} + +function trRenderTradePairs(pairs) { + const grid = document.getElementById('tr-pairs-grid'); + const emptyEl = document.getElementById('tr-pairs-empty'); + + if (!pairs || pairs.length === 0) { + grid.innerHTML = ''; + emptyEl.style.display = 'flex'; + return; + } + emptyEl.style.display = 'none'; + + grid.innerHTML = pairs.map(p => { + const pnlClass = p.net_pnl >= 0 ? 'profit' : 'loss'; + const dirText = p.direction === '多' ? '做多' : '做空'; + const dirClass = p.direction === '多' ? 'tr-dir-buy' : 'tr-dir-sell'; + return ` +
+
+ ${p.symbol_name || p.symbol} ${dirText} + ${p.net_pnl >= 0 ? '+' : ''}${p.net_pnl} +
+
+
开仓${p.open_date} ${p.open_time || ''}
+
平仓${p.close_date || '-'} ${p.close_time || ''}
+
开仓价${p.open_price != null ? p.open_price.toFixed(2) : '-'}
+
平仓价${p.close_price != null ? p.close_price.toFixed(2) : '-'}
+
手数${p.volume || '-'}
+
手续费${p.commission}
+
+
+ +
+
`; + }).join(''); +} + +async function trAnalyzeTrade(pair) { + const modal = document.getElementById('tr-analysis-modal'); + const title = document.getElementById('tr-analysis-title'); + const content = document.getElementById('tr-analysis-content'); + + title.textContent = `AI 分析 - ${pair.symbol_name || pair.symbol} ${pair.direction}`; + content.textContent = '正在分析中,请稍候...'; + modal.classList.add('show'); + + try { + const res = await fetch(`${TR_API_BASE}/analyze-trade`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + symbol: pair.symbol, + open_date: pair.open_date, + open_time: pair.open_time, + close_date: pair.close_date, + close_time: pair.close_time, + direction: pair.direction, + open_price: pair.open_price, + close_price: pair.close_price, + }), + }); + const json = await res.json(); + if (json.success) { + content.textContent = json.data.analysis; + } else { + content.textContent = json.message || '分析失败'; + } + } catch (e) { + content.textContent = '分析请求失败: ' + e.message; + } +} + +function trCloseAnalysisModal() { + document.getElementById('tr-analysis-modal').classList.remove('show'); +} + +// 子页面切换时懒加载 +document.addEventListener('click', function(e) { + if (!e.target.classList.contains('tr-sub-nav-item')) return; + const sub = e.target.dataset.sub; + setTimeout(() => { + if (sub === 'daily') trLoadDailySummary(); + else if (sub === 'variety') trLoadVarietySummary(); + else if (sub === 'pairs') trLoadTradePairs(); + }, 50); +}); + function formatNumber(num) { if (num === 0 || num === undefined || num === null) return '--'; return num.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 }); diff --git a/data/buffer.db b/data/buffer.db index 8e8743d5a89314bdc76cd2602fa4c6caea3008bf..75849b07d1678111d7bde628a06f2bb7e8507465 100644 GIT binary patch delta 604 zcmb8rOHUI~6bEpIDVC|!J3IvB(Wxj;ZMpZ(boxk0=pe>KU`n*4baj+uqOmlk3W08z zE>4UaBP+HhegYC;7JLOh1WWdQ0Z$Xs$cBW)?{3b?J^yq3t4qZvzebhj(b$sGYyyIU z(21ZTgb=zA9w3Afx)C&l2*N{z9)u{uBLs$^BlIHlA@m~*AUsAGL>NLCMtFiSf-s6O zhA@5xv7d*1lE--}$3>1?$xJ4bw&b}V3;kAg%|8yujq#9nt@%N(A1i5xo*wSdw(@z6 z*LWqJS}DK!=*ogqDXx9q+%J1|_nUlMb*k^=s_oX6H+B*SMJ~Ct6WmVRnv*syF54CH zUxKtWE#a}7InLKqW3yE7>f0NRv$mf1QkC-hn`&cMHkPu*y;8mAynbJ{znFzjtL^V- zAv0&0IbkP7RtU>%S7=bXk}-vq4GMF{X;(y#DjiXU?LMR>$2 zQ6*}`8nI4n5Szpnu}$m{yF{I65X~?6FORk8GKdDlI3D)nUO2eAh@HD!BWEiwOP!nln|jaLNQ*%1iG6V(4#kmZ biMBWvC!!-xMOU1Oo;Vkwh)g!>-!1+CliOa} diff --git a/data/futures_analysis.db b/data/futures_analysis.db index f72c9b5533dd0c03c89f97813d3219809a5b63d7..5c5da999625ef2f32ea793d37380b1160ac03ab0 100644 GIT binary patch delta 25117 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 diff --git a/data/new_ui/desktop/prototype_desktop_light-v2.html b/data/new_ui/desktop/prototype_desktop_light-v2.html new file mode 100644 index 0000000..ededf6c --- /dev/null +++ b/data/new_ui/desktop/prototype_desktop_light-v2.html @@ -0,0 +1,241 @@ + + + + + + 期货智析 - 全平台 (Apple Light) + + + +
+ + +
LIVE
+
+ + + +
+
+
全部
能源
金属
农产品
+
32
监控品种
7
上涨趋势
10
下跌趋势
15
震荡整理
+
+
SC
原油
SC2606
¥644.5
↓ -2.02%
观望
成功率 60%
趋势评分 30
15M
1H
4H
+
AG
沪银
AG2606
¥18,643
↑ +2.27%
做多
成功率 85%
趋势评分 65
15M
1H
4H
+
+
+
+ + +
+
+
+
🔥 突发T-00:15
EIA 原油库存意外大幅下降 500 万桶
利多原油
SCLU
+
🏛️ 政策已公布
央行宣布降准 0.25%,释放长期资金
利多有色
IFCU
+
+
+
+
🔥 市场热力图
+
+
SC 原油
+3.8%
+
AG 沪银
+2.8%
+
SA 纯碱
-3.1%
+
M 豆粕
0.0%
+
IF 股指
+1.8%
+
RB 螺纹
-0.5%
+
+

🔴 领涨

SC 原油+3.8%
AG 沪银+2.8%

🟢 领跌

SA 纯碱-3.1%
FG 玻璃-1.9%
+
+
+
💰 主力资金流向
SC
+5.2亿
SA
-4.5亿
+
📅 宏观日历
23:30
EIA 原油库存
★★★
周五
非农数据
★★★★
+
+
+
+
+ + +
+
+
+ +
+
✦ 今日风险指数: 72/100
疑似洗盘
纯碱 10:45 急跌后迅速拉回,量能异常,疑似主力洗盘。
流动性枯竭
胶板 2610 连续 3 分钟无成交,注意滑点风险。
+
📊 预警统计
今日触发24 次
误报率12%
+
+ + +
+
全部
紧急 (3)
事件驱动 (2)
+
14:32 · 关联事件:EIA 库存
🔥 燃油快速突破 60 日均线
关联事件:EIA 库存超预期下降 | 5 分钟涨幅 1.5%
查看详情AI 分析加入自选
+
13:15
🔴 沪银成交量异常放大
当前成交量为过去 20 日均值的 4.2 倍 | 疑似主力入场
查看详情AI 分析
+
11:20
螺纹 MACD 出现金叉
15 分钟级别 MACD 快线上穿慢线
查看详情AI 分析
+
09:30
开盘跳空 5 品种
原油、沪银、铁矿等高开超 1%
查看详情
+
+ + +
+
价格与指标
价格突破 60 日均线
MACD 金叉/死叉
+
量能与事件
成交量突增 > 3 倍
热点事件关联异动
+
+
+
+
+ + + \ No newline at end of file diff --git a/data/new_ui/desktop/prototype_desktop_light-v3.html b/data/new_ui/desktop/prototype_desktop_light-v3.html new file mode 100644 index 0000000..2b504b2 --- /dev/null +++ b/data/new_ui/desktop/prototype_desktop_light-v3.html @@ -0,0 +1,459 @@ + + + + +期货智析 + + +
+ + +
LIVE
+
+ +
+
全部
能源
金属
农产品
化工
+
+
=
32
监控品种
+
7
上涨趋势
+
10
下跌趋势
+
15
震荡整理
+
+
+
SC
原油
SC2606
644.5
-2.02%
观望
成功率 60%
15M
1H
4H
+
AG
沪银
AG2606
18,643
+2.27%
做多
成功率 85%
15M
1H
4H
+
+
+
+
突发T-00:15
EIA 原油库存意外大幅下降 500 万桶
利多原油
SCLU
+
政策已公布
央行宣布降准 0.25%
利多有色
IFCU
+
+
+
市场热力图
+
+
SC 原油
+3.8%
+
AG 沪银
+2.8%
+
SA 纯碱
-3.1%
+
M 豆粕
0.0%
+
IF 股指
+1.8%
+
RB 螺纹
-0.5%
+
+
+
主力资金流向
+
SC
+5.2亿
+
SA
-4.5亿
+
+
宏观日历
+
23:30
EIA 原油库存
★★★
+
周五
非农数据
★★★★
+
+
+
+
今日风险指数: 72/100
+
疑似洗盘
纯碱 10:45 急跌后迅速拉回。
+
流动性枯竭
胶板 2610 连续 3 分钟无成交。
+
预警统计
+
今日触发24 次
+
误报率12%
+
+
全部
紧急
事件驱动
+
14:32 - 关联事件: EIA
燃油快速突破 60 日均线
EIA 库存超预期下降 | 5 分钟涨幅 1.5%
查看详情AI 分析加入自选
+
13:15
沪银成交量异常放大
成交量为 20 日均值的 4.2 倍
查看详情AI 分析
+
11:20
螺纹 MACD 金叉
15 分钟级别 MACD 快线上穿慢线
查看详情
+
+
+
价格与指标
+
价格突破 60 日均线
+
MACD 金叉/死叉
+
+
量能与事件
+
成交量突增 > 3 倍
+
热点事件关联异动
+
+
+
+

复盘与交易计划

2026-06-01 (周一)
+
+
成交量排名 Top 5
+
1
SC 原油
58.2 万手
+
2
RB 螺纹
52.1 万手
+
3
AG 沪银
45.8 万手
+
4
SA 纯碱
38.6 万手
+
5
IF 股指
32.4 万手
+
+
振幅排名 Top 5
+
1
SA 纯碱
6.8%
+
2
SC 原油
5.2%
+
3
AG 沪银
4.5%
+
4
FG 玻璃
3.9%
+
5
LU 燃油
3.2%
+
+
涨跌幅排名 Top 5
+
1
SC 原油
+3.8%
+
2
AG 沪银
+2.8%
+
3
IF 股指
+1.8%
+
4
SA 纯碱
-3.1%
+
5
FG 玻璃
-1.9%
+
+
持仓量排名 Top 5
+
1
IF 股指
42.8 万手
+
2
RB 螺纹
38.5 万手
+
3
SC 原油
28.4 万手
+
4
M 豆粕
24.2 万手
+
5
MA 甲醇
20.8 万手
+
+
+
交易机会
+
+ +
+
SC 原油做多
+
88
+
+
+
+
评分: 88/100
多空逻辑: EIA 库存超预期下降 + OPEC+ 延长减产 + 地缘局势升温,供给端紧缩共振。
入选理由: 量价齐升,5/15/30min 周期共振向上,MACD 金叉确认。
+
+
入场
645-648
+
止损
638
+
止盈
670-685
+
+
+
置信度: 高 | 仓位建议: 15%-20%
+
+ +
+
SA 纯碱做空
+
82
+
+
+
+
评分: 82/100
多空逻辑: 高库存 + 光伏玻璃需求下滑 + 新产能投产预期,基本面偏弱。
入选理由: 日内振幅 6.8% 居首,15min 级别顶背离确认,资金净流出 4.5 亿。
+
+
入场
1,820-1,830
+
止损
1,860
+
止盈
1,750-1,720
+
+
+
置信度: 中高 | 仓位建议: 10%-15%
+
+ +
+
AG 沪银做多
+
79
+
+
+
+
评分: 79/100
多空逻辑: 全球降息预期 + 央行持续增持黄金 + 避险情绪升温。
入选理由: 成交量 45.8 万手放量突破,多周期均线多头排列。
+
+
入场
18,500-18,600
+
止损
18,350
+
止盈
19,000-19,200
+
+
+
置信度: 中 | 仓位建议: 10%
+
+ +
+
以上由 AI 基于多维因子自动生成,仅供参考,不构成投资建议。
+
+ + \ No newline at end of file diff --git a/data/new_ui/mobile/prototype_tablet_android-v2.html b/data/new_ui/mobile/prototype_tablet_android-v2.html new file mode 100644 index 0000000..4a7af66 --- /dev/null +++ b/data/new_ui/mobile/prototype_tablet_android-v2.html @@ -0,0 +1,338 @@ + + + + + + 期货智析 - Android 平板版 + + + + +
+ +
LIVE
+
+
+
品种分析
+
市场概览
+
风险预警
+
+ + + + +
+
+
+ +
+
+
全部
能源
金属
农产品
+
+ +
+
32
监控品种
+
7
上涨趋势
+
10
下跌趋势
+
15
震荡整理
+
+ +
+
+
SC
原油
SC2606
¥644.5
↓ -2.02%
+
观望
+
成功率 60%
趋势评分 30
+
15M
1H
4H
+ +
+
+
AG
沪银
AG2606
¥18,643
↑ +2.27%
+
做多
+
成功率 85%
趋势评分 65
+
15M
1H
4H
+ +
+
+
+
+ + +
+
+
+
+
🔥 突发T-00:15
+
EIA 原油库存意外大幅下降 500 万桶
+
利多原油
+
SCLU
+
+
+
🏛️ 政策已公布
+
央行宣布降准 0.25%
+
利多有色
+
IFCU
+
+
+ +
🔥 市场热力图
+
+
SC 原油
+3.8%
+
AG 沪银
+2.8%
+
SA 纯碱
-3.1%
+
RB 螺纹
-0.5%
+
M 豆粕
0.0%
+
IF 股指
+1.8%
+
+ +
+

🔴 领涨

SC 原油+3.8%
AG 沪银+2.8%
+

🟢 领跌

SA 纯碱-3.1%
FG 玻璃-1.9%
+
+ +
+
+
💰 主力资金流向
+
SC
+5.2亿
+
SA
-4.5亿
+
+
+
📅 宏观日历
+
23:30
EIA 原油库存
★★★
+
周五
非农数据
★★★★
+
+
+
+
+ + +
+
+
+
+
+
价格与指标
+
价格突破 60 日均线
+
MACD 金叉/死叉
+
+
+
量能与事件
+
成交量突增 > 3 倍
+
热点事件关联异动
+
+
+ +
+
+
✦ 今日风险指数: 72/100
+
疑似洗盘
纯碱 10:45 急跌后迅速拉回,量能异常。
+
流动性枯竭
胶板 2610 连续 3 分钟无成交。
+
+ +
+
全部
+
紧急 (3)
+
事件驱动
+
+
+
+
14:32 · 关联事件:EIA 库存
+
🔥 燃油快速突破 60 日均线
+
关联事件:EIA 库存超预期下降 | 5 分钟涨幅 1.5%
+
查看详情AI 分析加入自选
+
+
+
13:15
+
🔴 沪银成交量异常放大
+
当前成交量为过去 20 日均值的 4.2 倍
+
查看详情AI 分析
+
+
+
11:20
+
螺纹 MACD 出现金叉
+
15 分钟级别 MACD 快线上穿慢线
+
查看详情
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/data/new_ui/windows_desktop/prototype_windows_desktop.html b/data/new_ui/windows_desktop/prototype_windows_desktop.html new file mode 100644 index 0000000..f322319 --- /dev/null +++ b/data/new_ui/windows_desktop/prototype_windows_desktop.html @@ -0,0 +1,381 @@ + + + + + + 期货智析 - Windows 桌面端 (完整版) + + + + + + +
+
+
期货智析 Pro
+
+
+
+
+
+
+ +
+ + + +
+
+ +
全部
能源
金属
+
+
+
+
+
SC
原油
SC2606
644.5
↓ -2.02%
观望
+
FU
燃油
FU2606
4,680
↑ +1.56%
做多
+
AG
沪银
AG2606
18,643
↑ +2.27%
做多
+
AU
沪金
AU2606
993
↑ +0.4%
观望
+
+
+
📡 CTP
自动刷新 ON
+
+ + +
+
← 返回
原油 SC2606
644.5
+
+
+
15M
1H
4H
+
📈 K 线图区域
+
AI 多因子分析报告
综合评分 38 · 偏空
+
+
+
持仓数据
持仓量28.4 万手
日增仓-1.2 万
+
资金流向
主力净流入-2.8 亿
+ +
+
+
📡 CTP
SC2606
+
+ + +
+
+
+
🔥 突发T-00:15
+
EIA 原油库存意外大幅下降 500 万桶
+
利多原油供给收缩预期强烈
+
SCLU
+
+
+
🏛️ 政策已公布
+
央行宣布降准 0.25%,释放长期资金
+
利多有色/股指流动性改善
+
IFCU
+
+
+
+
+
🔥 市场热力图按涨跌幅
+
+
SC 原油
+3.8%
+
AG 沪银
+2.8%
+
SA 纯碱
-3.1%
+
RB 螺纹
-0.5%
+
M 豆粕
0.0%
+
IF 股指
+1.8%
+
FG 玻璃
-1.9%
+
+
+
🔴 领涨
SC 原油+3.8%
AG 沪银+2.8%
+
🟢 领跌
SA 纯碱-3.1%
FG 玻璃-1.9%
+
+
+
+
+
💰 主力资金流向
+
SC
+5.2亿
+
SA
-4.5亿
+
+
+
📅 宏观日历
+
23:30
EIA 原油库存
★★★
+
周五
非农数据
★★★★
+
+
+
+
📡 概览数据
刷新 ON
+
+ + +
+
+
+
⚙️ 预警规则
+ +
价格与指标
+
价格突破 60 日均线
+
MACD 金叉/死叉
+
量能与事件
+
成交量突增 > 3 倍
+
热点事件关联异动
+
+
+
全部
紧急 (3)
事件驱动 (2)
+
+
+
14:32 · 关联事件:EIA 库存
+
🔥 [热点] 燃油快速突破 60 日均线
+
关联事件:EIA 库存超预期下降 | 5 分钟涨幅 1.5%
+
查看详情AI 分析加入自选
+
+
+
13:15
+
🔴 沪银成交量异常放大
+
当前成交量为过去 20 日均值的 4.2 倍 | 疑似主力入场
+
查看详情AI 分析
+
+
+
11:20
+
螺纹 MACD 出现金叉
+
15 分钟级别 MACD 快线上穿慢线
+
查看详情AI 分析
+
+
+
09:30
+
开盘跳空 5 品种
+
原油、沪银、铁矿等高开超 1%
+
查看详情
+
+
+
+
+
🤖 AI 异动侦测
+
+
✦ 今日风险指数: 72/100
+
+
疑似洗盘
+
纯碱 10:45 急跌后迅速拉回,量能异常,疑似主力洗盘。
+
+
+
流动性枯竭
+
胶板 2610 连续 3 分钟无成交,注意滑点风险。
+
+
+
+
📊 预警统计
+
今日触发24 次
+
误报率12%
+
+
+
+
📡 预警服务
推送 ON
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/data/trading_calculate.py b/data/trading_calculate.py new file mode 100644 index 0000000..2082376 --- /dev/null +++ b/data/trading_calculate.py @@ -0,0 +1,534 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +期货结算单数据处理脚本 +读取期货结算单Excel,提取期货和期权交易数据,计算盈亏和持仓,合并输出 +""" + +import os +import glob +import re +from datetime import datetime +from collections import defaultdict + +import xlrd +import pandas as pd + + +def find_header(sheet, keywords): + """ + 动态识别表头行 + :param sheet: xlrd sheet对象 + :param keywords: 关键词列表,如["合约", "品种合约"] + :return: 表头行索引 + """ + for row_idx in range(min(20, sheet.nrows)): # 只扫描前20行 + row_values = [str(sheet.cell_value(row_idx, col_idx)).strip() + for col_idx in range(sheet.ncols)] + row_text = ' '.join(row_values) + + # 检查是否包含关键词 + has_keyword = any(kw in row_text for kw in keywords) + # 检查是否包含辅助关键词 + has_aux = any(aux in row_text for aux in ["买/卖", "成交价", "权利金"]) + + if has_keyword and has_aux: + return row_idx + + return None + + +def read_sheet(xls_file, sheet_name, trade_type): + """ + 读取单个Sheet的数据 + :param xls_file: Excel文件路径 + :param sheet_name: Sheet名称 + :param trade_type: 交易类型(期货/期权) + :return: DataFrame + """ + try: + wb = xlrd.open_workbook(xls_file) + if sheet_name not in wb.sheet_names(): + print(f" 警告: {xls_file} 中未找到 {sheet_name}") + return pd.DataFrame() + + sheet = wb.sheet_by_name(sheet_name) + if sheet.nrows == 0: + return pd.DataFrame() + + # 根据类型确定关键词 + if trade_type == '期货': + keywords = ["合约"] + else: # 期权 + keywords = ["品种合约"] + + # 查找表头行 + header_row = find_header(sheet, keywords) + if header_row is None: + print(f" 警告: {sheet_name} 中未找到表头") + return pd.DataFrame() + + # 读取表头 + headers = [str(sheet.cell_value(header_row, col_idx)).strip() + for col_idx in range(sheet.ncols)] + + # 读取数据行 + data = [] + for row_idx in range(header_row + 1, sheet.nrows): + row_data = {} + first_cell = str(sheet.cell_value(row_idx, 0)).strip() + + # 跳过合计行 + if first_cell == '合计' or first_cell == '': + continue + + for col_idx, header in enumerate(headers): + if col_idx < sheet.ncols: + value = sheet.cell_value(row_idx, col_idx) + # 处理"--" + if str(value).strip() == '--': + value = None + row_data[header] = value + + # 添加类型标签 + row_data['类型'] = trade_type + data.append(row_data) + + df = pd.DataFrame(data) + print(f" 读取 {sheet_name}: {len(df)} 条记录") + return df + + except Exception as e: + print(f" 错误: 读取 {sheet_name} 失败 - {e}") + return pd.DataFrame() + + +def extract_variety(contract): + """ + 提取品种代码(所有前导字母) + :param contract: 合约代码 + :return: 品种代码 + """ + if pd.isna(contract) or not contract: + return '' + contract = str(contract).strip() + match = re.match(r'^([A-Za-z]+)', contract) + if match: + return match.group(1).upper() + return contract[:2].upper() + + +def safe_float(value, default=0.0): + """ + 安全转换为float + """ + if pd.isna(value) or value is None or value == '': + return default + try: + return float(value) + except (ValueError, TypeError): + return default + + +def calc_pnl_futures(df): + """ + 计算期货盈亏 + """ + if df.empty: + return {'平仓盈亏': 0, '手续费': 0, '净盈亏': 0} + + # 平仓盈亏 + pnl_close = 0 + if '平仓盈亏' in df.columns: + for val in df['平仓盈亏']: + if not pd.isna(val): + pnl_close += safe_float(val) + + # 手续费 + commission = 0 + if '手续费' in df.columns: + for val in df['手续费']: + commission += safe_float(val) + + net_pnl = pnl_close - commission + + return { + '平仓盈亏': pnl_close, + '手续费': commission, + '净盈亏': net_pnl + } + + +def calc_pnl_options(df): + """ + 计算期权盈亏 + """ + if df.empty: + return {'权利金净收入': 0, '手续费': 0, '净盈亏': 0} + + # 权利金(带方向) + premium_total = 0 + if '权利金' in df.columns: + for val in df['权利金']: + premium_total += safe_float(val) + + # 手续费 + commission = 0 + if '手续费' in df.columns: + for val in df['手续费']: + commission += safe_float(val) + + net_pnl = premium_total - commission + + return { + '权利金净收入': premium_total, + '手续费': commission, + '净盈亏': net_pnl + } + + +def calc_position_futures(df): + """ + 计算期货持仓 + """ + positions = defaultdict(float) + + if df.empty: + return positions + + for _, row in df.iterrows(): + contract = str(row.get('合约', '')).strip() + if not contract or contract == '合计': + continue + + bs_flag = str(row.get('买/卖', '')).strip() + oc_flag = str(row.get('开/平', '')).strip() + volume = safe_float(row.get('手数', 0)) + + # 买+开 → +手数;卖+开 → -手数;买+平 → -手数;卖+平 → +手数 + if '买' in bs_flag and '开' in oc_flag: + positions[contract] += volume + elif '卖' in bs_flag and '开' in oc_flag: + positions[contract] -= volume + elif '买' in bs_flag and '平' in oc_flag: + positions[contract] -= volume + elif '卖' in bs_flag and '平' in oc_flag: + positions[contract] += volume + + # 过滤0持仓 + return {k: v for k, v in positions.items() if abs(v) > 0.001} + + +def calc_position_options(df): + """ + 计算期权持仓 + """ + positions = defaultdict(float) + + if df.empty: + return positions + + for _, row in df.iterrows(): + contract = str(row.get('品种合约', '')).strip() + if not contract or contract == '合计': + continue + + bs_flag = str(row.get('买/卖', '')).strip() + volume = safe_float(row.get('成交量', 0)) + + if '买' in bs_flag: + positions[contract] += volume + elif '卖' in bs_flag: + positions[contract] -= volume + + # 过滤0持仓 + return {k: v for k, v in positions.items() if abs(v) > 0.001} + + +def merge_trades(df_futures, df_options): + """ + 合并期货和期权交易记录 + """ + # 统一字段 + columns_map_futures = { + '合约': '合约代码', + '买/卖': '买卖', + '成交价': '价格/权利金单价', + '手数': '手数/成交量', + '成交额': '金额/权利金', + '实际成交日期': '实际成交日期', + '成交时间': '成交时间' + } + + columns_map_options = { + '品种合约': '合约代码', + '买/卖': '买卖', + '权利金单价': '价格/权利金单价', + '成交量': '手数/成交量', + '权利金': '金额/权利金', + '成交日期': '实际成交日期', + '成交时间': '成交时间' + } + + # 处理期货数据 + if not df_futures.empty: + df_f = df_futures.rename(columns=columns_map_futures).copy() + df_f['品种'] = df_f['合约代码'].apply(extract_variety) + df_f['方向'] = df_f['买卖'].apply(lambda x: '买' if '买' in str(x) else '卖') + df_f['开平仓'] = df_f.get('开/平', '') + + # 确保必要字段存在 + for col in ['平仓盈亏', '手续费']: + if col not in df_f.columns: + df_f[col] = 0 + else: + df_f = pd.DataFrame() + + # 处理期权数据 + if not df_options.empty: + df_o = df_options.rename(columns=columns_map_options).copy() + df_o['品种'] = df_o['合约代码'].apply(extract_variety) + df_o['方向'] = df_o['买卖'].apply(lambda x: '买' if '买' in str(x) else '卖') + df_o['开平仓'] = '' + df_o['平仓盈亏'] = 0 + + # 确保必要字段存在 + for col in ['手续费']: + if col not in df_o.columns: + df_o[col] = 0 + else: + df_o = pd.DataFrame() + + # 合并 + common_cols = ['品种', '合约代码', '类型', '买卖', '方向', '成交时间', + '实际成交日期', '价格/权利金单价', '手数/成交量', '金额/权利金', + '手续费', '平仓盈亏', '开平仓'] + + if not df_f.empty: + df_f = df_f[[c for c in common_cols if c in df_f.columns]] + if not df_o.empty: + df_o = df_o[[c for c in common_cols if c in df_o.columns]] + + if not df_f.empty and not df_o.empty: + return pd.concat([df_f, df_o], ignore_index=True) + elif not df_f.empty: + return df_f + elif not df_o.empty: + return df_o + else: + return pd.DataFrame() + + +def save_output(trades_df, df_futures, df_options, output_dir): + """ + 保存输出文件 + """ + os.makedirs(output_dir, exist_ok=True) + + # 文件1:trades_merged.csv + csv_path = os.path.join(output_dir, 'trades_merged.csv') + if not trades_df.empty: + trades_df.to_csv(csv_path, index=False, encoding='utf-8-sig') + print(f"已保存交易明细: {csv_path}") + else: + print("警告: 无交易数据") + + # 计算分析数据 + analysis_data = {} + + # Sheet1: 品种盈亏汇总 + variety_summary = [] + + # 期货品种汇总 + if not df_futures.empty: + df_f = df_futures.copy() + df_f['品种'] = df_f['合约'].apply(extract_variety) + + for variety, group in df_f.groupby('品种'): + pnl = calc_pnl_futures(group) + total_volume = sum(safe_float(v) for v in group.get('手数', [])) + total_amount = sum(safe_float(v) for v in group.get('成交额', [])) + buy_count = sum(1 for v in group.get('买/卖', []) if '买' in str(v)) + sell_count = sum(1 for v in group.get('买/卖', []) if '卖' in str(v)) + + variety_summary.append({ + '品种': variety, + '类型': '期货', + '总手数': total_volume, + '总成交额': total_amount, + '总手续费': pnl['手续费'], + '平仓盈亏': pnl['平仓盈亏'], + '净盈亏': pnl['净盈亏'], + '买入次数': buy_count, + '卖出次数': sell_count + }) + + # 期权品种汇总 + if not df_options.empty: + df_o = df_options.copy() + df_o['品种'] = df_o['品种合约'].apply(extract_variety) + + for variety, group in df_o.groupby('品种'): + pnl = calc_pnl_options(group) + total_volume = sum(safe_float(v) for v in group.get('成交量', [])) + total_premium = sum(abs(safe_float(v)) for v in group.get('权利金', [])) + buy_count = sum(1 for v in group.get('买/卖', []) if '买' in str(v)) + sell_count = sum(1 for v in group.get('买/卖', []) if '卖' in str(v)) + + variety_summary.append({ + '品种': variety, + '类型': '期权', + '总手数': total_volume, + '总成交额/权利金': total_premium, + '总手续费': pnl['手续费'], + '平仓盈亏': 0, + '净盈亏': pnl['净盈亏'], + '买入次数': buy_count, + '卖出次数': sell_count + }) + + df_variety = pd.DataFrame(variety_summary) + + # Sheet2: 期货持仓 + futures_positions = calc_position_futures(df_futures) + futures_pos_data = [] + for contract, pos in futures_positions.items(): + futures_pos_data.append({ + '合约': contract, + '净持仓': pos, + '方向': '多' if pos > 0 else '空' + }) + df_futures_pos = pd.DataFrame(futures_pos_data) + + # Sheet3: 期权持仓 + options_positions = calc_position_options(df_options) + options_pos_data = [] + for contract, pos in options_positions.items(): + options_pos_data.append({ + '品种合约': contract, + '净持仓': pos, + '方向': '权利' if pos > 0 else '义务' + }) + df_options_pos = pd.DataFrame(options_pos_data) + + # 文件2:analysis.xlsx + xlsx_path = os.path.join(output_dir, 'analysis.xlsx') + with pd.ExcelWriter(xlsx_path, engine='openpyxl') as writer: + if not df_variety.empty: + df_variety.to_excel(writer, sheet_name='品种盈亏汇总', index=False) + if not df_futures_pos.empty: + df_futures_pos.to_excel(writer, sheet_name='期货持仓', index=False) + if not df_options_pos.empty: + df_options_pos.to_excel(writer, sheet_name='期权持仓', index=False) + + print(f"已保存分析结果: {xlsx_path}") + + return { + 'variety_summary': variety_summary, + 'futures_positions': futures_positions, + 'options_positions': options_positions + } + + +def print_summary(df_futures, df_options, results): + """ + 打印摘要 + """ + print("\n" + "="*50) + print("=== 交易摘要 ===") + + # 期货统计 + if not df_futures.empty: + pnl_f = calc_pnl_futures(df_futures) + varieties_f = df_futures['合约'].apply(extract_variety).nunique() + print(f"期货:{len(df_futures)}笔交易,{varieties_f}个品种") + print(f" 总盈亏:{pnl_f['净盈亏']:.2f}元(含手续费{pnl_f['手续费']:.2f}元)") + else: + print("期货:无交易") + + # 期权统计 + if not df_options.empty: + pnl_o = calc_pnl_options(df_options) + varieties_o = df_options['品种合约'].apply(extract_variety).nunique() + print(f"期权:{len(df_options)}笔交易,{varieties_o}个品种") + print(f" 总盈亏:{pnl_o['净盈亏']:.2f}元(含手续费{pnl_o['手续费']:.2f}元)") + else: + print("期权:无交易") + + # 持仓统计 + print("\n=== 收盘持仓 ===") + + futures_pos = results['futures_positions'] + if futures_pos: + for contract, pos in sorted(futures_pos.items()): + direction = '多' if pos > 0 else '空' + print(f"期货:{contract} {direction} {abs(pos):.0f}手") + else: + print("期货:无持仓") + + options_pos = results['options_positions'] + if options_pos: + for contract, pos in sorted(options_pos.items()): + direction = '权利' if pos > 0 else '义务' + print(f"期权:{contract} {direction} {abs(pos):.0f}手") + else: + print("期权:无持仓") + + print("="*50 + "\n") + + +def main(): + """ + 主函数 + """ + # 数据目录 + data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') + output_dir = os.path.dirname(os.path.abspath(__file__)) + + print(f"数据目录: {data_dir}") + print(f"输出目录: {output_dir}\n") + + # 查找所有xls文件 + xls_files = glob.glob(os.path.join(data_dir, '*.xls')) + if not xls_files: + print(f"错误: 在 {data_dir} 中未找到 .xls 文件") + return + + print(f"找到 {len(xls_files)} 个结算单文件\n") + + # 读取所有文件 + all_futures = [] + all_options = [] + + for xls_file in sorted(xls_files): + filename = os.path.basename(xls_file) + print(f"处理: {filename}") + + # 读取期货成交明细 + df_f = read_sheet(xls_file, '成交明细', '期货') + if not df_f.empty: + all_futures.append(df_f) + + # 读取期权成交明细 + df_o = read_sheet(xls_file, '期权成交明细', '期权') + if not df_o.empty: + all_options.append(df_o) + + # 合并数据 + df_futures = pd.concat(all_futures, ignore_index=True) if all_futures else pd.DataFrame() + df_options = pd.concat(all_options, ignore_index=True) if all_options else pd.DataFrame() + + print(f"\n汇总: 期货 {len(df_futures)} 条, 期权 {len(df_options)} 条\n") + + # 合并交易记录 + trades_merged = merge_trades(df_futures, df_options) + + # 保存输出 + results = save_output(trades_merged, df_futures, df_options, output_dir) + + # 打印摘要 + print_summary(df_futures, df_options, results) + + +if __name__ == '__main__': + main() diff --git a/data/计划/2026-06-16_周一交易计划.html b/data/计划/2026-06-16_周一交易计划.html new file mode 100644 index 0000000..3be88c6 --- /dev/null +++ b/data/计划/2026-06-16_周一交易计划.html @@ -0,0 +1,1695 @@ + + +周一交易计划 | 2026-06-16 +
+

📊 周一交易计划 | 2026-06-16

+
数据基准: 2026-06-12 周五夜盘收盘 (01:55 BJT) | 38 品种 | 多周期共振分析
+
+

📌 核心结论

多头格局极致延续
贵金属+黑色系+化工三大板块共振做多,极值品种追高风险极大
+

🏆 Top 3 核心机会

+
J2609 焦炭96.8分 📈做多
+
AG2608 沪银91.4分 📈做多
+
AU2608 沪金85.2分 📈做多
+
+

📈 市场全景

+
🟢 多头25 个
+
⚪ 震荡7 个
+
🔴 空头6 个
+
🟢 交易机会22 个
+
+

🚨 最大风险提示

    +
  • 极值品种严禁追多:J2609(96.8分)、AG2608(91.4分) 周一大概率高开,必须等待回踩15m/60m支撑企稳或右侧放量突破再入场
  • +
  • 空头品种谨慎:SC2607/LU2607/TA2609 趋势偏空,但原油/化工系受地缘与消息面影响大,空单需严格止损
  • +
  • 周末消息面:关注美联储降息预期、地缘冲突等宏观数据,可能影响周一开盘方向
  • +
  • 量能持续性:J2609量比3.33倍属极端放量,需确认周一量能是否可持续,谨防脉冲式行情回落
  • +
+ +
🟢 交易机会品种 (22个)
+
J2609 焦炭
96.8
+
方向📈 做多
+
当前价2123.00
+
涨跌幅+3.33%
+
量比3.33
+
+
振幅
95
+
量能
100
+
趋势
98
+
活跃
100
+
+
+
📈 做多计划
+
入场2038 ~ 2067
+
止损2007 (S1)
+
目标一2137 (R1)
+
目标二2197 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
AG2608 沪银
91.4
+
方向📈 做多
+
当前价16446.00
+
涨跌幅+3.61%
+
量比1.02
+
+
振幅
92
+
量能
65
+
趋势
98
+
活跃
100
+
+
+
📈 做多计划
+
入场15903 ~ 15811
+
止损15405 (S1)
+
目标一16377 (R1)
+
目标二16783 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
AU2608 沪金
85.2
+
方向📈 做多
+
当前价920.32
+
涨跌幅+1.86%
+
量比1.11
+
+
振幅
78
+
量能
73
+
趋势
88
+
活跃
100
+
+
+
📈 做多计划
+
入场910 ~ 905
+
止损892 (S1)
+
目标一925 (R1)
+
目标二938 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
BR2607 合成橡胶
83.1
+
方向📈 做多
+
当前价13485.00
+
涨跌幅+1.21%
+
量比1.15
+
+
振幅
76
+
量能
84
+
趋势
88
+
活跃
84
+
+
+
📈 做多计划
+
入场13300 ~ 13348
+
止损13122 (S1)
+
目标一13597 (R1)
+
目标二13823 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
eg2609 乙二醇
80.5
+
方向📈 做多
+
当前价4703.00
+
涨跌幅+0.91%
+
量比1.12
+
+
振幅
65
+
量能
76
+
趋势
88
+
活跃
100
+
+
+
📈 做多计划
+
入场4605 ~ 4655
+
止损4609 (S1)
+
目标一4724 (R1)
+
目标二4770 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
AO2609 氧化铝
79.2
+
方向📈 做多
+
当前价2947.00
+
涨跌幅+0.83%
+
量比1.16
+
+
振幅
54
+
量能
86
+
趋势
88
+
活跃
100
+
+
+
📈 做多计划
+
入场2908 ~ 2917
+
止损2892 (S1)
+
目标一2947 (R1)
+
目标二2972 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
CU2607 沪铜
78.2
+
方向📈 做多
+
当前价104960.00
+
涨跌幅+1.45%
+
量比0.87
+
+
振幅
60
+
量能
54
+
趋势
88
+
活跃
100
+
+
+
📈 做多计划
+
入场104250 ~ 104260
+
止损103410 (S1)
+
目标一105510 (R1)
+
目标二106360 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
RB2610 螺纹钢
76.2
+
方向📈 做多
+
当前价3187.00
+
涨跌幅+0.70%
+
量比1.14
+
+
振幅
32
+
量能
81
+
趋势
98
+
活跃
99
+
+
+
📈 做多计划
+
入场3170 ~ 3174
+
止损3158 (S1)
+
目标一3194 (R1)
+
目标二3210 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
hc2610 热卷
74.6
+
方向📈 做多
+
当前价3394.00
+
涨跌幅+0.63%
+
量比1.14
+
+
振幅
30
+
量能
78
+
趋势
98
+
活跃
100
+
+
+
📈 做多计划
+
入场3371 ~ 3376
+
止损3359 (S1)
+
目标一3397 (R1)
+
目标二3414 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
ZN2607 沪锌
74.0
+
方向📈 做多
+
当前价24630.00
+
涨跌幅+0.81%
+
量比0.98
+
+
振幅
49
+
量能
62
+
趋势
88
+
活跃
100
+
+
+
📈 做多计划
+
入场24280 ~ 24292
+
止损24138 (S1)
+
目标一24513 (R1)
+
目标二24667 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
SN2606 沪锡
73.5
+
方向📈 做多
+
当前价409850.00
+
涨跌幅+2.45%
+
量比0.06
+
+
振幅
70
+
量能
3
+
趋势
88
+
活跃
100
+
+
+
📈 做多计划
+
入场399170 ~ 405380
+
止损400780 (S1)
+
目标一412490 (R1)
+
目标二417090 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
PB2607 沪铅
71.4
+
方向📈 做多
+
当前价16100.00
+
涨跌幅-1.02%
+
量比1.32
+
+
振幅
16
+
量能
95
+
趋势
80
+
活跃
100
+
+
+
📈 做多计划
+
入场16035 ~ 16072
+
止损15983 (S1)
+
目标一16143 (R1)
+
目标二16232 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
JM2609 焦煤
70.8
+
方向📈 做多
+
当前价1376.50
+
涨跌幅+0.62%
+
量比0.83
+
+
振幅
84
+
量能
38
+
趋势
80
+
活跃
87
+
+
+
📈 做多计划
+
入场1360 ~ 1376
+
止损1343 (S1)
+
目标一1403 (R1)
+
目标二1436 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
MA2609 甲醇
70.6
+
方向📈 做多
+
当前价3034.00
+
涨跌幅-0.50%
+
量比1.18
+
+
振幅
81
+
量能
92
+
趋势
63
+
活跃
85
+
+
+
📈 做多计划
+
入场2985 ~ 3016
+
止损2957 (S1)
+
目标一3068 (R1)
+
目标二3127 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
SR2609 白糖
68.9
+
方向📈 做多
+
当前价5320.00
+
涨跌幅+0.45%
+
量比1.17
+
+
振幅
27
+
量能
89
+
趋势
88
+
活跃
100
+
+
+
📈 做多计划
+
入场5276 ~ 5306
+
止损5282 (S1)
+
目标一5340 (R1)
+
目标二5364 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
v2609 PVC
68.4
+
方向📈 做多
+
当前价4767.00
+
涨跌幅+0.53%
+
量比0.91
+
+
振幅
51
+
量能
57
+
趋势
88
+
活跃
83
+
+
+
📈 做多计划
+
入场4685 ~ 4724
+
止损4689 (S1)
+
目标一4776 (R1)
+
目标二4811 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
SH2607 烧碱
68.1
+
方向📈 做多
+
当前价1914.00
+
涨跌幅+0.53%
+
量比0.63
+
+
振幅
68
+
量能
19
+
趋势
88
+
活跃
100
+
+
+
📈 做多计划
+
入场1885 ~ 1892
+
止损1873 (S1)
+
目标一1920 (R1)
+
目标二1939 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
RU2609 橡胶
66.8
+
方向📈 做多
+
当前价17805.00
+
涨跌幅+0.54%
+
量比0.86
+
+
振幅
22
+
量能
49
+
趋势
98
+
活跃
94
+
+
+
📈 做多计划
+
入场17505 ~ 17562
+
止损17473 (S1)
+
目标一17653 (R1)
+
目标二17742 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
SA2609 纯碱
65.5
+
方向📈 做多
+
当前价1172.00
+
涨跌幅-0.69%
+
量比0.85
+
+
振幅
11
+
量能
41
+
趋势
98
+
活跃
100
+
+
+
📈 做多计划
+
入场1148 ~ 1152
+
止损1146 (S1)
+
目标一1156 (R1)
+
目标二1162 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
FG2609 玻璃
60.2
+
方向📈 做多
+
当前价1008.00
+
涨跌幅-0.40%
+
量比0.79
+
+
振幅
38
+
量能
35
+
趋势
88
+
活跃
87
+
+
+
📈 做多计划
+
入场984 ~ 988
+
止损982 (S1)
+
目标一994 (R1)
+
目标二1000 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
AL2607 沪铝
58.9
+
方向📈 做多
+
当前价24250.00
+
涨跌幅+0.46%
+
量比0.86
+
+
振幅
14
+
量能
46
+
趋势
88
+
活跃
92
+
+
+
📈 做多计划
+
入场24145 ~ 24175
+
止损24060 (S1)
+
目标一24280 (R1)
+
目标二24395 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
NR2607 20号胶
57.7
+
方向📈 做多
+
当前价15275.00
+
涨跌幅+0.30%
+
量比0.34
+
+
振幅
46
+
量能
6
+
趋势
88
+
活跃
100
+
+
+
📈 做多计划
+
入场14970 ~ 15077
+
止损14958 (S1)
+
目标一15183 (R1)
+
目标二15302 (R2)
+
触发: 回踩支撑企稳 + 5m放量突破
+
+
+
+
👀 重点关注品种 (8个)
+
多晶硅
75
⚪ 震荡
-1.18%
+
中证1000
62
⚪ 震荡
+0.87%
+
碳酸锂
54
⚪ 震荡
+0.40%
+
燃油
51
🔴 偏空
-1.19%
+
铝合金
50
🟢 偏多
+0.15%
+
棕榈油
46
⚪ 震荡
-0.77%
+
沪镍
46
🟢 偏多
-0.36%
+
棉花
45
🟡 弱多
+0.32%
+
+
🔴 规避品种 (10个)
+
原油
64
🔴 偏空
趋势极弱
+
低硫燃油
61
🔴 偏空
趋势极弱
+
燃油
51
🔴 偏空
趋势极弱
+
棕榈油
46
⚪ 震荡
空头主导
+
工业硅
43
⚪ 震荡
无方向震荡
+
集运欧线
43
⚪ 震荡
无方向震荡
+
PTA
41
🔴 强空
趋势极弱
+
尿素
40
🟡 弱空
趋势极弱
+
铁矿石
36
⚪ 震荡
无方向震荡
+
豆粕
33
⚪ 震荡
空头主导
+
+
📋 全品种多维度排名
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#品种名称收盘价综合评分振幅%涨跌幅%量比趋势分方向活跃度
1J2609焦炭2123.00
97
6.26%+3.33%3.3395🟢 强多100
2AG2608沪银16446.00
91
6.09%+3.61%1.0295🟢 强多100
3AU2608沪金920.32
85
3.62%+1.86%1.1175🟢 偏多100
4BR2607合成橡胶13485.00
83
3.55%+1.21%1.1575🟢 偏多84
5eg2609乙二醇4703.00
80
2.46%+0.91%1.1275🟢 偏多100
6AO2609氧化铝2947.00
79
1.88%+0.83%1.1675🟢 偏多100
7CU2607沪铜104960.00
78
2.01%+1.45%0.8775🟢 偏多100
8RB2610螺纹钢3187.00
76
1.13%+0.70%1.1495🟢 强多99
9PS2609多晶硅37280.00
75
5.50%-1.18%2.55-7⚪ 震荡100
10hc2610热卷3394.00
75
1.12%+0.63%1.1495🟢 强多100
11ZN2607沪锌24630.00
74
1.54%+0.81%0.9875🟢 偏多100
12SN2606沪锡409850.00
74
2.87%+2.45%0.0675🟢 偏多100
13PB2607沪铅16100.00
71
1.00%-1.02%1.3260🟡 弱多100
14JM2609焦煤1376.50
71
4.38%+0.62%0.8360🟡 弱多87
15MA2609甲醇3034.00
71
3.69%-0.50%1.1827⚪ 震荡85
16SR2609白糖5320.00
69
1.09%+0.45%1.1775🟢 偏多100
17v2609PVC4767.00
68
1.84%+0.53%0.9175🟢 偏多83
18SH2607烧碱1914.00
68
2.47%+0.53%0.6375🟢 偏多100
19RU2609橡胶17805.00
67
1.02%+0.54%0.8695🟢 强多94
20SA2609纯碱1172.00
66
0.87%-0.69%0.8595🟢 强多100
21SC2607原油545.50
64
7.10%-4.91%1.03-75🔴 偏空100
22IM2606中证10008196.80
62
1.93%+0.87%0.873⚪ 震荡100
23LU2607低硫燃油4456.00
61
5.87%-4.94%0.91-75🔴 偏空100
24FG2609玻璃1008.00
60
1.22%-0.40%0.7975🟢 偏多87
25AL2607沪铝24250.00
59
0.91%+0.46%0.8675🟢 偏多92
26NR260720号胶15275.00
58
1.49%+0.30%0.3475🟢 偏多100
27LC2609碳酸锂175300.00
54
2.36%+0.40%0.85-7⚪ 震荡100
28FU2606燃油4150.00
51
25.37%-1.19%0.01-75🔴 偏空100
29AD2607铝合金23270.00
50
0.69%+0.15%0.5775🟢 偏多100
30P2609棕榈油9308.00
46
1.05%-0.77%0.75-27⚪ 震荡100
31NI2606沪镍133400.00
46
1.00%-0.36%0.3875🟢 偏多50
32CF2609棉花15770.00
45
0.67%+0.32%0.6260🟡 弱多83
33SI2609工业硅8745.00
43
1.14%-0.17%0.70-5⚪ 震荡91
34ec2606集运欧线3128.50
43
1.49%-0.33%0.42-17⚪ 震荡100
35TA2609PTA6268.00
41
3.09%+0.03%1.09-95🔴 强空96
36UR2609尿素1793.00
40
1.40%-0.50%0.71-60🟡 弱空100
37I2609铁矿石765.00
36
0.72%+0.00%0.70-13⚪ 震荡100
38M2609豆粕2934.00
33
0.68%+0.07%0.66-20⚪ 震荡97
+
📝 品种详细分析
+

J2609 焦炭 🟢 强多

+
+
综合评分
96.8
+
当前价
2123.00
+
涨跌幅
+3.33%
+
量比
3.33
+
60m趋势
+50
+
15m趋势
+50
+
5m趋势
+50
+
活跃度
100
+
+
+
pivot 2067
+
r1 2137
+
r2 2197
+
s1 2007
+
s2 1937
+
daily_ma10 2011
+
daily_ma20 1917
+
h1_resistance 2130
+
h1_support 1979
+
m15_resistance 2130
+
m15_support 2038
+
+
+

AG2608 沪银 🟢 强多

+
+
综合评分
91.4
+
当前价
16446.00
+
涨跌幅
+3.61%
+
量比
1.02
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
+25
+
活跃度
100
+
+
+
pivot 15811
+
r1 16377
+
r2 16783
+
s1 15405
+
s2 14839
+
daily_ma10 16962
+
daily_ma20 17724
+
h1_resistance 16431
+
h1_support 15300
+
m15_resistance 16450
+
m15_support 15903
+
+
+

AU2608 沪金 🟢 偏多

+
+
综合评分
85.2
+
当前价
920.32
+
涨跌幅
+1.86%
+
量比
1.11
+
60m趋势
+45
+
15m趋势
+25
+
5m趋势
+25
+
活跃度
100
+
+
+
pivot 905
+
r1 925
+
r2 938
+
s1 892
+
s2 872
+
daily_ma10 952
+
daily_ma20 972
+
h1_resistance 922
+
h1_support 889
+
m15_resistance 923
+
m15_support 910
+
+
+

BR2607 合成橡胶 🟢 偏多

+
+
综合评分
83.1
+
当前价
13485.00
+
涨跌幅
+1.21%
+
量比
1.15
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
-25
+
活跃度
84
+
+
+
pivot 13348
+
r1 13597
+
r2 13823
+
s1 13122
+
s2 12873
+
daily_ma10 13910
+
daily_ma20 14462
+
h1_resistance 13585
+
h1_support 13100
+
m15_resistance 13585
+
m15_support 13300
+
+
+

eg2609 乙二醇 🟢 偏多

+
+
综合评分
80.5
+
当前价
4703.00
+
涨跌幅
+0.91%
+
量比
1.12
+
60m趋势
+50
+
15m趋势
+50
+
5m趋势
-45
+
活跃度
100
+
+
+
pivot 4655
+
r1 4724
+
r2 4770
+
s1 4609
+
s2 4540
+
daily_ma10 4552
+
daily_ma20 4563
+
h1_resistance 4762
+
h1_support 4584
+
m15_resistance 4762
+
m15_support 4605
+
+
+

AO2609 氧化铝 🟢 偏多

+
+
综合评分
79.2
+
当前价
2947.00
+
涨跌幅
+0.83%
+
量比
1.16
+
60m趋势
+25
+
15m趋势
+50
+
5m趋势
+50
+
活跃度
100
+
+
+
pivot 2917
+
r1 2947
+
r2 2972
+
s1 2892
+
s2 2862
+
daily_ma10 2842
+
daily_ma20 2812
+
h1_resistance 2947
+
h1_support 2906
+
m15_resistance 2947
+
m15_support 2908
+
+
+

CU2607 沪铜 🟢 偏多

+
+
综合评分
78.2
+
当前价
104960.00
+
涨跌幅
+1.45%
+
量比
0.87
+
60m趋势
+45
+
15m趋势
+25
+
5m趋势
+50
+
活跃度
100
+
+
+
pivot 104260
+
r1 105510
+
r2 106360
+
s1 103410
+
s2 102160
+
daily_ma10 104868
+
daily_ma20 104744
+
h1_resistance 105110
+
h1_support 103250
+
m15_resistance 105050
+
m15_support 104250
+
+
+

RB2610 螺纹钢 🟢 强多

+
+
综合评分
76.2
+
当前价
3187.00
+
涨跌幅
+0.70%
+
量比
1.14
+
60m趋势
+50
+
15m趋势
+50
+
5m趋势
+50
+
活跃度
99
+
+
+
pivot 3174
+
r1 3194
+
r2 3210
+
s1 3158
+
s2 3138
+
daily_ma10 3168
+
daily_ma20 3171
+
h1_resistance 3190
+
h1_support 3151
+
m15_resistance 3190
+
m15_support 3170
+
+
+

PS2609 多晶硅 ⚪ 震荡

+
+
综合评分
74.6
+
当前价
37280.00
+
涨跌幅
-1.18%
+
量比
2.55
+
60m趋势
+50
+
15m趋势
-25
+
5m趋势
-45
+
活跃度
100
+
+
+
pivot 37758
+
r1 38537
+
r2 39808
+
s1 36487
+
s2 35708
+
daily_ma10 36434
+
daily_ma20 36876
+
h1_resistance 39000
+
h1_support 33705
+
m15_resistance 39000
+
m15_support 36000
+
+
+

hc2610 热卷 🟢 强多

+
+
综合评分
74.6
+
当前价
3394.00
+
涨跌幅
+0.63%
+
量比
1.14
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
+50
+
活跃度
100
+
+
+
pivot 3376
+
r1 3397
+
r2 3414
+
s1 3359
+
s2 3338
+
daily_ma10 3379
+
daily_ma20 3388
+
h1_resistance 3397
+
h1_support 3354
+
m15_resistance 3397
+
m15_support 3371
+
+
+

ZN2607 沪锌 🟢 偏多

+
+
综合评分
74.0
+
当前价
24630.00
+
涨跌幅
+0.81%
+
量比
0.98
+
60m趋势
+45
+
15m趋势
+25
+
5m趋势
+25
+
活跃度
100
+
+
+
pivot 24292
+
r1 24513
+
r2 24667
+
s1 24138
+
s2 23917
+
daily_ma10 24697
+
daily_ma20 24760
+
h1_resistance 24635
+
h1_support 24125
+
m15_resistance 24650
+
m15_support 24280
+
+
+

SN2606 沪锡 🟢 偏多

+
+
综合评分
73.5
+
当前价
409850.00
+
涨跌幅
+2.45%
+
量比
0.06
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
-45
+
活跃度
100
+
+
+
pivot 405380
+
r1 412490
+
r2 417090
+
s1 400780
+
s2 393670
+
daily_ma10 417412
+
daily_ma20 417832
+
h1_resistance 410610
+
h1_support 395340
+
m15_resistance 410610
+
m15_support 399170
+
+
+

PB2607 沪铅 🟡 弱多

+
+
综合评分
71.4
+
当前价
16100.00
+
涨跌幅
-1.02%
+
量比
1.32
+
60m趋势
+25
+
15m趋势
+10
+
5m趋势
-45
+
活跃度
100
+
+
+
pivot 16072
+
r1 16143
+
r2 16232
+
s1 15983
+
s2 15912
+
daily_ma10 16355
+
daily_ma20 16496
+
h1_resistance 16150
+
h1_support 16005
+
m15_resistance 16125
+
m15_support 16035
+
+
+

JM2609 焦煤 🟡 弱多

+
+
综合评分
70.8
+
当前价
1376.50
+
涨跌幅
+0.62%
+
量比
0.83
+
60m趋势
+45
+
15m趋势
+5
+
5m趋势
+50
+
活跃度
87
+
+
+
pivot 1376
+
r1 1403
+
r2 1436
+
s1 1343
+
s2 1316
+
daily_ma10 1390
+
daily_ma20 1314
+
h1_resistance 1408
+
h1_support 1339
+
m15_resistance 1408
+
m15_support 1360
+
+
+

MA2609 甲醇 ⚪ 震荡

+
+
综合评分
70.6
+
当前价
3034.00
+
涨跌幅
-0.50%
+
量比
1.18
+
60m趋势
+5
+
15m趋势
+50
+
5m趋势
+25
+
活跃度
85
+
+
+
pivot 3016
+
r1 3068
+
r2 3127
+
s1 2957
+
s2 2905
+
daily_ma10 2948
+
daily_ma20 2919
+
h1_resistance 3074
+
h1_support 2977
+
m15_resistance 3043
+
m15_support 2985
+
+
+

SR2609 白糖 🟢 偏多

+
+
综合评分
68.9
+
当前价
5320.00
+
涨跌幅
+0.45%
+
量比
1.17
+
60m趋势
+25
+
15m趋势
+50
+
5m趋势
-25
+
活跃度
100
+
+
+
pivot 5306
+
r1 5340
+
r2 5364
+
s1 5282
+
s2 5248
+
daily_ma10 5359
+
daily_ma20 5374
+
h1_resistance 5328
+
h1_support 5273
+
m15_resistance 5331
+
m15_support 5276
+
+
+

v2609 PVC 🟢 偏多

+
+
综合评分
68.4
+
当前价
4767.00
+
涨跌幅
+0.53%
+
量比
0.91
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
-25
+
活跃度
83
+
+
+
pivot 4724
+
r1 4776
+
r2 4811
+
s1 4689
+
s2 4637
+
daily_ma10 4813
+
daily_ma20 4873
+
h1_resistance 4784
+
h1_support 4675
+
m15_resistance 4784
+
m15_support 4685
+
+
+

SH2607 烧碱 🟢 偏多

+
+
综合评分
68.1
+
当前价
1914.00
+
涨跌幅
+0.53%
+
量比
0.63
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
-25
+
活跃度
100
+
+
+
pivot 1892
+
r1 1920
+
r2 1939
+
s1 1873
+
s2 1845
+
daily_ma10 1908
+
daily_ma20 1950
+
h1_resistance 1923
+
h1_support 1865
+
m15_resistance 1923
+
m15_support 1885
+
+
+

RU2609 橡胶 🟢 强多

+
+
综合评分
66.8
+
当前价
17805.00
+
涨跌幅
+0.54%
+
量比
0.86
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
+25
+
活跃度
94
+
+
+
pivot 17562
+
r1 17653
+
r2 17742
+
s1 17473
+
s2 17382
+
daily_ma10 17770
+
daily_ma20 17668
+
h1_resistance 17835
+
h1_support 17360
+
m15_resistance 17835
+
m15_support 17505
+
+
+

SA2609 纯碱 🟢 强多

+
+
综合评分
65.5
+
当前价
1172.00
+
涨跌幅
-0.69%
+
量比
0.85
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
+25
+
活跃度
100
+
+
+
pivot 1152
+
r1 1156
+
r2 1162
+
s1 1146
+
s2 1142
+
daily_ma10 1174
+
daily_ma20 1185
+
h1_resistance 1174
+
h1_support 1148
+
m15_resistance 1174
+
m15_support 1148
+
+
+

SC2607 原油 🔴 偏空

+
+
综合评分
63.6
+
当前价
545.50
+
涨跌幅
-4.91%
+
量比
1.03
+
60m趋势
-50
+
15m趋势
-50
+
5m趋势
+25
+
活跃度
100
+
+
+
pivot 563
+
r1 578
+
r2 603
+
s1 539
+
s2 524
+
daily_ma10 590
+
daily_ma20 611
+
h1_resistance 588
+
h1_support 543
+
m15_resistance 562
+
m15_support 543
+
+
+

IM2606 中证1000 ⚪ 震荡

+
+
综合评分
62.3
+
当前价
8196.80
+
涨跌幅
+0.87%
+
量比
0.87
+
60m趋势
+45
+
15m趋势
-25
+
5m趋势
-10
+
活跃度
100
+
+
+
pivot 8229
+
r1 8292
+
r2 8387
+
s1 8133
+
s2 8070
+
daily_ma10 8244
+
daily_ma20 8404
+
h1_resistance 8324
+
h1_support 8050
+
m15_resistance 8324
+
m15_support 8064
+
+
+

LU2607 低硫燃油 🔴 偏空

+
+
综合评分
61.1
+
当前价
4456.00
+
涨跌幅
-4.94%
+
量比
0.91
+
60m趋势
-50
+
15m趋势
-25
+
5m趋势
-40
+
活跃度
100
+
+
+
pivot 4577
+
r1 4671
+
r2 4841
+
s1 4407
+
s2 4313
+
daily_ma10 4733
+
daily_ma20 4826
+
h1_resistance 4796
+
h1_support 4433
+
m15_resistance 4558
+
m15_support 4433
+
+
+

FG2609 玻璃 🟢 偏多

+
+
综合评分
60.2
+
当前价
1008.00
+
涨跌幅
-0.40%
+
量比
0.79
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
+5
+
活跃度
87
+
+
+
pivot 988
+
r1 994
+
r2 1000
+
s1 982
+
s2 976
+
daily_ma10 1016
+
daily_ma20 1024
+
h1_resistance 1010
+
h1_support 984
+
m15_resistance 1010
+
m15_support 984
+
+
+

AL2607 沪铝 🟢 偏多

+
+
综合评分
58.9
+
当前价
24250.00
+
涨跌幅
+0.46%
+
量比
0.86
+
60m趋势
+50
+
15m趋势
+50
+
5m趋势
-25
+
活跃度
92
+
+
+
pivot 24175
+
r1 24280
+
r2 24395
+
s1 24060
+
s2 23955
+
daily_ma10 24266
+
daily_ma20 24350
+
h1_resistance 24275
+
h1_support 24080
+
m15_resistance 24280
+
m15_support 24145
+
+
+

NR2607 20号胶 🟢 偏多

+
+
综合评分
57.7
+
当前价
15275.00
+
涨跌幅
+0.30%
+
量比
0.34
+
60m趋势
+45
+
15m趋势
+50
+
5m趋势
-45
+
活跃度
100
+
+
+
pivot 15077
+
r1 15183
+
r2 15302
+
s1 14958
+
s2 14852
+
daily_ma10 15357
+
daily_ma20 15120
+
h1_resistance 15345
+
h1_support 14900
+
m15_resistance 15345
+
m15_support 14970
+
+
+

LC2609 碳酸锂 ⚪ 震荡

+
+
综合评分
54.3
+
当前价
175300.00
+
涨跌幅
+0.40%
+
量比
0.85
+
60m趋势
+50
+
15m趋势
-25
+
5m趋势
-45
+
活跃度
100
+
+
+
pivot 176213
+
r1 177827
+
r2 180353
+
s1 173687
+
s2 172073
+
daily_ma10 169284
+
daily_ma20 175167
+
h1_resistance 178740
+
h1_support 165000
+
m15_resistance 178740
+
m15_support 171700
+
+
+

FU2606 燃油 🔴 偏空

+
+
综合评分
51.1
+
当前价
4150.00
+
涨跌幅
-1.19%
+
量比
0.01
+
60m趋势
-25
+
15m趋势
-50
+
5m趋势
+25
+
活跃度
100
+
+
+
pivot 4190
+
r1 4696
+
r2 5243
+
s1 3643
+
s2 3137
+
daily_ma10 4407
+
daily_ma20 4402
+
h1_resistance 4736
+
h1_support 3683
+
m15_resistance 4736
+
m15_support 3683
+
+
+

AD2607 铝合金 🟢 偏多

+
+
综合评分
50.0
+
当前价
23270.00
+
涨跌幅
+0.15%
+
量比
0.57
+
60m趋势
+50
+
15m趋势
+25
+
5m趋势
+5
+
活跃度
100
+
+
+
pivot 23158
+
r1 23237
+
r2 23318
+
s1 23077
+
s2 22998
+
daily_ma10 23042
+
daily_ma20 23033
+
h1_resistance 23285
+
h1_support 23080
+
m15_resistance 23290
+
m15_support 23155
+
+
+

P2609 棕榈油 ⚪ 震荡

+
+
综合评分
46.5
+
当前价
9308.00
+
涨跌幅
-0.77%
+
量比
0.75
+
60m趋势
-5
+
15m趋势
-50
+
5m趋势
-25
+
活跃度
100
+
+
+
pivot 9344
+
r1 9381
+
r2 9442
+
s1 9283
+
s2 9246
+
daily_ma10 9490
+
daily_ma20 9523
+
h1_resistance 9420
+
h1_support 9280
+
m15_resistance 9400
+
m15_support 9280
+
+
+

NI2606 沪镍 🟢 偏多

+
+
综合评分
46.4
+
当前价
133400.00
+
涨跌幅
-0.36%
+
量比
0.38
+
60m趋势
+25
+
15m趋势
+25
+
5m趋势
+10
+
活跃度
50
+
+
+
pivot 132963
+
r1 133407
+
r2 134293
+
s1 132077
+
s2 131633
+
daily_ma10 138090
+
daily_ma20 140728
+
h1_resistance 133850
+
h1_support 132520
+
m15_resistance 133850
+
m15_support 132520
+
+
+

CF2609 棉花 🟡 弱多

+
+
综合评分
45.4
+
当前价
15770.00
+
涨跌幅
+0.32%
+
量比
0.62
+
60m趋势
+45
+
15m趋势
+5
+
5m趋势
-10
+
活跃度
83
+
+
+
pivot 15767
+
r1 15818
+
r2 15872
+
s1 15713
+
s2 15662
+
daily_ma10 15987
+
daily_ma20 16023
+
h1_resistance 15820
+
h1_support 15645
+
m15_resistance 15805
+
m15_support 15740
+
+
+

SI2609 工业硅 ⚪ 震荡

+
+
综合评分
43.0
+
当前价
8745.00
+
涨跌幅
-0.17%
+
量比
0.70
+
60m趋势
+50
+
15m趋势
-25
+
5m趋势
-40
+
活跃度
91
+
+
+
pivot 8762
+
r1 8803
+
r2 8862
+
s1 8703
+
s2 8662
+
daily_ma10 8707
+
daily_ma20 8608
+
h1_resistance 8825
+
h1_support 8635
+
m15_resistance 8820
+
m15_support 8680
+
+
+

ec2606 集运欧线 ⚪ 震荡

+
+
综合评分
42.7
+
当前价
3128.50
+
涨跌幅
-0.33%
+
量比
0.42
+
60m趋势
-25
+
15m趋势
+25
+
5m趋势
-50
+
活跃度
100
+
+
+
pivot 3126
+
r1 3151
+
r2 3173
+
s1 3104
+
s2 3080
+
daily_ma10 3083
+
daily_ma20 2995
+
h1_resistance 3190
+
h1_support 3102
+
m15_resistance 3149
+
m15_support 3102
+
+
+

TA2609 PTA 🔴 强空

+
+
综合评分
40.8
+
当前价
6268.00
+
涨跌幅
+0.03%
+
量比
1.09
+
60m趋势
-45
+
15m趋势
-50
+
5m趋势
-40
+
活跃度
96
+
+
+
pivot 6336
+
r1 6442
+
r2 6532
+
s1 6246
+
s2 6140
+
daily_ma10 6296
+
daily_ma20 6252
+
h1_resistance 6426
+
h1_support 6226
+
m15_resistance 6378
+
m15_support 6226
+
+
+

UR2609 尿素 🟡 弱空

+
+
综合评分
39.9
+
当前价
1793.00
+
涨跌幅
-0.50%
+
量比
0.71
+
60m趋势
-50
+
15m趋势
-10
+
5m趋势
+25
+
活跃度
100
+
+
+
pivot 1789
+
r1 1803
+
r2 1814
+
s1 1778
+
s2 1764
+
daily_ma10 1800
+
daily_ma20 1821
+
h1_resistance 1814
+
h1_support 1775
+
m15_resistance 1807
+
m15_support 1775
+
+
+

I2609 铁矿石 ⚪ 震荡

+
+
综合评分
35.5
+
当前价
765.00
+
涨跌幅
+0.00%
+
量比
0.70
+
60m趋势
+10
+
15m趋势
-5
+
5m趋势
-45
+
活跃度
100
+
+
+
pivot 765
+
r1 767
+
r2 771
+
s1 762
+
s2 760
+
daily_ma10 770
+
daily_ma20 780
+
h1_resistance 768
+
h1_support 762
+
m15_resistance 768
+
m15_support 762
+
+
+

M2609 豆粕 ⚪ 震荡

+
+
综合评分
33.2
+
当前价
2934.00
+
涨跌幅
+0.07%
+
量比
0.66
+
60m趋势
+10
+
15m趋势
-25
+
5m趋势
-45
+
活跃度
97
+
+
+
pivot 2936
+
r1 2949
+
r2 2956
+
s1 2929
+
s2 2916
+
daily_ma10 2940
+
daily_ma20 2967
+
h1_resistance 2944
+
h1_support 2924
+
m15_resistance 2941
+
m15_support 2926
+
+
+
+
🔥 板块热度分析
+

贵金属 🔥🔥🔥

+
均分: 88.3 | 趋势: 85 | 方向: 多头
+
龙头: AG2608 (91.4分)
+
+

有色金属 🔥🔥

+
均分: 64.6 | 趋势: 73 | 方向: 多头
+
龙头: CU2607 (78.2分)
+
+

黑色系 🔥🔥

+
均分: 70.8 | 趋势: 66 | 方向: 多头
+
龙头: J2609 (96.8分)
+
+

化工 🔥🔥

+
均分: 63.0 | 趋势: 24 | 方向: 多头
+
龙头: BR2607 (83.1分)
+
+

新能源 🔥🔥

+
均分: 62.8 | 趋势: 14 | 方向: 震荡
+
龙头: AO2609 (79.2分)
+
+

农产品 ❄️

+
均分: 48.5 | 趋势: 22 | 方向: 多头
+
龙头: SR2609 (68.9分)
+
+

能源 🔥🔥

+
均分: 63.6 | 趋势: -75 | 方向: 空头
+
龙头: SC2607 (63.6分)
+
+

股指 🔥🔥

+
均分: 62.3 | 趋势: 3 | 方向: 震荡
+
龙头: IM2606 (62.3分)
+
+

航运 ❄️

+
均分: 42.7 | 趋势: -17 | 方向: 震荡
+
龙头: ec2606 (42.7分)
+
+

橡胶 🔥

+
均分: 57.7 | 趋势: 75 | 方向: 多头
+
龙头: NR2607 (57.7分)
+
+
+
⚠️ 本计划基于技术面分析,仅供参考,不构成投资建议。市场有风险,交易需谨慎。
交易智多星 | 2026-06-14 生成
+ +
\ No newline at end of file diff --git a/data/计划/每日收盘交易计划_执行逻辑说明_v2 (1).md b/data/计划/每日收盘交易计划_执行逻辑说明_v2 (1).md new file mode 100644 index 0000000..5fdf0e1 --- /dev/null +++ b/data/计划/每日收盘交易计划_执行逻辑说明_v2 (1).md @@ -0,0 +1,156 @@ +# 每日收盘交易计划 - 执行逻辑说明 (v2) + +> **任务名称**:每日收盘交易计划 (16:01) +> **任务 ID**:`3b960afd-45b3-489a-9029-fdbadbdc4f64` +> **调度时间**:`1 8 * * mon-fri`(UTC 08:01 = 北京时间 16:01) +> **超时限制**:1200 秒(20 分钟) +> **分发渠道**:console + 收件箱 +> **状态**:✅ 已启用 + +--- + +## 📋 完整执行流程 + +### 第一步:数据收集 + +``` +数据源:share_data/symbols_config.json(38个期货品种) +采集工具:batch_get_market_data (Market Data Collector MCP) +``` + +| 周期 | 用途 | +|------|------| +| 日线 (daily) | 计算当日振幅、涨跌幅、近20日均振幅 | +| 60分钟 (60min) | 趋势判断(MA排列 + MACD) | +| 15分钟 (15min) | 趋势判断 + 入场时机 | +| 5分钟 (5min) | 活跃度评估 | + +**参数**:`include_indicators=True`(包含 MA、MACD 指标) + +--- + +### 第二步:多维度打分排名(全品种扫描) + +对每个品种计算 **5 个维度** 并排名: + +| 维度 | 权重 | 计算方法 | 排名依据 | 意义 | +|------|------|---------|---------|------| +| **A. 当日振幅** | 20% | (今日最高 - 今日最低) / 今日收盘 × 100% | 从大到小 | 振幅越大,日内操作空间越大 | +| **B. 成交量** | 15% | 今日成交量 / 近5日均量(量比) | 从大到小 | 放量品种更值得关注 | +| **C. 涨跌幅** | 15% | (今日收盘 - 昨日收盘) / 昨日收盘 × 100% | 绝对值从大到小 | 大幅波动可能有趋势延续或反转机会 | +| **D. 多周期趋势共振** | 35% | 60m/15m/5m 的 MA排列 + MACD状态 → 三周期共振 | 趋势分 0-100 | 趋势越明确,操作胜率越高 | +| **E. 活跃度** | 15% | 5min K线近10根的量活跃度 + 价格变动率 | 综合评分 0-100 | 活跃品种流动性好、滑点小 | + +#### 趋势分计算细则(维度D) + +| 周期 | MA排列方向 | MACD状态 | 单周期趋势分 | +|------|-----------|---------|-------------| +| 60min | 多头排列 +50 / 空头排列 -50 | DIF>DEA +30 / DIF 20 | 多头共振 | 80-100 | +| 60m/15m/5m 趋势分均 < -20 | 空头共振 | 80-100 | +| 60m>0 且 15m>0 | 偏多震荡 | 50-70 | +| 60m<0 且 15m<0 | 偏空震荡 | 50-70 | +| 其他 | 多空交织 | 0-40 | + +--- + +### 第三步:综合评分与筛选 + +#### 综合评分公式 + +``` +composite = 振幅得分×0.20 + 成交量得分×0.15 + 涨跌幅得分×0.15 + 趋势得分×0.35 + 活跃度×0.15 +``` + +#### 筛选规则 + +| 标签 | 条件 | 处理 | +|------|------|------| +| 🟢 **交易机会** | 综合≥55 且 趋势明确 | 纳入交易计划 | +| 👀 **重点关注** | 综合45-55 或 形态构筑中 | 列入观察清单 | +| 🔴 **规避** | 综合<45 或 多空交织无方向 | 不建议参与 | + +--- + +### 第四步:制定具体交易计划 + +针对 🟢 交易机会品种(Top 3-5),逐一出具: + +``` +每个品种包含: +├── 品种/合约 & 方向(多/空) +├── 入场区间(具体支撑/阻力位) +├── 止损位(必须明确,证伪点) +├── 目标位(第一目标 / 第二目标) +├── 触发条件(如:突破XX进场、回踩XX不破进场) +└── 综合评分及各维度得分明细 +``` + +--- + +### 第五步:交付 + +| 交付方式 | 内容 | +|---------|------| +| **文件保存** | `reports/daily_plan/YYYY-MM-DD_交易计划.md` | +| **文件发送** | 通过 `send_file_to_user` 发送完整报告 | +| **聊天窗口** | 精简摘要:今日一句话总结 + Top3 核心机会 + 最大风险提示 | +| **收件箱** | 自动保存一份(`save_result_to_inbox: true`) | + +#### 完整报告结构 + +1. **全品种多维度排名表**(振幅/成交量/涨跌幅/趋势/综合评分) +2. **🟢 交易机会品种详细计划** +3. **👀 重点关注品种清单** +4. **🔴 规避品种及原因** +5. **板块热度分析**(按黑色系/贵金属/有色/能源化工/农产品/建材分组) +6. **风险提示** + +--- + +## 🔧 技术配置 + +| 配置项 | 值 | +|--------|-----| +| 任务类型 | agent | +| 会话 ID | 1779157619683 | +| 用户 ID | default | +| 分发模式 | final(仅最终结果) | +| 最大并发 | 1 | +| 超时 | 1200 秒 | +| 错过宽限 | 60 秒 | +| 共享会话 | ✅ | +| 收件箱 | ✅ | + +--- + +## ⏰ 调度说明 + +| 项目 | 值 | +|------|-----| +| Cron 表达式 | `1 8 * * mon-fri` | +| UTC 时间 | 每周一至周五 08:01 | +| **北京时间** | **每周一至周五 16:01**(收盘后1分钟) | + +--- + +## 📊 v1 → v2 变更摘要 + +| 变更项 | v1(旧版) | v2(新版) | +|--------|-----------|-----------| +| 数据收集 | 优先本地缓存,缺失才调MCP | **直接调用 batch_get_market_data 获取全品种数据** | +| 分析范围 | 仅有机会品种 | **全品种扫描 + 多维度打分排名** | +| 筛选维度 | 多周期共振 + 量价 | **新增:当日振幅排名、成交量排名、涨跌幅排名** | +| 评分体系 | 无量化评分 | **5维度加权综合评分(振幅20%+量15%+涨跌幅15%+趋势35%+活跃度15%)** | +| 输出格式 | Top3-5计划 | **全品种排名表 + 分级清单(🟢👀🔴) + Top3-5详细计划 + 板块热度** | + +--- + +*文档生成时间:2026-06-14* diff --git a/logs.bat b/logs.bat new file mode 100644 index 0000000..6d1764d --- /dev/null +++ b/logs.bat @@ -0,0 +1,25 @@ +@echo off +chcp 65001 >nul +echo ======================================== +echo 查看期货智析缓冲平台日志 +echo ======================================== +echo. + +echo 请选择日志查看模式: +echo 1. 查看最近100行日志 +echo 2. 实时跟踪日志 +echo 3. 查看最近1小时日志 +echo. +set /p MODE=请输入选项 (1/2/3): + +if "%MODE%"=="1" ( + docker-compose logs --tail=100 +) else if "%MODE%"=="2" ( + docker-compose logs -f +) else if "%MODE%"=="3" ( + docker-compose logs --since 1h +) else ( + echo 无效选项 +) + +pause