文档路径:
docs/plans/README.md最后更新:2026-07-07(v4) 仓库:https://gitee.com/mlpt-dev/mlpt-rc
MLPT-RC 是一个面向新疆电力现货市场的滚动撮合与电价预测系统,采用前后端分离架构:
┌─────────────────────────────────────────────────────────────────┐
│ 前端 (Vue 3) │
│ web/ → Vben Admin + Ant Design Vue + Vite + Pinia + TypeScript │
│ 生产构建产物 → backend/static_web/ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 后端 (Django + Ninja) │
│ backend/application/ │ backend/spot/ │ backend/slove/ │
│ backend/sider/ │ backend/xhxtapi/ │ backend/gwapi/ │
│ backend/trading/ │ backend/system/ │ backend/common/ │
│ backend/feishu/ │ backend/database/ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 数据层:MySQL / SQLite / 文件(Excel/模型) │
│ 电价预测模型 │ PMOS Excel │ xhxtapi 代理 │ 金风科技功率预测 │
│ 飞书机器人 │
└─────────────────────────────────────────────────────────────────┘
| 模块 | 路径 | 主要职责 |
|---|---|---|
application |
backend/application/ |
Django 壳:settings.py、urls.py、Django-Ninja 入口 main.py、Celery celery.py、WSGI/ASGI |
common |
backend/common/ |
公共基础设施:JWT 认证 fu_auth.py、通用 CRUD fu_crud.py、软删/审计基类 fu_model.py、分页、API 日志、代码生成模板 |
config |
backend/config/ |
多环境配置(dev/uat/prd/win),提供数据库、用户数据目录等配置 |
database |
backend/database/ |
数据库模型/API 封装 |
fixtures |
backend/fixtures/ |
Django 初始演示数据 |
gwapi |
backend/gwapi/ |
金风科技功率预测接口(通过 xhxt.chng.com.cn 拉取):power_forecast_client.py、forecast_d2d4.py、auth.py
|
hooks |
backend/hooks/ |
钩子脚本目录 |
logs |
backend/logs/ |
运行时日志 |
sider |
backend/sider/ |
PMOS 数据抓取与监控、Flask 大屏 |
submit |
backend/submit/ |
PMOS 自动批量申报:通过 CDP 连接 Chrome,自动填写 PMOS 网页表单并提交滚动撮合交易申报 |
slove |
backend/slove/ |
AI 电价预测核心:训练、预测、滚动验证、物理后处理、模型注册 |
spot |
backend/spot/ |
现货/滚动撮合/中长期业务核心:模型、API、服务、策略信号、Python 规则引擎、偏差回收、策略回测 |
system |
backend/system/ |
RBAC 基础平台:用户、登录、菜单、角色、部门、字典、文件、代码生成、日志、Celery 任务 |
trading |
backend/trading/ |
通用滚动套利核心:rolling_arb_core/(信号、状态机、Walk-Forward 回测)、spot_adapter.py(SpotSignalProvider)、spot_config.py
|
workflow |
backend/workflow/ |
工作流引擎 |
xhxtapi |
backend/xhxtapi/ |
现货系统数据代理:server.py(FastAPI,端口 12345)、client.py、daily_clearing_calculator.py(日清分计算) |
feishu |
backend/feishu/ |
飞书机器人集成:长连接消息处理、PMOS 在线表格推送、群公告管理 |
slove 负责新疆日前现货电价预测,输出 D+1~D+4 共 384 点(15 分钟粒度),包含训练、生产预测、滚动验证、物理后处理、真实电价回填。
| 文件 | 作用 |
|---|---|
backend/slove/train_xinjiang.py |
模型训练入口:加载历史数据,训练特征工程 + 4 个 horizon 的 MinimalL3 模型 + 物理后处理 + 稀缺校正 |
backend/slove/predict_xinjiang.py |
生产预测入口:加载 outputs/models/current 下模型,逐 horizon 自回归预测,输出 CSV |
backend/slove/wfv_xinjiang.py |
Walk-Forward Validation:对每个 anchor 日期重新训练并预测 D+1~D+4,输出 MAE 到 outputs/wfv_xinjiang.csv
|
backend/slove/actual_price_backfill.py |
从 xhxtapi 拉取真实日前出清电价,回填 spot.PriceForecastRecord.actual_price
|
backend/slove/update_from_api.py |
从 API 拉取市场供需、负荷预测数据,生成 training_data/新疆合并数据.json(已从 xlsx 改为 JSON 单文件全量) |
backend/slove/model_registry.py |
模型目录管理,按周归档/滚动模型 |
backend/slove/src/)| 文件 | 作用 |
|---|---|
snapshot_builder.py |
从 Excel 合并数据构建预测 snapshot |
stateful_fe.py |
有状态特征工程,按月份/星期/小时的历史同期中位数填充 |
minimal_l3.py |
horizon L3 预测模型(当前主力,仅新疆使用),内含 _apply_recency_weight 近期样本加权 |
physics_postprocess.py |
物理一致性后处理:零价闸门、尖峰闸门、价格上下限 |
morning_scarcity_calibrator.py |
凌晨/晚高峰稀缺时段偏置校正 |
walk_forward.py |
通用 WFV 协议与可视化 |
api_data_fetcher.py |
现货系统数据拉取器 |
backend/slove/outputs/models/
└── current/ # 当前生产模型(7 个 .pkl + metadata.json),每天10:00自动训练更新
├── xinjiang_fe.pkl
├── xinjiang_h1_l3.pkl # horizon 1 模型
├── xinjiang_h2_l3.pkl # horizon 2 模型
├── xinjiang_h3_l3.pkl # horizon 3 模型
├── xinjiang_h4_l3.pkl # horizon 4 模型
├── xinjiang_morning_calib.pkl
├── xinjiang_physics.pkl
└── metadata.json # 训练元数据(样本范围、训练时间等)
slove 预测流程:
训练/预测任务 ──→ slove/train_xinjiang.py / predict_xinjiang.py
│
▼
输出 D+1~D+4 384 点电价
│
▼
写入 spot.PriceForecastRecord
│
▼
spot.api 提供给前端 dashboard / 滚动撮合页面
│
▼
actual_price_backfill.py 反向回填真实电价
| 模型 | 说明 |
|---|---|
SpotStation |
场站:名称、编码(外部API机组ID)、容量、省份、城市、机制比例、station_type(wind/solar)、branch_company(分公司)、trader(交易员)、unit_ids(JSON机组ID列表)、generation_mapping(预测电量映射) |
RehearsalScenario |
中长期模拟方案 |
RehearsalContract / RehearsalContractHourly
|
中长期合同及 24 点明细 |
SpotRollingContract |
滚动撮合合同 |
SpotTradeOperation |
交易操作流水 |
SpotRollingResult |
滚动撮合结果(预测/实际收益) |
SpotRollingStrategyResult |
滚动撮合策略信号结果(24 小时) |
SpotRollingStrategyTrade |
策略执行/模拟交易记录 |
RollingStrategyRule |
Python 策略规则 |
PriceForecastRecord |
电价预测记录(slove/官方/真实电价) |
MonthlySpotPrice |
[新增] 月度中长期预测价格录入(24时点,支持日覆盖覆盖月度默认值) |
DeviationRecoveryResult |
[新增] 偏差回收测算结果(含 24 时点明细) |
SpotRollingStrategyBacktest |
[新增] 策略回测记录 |
RevenueDaily |
日收益汇总 |
SettlementPrice |
结算价格 |
RehearsalCalculationService(backend/spot/services.py)中长期合同日分解、24 点汇总、加权均价、可用容量约束计算。
SpotRollingTradeService(backend/spot/services.py)| 方法 | 作用 |
|---|---|
create_contract_simulate() |
生成模拟合同,申报编码格式 `YYYYMMDDHH{s |
execute_trade() |
执行 BUY/SELL,更新合同当前电量/电价/调整量 |
calculate_revenue_forecast() |
收益预测 |
calculate_revenue_bounds() |
收益边界 |
sync_from_external_api() |
从外部 API 同步真实合同 |
RollingMarketViewService(backend/spot/services.py)构建 24 点市场视图,聚合:
RollingStrategyService(backend/spot/strategy_service.py)统一策略信号生成入口,协调市场视图构建、信号注入、Python 规则链式执行、收益计算。
DeviationRecoveryService(backend/spot/deviation_recovery_service.py)[新增]
偏差回收机制测算,计算 24 时点持仓率、触发判定、回收金额:
[max(0, 0.8 - 机制比例), 1.2 - 机制比例]
full_month(全月外推)和 d_minus_1(D-1口径)两种计算模式BacktestService(backend/spot/backtest_service.py)[新增]
策略历史回测服务,支持标准/高级信号模式回测,计算总收益、年化收益、夏普比率、最大回撤、胜率等指标。
SimpleBacktestService(backend/spot/simple_backtest_service.py)[新增]
简单模式回测与参数择优,遍历多组参数组合寻找最优策略配置。
| 文件 | 职责 |
|---|---|
signal_provider.py |
封装 trading.spot_adapter.SpotSignalProvider,将高级信号转换为 dict 注入到市场上下文中 |
risk_assessment.py |
风险评估服务,输出 LOW/MEDIUM/HIGH 等级 |
prediction_accuracy.py |
电价预测准确率计算(MAE/RMSE/方向准确率) |
prediction_confidence.py |
预测置信区间服务 |
pmos_realtime.py |
PMOS 实时价格服务 |
powerplant_mapping.py |
场站-发电厂预测电量映射 |
外部数据
├── xhxtapi:中长期合同数据
├── PMOS Excel:实时报价排名
├── slove:日前电价预测
└── gwapi:金风科技场站功率预测/实测电量(经 xhxt 接口)
│
▼
RollingMarketViewService.build_market_view()
│
▼
RollingStrategyService.generate_signals()
│
▼
PythonRuleService.execute() 逐小时执行规则
│
▼
生成 SpotRollingStrategyResult(24 点信号)
│
▼
前端可执行交易 → SpotTradeOperation → 更新 SpotRollingContract
注:项目已彻底移除 Lua 执行栈,全部策略规则通过 Python 脚本执行。
| 文件 | 职责 |
|---|---|
backend/spot/python_engine.py |
Python 脚本沙箱执行引擎:execute_python_script() 通过子进程 -m spot.python_worker 执行脚本,各请求线程独立 worker,带超时与错误处理 |
backend/spot/python_worker.py |
独立 worker 子进程(__main__ 入口),从 stdin 逐行读取 JSON 脚本并执行,输出结果 JSON |
backend/spot/python_rule_service.py |
规则匹配、链式执行、默认规则管理(PythonRuleService 类) |
backend/spot/python_defaults.py |
默认脚本字符串:DEFAULT_STANDARD_PYTHON_SCRIPT、DEFAULT_ADVANCED_PYTHON_SCRIPT、规则模板 |
backend/spot/python_strategy.py |
策略接口封装:PythonStrategyExecutor 继承 PythonRuleService,添加策略级方法;统一导出 get_default_scripts、get_rule_templates
|
backend/spot/strategy_service.py |
统一策略信号生成服务 RollingStrategyService
|
backend/spot/signal_provider.py |
封装 trading.spot_adapter.SpotSignalProvider,将高级信号转换为 dict 注入到市场上下文中 |
RollingStrategyRule
| 字段 | 含义 |
|---|---|
name |
规则名称 |
signal_mode |
all / standard / advanced
|
rule_type |
full(完整策略) / post_override(后置覆盖) |
priority |
优先级,数字小的先执行(升序),相同 priority 按创建时间倒序 |
enabled |
是否启用 |
station_ids |
生效场站列表,空表示全局 |
target_date_start / target_date_end
|
生效日期范围 |
hour_start / hour_end / hours
|
生效时段 |
python_script |
Python 策略脚本 |
params |
规则参数 JSON |
RollingStrategyService.generate_signals(
target_date, station_id, signal_mode
)
│
▼
RollingMarketViewService.build_market_view()
│
▼
if signal_mode == 'advanced':
SignalProviderService.generate() # 注入 rolling_arb_core 信号
│
▼
逐小时构造 market 上下文
│
▼
PythonRuleService.execute(station_id, target_date, hour, signal_mode, market)
│
▼
匹配规则 → 按 priority 排序 → 链式执行
│
▼
规则脚本 decide_action(...) 返回 {action, target_qty, trade_price, reason}
│
▼
_compute_action_revenue() 计算收益
│
▼
批量写入 SpotRollingStrategyResult
PythonRuleService.execute() 逻辑:
match_rules() 按场站、日期、时段、signal_mode 筛选启用规则;standard 或 advanced);priority 升序执行(数字小的先执行);default_action 为空,后续规则使用上一条结果;HOLD 并记录错误原因;脚本约定:
def decide_action(market, config, hour, target_date, station_id, station_code, default_action):
# ...
return {
'action': 'BUY' | 'SELL' | 'HOLD',
'target_qty': float,
'trade_price': float,
'reason': str,
'predicted_ref_price': float, # 可选
'reference_price_source': str, # 可选
}
偏差回收机制基于新疆电力市场中长期持仓率考核规则,当场站持仓率越界且价格方向一致时,触发偏差收益回收。
持仓率 = Q中长期 / Q实际上网
合理区间:
下限 = max(0, 0.8 - 机制比例 m)
上限 = 1.2 - 机制比例 m
持仓不足(under):持仓率 < 下限
持仓过多(over) :持仓率 > 上限
价格方向一致性判定:
持仓不足(under):
R = (P日前(全网) − P中长期(全网)) × ((0.8−m)×Q实际上网 − Q中长期) × K
持仓过多(over):
R = (P中长期(全网) − P日前(全网)) × (Q中长期 − (1.2−m)×Q实际上网) × K
回收系数 K = 1
全月回收总额 = Σ24时点 R
| 字段 | 来源 |
|---|---|
| Q中长期 | xhxtapi 中长期合同数据(contractEle)+ 本库滚动撮合合同调整量,区间 [月初, D-1] |
| Q实际上网 | gwapi 全月预测发电量(full_month 口径含 D+3 外推,d_minus_1 口径仅用实测) |
| P日前(全网) | xhxtapi 市场供需汇总 totalAvgClearingPriceRt,[月初, D-1] 日均 |
| P中长期(全网) |
MonthlySpotPrice 手动录入表(支持日覆盖覆盖月度默认值) |
| P日前(自己) | xhxtapi 日清分明细,省内日前电量加权电价 |
| P中长期(自己) | 复用高级信号 contract_price(合同电量加权) |
backend/sider/)| 文件 | 作用 |
|---|---|
scrape_pmos.py |
Playwright CDP 连接 Chrome(localhost:9222),从 PMOS 网站提取表格到 pmos_table_data.xlsx
|
scrape_pmos_monitor.py |
每 60 秒监控所有日期标签页,提取最新价、买1/卖1 价量,追加到 pmos_data.xlsx
|
api.py |
Django Ninja API:/api/sider/* 提供 sheet 列表、报价、排名、K线、统计、预警 |
dashboard/app.py |
Flask 大屏服务(端口 5000),可选启动 |
| 文件 | 作用 |
|---|---|
backend/xhxtapi/server.py |
FastAPI 服务(端口 12345),接收自然语言指令,代理查询现货系统各类市场数据 |
backend/xhxtapi/client.py |
Python 客户端,封装登录、会话刷新、各类查询 |
backend/xhxtapi/apis.json |
接口配置(dsId、timeDeltaEnum、默认参数) |
backend/xhxtapi/daily_clearing_calculator.py |
日清分结算计算引擎,拉取日清分明细 + 日前统一电价,计算各市场结算结果。与 spot/api.py nlq_daily_clearing 共用同一套逻辑 |
| 端点 | 方法 | 作用 |
|---|---|---|
/query |
POST | 通用数据查询,自然语言 → 现货系统数据 |
/clearing |
POST | 日清分结算查询,支持按场站/交易员/分公司维度过滤 |
/health |
GET | 健康检查 |
/refresh |
POST | 手动刷新登录态 |
/clearing 日清分结算接收自然语言指令,自动解析场站/交易员/分公司维度,支持多种日期格式:
SpotStation 表动态解析场站名、交易员、分公司YYYY-MM-DD、YYYY-MM月、今天/昨天/最近N天示例指令:
查询贾赛交易员2026-06月日清分结算数据
查询新特1期场站昨天日清分总体结算
查询清能分公司最近3天中长期市场合计
| 文件 | 作用 |
|---|---|
backend/gwapi/power_forecast_client.py |
登录 xhxt.chng.com.cn,按 powerplant_id 获取近期准确率排名第一的短期预测电量(金风科技),支持多场站合并、24 点聚合 |
backend/gwapi/forecast_d2d4.py |
D+2~D+4 金风预测电量示例 |
注:预测发电量已改为使用 observation(实测电量)接口
get_observed_energy_for_powerplants,而非 forecast 接口。
backend/feishu/)| 文件 | 作用 |
|---|---|
feishu_handler.py |
飞书消息处理:解析群聊事件、提取查询指令、调用 xhxtapi、回复结果。支持上下文统计(平均值/最大值/最小值/总和/中位数) |
feishu_config.py |
飞书配置:凭证、Token 缓存、消息回复、在线表格 CRUD、群公告更新 |
pmos_notify.py |
PMOS 市场监测定时推送:每 5 分钟读取 pmos_data.xlsx,创建飞书在线表格并发送链接到群 |
start_all.py |
统一启动器:同时管理 xhxtapi、feishu bot、PMOS 推送的启停 |
feishu_bot.py |
飞书长连接模式(WebSocket),接收并分发事件 |
bot_server.py |
飞书事件回调 HTTP 服务器(事件接收、URL 验证) |
飞书群 @MLPT_AI 消息
│
▼
feishu_bot.py 接收事件
│
▼
feishu_handler.py.handle_message()
│
├── 日清分查询 → POST http://127.0.0.1:12345/clearing
│ │
│ ├── > 10 条 → 导出飞书在线表格(_send_clearing_sheet)
│ └── ≤ 10 条 → 文本回复
│
├── 统计查询 → 从上下文缓存计算 平均值/最大值/最小值/总和/中位数
│
└── 其他查询 → POST http://127.0.0.1:12345/query
│
├── 在线表格模式 → 创建飞书表格 + 图表
├── 图表模式 → 发送折线图
├── CSV 模式 → 发送文件
└── 默认 → 文本回复
pmos_notify.py 每 5 分钟(00, 05, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 各整分时刻):
backend/sider/pmos_data.xlsx 各标的日 SheetTARGET_CHAT_ID
backend/application/celery.py
sqlalchemy+mysql://...
django-db
django_celery_beat
backend/spot/tasks.py
run_price_prediction_rebuild:每天 10:00 训练模型并预测 D+1~D+4backfill_actual_prices:回填真实电价run_rolling_strategy_backtest:滚动策略回测backend/submit/)通过 Chrome DevTools Protocol (CDP) 连接已登录的 PMOS 平台,自动化完成滚动撮合交易的批量申报。根据合同对应的单个时段(SpotRollingContract.target_hour),自动在 PMOS 网页表单 #multipleTable 中填写电量、电价、买卖方向,并提交至平台。
1. 选择标的日期 Tab ── select_date_tab()
├─ 匹配 .el-tabs__item 文本中的日期
├─ 点击 Tab 后等待 networkidle + 验证 is-active 状态
└─ 验证 active tab 日期与目标日期一致
2. 选择交易单元 ── select_trading_unit()
└─ 通过 input / __vue__ 实例设置 Element UI el-select
3. 点击"批量上报" ── click_batch_report()
├─ 点击前验证当前 active tab 日期是否正确
├─ 检查按钮是否被禁用(11:00-15:00 限制)
└─ 等待弹窗 networkidle + 3s 渲染
4. 填写网页表单 ── fill_web_form()
├─ 第一步:勾选复选框 + 填写电量/电价输入框
└─ 第二步:设置买卖方向(td[6]/div 内的买入/卖出选项)
5. 提交申报 ── submit_declaration()
├─ dry_run=True → 跳过提交
└─ dry_run=False → 定位提交按钮(当前已禁用点击,需手动启用)
表格 ID:#multipleTable
| 列 (td 索引) | 说明 |
|---|---|
| td[1] (index=0) | 复选框(需勾选) |
| td[2] (index=1) | 时段文字,格式 段1(00:00-00:00)
|
| td[6] (index=5) | 买卖方向(第一个=买入,第二个=卖出) |
| td[7] (index=6) | 挂牌电量输入框 |
| td[8] (index=7) | 挂牌电价输入框 |
时段匹配:段N → hour = N - 1 → data_by_hour[str(N-1)],每个合同只提交其 target_hour 对应的单个时段。
sider 监控脚本(每 60 秒切换所有日期 Tab)和 submit 提交脚本共用同一个 Chrome CDP,通过文件锁 .cdp_submit.lock 协调:
run_full_flow 开始时创建锁文件,finally 中删除| 端点 | 方法 | 作用 |
|---|---|---|
/api/submit/pmos/{uuid:contract_id}/ |
POST | 单合同申报(传入 target_date, station_id, dry_run) |
/api/submit/pmos/batch/ |
POST | 批量申报(遍历指定日期所有 approved 合同) |
/api/submit/pmos/status/{uuid:contract_id}/ |
GET | 查询合同 PMOS 提交状态 |
/api/submit/pmos/cdp-status/ |
GET | 检查 Chrome CDP 连接状态 |
# settings.py
PMOS_CDP_ENDPOINT = 'http://localhost:9222'
PMOS_TARGET_URL_PATTERN = 'pmos.xj.sgcc.com.cn'
PMOS_FRAME_URL_PATTERN = 'pxf-trade-auction-extranet'
PMOS_TRADING_UNIT_ID = '' # 交易单元 ID
PMOS_TRADING_UNIT_NAME = '' # 交易单元名称
PMOS_MARKET_MEMBER_ID = '' # 市场主体 ID
PMOS_MARKET_MEMBER_NAME = '' # 市场主体名称
PMOS_MARKET_MEMBER_TYPE = '10' # 市场主体类型
| 文件 | 职责 |
|---|---|
backend/submit/pmos_submitter.py |
自动化核心逻辑(PMOSSubmitter 类,CDP 连接、Tab 切换、表单填写、提交) |
backend/submit/views.py |
REST API 视图(4 个端点,申报数据构建) |
backend/submit/urls.py |
Ninja Router 导出 |
backend/submit/README.md |
模块详细文档 |
defHttp
| 目录 | 作用 |
|---|---|
web/src/api/spot/ |
spot 业务 API 封装 |
web/src/views/spot/rolling/ |
滚动撮合页面(交易监控/日融合合同/标准策略信号/高级策略信号/策略配置/策略回测/偏差回收) |
web/src/views/spot/station/ |
场站管理页面(增删改查 + Excel 导入导出) |
web/src/views/spot/dashboard/ |
仪表盘 |
web/src/views/spot/utils/export.ts |
表格导出 Excel 工具 |
web/src/views/spot/rolling/rolling-theme.css |
滚动撮合模块科技蓝统一主题(设计令牌 + 工具类) |
web/src/components/CodeEditor/ |
代码编辑器组件(CodeMirror 6) |
web/src/components/Excel/ |
Excel 导入导出组件(ImpExcel / ExpExcelModal) |
backend/static_web/ |
Vite 构建直接输出目录,Django 静态文件服务根目录 |
web/src/views/spot/rolling/)| 文件 | 页面 | 功能 |
|---|---|---|
index.vue |
交易监控主页 | 共享筛选(场站/标的日期)+ Tabs 导航 |
monitor/index.vue |
交易监控 | 24 时点市场视图、PMOS 报价、持仓汇总 |
RollingAdjustment.vue |
日融合合同 | 滚动撮合合同管理(新建/审批/收益预测/策略推荐) |
StrategySignals.vue |
标准策略信号 | 标准模式 24 时点信号 + 统计卡片 + 应用交易 |
StrategySignalsAdvanced.vue |
高级策略信号 | 高级模式 24 时点信号(含 rolling_arb_core 7 信号) |
PythonRules.vue |
策略配置 | Python 规则脚本管理(CodeMirror 编辑器) |
StrategyBacktest.vue |
策略回测 | 历史回测任务管理 + 收益指标 + 图表 |
DeviationRecovery.vue |
偏差回收 | 偏差回收测算 + 24 时点明细 + 价格录入 |
rolling-theme.css 提供共享设计令牌和工具类,被 rolling/ 下所有页面通过 @import 引入:
--tech-primary(#2563eb)、--tech-text-secondary、--tech-radius-*、--tech-shadow-* 等.tech-stat-card(数据卡片)、.tech-form-label(表单标签)、.tech-toolbar(工具栏容器)、.tech-btn(按钮)等spot/ 页面的工具栏标签、统计卡片、表格表头、按钮均使用科技蓝风格Remove-Item -Recurse -Force node_modules/.vite; pnpm build --emptyOutDir
../backend/static_web,构建后无需手动拷贝ENV='win' 时将其加入 STATICFILES_DIRS
backend/application/urls.py 中的 serve_static_or_index 提供静态文件服务,并 SPA fallback 到 index.html
start_all.py 服务组合| 编号 | 服务 | 端口 | 说明 |
|---|---|---|---|
| 1 | Django | 8000 | Web/API |
| 2 | Web Dev | 5173 | Vite 开发服务器 |
| 3 | Celery Worker | - | 异步任务 |
| 4 | Celery Beat | - | 定时任务调度 |
| 5 | PMOS 监控 | - | 每60秒抓取 PMOS 买1/卖1数据(与 submit 共享 CDP,通过 .cdp_submit.lock 文件锁互斥) |
| 6 | Flask 大屏 | 5000 | PMOS 数据大屏(可选) |
| 7 | xhxtapi | 12345 | 现货系统数据查询 + 日清分计算 |
| 8 | 飞书机器人 | - | 长连接消息处理 |
| 9 | PMOS 推送 | - | 每5分钟推送在线表格到飞书群 |
预设组合:
core:1, 4, 5web:1, 2, 4, 5feishu:7, 8, 9all:1, 2, 3, 4, 5, 7, 8, 9注:根目录
start_all.py(交互式) 和backend/feishu/start_all.py(命令行) 是两套启动器, 前者管理全体服务,后者专注于 xhxtapi + feishu + PMOS 推送的组合。
注:以下为标准/高级模式的策略逻辑简化概述。实际实现以
backend/spot/python_defaults.py中DEFAULT_STANDARD_PYTHON_SCRIPT和DEFAULT_ADVANCED_PYTHON_SCRIPT为准, 逻辑比下文更复杂(含组合价差、强制机制修正、月度累计机制等)。
每个时段执行策略计算:
priority 升序执行规则链;decide_action(...) 结果;target_qty 用于计算新合同量、均价、收益。SpotStation.mechanism_ratio 表示机制电量占比(0~1)。
standard 信号模式下,默认脚本按当前小时/日的预测发电量计算机制边界:
mechanism_floor = max(0.0, sell_qty_gen_ratio_min - mechanism_ratio) # 默认 sell_qty_gen_ratio_min=0.8
mechanism_ceiling = max(0.0, sell_qty_gen_ratio_max - mechanism_ratio) # 默认 sell_qty_gen_ratio_max=1.2
position_floor = mechanism_floor * predicted_generation
position_ceiling = mechanism_ceiling * predicted_generation
< mechanism_floor 或 > mechanism_ceiling 时,视为纳入机制。mechanism_ratio=0.3 时,下界为 0.5,上界为 0.9;mechanism_ratio=0.9 时,下界为 0,上界为 0.3。高级/CAR 模式:月度累计机制规则见
docs/plans/trading.md,当前尚未开发完成。README 中仅记录已落地的标准模式。
自定义脚本应读取 market['mechanism_ratio'],并按当前模式选择日/时或月度累计口径应用边界逻辑。标准模式默认脚本已实现上述机制 guard。
默认脚本:DEFAULT_STANDARD_PYTHON_SCRIPT
核心信号(默认 direction_signal_source='market_spread'):
predicted_ref_price = slove_price 或 official_price(由 reference_price_source 决定)
contract_spread = contract_price - predicted_ref_price
market_spread = pmos_latest_price - predicted_ref_price
combined_spread = contract_spread + market_spread_weight * market_spread
关键:
pmos_latest_price为空/"-"/None 时视为 0,此时market_spread = 0 - predicted_ref_price = -predicted_ref_price(负值 → 触发买入)。
方向信号由 direction_signal_source 选择(默认 market_spread):
direction_signal < buy_threshold(默认 -10,即市场价差低于 -10 元才触发:PMOS 最新成交价明显低于预测结算价)→ BUY,买入量 = contract_qty(买回至零仓位),交易价取 pmos_sell_price
direction_signal > sell_threshold(默认 15,即市场价差高于 15 元才触发:PMOS 最新成交价明显高于预测结算价)→ 比较实际仓位与偏差考核目标 predicted_generation * (0.8 - mechanism_ratio):
SELL 补齐,交易价取 pmos_buy_price
BUY 压降,交易价取 pmos_sell_price
-10 ≤ direction_signal ≤ 15 → HOLD(误差带死区,过滤预测不准的小波动)强制偏差考核修正(在方向信号判断之前执行,最高优先级):
actual_qty < position_floor → 强制 SELL 补齐至偏差考核下界 mechanism_floor * predicted_generation
actual_qty > position_ceiling → 强制 BUY 压降至偏差考核上界 mechanism_ceiling * predicted_generation
默认脚本:DEFAULT_ADVANCED_PYTHON_SCRIPT
依赖 rolling_arb_core 生成的 7 个原始信号(info_gap、re_surprise、momentum、market_spread、price_volatility、forecast_deviation、thermal_quantity),
在脚本内按可配置权重合成 composite(综合信号),再通过 tanh(composite * k) 映射为目标仓位比例。
核心逻辑:
abs(composite) < min_signal_strength → HOLD
composite > 0(看多)且 pmos_buy_price 可用 → SELL,卖出量 = min(qty_ratio * max_sell_qty, position_ceiling - month_contract_qty, pmos_buy_qty)
composite < 0(看空)且 pmos_sell_price 可用 → BUY,买入量 = min(qty_ratio * max_buy_qty, month_contract_qty - position_floor, pmos_sell_qty)
month_contract_qty 为基准,约束在 [position_floor, position_ceiling]
注:引擎方向与现货交易方向相反(看多→卖出增仓,看空→买入减仓)。
rule_type='post_override' 读取上一条结果,只修改特定字段。例如默认覆盖模板会把 BUY 的 target_qty 最低提升到 max(0, 0.8 - mechanism_ratio) * predicted_generation。
| 参数 | 默认值 | 含义 |
|---|---|---|
reference_price_source |
'slove' |
参考电价来源 |
sell_qty_gen_ratio_min |
0.8 |
偏差考核下界基数,实际下界 = max(0, 0.8 - mechanism_ratio)
|
sell_qty_gen_ratio_max |
1.2 |
偏差考核上界基数,实际上界 = max(0, 1.2 - mechanism_ratio)
|
market_spread_weight |
1.0 |
市场价差在组合价差中的权重(仅当 direction_signal_source='combined_spread' 时生效) |
min_signal_strength |
0.0 |
advanced 最小信号强度 |
max_position |
0.5 |
advanced 最大目标仓位比例 |
tanh_k |
3.0 |
advanced tanh 压缩系数 |
mechanism_scope |
'daily' |
advanced 机制范围: daily / monthly
|
| 误区 | 实际逻辑 |
|---|---|
| 市场价差 < 0 就买 | 买入量 = contract_qty(买回至零仓位),不是依据价差比例计算 |
| 市场价差 > 0 就卖 | 仅在实际仓位低于偏差考核目标 predicted_generation * (0.8 - mechanism_ratio) 时卖出补齐 |
| 最新成交价为空就不交易 | 空值/"-"视为 0,价差 = 0 - predicted_ref_price = -predicted_ref_price(负值仍触发买入) |
| 最新成交价 = 买1卖1均价 | 最新成交价是 PMOS 的"最新价"字段;(买1价+卖1价)/2 是推送飞书表格里的"成交价"列,仅用于展示 |
卖出量 = (预测发电量 - 合同量) * 0.8
|
卖出量 = 偏差考核目标 predicted_generation * (0.8 - mechanism_ratio) - actual_qty,不乘比例 |
| 机制比例只影响标准模式 | Advanced 模式也有月度累计机制约束 [position_floor, position_ceiling]
|
实现"市场价差 < 0 买入(最新成交价为空视为 0);价差 > 0 按偏差考核目标仓位调整":
DEFAULT_PARAMS = {
'reference_price_source': 'slove',
'sell_qty_gen_ratio_min': 0.8,
'sell_qty_gen_ratio_max': 1.2,
'market_spread_weight': 1.0,
}
def to_number(v, default_val=0):
if v is None:
return default_val
try:
return float(v)
except Exception:
return default_val
def decide_action(market, config, hour, target_date, station_id, station_code, default_action):
params = dict(DEFAULT_PARAMS)
params.update(config.get('rule_params', {}) or {})
predicted_ref_price = market.get('slove_price')
if params['reference_price_source'] == 'official':
predicted_ref_price = market.get('official_price')
predicted_ref_price = to_number(predicted_ref_price, None)
if predicted_ref_price is None:
return {'action': 'HOLD', 'target_qty': 0, 'trade_price': None,
'reason': '预测参考电价缺失,持有不动'}
contract_qty = to_number(market.get('contract_qty'), 0)
actual_qty = to_number(market.get('actual_qty'), 0)
predicted_generation = to_number(market.get('predicted_generation'), 0)
mechanism_ratio = to_number(market.get('mechanism_ratio'), 0)
pmos_buy_price = market.get('pmos_buy_price')
pmos_sell_price = market.get('pmos_sell_price')
pmos_latest_price = market.get('pmos_latest_price')
mechanism_floor = max(0.0, params['sell_qty_gen_ratio_min'] - mechanism_ratio)
mechanism_ceiling = max(0.0, params['sell_qty_gen_ratio_max'] - mechanism_ratio)
position_floor = mechanism_floor * predicted_generation
position_ceiling = mechanism_ceiling * predicted_generation
# 偏差考核强制修正:仓位越界先修正
if actual_qty < position_floor and pmos_buy_price is not None:
qty = position_floor - actual_qty
return {'action': 'SELL', 'target_qty': qty, 'trade_price': pmos_buy_price,
'reason': '合同电量低于偏差考核下界(预测发电量%.1fMW×%.2f倍),强制卖出%.3fMW补齐' % (predicted_generation, mechanism_floor, qty)}
if actual_qty > position_ceiling and pmos_sell_price is not None:
qty = actual_qty - position_ceiling
return {'action': 'BUY', 'target_qty': qty, 'trade_price': pmos_sell_price,
'reason': '合同电量高于偏差考核上界(预测发电量%.1fMW×%.2f倍),强制买回%.3fMW压降' % (predicted_generation, mechanism_ceiling, qty)}
# 市场价差信号:最新成交价为空/"-"时视为 0
latest_val = to_number(pmos_latest_price, 0)
market_spread = latest_val - predicted_ref_price
if market_spread < 0 and pmos_sell_price is not None:
return {'action': 'BUY', 'target_qty': contract_qty, 'trade_price': pmos_sell_price,
'reason': '市场价差%.2f<0(最新成交价%.2f-预测参考价%.2f),买入%.2fMW' % (market_spread, latest_val, predicted_ref_price, contract_qty)}
if market_spread > 0 and pmos_buy_price is not None:
target = predicted_generation * (0.8 - mechanism_ratio)
if actual_qty < target:
qty = target - actual_qty
return {'action': 'SELL', 'target_qty': qty, 'trade_price': pmos_buy_price,
'reason': '市场价差%.2f>0,仓位低于偏差考核目标%.3f,卖出%.3fMW补齐' % (market_spread, target, qty)}
return {'action': 'HOLD', 'target_qty': 0, 'trade_price': None,
'reason': '市场价差%.2f,持有不动' % market_spread}
_result = decide_action(market, config, hour, target_date, station_id, station_code, default_action)
SpotStation 字段| 字段 | 类型 | 说明 |
|---|---|---|
name |
CharField(100) | 场站名称 |
code |
CharField(50) | 外部 API 机组 ID(唯一) |
capacity |
DecimalField | 装机容量(MW) |
province / city
|
CharField | 省份 / 城市 |
mechanism_ratio |
DecimalField(0~1) | 机制比例,用于计算偏差回收下限 |
station_type |
CharField | 场站类型:wind(风电) / solar(光伏) |
branch_company |
CharField | 分公司(清能公司/南疆公司/东疆公司等),用于数据权限隔离 |
trader |
CharField | 交易员姓名,用于数据权限隔离 |
unit_ids |
JSONField | 外部 API 机组 ID 列表(支持一站多机) |
generation_mapping |
JSONField | 预测电量场站映射:[(powerplant_id, ratio), ...]
|
status |
CharField | 状态:active(启用) / inactive(停用) |
exportTableToExcel() 按表格列定义导出当前数据POST /api/spot/stations/import,按 code 字段匹配 upsert(已存在则更新,不存在则新建)
{ total, created, updated, errors },逐行容错基于 system_user.data_scope 字段实现数据隔离:
| data_scope | 可见范围 |
|---|---|
all |
全部场站(超管) |
branch |
本分公司场站(按 branch_company 过滤) |
trader |
本人负责场站(按 trader 过滤) |
_filter_stations_by_user_scope() 和 _get_user_allowed_station_ids() 在 API 层统一过滤。