deeptrade-quant 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deeptrade/__init__.py +8 -0
- deeptrade/channels_builtin/__init__.py +0 -0
- deeptrade/channels_builtin/stdout/__init__.py +0 -0
- deeptrade/channels_builtin/stdout/deeptrade_plugin.yaml +25 -0
- deeptrade/channels_builtin/stdout/migrations/20260429_001_init.sql +13 -0
- deeptrade/channels_builtin/stdout/stdout_channel/__init__.py +0 -0
- deeptrade/channels_builtin/stdout/stdout_channel/channel.py +180 -0
- deeptrade/cli.py +214 -0
- deeptrade/cli_config.py +396 -0
- deeptrade/cli_data.py +33 -0
- deeptrade/cli_plugin.py +176 -0
- deeptrade/core/__init__.py +8 -0
- deeptrade/core/config.py +344 -0
- deeptrade/core/config_migrations.py +138 -0
- deeptrade/core/db.py +176 -0
- deeptrade/core/llm_client.py +591 -0
- deeptrade/core/llm_manager.py +174 -0
- deeptrade/core/logging_config.py +61 -0
- deeptrade/core/migrations/__init__.py +0 -0
- deeptrade/core/migrations/core/20260427_001_init.sql +121 -0
- deeptrade/core/migrations/core/20260501_002_drop_llm_calls_stage.sql +10 -0
- deeptrade/core/migrations/core/__init__.py +0 -0
- deeptrade/core/notifier.py +302 -0
- deeptrade/core/paths.py +49 -0
- deeptrade/core/plugin_manager.py +616 -0
- deeptrade/core/run_status.py +29 -0
- deeptrade/core/secrets.py +152 -0
- deeptrade/core/tushare_client.py +824 -0
- deeptrade/plugins_api/__init__.py +44 -0
- deeptrade/plugins_api/base.py +66 -0
- deeptrade/plugins_api/channel.py +42 -0
- deeptrade/plugins_api/events.py +61 -0
- deeptrade/plugins_api/llm.py +46 -0
- deeptrade/plugins_api/metadata.py +84 -0
- deeptrade/plugins_api/notify.py +67 -0
- deeptrade/strategies_builtin/__init__.py +0 -0
- deeptrade/strategies_builtin/limit_up_board/__init__.py +0 -0
- deeptrade/strategies_builtin/limit_up_board/deeptrade_plugin.yaml +101 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/__init__.py +0 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/calendar.py +65 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/cli.py +269 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/config.py +76 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/data.py +1191 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/pipeline.py +869 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/plugin.py +30 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/profiles.py +85 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/prompts.py +485 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/render.py +890 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/runner.py +1087 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/runtime.py +172 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/schemas.py +178 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260430_001_init.sql +150 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260501_002_lub_stage_results_llm_provider.sql +8 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_001_lub_lhb_tables.sql +36 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_002_lub_cyq_perf.sql +18 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_003_lub_lhb_pk_fix.sql +46 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_004_lub_lhb_drop_pk.sql +53 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_005_lub_config.sql +17 -0
- deeptrade/strategies_builtin/volume_anomaly/__init__.py +0 -0
- deeptrade/strategies_builtin/volume_anomaly/deeptrade_plugin.yaml +59 -0
- deeptrade/strategies_builtin/volume_anomaly/migrations/20260430_001_init.sql +94 -0
- deeptrade/strategies_builtin/volume_anomaly/migrations/20260601_001_realized_returns.sql +44 -0
- deeptrade/strategies_builtin/volume_anomaly/migrations/20260601_002_dimension_scores.sql +13 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/__init__.py +0 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/calendar.py +52 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/cli.py +247 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/data.py +2154 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/pipeline.py +327 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/plugin.py +22 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/profiles.py +49 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/prompts.py +187 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/prompts_examples.py +84 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/render.py +906 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/runner.py +772 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/runtime.py +90 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/schemas.py +97 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/stats.py +174 -0
- deeptrade/theme.py +48 -0
- deeptrade_quant-0.0.2.dist-info/METADATA +166 -0
- deeptrade_quant-0.0.2.dist-info/RECORD +83 -0
- deeptrade_quant-0.0.2.dist-info/WHEEL +4 -0
- deeptrade_quant-0.0.2.dist-info/entry_points.txt +2 -0
- deeptrade_quant-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""LimitUpBoardPlugin — Plugin Protocol entry for the打板策略.
|
|
2
|
+
|
|
3
|
+
Satisfies the framework's minimal :class:`deeptrade.plugins_api.Plugin`
|
|
4
|
+
contract: ``metadata`` + ``validate_static`` + ``dispatch``.
|
|
5
|
+
|
|
6
|
+
Everything else (run lifecycle, history, report, tushare/llm wiring) lives
|
|
7
|
+
inside the plugin: see ``cli.py`` and ``runner.py``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from . import cli as _cli
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
17
|
+
from deeptrade.plugins_api.base import PluginContext
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LimitUpBoardPlugin:
|
|
21
|
+
"""Framework entry class for the limit-up-board plugin."""
|
|
22
|
+
|
|
23
|
+
metadata = None # injected by framework after install
|
|
24
|
+
|
|
25
|
+
def validate_static(self, ctx: PluginContext) -> None: # noqa: ARG002
|
|
26
|
+
# No network. Light import-only sanity check.
|
|
27
|
+
from . import schemas # noqa: F401, PLC0415
|
|
28
|
+
|
|
29
|
+
def dispatch(self, argv: list[str]) -> int:
|
|
30
|
+
return _cli.main(argv)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""LLM stage profile presets for limit-up-board.
|
|
2
|
+
|
|
3
|
+
v0.7 — stage 调参档归插件维护。preset 名(``fast/balanced/quality``)仍是
|
|
4
|
+
框架级用户配置(``app.profile``),但每个 preset 在本插件三个 stage 上的
|
|
5
|
+
具体 tuning 由本模块持有。
|
|
6
|
+
|
|
7
|
+
Preset → stage tuning 表沿用 v0.6 ``PROFILES_DEFAULT`` 的语义:
|
|
8
|
+
* fast — 全程关 thinking,低成本
|
|
9
|
+
* balanced — R1 关 thinking,R2 / final_ranking 开
|
|
10
|
+
* quality — 全程开 thinking
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from deeptrade.plugins_api import StageProfile
|
|
16
|
+
|
|
17
|
+
STAGE_R1 = "strong_target_analysis"
|
|
18
|
+
STAGE_R2 = "continuation_prediction"
|
|
19
|
+
STAGE_FINAL = "final_ranking"
|
|
20
|
+
STAGE_R3 = "continuation_revision"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
PROFILES: dict[str, dict[str, StageProfile]] = {
|
|
24
|
+
"fast": {
|
|
25
|
+
STAGE_R1: StageProfile(
|
|
26
|
+
thinking=False, reasoning_effort="medium", temperature=0.1, max_output_tokens=32768
|
|
27
|
+
),
|
|
28
|
+
STAGE_R2: StageProfile(
|
|
29
|
+
thinking=False, reasoning_effort="medium", temperature=0.2, max_output_tokens=32768
|
|
30
|
+
),
|
|
31
|
+
STAGE_FINAL: StageProfile(
|
|
32
|
+
thinking=False, reasoning_effort="medium", temperature=0.0, max_output_tokens=8192
|
|
33
|
+
),
|
|
34
|
+
STAGE_R3: StageProfile(
|
|
35
|
+
thinking=False, reasoning_effort="medium", temperature=0.2, max_output_tokens=32768
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
"balanced": {
|
|
39
|
+
STAGE_R1: StageProfile(
|
|
40
|
+
thinking=False, reasoning_effort="medium", temperature=0.1, max_output_tokens=32768
|
|
41
|
+
),
|
|
42
|
+
STAGE_R2: StageProfile(
|
|
43
|
+
thinking=True, reasoning_effort="high", temperature=0.2, max_output_tokens=32768
|
|
44
|
+
),
|
|
45
|
+
STAGE_FINAL: StageProfile(
|
|
46
|
+
thinking=True, reasoning_effort="high", temperature=0.0, max_output_tokens=8192
|
|
47
|
+
),
|
|
48
|
+
STAGE_R3: StageProfile(
|
|
49
|
+
thinking=True, reasoning_effort="high", temperature=0.2, max_output_tokens=32768
|
|
50
|
+
),
|
|
51
|
+
},
|
|
52
|
+
"quality": {
|
|
53
|
+
STAGE_R1: StageProfile(
|
|
54
|
+
thinking=True, reasoning_effort="high", temperature=0.2, max_output_tokens=32768
|
|
55
|
+
),
|
|
56
|
+
STAGE_R2: StageProfile(
|
|
57
|
+
thinking=True, reasoning_effort="high", temperature=0.2, max_output_tokens=32768
|
|
58
|
+
),
|
|
59
|
+
STAGE_FINAL: StageProfile(
|
|
60
|
+
thinking=True, reasoning_effort="high", temperature=0.0, max_output_tokens=8192
|
|
61
|
+
),
|
|
62
|
+
STAGE_R3: StageProfile(
|
|
63
|
+
thinking=True, reasoning_effort="high", temperature=0.2, max_output_tokens=32768
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def resolve_profile(preset: str, stage: str) -> StageProfile:
|
|
70
|
+
"""Look up the StageProfile for ``preset × stage``.
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
KeyError — unknown preset or stage name (caller decides how to surface).
|
|
74
|
+
"""
|
|
75
|
+
if preset not in PROFILES:
|
|
76
|
+
raise KeyError(
|
|
77
|
+
f"unknown profile preset {preset!r}; expected one of {sorted(PROFILES)}"
|
|
78
|
+
)
|
|
79
|
+
table = PROFILES[preset]
|
|
80
|
+
if stage not in table:
|
|
81
|
+
raise KeyError(
|
|
82
|
+
f"unknown stage {stage!r} for preset {preset!r}; "
|
|
83
|
+
f"expected one of {sorted(table)}"
|
|
84
|
+
)
|
|
85
|
+
return table[stage]
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
"""Prompt templates for limit-up-board LLM stages.
|
|
2
|
+
|
|
3
|
+
The wording follows DESIGN §12.4.3 / §12.5.5 / §12.5.4 with v0.3.1 fixes:
|
|
4
|
+
F2 — sector_strength_source rendered into prompt
|
|
5
|
+
F5 — explicit length caps on rationale / evidence / risk_flags
|
|
6
|
+
M3 — system prompts forbid external info
|
|
7
|
+
M4 — separate final_ranking template
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# R1: strong target analysis
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
R1_SYSTEM = """\
|
|
20
|
+
你是一个 A 股打板策略研究助手。你只能基于本次消息中提供的结构化数据进行分析。
|
|
21
|
+
|
|
22
|
+
【硬性纪律】
|
|
23
|
+
1. 严禁使用外部搜索、新闻网站、公告网站、实时行情、社交媒体、机构观点或任何未提供的数据。
|
|
24
|
+
2. 严禁编造新闻、公告、盘口、传闻、龙虎榜席位(除非数据中明确提供)、资金分歧、ETF 申赎流向。
|
|
25
|
+
3. 如果某字段缺失(出现在 data_unavailable 中),必须在该候选股的 missing_data 列出,禁止猜测或虚构。
|
|
26
|
+
4. 本批次中的每一只候选股都必须出现在 candidates 数组中,且 candidate_id 与输入完全一致。
|
|
27
|
+
5. 仅输出 JSON,不要 Markdown 代码块包裹,不要解释性前后缀。
|
|
28
|
+
|
|
29
|
+
【任务】
|
|
30
|
+
对本批次涨停候选股进行"强势标的分析",判断其是否具备进入下一轮"连板预测"的资格。
|
|
31
|
+
|
|
32
|
+
【分析维度】
|
|
33
|
+
- 封板强度:first_time / last_time / open_times / fd_amount_yi / limit_amount_yi / fd_amount_ratio(封单/成交额,>10% 为强势封板)
|
|
34
|
+
- 板块强度:参考下方【板块强度摘要】(注意 sector_strength_source;可信度 limit_cpt_list > lu_desc_aggregation > industry_fallback)
|
|
35
|
+
- 梯队地位:limit_times / up_stat
|
|
36
|
+
- 量价:pct_chg / amount_yi / turnover_ratio / amplitude_pct(振幅过大警惕分歧炸板)
|
|
37
|
+
- 形态:ma5 / ma10 / ma20 / ma_bull_aligned(多头排列时增强)
|
|
38
|
+
- 历史基因:up_count_30d(近 30 日涨停次数)/ up_stat
|
|
39
|
+
- 市场情绪:参考下方【市场摘要】中 limit_step_trend / yesterday_failure_rate / yesterday_winners_today
|
|
40
|
+
- 风险:是否一字板 / 过度连板 / 题材孤立 / 缺数据
|
|
41
|
+
|
|
42
|
+
【evidence 要求】
|
|
43
|
+
每个候选股至少给出 1 条、至多 4 条 evidence;每条必须引用真实出现在输入中的字段名 (`field`),并填上对应数值 (`value`)、单位 (`unit`) 和你的解读 (`interpretation`)。
|
|
44
|
+
任何无法用输入字段佐证的 rationale 都视为幻觉。
|
|
45
|
+
当 candidate 的 missing_data 包含某字段时,evidence 中**不得**引用该字段。
|
|
46
|
+
rationale 不超过 80 字(输出截断会触发 JSON 失败)。
|
|
47
|
+
|
|
48
|
+
【输出格式】(严格按照此 JSON Schema 输出;不要省略任何字段,不要新增字段)
|
|
49
|
+
{
|
|
50
|
+
"stage": "strong_target_analysis",
|
|
51
|
+
"trade_date": "<原样回传输入中的 trade_date>",
|
|
52
|
+
"batch_no": <原样回传输入中的 batch_no>,
|
|
53
|
+
"batch_total": <原样回传输入中的 batch_total>,
|
|
54
|
+
"batch_summary": "<本批整体观察 ≤ 80 字>",
|
|
55
|
+
"candidates": [
|
|
56
|
+
{
|
|
57
|
+
"candidate_id": "<原样回传输入中的 candidate_id>",
|
|
58
|
+
"ts_code": "<原样回传,含 .SH/.SZ 后缀,如 600519.SH>",
|
|
59
|
+
"name": "<原样回传输入中的股票名称>",
|
|
60
|
+
"selected": true,
|
|
61
|
+
"score": 0,
|
|
62
|
+
"strength_level": "high",
|
|
63
|
+
"rationale": "<≤ 80 字的核心判断>",
|
|
64
|
+
"evidence": [
|
|
65
|
+
{
|
|
66
|
+
"field": "<必须是输入字段名,如 fd_amount_yi / first_time / up_stat>",
|
|
67
|
+
"value": 0,
|
|
68
|
+
"unit": "<亿/万/%/次/秒/无>",
|
|
69
|
+
"interpretation": "<对该数值的简短解读>"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"risk_flags": [],
|
|
73
|
+
"missing_data": []
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
【字段值约束】
|
|
79
|
+
- selected: true 或 false(true 表示进入下一轮)
|
|
80
|
+
- score: 0–100 的浮点数
|
|
81
|
+
- strength_level: 必须是 "high" / "medium" / "low" 三选一
|
|
82
|
+
- evidence: 每只 1–4 条,每条 4 个字段不可省
|
|
83
|
+
- risk_flags: 空数组或字符串数组,最多 5 条
|
|
84
|
+
- missing_data: 数据缺失字段名数组(参见 data_unavailable)
|
|
85
|
+
- 本批每只候选股都必须出现在 candidates 中,candidate_id 与输入完全一致,不可漏不可加。
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def r1_user_prompt(
|
|
90
|
+
*,
|
|
91
|
+
trade_date: str,
|
|
92
|
+
batch_no: int,
|
|
93
|
+
batch_total: int,
|
|
94
|
+
candidates: list[dict[str, Any]],
|
|
95
|
+
market_summary: dict[str, Any],
|
|
96
|
+
sector_strength_source: str,
|
|
97
|
+
sector_strength_data: dict[str, Any],
|
|
98
|
+
data_unavailable: list[str],
|
|
99
|
+
) -> str:
|
|
100
|
+
"""Render the R1 user prompt for one batch."""
|
|
101
|
+
return _render_user(
|
|
102
|
+
title=f"trade_date = {trade_date}\nbatch_no = {batch_no}\nbatch_total= {batch_total}",
|
|
103
|
+
n=len(candidates),
|
|
104
|
+
market_summary=market_summary,
|
|
105
|
+
sector_strength_source=sector_strength_source,
|
|
106
|
+
sector_strength_data=sector_strength_data,
|
|
107
|
+
candidates=candidates,
|
|
108
|
+
data_unavailable=data_unavailable,
|
|
109
|
+
instruction=(
|
|
110
|
+
"请对本批次每一只候选股输出 StrongCandidate;candidate_id 与输入一一对应;"
|
|
111
|
+
"selected=true 表示进入下一轮;rationale ≤ 80 字。"
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
# R2: continuation prediction
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
R2_SYSTEM = """\
|
|
121
|
+
你是一个 A 股打板策略研究助手,正在执行第二轮"连板预测"。
|
|
122
|
+
|
|
123
|
+
【硬性纪律】(与第一轮一致)
|
|
124
|
+
1. 严禁使用外部搜索或任何未提供的数据。
|
|
125
|
+
2. 严禁编造盘口、龙虎榜席位(除非输入中明确提供)、消息面、传闻、ETF 申赎流向。
|
|
126
|
+
3. 输入清单中的每一只标的都必须出现在 candidates 数组中,candidate_id 原样回传。
|
|
127
|
+
4. 信息不足时,只能降低 confidence 并在 missing_data 列出缺失字段,禁止猜测。
|
|
128
|
+
5. 仅输出 JSON。
|
|
129
|
+
|
|
130
|
+
【判断重点】
|
|
131
|
+
- 是否处于主线强势板块(参考输入【板块强度摘要】section;sector_strength_source 越靠 limit_cpt_list 越权威)。
|
|
132
|
+
- 是否为板块龙头或具备空间板地位(参考 limit_step 全市场最高连板数)。
|
|
133
|
+
- 封板质量是否支持次日溢价 (fd_amount_yi、fd_amount_ratio、open_times、first_time)。
|
|
134
|
+
- 资金近 5 日是否持续确认。
|
|
135
|
+
- 风险:高位加速 / 连续一字 / 流动性不足。
|
|
136
|
+
- 市场亏钱效应(market_summary.yesterday_failure_rate.interpretation == 'high')下,
|
|
137
|
+
所有 confidence 自动下调一档(high → medium,medium → low),rationale 需明示。
|
|
138
|
+
- 涨停梯队拉升(market_summary.limit_step_trend.interpretation == 'spectrum_lifting')下,
|
|
139
|
+
最高板地位的标的可适度上调 continuation_score;score 仍受 0–100 上限约束。
|
|
140
|
+
- 不允许引用 missing_data 中的字段;可引用所有派生字段
|
|
141
|
+
(amplitude_pct / fd_amount_ratio / ma_* / up_count_30d)。
|
|
142
|
+
- 筹码维度(参考候选行 cyq_winner_pct / cyq_top10_concentration /
|
|
143
|
+
cyq_avg_cost_yuan / cyq_close_to_avg_cost_pct):
|
|
144
|
+
· cyq_winner_pct > 70% 视为"获利盘抛压重",下调 confidence;
|
|
145
|
+
cyq_close_to_avg_cost_pct < -10% 视为"严重套牢盘解套",谨慎评估;
|
|
146
|
+
cyq_top10_concentration > 60% 视为"筹码高度集中",可作为正面 evidence。
|
|
147
|
+
· 仅当数据存在时引用;missing_data 中的字段不得引用、不得编造结论。
|
|
148
|
+
- 龙虎榜(参考候选行 lhb_net_buy_yi / lhb_inst_count / lhb_famous_seats_count / lhb_famous_seats_text):
|
|
149
|
+
· lhb_* 全部为 null 表示"该股未上龙虎榜"——这是合法事实,不视为数据缺失,
|
|
150
|
+
rationale 可以说"未触发龙虎榜异动"。
|
|
151
|
+
· lhb_famous_seats_count > 0 且 lhb_net_buy_yi > 0 时,可作为"游资认可"的正面 evidence;
|
|
152
|
+
lhb_net_buy_yi < 0 时不得作为正面 evidence(即便 famous_seats_count > 0 也只能作为
|
|
153
|
+
中性或负面信号)。
|
|
154
|
+
· lhb_famous_seats_text 是分号分隔的席位名称合并字符串,仅可在 interpretation 中
|
|
155
|
+
照抄原文片段;不可推断"哪一位游资"或具体身份。
|
|
156
|
+
· 作为 key_evidence 引用时,field 用 lhb_famous_seats_count(value 填整数席位数)
|
|
157
|
+
或 lhb_famous_seats_text(value 填字符串原文),严禁把席位列表当数组写入 value。
|
|
158
|
+
|
|
159
|
+
【输出语义】
|
|
160
|
+
- continuation_score (0-100) 仅是模型内部排序分。
|
|
161
|
+
- prediction ∈ {top_candidate, watchlist, avoid}.
|
|
162
|
+
- rationale ≤ 200 字。
|
|
163
|
+
|
|
164
|
+
【输出格式】(严格按照此 JSON Schema 输出;不要省略任何字段,不要新增字段)
|
|
165
|
+
{
|
|
166
|
+
"stage": "limit_up_continuation_prediction",
|
|
167
|
+
"trade_date": "<原样回传输入中的 trade_date>",
|
|
168
|
+
"next_trade_date": "<原样回传输入中的 next_trade_date>",
|
|
169
|
+
"market_context_summary": "<整体市场背景 ≤ 100 字>",
|
|
170
|
+
"risk_disclaimer": "<风险提示 ≤ 80 字>",
|
|
171
|
+
"candidates": [
|
|
172
|
+
{
|
|
173
|
+
"candidate_id": "<原样回传>",
|
|
174
|
+
"ts_code": "<原样回传,含 .SH/.SZ>",
|
|
175
|
+
"name": "<原样回传>",
|
|
176
|
+
"rank": 1,
|
|
177
|
+
"continuation_score": 0,
|
|
178
|
+
"confidence": "high",
|
|
179
|
+
"prediction": "top_candidate",
|
|
180
|
+
"rationale": "<≤ 200 字的预测理由>",
|
|
181
|
+
"key_evidence": [
|
|
182
|
+
{
|
|
183
|
+
"field": "<输入字段名>",
|
|
184
|
+
"value": 0,
|
|
185
|
+
"unit": "<亿/万/%/次/秒/无>",
|
|
186
|
+
"interpretation": "<对该数值的简短解读>"
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
"next_day_watch_points": ["<次日需要观察的 1-4 个关键点>"],
|
|
190
|
+
"failure_triggers": ["<会让预测失效的 1-4 个触发条件>"],
|
|
191
|
+
"missing_data": []
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
【字段值约束】
|
|
197
|
+
- rank: 本批内 1..N 连续唯一整数(不可重复、不可跳号)
|
|
198
|
+
- continuation_score: 0–100 浮点数(模型内部排序分)
|
|
199
|
+
- confidence: "high" / "medium" / "low" 三选一
|
|
200
|
+
- prediction: "top_candidate" / "watchlist" / "avoid" 三选一
|
|
201
|
+
- key_evidence: 每只 1–5 条;每条 value 必须是标量(字符串/整数/浮点数/null),
|
|
202
|
+
严禁填入数组或对象——若需引用 list 类输入字段,请改用其同名
|
|
203
|
+
_count(条数)或 _text(合并字符串)的标量伴生字段。
|
|
204
|
+
- next_day_watch_points / failure_triggers: 各 1–4 条字符串数组(不可为空)
|
|
205
|
+
- 输入清单中的每一只标的都必须出现在 candidates 中,candidate_id 与输入完全一致。
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def r2_user_prompt(
|
|
210
|
+
*,
|
|
211
|
+
trade_date: str,
|
|
212
|
+
next_trade_date: str,
|
|
213
|
+
candidates: list[dict[str, Any]],
|
|
214
|
+
market_context: dict[str, Any],
|
|
215
|
+
sector_strength_source: str,
|
|
216
|
+
sector_strength_data: dict[str, Any],
|
|
217
|
+
data_unavailable: list[str],
|
|
218
|
+
) -> str:
|
|
219
|
+
return _render_user(
|
|
220
|
+
title=(f"trade_date = {trade_date}\nnext_trade_date= {next_trade_date}"),
|
|
221
|
+
n=len(candidates),
|
|
222
|
+
market_summary=market_context,
|
|
223
|
+
sector_strength_source=sector_strength_source,
|
|
224
|
+
sector_strength_data=sector_strength_data,
|
|
225
|
+
candidates=candidates,
|
|
226
|
+
data_unavailable=data_unavailable,
|
|
227
|
+
instruction=("请对每一只标的输出 ContinuationCandidate;rank 在本批内唯一且 1..N 连续。"),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ---------------------------------------------------------------------------
|
|
232
|
+
# Final ranking (only when R2 multi-batch)
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
FINAL_RANKING_SYSTEM = """\
|
|
236
|
+
你是一个 A 股打板策略的全局排名助手。
|
|
237
|
+
|
|
238
|
+
【硬性纪律】
|
|
239
|
+
1. 严禁引入新事实;仅基于下方 finalists 的摘要 + 市场环境进行重排。
|
|
240
|
+
2. 不允许引用任何输入数据之外的信息。
|
|
241
|
+
3. final_rank 必须是 1..N 的连续置换。
|
|
242
|
+
4. delta_vs_batch ∈ {upgraded, kept, downgraded},相对该候选在批内的 prediction 给出。
|
|
243
|
+
5. reason_vs_peers ≤ 200 字。
|
|
244
|
+
6. 仅输出 JSON。
|
|
245
|
+
|
|
246
|
+
【输出格式】(严格按照此 JSON Schema 输出;不要省略任何字段,不要新增字段)
|
|
247
|
+
{
|
|
248
|
+
"stage": "final_ranking",
|
|
249
|
+
"trade_date": "<原样回传输入中的 trade_date>",
|
|
250
|
+
"next_trade_date": "<原样回传输入中的 next_trade_date>",
|
|
251
|
+
"finalists": [
|
|
252
|
+
{
|
|
253
|
+
"candidate_id": "<原样回传>",
|
|
254
|
+
"ts_code": "<原样回传,含 .SH/.SZ>",
|
|
255
|
+
"final_rank": 1,
|
|
256
|
+
"final_prediction": "top_candidate",
|
|
257
|
+
"final_confidence": "high",
|
|
258
|
+
"reason_vs_peers": "<≤ 200 字,与同批其他标的对比的理由>",
|
|
259
|
+
"delta_vs_batch": "kept"
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
【字段值约束】
|
|
265
|
+
- final_rank: 1..N 的连续置换(不可重复、不可跳号)
|
|
266
|
+
- final_prediction: "top_candidate" / "watchlist" / "avoid" 三选一
|
|
267
|
+
- final_confidence: "high" / "medium" / "low" 三选一
|
|
268
|
+
- delta_vs_batch: "upgraded" / "kept" / "downgraded" 三选一(相对批内原 prediction)
|
|
269
|
+
- 每个输入 finalist 都必须出现,candidate_id 与输入完全一致。
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def final_ranking_user_prompt(
|
|
274
|
+
*,
|
|
275
|
+
trade_date: str,
|
|
276
|
+
next_trade_date: str,
|
|
277
|
+
finalists: list[dict[str, Any]],
|
|
278
|
+
market_context: dict[str, Any],
|
|
279
|
+
) -> str:
|
|
280
|
+
payload = {
|
|
281
|
+
"trade_date": trade_date,
|
|
282
|
+
"next_trade_date": next_trade_date,
|
|
283
|
+
"market_context": market_context,
|
|
284
|
+
"finalists": finalists,
|
|
285
|
+
}
|
|
286
|
+
return (
|
|
287
|
+
f"trade_date = {trade_date}\n"
|
|
288
|
+
f"next_trade_date= {next_trade_date}\n"
|
|
289
|
+
f"finalists count = {len(finalists)}\n\n"
|
|
290
|
+
"【finalists 摘要】\n"
|
|
291
|
+
+ json.dumps(payload, ensure_ascii=False, indent=2)
|
|
292
|
+
+ "\n\n请对所有 finalists 输出 FinalRankItem 数组;final_rank 1..N 连续。"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# ---------------------------------------------------------------------------
|
|
297
|
+
# R3 — Debate-mode revision (multi-LLM)
|
|
298
|
+
# ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
R3_DEBATE_SYSTEM = """\
|
|
301
|
+
你是 A 股打板策略多 LLM 辩论中的一员。本轮你已经独立完成了"连板预测",
|
|
302
|
+
下方将给你看其他匿名同行(peer_a / peer_b / ...)对同一批候选股的预测结果。
|
|
303
|
+
|
|
304
|
+
【硬性纪律】
|
|
305
|
+
1. 严禁使用外部搜索或任何未提供的数据;不可引入新的事实。
|
|
306
|
+
2. 候选集 = 你本人 R2 输出过的 candidate_id 集合,不可漏不可加,candidate_id 原样回传。
|
|
307
|
+
3. 同行身份完全匿名,不要尝试推断"peer_a 是某模型",也不要把同行的偏见当作权威。
|
|
308
|
+
4. 你必须独立判断:可以采纳同行观点修正自己;也可以保留原判断,但需在 revision_note 中给出理由。
|
|
309
|
+
5. revision_note ≤ 120 字,必须解释相对你最初的预测有何变化(保持不变也要写明保持的理由)。
|
|
310
|
+
6. 仅输出 JSON,不要 Markdown 代码块包裹。
|
|
311
|
+
|
|
312
|
+
【可参考的同行视角】
|
|
313
|
+
- 每位同行给出的字段:candidate_id, prediction, continuation_score, confidence, rationale, key_evidence (最多 2 条)。
|
|
314
|
+
- 你看不到同行的完整 evidence/watch_points/failure_triggers — 只是为节约 token。
|
|
315
|
+
|
|
316
|
+
【判断重点】
|
|
317
|
+
- 多数同行与你判断一致 → 增强自信,但不必盲从。
|
|
318
|
+
- 多数同行与你不一致 → 重新审视证据;如同行论据更有力,采纳并下调你的 prediction/score;
|
|
319
|
+
否则保持判断并明确写出"为何坚持"。
|
|
320
|
+
- 同行间互相矛盾 → 你需要给出独立的最终判断。
|
|
321
|
+
|
|
322
|
+
【输出格式】(严格按照此 JSON Schema 输出;不要省略任何字段,不要新增字段)
|
|
323
|
+
{
|
|
324
|
+
"stage": "limit_up_continuation_revision",
|
|
325
|
+
"trade_date": "<原样回传>",
|
|
326
|
+
"next_trade_date": "<原样回传>",
|
|
327
|
+
"revision_summary": "<≤200 字,总结你与同行的整体分歧及本次修订思路>",
|
|
328
|
+
"candidates": [
|
|
329
|
+
{
|
|
330
|
+
"candidate_id": "<原样回传>",
|
|
331
|
+
"ts_code": "<原样回传>",
|
|
332
|
+
"name": "<原样回传>",
|
|
333
|
+
"rank": 1,
|
|
334
|
+
"continuation_score": 0,
|
|
335
|
+
"confidence": "high",
|
|
336
|
+
"prediction": "top_candidate",
|
|
337
|
+
"rationale": "<≤200 字>",
|
|
338
|
+
"key_evidence": [
|
|
339
|
+
{"field": "<输入字段名>", "value": 0, "unit": "<单位>", "interpretation": "<解读>"}
|
|
340
|
+
],
|
|
341
|
+
"next_day_watch_points": ["<1-4 个>"],
|
|
342
|
+
"failure_triggers": ["<1-4 个>"],
|
|
343
|
+
"missing_data": [],
|
|
344
|
+
"revision_note": "<≤120 字,解释相对你 R2 原判断的变化或保持原因>"
|
|
345
|
+
}
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
【字段值约束】
|
|
350
|
+
- rank: 本批 1..N 连续唯一整数
|
|
351
|
+
- continuation_score: 0–100 浮点
|
|
352
|
+
- confidence: high / medium / low
|
|
353
|
+
- prediction: top_candidate / watchlist / avoid
|
|
354
|
+
- key_evidence: 每只 1–5 条
|
|
355
|
+
- next_day_watch_points / failure_triggers: 各 1–4 条
|
|
356
|
+
- revision_note: 1–120 字(必填),保持原判时需写明理由
|
|
357
|
+
- 候选集与你 R2 输出完全一致,不可漏不可加。
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def assign_peer_labels(self_provider: str, all_providers: list[str]) -> dict[str, str]:
|
|
362
|
+
"""Map other providers to anonymous peer_a / peer_b / ... labels.
|
|
363
|
+
|
|
364
|
+
Sorting by provider name keeps the labelling stable inside one run; each
|
|
365
|
+
LLM sees the others under the same set of letters.
|
|
366
|
+
"""
|
|
367
|
+
others = sorted(p for p in all_providers if p != self_provider)
|
|
368
|
+
return {p: f"peer_{chr(ord('a') + i)}" for i, p in enumerate(others)}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _peer_view_row(c: Any) -> dict[str, Any]:
|
|
372
|
+
"""Compact view of a peer's ContinuationCandidate — keeps the top 1-2
|
|
373
|
+
pieces of evidence, drops watch points / failure triggers / missing_data
|
|
374
|
+
to control input tokens."""
|
|
375
|
+
return {
|
|
376
|
+
"candidate_id": c.candidate_id,
|
|
377
|
+
"ts_code": c.ts_code,
|
|
378
|
+
"name": c.name,
|
|
379
|
+
"prediction": c.prediction,
|
|
380
|
+
"continuation_score": c.continuation_score,
|
|
381
|
+
"confidence": c.confidence,
|
|
382
|
+
"rationale": c.rationale[:120],
|
|
383
|
+
"key_evidence": [
|
|
384
|
+
{
|
|
385
|
+
"field": e.field,
|
|
386
|
+
"value": e.value,
|
|
387
|
+
"unit": e.unit,
|
|
388
|
+
"interpretation": e.interpretation,
|
|
389
|
+
}
|
|
390
|
+
for e in c.key_evidence[:2]
|
|
391
|
+
],
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _self_view_row(c: Any) -> dict[str, Any]:
|
|
396
|
+
"""Self view: full ContinuationCandidate fields so the LLM can faithfully
|
|
397
|
+
revisit its own reasoning (vs the trimmed peer view)."""
|
|
398
|
+
return {
|
|
399
|
+
"candidate_id": c.candidate_id,
|
|
400
|
+
"ts_code": c.ts_code,
|
|
401
|
+
"name": c.name,
|
|
402
|
+
"rank": c.rank,
|
|
403
|
+
"prediction": c.prediction,
|
|
404
|
+
"continuation_score": c.continuation_score,
|
|
405
|
+
"confidence": c.confidence,
|
|
406
|
+
"rationale": c.rationale,
|
|
407
|
+
"key_evidence": [
|
|
408
|
+
{
|
|
409
|
+
"field": e.field,
|
|
410
|
+
"value": e.value,
|
|
411
|
+
"unit": e.unit,
|
|
412
|
+
"interpretation": e.interpretation,
|
|
413
|
+
}
|
|
414
|
+
for e in c.key_evidence
|
|
415
|
+
],
|
|
416
|
+
"next_day_watch_points": list(c.next_day_watch_points),
|
|
417
|
+
"failure_triggers": list(c.failure_triggers),
|
|
418
|
+
"missing_data": list(c.missing_data),
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def r3_user_prompt(
|
|
423
|
+
*,
|
|
424
|
+
trade_date: str,
|
|
425
|
+
next_trade_date: str,
|
|
426
|
+
own_predictions: list[Any],
|
|
427
|
+
peers: list[tuple[str, list[Any]]],
|
|
428
|
+
market_context: dict[str, Any],
|
|
429
|
+
) -> str:
|
|
430
|
+
"""Render the R3 debate prompt.
|
|
431
|
+
|
|
432
|
+
``peers`` is ``[(label, predictions), ...]`` where label is already
|
|
433
|
+
anonymised (``peer_a`` / ``peer_b`` / ...).
|
|
434
|
+
"""
|
|
435
|
+
payload: dict[str, Any] = {
|
|
436
|
+
"trade_date": trade_date,
|
|
437
|
+
"next_trade_date": next_trade_date,
|
|
438
|
+
"market_context": market_context,
|
|
439
|
+
"you": [_self_view_row(c) for c in own_predictions],
|
|
440
|
+
}
|
|
441
|
+
for label, preds in peers:
|
|
442
|
+
payload[label] = [_peer_view_row(c) for c in preds]
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
f"trade_date = {trade_date}\n"
|
|
446
|
+
f"next_trade_date= {next_trade_date}\n"
|
|
447
|
+
f"your candidate count = {len(own_predictions)}\n"
|
|
448
|
+
f"peers = {[lbl for lbl, _ in peers]}\n\n"
|
|
449
|
+
"【辩论输入】\n"
|
|
450
|
+
+ json.dumps(payload, ensure_ascii=False, indent=2)
|
|
451
|
+
+ "\n\n"
|
|
452
|
+
"请基于上述输入,对你自己的每一只候选股重新输出 RevisedContinuationCandidate;\n"
|
|
453
|
+
"rank 在本批内 1..N 连续;revision_note 必填且 ≤120 字。"
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
# ---------------------------------------------------------------------------
|
|
458
|
+
# Internal helper
|
|
459
|
+
# ---------------------------------------------------------------------------
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _render_user(
|
|
463
|
+
*,
|
|
464
|
+
title: str,
|
|
465
|
+
n: int,
|
|
466
|
+
market_summary: dict[str, Any],
|
|
467
|
+
sector_strength_source: str,
|
|
468
|
+
sector_strength_data: dict[str, Any],
|
|
469
|
+
candidates: list[dict[str, Any]],
|
|
470
|
+
data_unavailable: list[str],
|
|
471
|
+
instruction: str,
|
|
472
|
+
) -> str:
|
|
473
|
+
return (
|
|
474
|
+
f"{title}\n本批候选股 = {n} 只\n"
|
|
475
|
+
f"全局 data_unavailable = {data_unavailable}\n\n"
|
|
476
|
+
"【市场摘要】\n"
|
|
477
|
+
+ json.dumps(market_summary, ensure_ascii=False, indent=2)
|
|
478
|
+
+ "\n\n【板块强度摘要】\n"
|
|
479
|
+
f"sector_strength_source = {sector_strength_source}\n"
|
|
480
|
+
"sector_strength_data = "
|
|
481
|
+
+ json.dumps(sector_strength_data, ensure_ascii=False, indent=2)
|
|
482
|
+
+ "\n\n【候选清单】\n"
|
|
483
|
+
+ json.dumps(candidates, ensure_ascii=False, indent=2)
|
|
484
|
+
+ f"\n\n{instruction}\n"
|
|
485
|
+
)
|