mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 19:14:19 +08:00
release: v1.29.0 — 借鉴社区 Fork 六项特性:ZIG 策略 + 交易时段感知刷新 + 120M K 线 + 逐 bar 衍生字段 + 159 龙头池 + 多 Provider LLM 直连
- ZIG 右侧突破回补策略:MyTT 新增 ZIG 之字转向(未来函数,含前视偏差警示); 波谷启动建仓挂硬止损(OCO)→ 见顶清仓记前高 → 右侧突破回补;路径依赖不实现 entry_exit_masks(向量化守护测试白名单);含寻优预设网格与 --strategy-file 独立文件 - 交易时段感知刷新:realtime/session.py(09:15~11:30:30 / 13:00~15:05)+ GET /market/session;看板 30/60/120s 轮询休市自动暂停(三态状态栏 + 开关持久化 + 手动刷新不受限);SSE/WS 既有会话语义不动 - 120 分钟 K 线:/bars?category=MIN_120(MAC 原生 Period.MINS×120 优先, 2×60M 相邻聚合兜底,标准客户端上限 400 根);前端周期选择器同步 - 逐 bar 衍生字段:/bars 与 /bars/index 附带 pre_close/change/change_pct/ amplitude_pct(pre_close≤0.01 兜底防除零) - 159 只核心龙头池:数据资产取自 Fork(东财全行业龙头名单,四组分层); universe=core 接入 screen scan / SignalScanner / StrengthRanker / market strength; GET /market/core-leaders + WebUI「龙头池」页(搜索/个股详情) - 多 Provider LLM 直连:easy_tdx.ai + /llm/*(DeepSeek/通义/智谱/Kimi/MiniMax/ OpenAI/Claude/Ollama/自定义,openai 兼容 + anthropic 原生双协议); 配置落盘 ~/.easy_tdx/llm.json(WebUI「AI 设置」页 ⇆ 手工编辑双向兼容, 文件>环境变量>预设;key 脱敏回显/CLEAR 清除);「AI 解读」后台任务化 (复用 task_runner,提交+轮询,不占 HTTP 连接);思考型模型空白正文防御 (reasoning_content 耗尽 max_tokens → 可操作报错;默认 16000); AI 解读历史页(自动归档 Prompt/正文/策略上下文 + 去回测带参引导) - WebUI 加固:SPA fallback 对未知 /api/* 返回 JSON 404(不再 200 HTML 伪装解析错); index.html 一律 Cache-Control: no-store(防缓存旧资源引用);路由兜底重定向; 全局风险提示常驻底栏 + 龙头池/AI 解读针对性免责声明 - 测试:新增 9 个单测文件共 59 例;黄金基线仅新增 zig_breakout 条目(其余零漂移); 全量 1448 例通过
This commit is contained in:
@@ -15,11 +15,11 @@ import StrategyPicker from '../components/StrategyPicker.vue'
|
||||
import SymbolPicker from '../components/SymbolPicker.vue'
|
||||
import TradeTable from '../components/TradeTable.vue'
|
||||
import WalkForwardPanel from '../components/WalkForwardPanel.vue'
|
||||
import { formatError, saveStrategy } from '../api'
|
||||
import { formatError, saveStrategy, fetchLlmConfig, runLlmChatWithPolling } from '../api'
|
||||
import { detectMarket } from '../market'
|
||||
import { GRADE_META, gradePerformance } from '../grading'
|
||||
import { buildAiPrompt } from '../aiPrompt'
|
||||
import type { Category, ExecutionMode } from '../types'
|
||||
import type { Category, ExecutionMode, LlmChatResult } from '../types'
|
||||
import { useBacktestStore } from '../stores/backtest'
|
||||
|
||||
const store = useBacktestStore()
|
||||
@@ -204,6 +204,13 @@ async function onSave() {
|
||||
// ── AI 解读 Prompt(把当前报告组装成提示词,发给任意 LLM 解读)──────────────
|
||||
const showAiModal = ref(false)
|
||||
const aiMsg = ref('')
|
||||
// 直接解读(服务端 LLM 已配置时可用,配置见「AI 设置」页)
|
||||
const llmReady = ref(false)
|
||||
const llmLabel = ref('')
|
||||
const aiRunning = ref(false)
|
||||
const aiElapsed = ref(0)
|
||||
const aiReply = ref('')
|
||||
let aiTimer = 0
|
||||
|
||||
/** 实时组装:附加分析(WF/评估)跑完后内容自动变全 */
|
||||
const aiPromptText = computed(() => {
|
||||
@@ -230,7 +237,60 @@ const aiPromptText = computed(() => {
|
||||
|
||||
function openAiModal() {
|
||||
aiMsg.value = ''
|
||||
aiReply.value = ''
|
||||
showAiModal.value = true
|
||||
// 打开时探测 LLM 是否已配置(失败静默——导出 Prompt 的老路径不依赖后端)
|
||||
fetchLlmConfig()
|
||||
.then((resp) => {
|
||||
llmReady.value = resp.configured
|
||||
const p = resp.providers.find((x) => x.id === resp.config.provider)
|
||||
llmLabel.value = p ? `${p.label} · ${resp.resolved.model}` : resp.resolved.model
|
||||
})
|
||||
.catch(() => {
|
||||
llmReady.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/** 直接解读:把组装好的 Prompt 提交为后台任务并轮询(不占 HTTP 连接)。 */
|
||||
async function runAiInterpret() {
|
||||
if (!aiPromptText.value || aiRunning.value) return
|
||||
aiRunning.value = true
|
||||
aiMsg.value = ''
|
||||
aiReply.value = ''
|
||||
// 后台任务模式:模型生成 1-3 分钟正常——显示已耗时防误判卡死
|
||||
aiElapsed.value = 0
|
||||
aiTimer = window.setInterval(() => {
|
||||
aiElapsed.value += 1
|
||||
}, 1000)
|
||||
try {
|
||||
// 策略上下文随解读落历史库(AI 解读历史页「去回测」引导用)
|
||||
const ctx = {
|
||||
strategy: strategy.value,
|
||||
strategy_label: strategyLabel.value,
|
||||
symbol: code.value,
|
||||
category: category.value,
|
||||
params: { ...params.value },
|
||||
start_date: startDate.value,
|
||||
end_date: endDate.value,
|
||||
}
|
||||
const state = await runLlmChatWithPolling(aiPromptText.value, ctx)
|
||||
// TaskState.result 是多任务类型联合,按 LLM 任务结构收窄
|
||||
const r = state.result as LlmChatResult | null
|
||||
// 后端已保证非空正文(空白正文会以 failed 上浮),前端再拦一道纯空白
|
||||
if (state.status === 'done' && r?.reply?.trim()) {
|
||||
aiReply.value = r.reply
|
||||
aiMsg.value = `✓ ${r.provider} · ${r.model} 已解读(${aiElapsed.value}s)`
|
||||
} else if (state.status === 'done') {
|
||||
aiMsg.value = '解读失败:模型返回了空正文(可能被 Max Tokens 截断),可在「AI 设置」调大后重试'
|
||||
} else {
|
||||
aiMsg.value = `解读失败:${state.error ?? '未知错误'}(可在「AI 设置」检查配置,或复制 Prompt 手动使用)`
|
||||
}
|
||||
} catch (e) {
|
||||
aiMsg.value = `解读失败:${formatError(e)}(可在「AI 设置」检查配置,或复制 Prompt 手动使用)`
|
||||
} finally {
|
||||
window.clearInterval(aiTimer)
|
||||
aiRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function copyAiPrompt() {
|
||||
@@ -445,26 +505,45 @@ function downloadAiPrompt() {
|
||||
<!-- AI 解读 Prompt 对话框 -->
|
||||
<div v-if="showAiModal" class="modal-overlay" @click.self="showAiModal = false">
|
||||
<div class="modal modal-wide">
|
||||
<h3>🤖 AI 解读 Prompt</h3>
|
||||
<h3>🤖 AI 解读</h3>
|
||||
<p class="modal-desc">
|
||||
已把当前回测报告组装成提示词。复制后发给任意 AI 助手(ChatGPT / Claude /
|
||||
DeepSeek / 豆包…),即可获得针对性解读与改进建议。
|
||||
已把当前回测报告组装成提示词。
|
||||
<template v-if="llmReady">
|
||||
点击「直接解读」发送给已配置的模型({{ llmLabel }}),
|
||||
</template>
|
||||
<template v-else>
|
||||
在「AI 设置」页配置模型后可一键直接解读;也可
|
||||
</template>
|
||||
复制后发给任意 AI 助手(ChatGPT / Claude / DeepSeek / 豆包…)。
|
||||
<template v-if="wfEnabled || evaluateEnabled">
|
||||
建议等附加分析跑完再复制,Walk-Forward / 一条龙评估的数据会一并打包。
|
||||
建议等附加分析跑完再发,Walk-Forward / 一条龙评估的数据会一并打包。
|
||||
</template>
|
||||
</p>
|
||||
<textarea
|
||||
:value="aiPromptText"
|
||||
class="ai-prompt-area"
|
||||
:class="{ collapsed: !!aiReply }"
|
||||
readonly
|
||||
rows="16"
|
||||
:rows="aiReply ? 6 : 16"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<div v-if="aiReply" class="ai-reply">{{ aiReply }}</div>
|
||||
<div v-if="aiReply" class="ai-note">
|
||||
以上解读由 AI 模型生成,可能存在错误或过时信息,仅供参考,不构成投资建议。
|
||||
</div>
|
||||
<span v-if="aiMsg" class="ai-msg">{{ aiMsg }}</span>
|
||||
<div class="modal-actions">
|
||||
<button class="ghost" @click="showAiModal = false">关闭</button>
|
||||
<button class="ghost" @click="downloadAiPrompt">⬇ 下载 .md</button>
|
||||
<button class="primary" @click="copyAiPrompt">复制 Prompt</button>
|
||||
<button class="ghost" @click="copyAiPrompt">复制 Prompt</button>
|
||||
<button
|
||||
v-if="llmReady"
|
||||
class="primary"
|
||||
:disabled="aiRunning || !aiPromptText"
|
||||
@click="runAiInterpret"
|
||||
>
|
||||
{{ aiRunning ? `解读中… ${aiElapsed}s` : '✨ 直接解读' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -724,8 +803,31 @@ function downloadAiPrompt() {
|
||||
color: var(--text-muted);
|
||||
resize: vertical;
|
||||
}
|
||||
/* 直接解读出结果后 Prompt 区收窄,把版面让给回复 */
|
||||
.ai-prompt-area.collapsed {
|
||||
max-height: 18vh;
|
||||
}
|
||||
.ai-reply {
|
||||
margin-top: 8px;
|
||||
max-height: 38vh;
|
||||
overflow: auto;
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.ai-msg {
|
||||
font-size: 12px;
|
||||
color: var(--up);
|
||||
}
|
||||
.ai-note {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: var(--warn, #ffc107);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user