-
品种盈亏排行(点击查看品种详情)
-
-
-
- | 排名 | 代码 | 名称 | 净盈亏 |
- 平仓盈亏 | 手续费 | 胜率 |
- 盈亏比 | 笔数 | 操作 |
-
-
-
-
-
-
📋
-
暂无品种数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/static/futures_analysis.js b/app/static/futures_analysis.js
index 8359b70..b5eec5c 100644
--- a/app/static/futures_analysis.js
+++ b/app/static/futures_analysis.js
@@ -627,6 +627,623 @@ function renderFuturesGrid(data) {
`}).join('');
}
+// ==================== 交易反思子系统 ====================
+
+let trReflCurrentDate = null;
+let trReflData = null;
+let trReflTags = [];
+let trReflExperiences = [];
+let trReflTagState = new Set();
+let trReflCurrentPairId = null;
+let trReflCurrentAIResult = null;
+
+function trSwitchSubtab(tab) {
+ document.querySelectorAll('#tr-subtabs .tr-subtab').forEach(btn => {
+ btn.classList.toggle('active', btn.dataset.subtab === tab);
+ });
+ document.querySelectorAll('#trade-review-view > .tr-tab-panel').forEach(panel => {
+ panel.classList.toggle('active', panel.id === 'tr-tab-' + tab);
+ });
+ if (tab === 'experience') trExpLoad();
+}
+
+function trSwitchToReflection(date) {
+ trSwitchSubtab('reflection');
+ trReflSetDate(date);
+}
+
+function trReflInit() {
+ const dateInput = document.getElementById('tr-refl-current-date');
+ if (!dateInput) return;
+
+ // 默认日期取统计分析的最新交易日或今天
+ const today = new Date().toISOString().slice(0, 10);
+ trReflSetDate(today);
+
+ document.getElementById('tr-refl-prev').addEventListener('click', () => trReflChangeDate(-1));
+ document.getElementById('tr-refl-next').addEventListener('click', () => trReflChangeDate(1));
+ document.getElementById('tr-refl-edit-daily').addEventListener('click', trReflOpenDailyModal);
+
+ document.getElementById('tr-refl-edit-form').addEventListener('submit', e => {
+ e.preventDefault();
+ trReflSaveEdit();
+ });
+ document.getElementById('tr-refl-daily-form').addEventListener('submit', e => {
+ e.preventDefault();
+ trReflSaveDaily();
+ });
+
+ document.querySelectorAll('#tr-refl-discipline-score .tr-form-star, #tr-refl-daily-discipline-score .tr-form-star').forEach(star => {
+ star.addEventListener('click', () => {
+ const container = star.parentElement;
+ const score = parseInt(star.dataset.score);
+ container.dataset.value = score;
+ trReflUpdateStars(container, score);
+ });
+ });
+
+ document.getElementById('tr-exp-search').addEventListener('input', trExpRender);
+ document.getElementById('tr-exp-tag-filter').addEventListener('change', trExpRender);
+ document.getElementById('tr-exp-type-filter').addEventListener('change', trExpRender);
+ document.querySelectorAll('.tr-exp-viewtab').forEach(btn => {
+ btn.addEventListener('click', () => {
+ document.querySelectorAll('.tr-exp-viewtab').forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+ trExpRender();
+ });
+ });
+
+ trReflLoadTags();
+}
+
+function trReflSetDate(date) {
+ trReflCurrentDate = date;
+ document.getElementById('tr-refl-current-date').textContent = date;
+ trReflLoadData();
+}
+
+function trReflChangeDate(days) {
+ const d = new Date(trReflCurrentDate);
+ d.setDate(d.getDate() + days);
+ trReflSetDate(d.toISOString().slice(0, 10));
+}
+
+async function trReflLoadData() {
+ if (!trReflCurrentDate) return;
+ try {
+ const res = await fetch(`${TR_API_BASE}/daily-trades/${trReflCurrentDate}`);
+ const json = await res.json();
+ trReflData = json.success ? json.data : null;
+ if (!trReflData) trReflData = { pairs: [], unpaired: [], daily_reflection: null, summary: {} };
+ trReflRender();
+ } catch (e) {
+ console.error('加载反思数据失败', e);
+ trReflData = { pairs: [], unpaired: [], daily_reflection: null, summary: {} };
+ trReflRender();
+ }
+}
+
+function trReflRender() {
+ const data = trReflData || { pairs: [], unpaired: [], daily_reflection: null, summary: {} };
+ const summary = data.summary || {};
+ const daily = data.daily_reflection || {};
+
+ const pnl = summary.total_pnl || 0;
+ const pnlEl = document.getElementById('tr-refl-stat-pnl');
+ pnlEl.textContent = (pnl >= 0 ? '+' : '') + pnl.toFixed(2);
+ pnlEl.className = 'tr-refl-statvalue ' + (pnl >= 0 ? 'profit' : 'loss');
+
+ document.getElementById('tr-refl-stat-trades').textContent = summary.total_trades || 0;
+ document.getElementById('tr-refl-stat-winrate').textContent = (summary.win_rate || 0) + '%';
+
+ const reflectedEl = document.getElementById('tr-refl-stat-reflected');
+ const reflected = summary.reflected_count || 0;
+ const totalPairs = (data.pairs || []).length;
+ if (totalPairs === 0) {
+ reflectedEl.innerHTML = '
无交易';
+ } else if (reflected >= totalPairs) {
+ reflectedEl.innerHTML = '
✓ 已完成';
+ } else {
+ reflectedEl.innerHTML = `
${reflected}/${totalPairs}`;
+ }
+
+ document.getElementById('tr-refl-emotion').textContent = trReflTextMap.emotion[daily.emotion_state] || daily.emotion_state || '未填写';
+ document.getElementById('tr-refl-emotion').className = 'tr-refl-daily-value ' + (daily.emotion_state ? '' : 'empty');
+ document.getElementById('tr-refl-discipline').textContent = daily.discipline_score ? daily.discipline_score + '/5' : '未填写';
+ document.getElementById('tr-refl-discipline').className = 'tr-refl-daily-value ' + (daily.discipline_score ? '' : 'empty');
+ document.getElementById('tr-refl-overall').textContent = trReflTextMap.overall[daily.overall_evaluation] || daily.overall_evaluation || '未填写';
+ document.getElementById('tr-refl-overall').className = 'tr-refl-daily-value ' + (daily.overall_evaluation ? '' : 'empty');
+ document.getElementById('tr-refl-market').textContent = trReflTextMap.market[daily.market_judgement] || daily.market_judgement || '未填写';
+ document.getElementById('tr-refl-market').className = 'tr-refl-daily-value ' + (daily.market_judgement ? '' : 'empty');
+
+ const pairsContainer = document.getElementById('tr-refl-pairs');
+ const pairs = data.pairs || [];
+ if (!pairs.length) {
+ pairsContainer.innerHTML = '
';
+ } else {
+ pairsContainer.innerHTML = pairs.map(p => trReflRenderPairCard(p)).join('');
+ }
+
+ const unpairedBody = document.getElementById('tr-refl-unpaired-body');
+ const unpairedEmpty = document.getElementById('tr-refl-unpaired-empty');
+ const unpaired = data.unpaired || [];
+ if (!unpaired.length) {
+ unpairedBody.innerHTML = '';
+ unpairedEmpty.style.display = 'flex';
+ } else {
+ unpairedEmpty.style.display = 'none';
+ unpairedBody.innerHTML = unpaired.map(u => {
+ const pnl = u.net_pnl || u.close_pnl || 0;
+ const pnlCls = pnl > 0 ? 'tr-pnl-positive' : (pnl < 0 ? 'tr-pnl-negative' : '');
+ return `
+ | ${u.trade_time || '-'} |
+ ${u.variety}${u.symbol_name ? '(' + u.symbol_name + ')' : ''} |
+ ${u.direction || '-'} |
+ ${(u.price || 0).toFixed(2)} |
+ ${u.volume || 0} |
+ ${pnl !== 0 ? (pnl >= 0 ? '+' : '') + pnl.toFixed(2) : '-'} |
+
`;
+ }).join('');
+ }
+}
+
+function trReflRenderPairCard(p) {
+ const pnl = p.net_pnl || 0;
+ const pnlCls = pnl >= 0 ? 'profit' : 'loss';
+ const dirCls = p.direction === '多' ? 'long' : 'short';
+ const dirText = p.direction === '多' ? '做多' : '做空';
+ const tags = (p.tags || []).map(t => `
${t}`).join('');
+ const hasReflection = p.reflection && (p.reflection.entry_reason || p.reflection.free_reflection);
+ return `
+
+
+ 开仓:${p.open_date || '-'} ${p.open_time || ''} @ ${(p.open_price || 0).toFixed(2)}
+ 平仓:${p.close_date || '-'} ${p.close_time || ''} @ ${(p.close_price || 0).toFixed(2)}
+
+
${tags}
+
+
+
+
+
+
`;
+}
+
+async function trReflLoadTags() {
+ try {
+ const res = await fetch(`${TR_API_BASE}/tags`);
+ const json = await res.json();
+ if (json.success) trReflTags = json.data || [];
+ } catch (e) { console.error('加载标签失败', e); }
+}
+
+// ===== 反思编辑 Modal =====
+async function trReflOpenEditModal(pairId) {
+ const data = trReflData || { pairs: [] };
+ const pair = data.pairs.find(p => p.id === pairId);
+ if (!pair) return;
+ trReflCurrentPairId = pairId;
+ const reflection = pair.reflection || {};
+
+ document.getElementById('tr-refl-edit-pair-id').value = pairId;
+ document.getElementById('tr-refl-entry-reason').value = reflection.entry_reason || '';
+ document.getElementById('tr-refl-exit-reason').value = reflection.exit_reason || '';
+ document.getElementById('tr-refl-entry-timing').value = reflection.entry_timing || '';
+ document.getElementById('tr-refl-exit-timing').value = reflection.exit_timing || '';
+ document.getElementById('tr-refl-position').value = reflection.position_management || '';
+ document.getElementById('tr-refl-free').value = reflection.free_reflection || '';
+
+ const score = reflection.discipline_score || 0;
+ const container = document.getElementById('tr-refl-discipline-score');
+ container.dataset.value = score;
+ trReflUpdateStars(container, score);
+
+ // 渲染标签选择
+ const tagPool = document.getElementById('tr-refl-edit-tags');
+ const selected = new Set(reflection.tags || pair.tags || []);
+ tagPool.innerHTML = trReflTags.map(t => {
+ const name = typeof t === 'string' ? t : t.name;
+ const active = selected.has(name) ? 'selected' : '';
+ return `
${name}`;
+ }).join('');
+
+ document.getElementById('tr-refl-edit-modal').classList.add('show');
+}
+
+function trReflCloseEditModal() {
+ document.getElementById('tr-refl-edit-modal').classList.remove('show');
+ trReflCurrentPairId = null;
+}
+
+async function trReflSaveEdit() {
+ const pairId = parseInt(document.getElementById('tr-refl-edit-pair-id').value);
+ if (!pairId) return;
+
+ const selectedTags = Array.from(document.querySelectorAll('#tr-refl-edit-tags .tr-tag-chip.selected')).map(el => el.textContent);
+ const body = {
+ trade_pair_id: pairId,
+ entry_reason: document.getElementById('tr-refl-entry-reason').value,
+ exit_reason: document.getElementById('tr-refl-exit-reason').value,
+ entry_timing: document.getElementById('tr-refl-entry-timing').value,
+ exit_timing: document.getElementById('tr-refl-exit-timing').value,
+ position_management: document.getElementById('tr-refl-position').value,
+ discipline_score: parseInt(document.getElementById('tr-refl-discipline-score').dataset.value || '0'),
+ free_reflection: document.getElementById('tr-refl-free').value,
+ tags: selectedTags,
+ };
+
+ try {
+ const res = await fetch(`${TR_API_BASE}/reflections`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body),
+ });
+ const json = await res.json();
+ if (json.success) {
+ showToast('success', '保存成功', '反思已保存');
+ trReflCloseEditModal();
+ trReflLoadData();
+ } else {
+ showToast('error', '保存失败', json.message || '请重试');
+ }
+ } catch (e) {
+ showToast('error', '保存失败', e.message);
+ }
+}
+
+// ===== 当日反思 Modal =====
+function trReflOpenDailyModal() {
+ const daily = (trReflData && trReflData.daily_reflection) || {};
+ document.getElementById('tr-refl-daily-emotion').value = daily.emotion_state || '';
+ document.getElementById('tr-refl-daily-overall').value = daily.overall_evaluation || '';
+ document.getElementById('tr-refl-daily-market').value = daily.market_judgement || '';
+ document.getElementById('tr-refl-daily-summary').value = daily.summary || '';
+ document.getElementById('tr-refl-daily-improvement').value = daily.improvement || '';
+
+ const score = daily.discipline_score || 0;
+ const container = document.getElementById('tr-refl-daily-discipline-score');
+ container.dataset.value = score;
+ trReflUpdateStars(container, score);
+
+ document.getElementById('tr-refl-daily-modal').classList.add('show');
+}
+
+function trReflCloseDailyModal() {
+ document.getElementById('tr-refl-daily-modal').classList.remove('show');
+}
+
+async function trReflSaveDaily() {
+ const body = {
+ trade_date: trReflCurrentDate,
+ emotion_state: document.getElementById('tr-refl-daily-emotion').value,
+ overall_evaluation: document.getElementById('tr-refl-daily-overall').value,
+ market_judgement: document.getElementById('tr-refl-daily-market').value,
+ discipline_score: parseInt(document.getElementById('tr-refl-daily-discipline-score').dataset.value || '0'),
+ summary: document.getElementById('tr-refl-daily-summary').value,
+ improvement: document.getElementById('tr-refl-daily-improvement').value,
+ };
+
+ try {
+ const res = await fetch(`${TR_API_BASE}/daily-reflections`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body),
+ });
+ const json = await res.json();
+ if (json.success) {
+ showToast('success', '保存成功', '当日反思已保存');
+ trReflCloseDailyModal();
+ trReflLoadData();
+ } else {
+ showToast('error', '保存失败', json.message || '请重试');
+ }
+ } catch (e) {
+ showToast('error', '保存失败', e.message);
+ }
+}
+
+// ===== 标签管理 Modal =====
+async function trReflOpenTagsModal(pairId) {
+ const data = trReflData || { pairs: [] };
+ const pair = data.pairs.find(p => p.id === pairId);
+ if (!pair) return;
+ trReflCurrentPairId = pairId;
+ trReflTagState = new Set((pair.tags || []).map(t => typeof t === 'string' ? t : t.name));
+
+ document.getElementById('tr-refl-tags-pair-id').value = pairId;
+ trReflRenderTagPreset();
+ document.getElementById('tr-refl-tags-modal').classList.add('show');
+}
+
+function trReflRenderTagPreset() {
+ const container = document.getElementById('tr-refl-tags-preset');
+ const grouped = trReflGroupTags(trReflTags);
+ container.innerHTML = Object.entries(grouped).map(([category, tags]) => `
+
+
${category}
+
+ ${tags.map(t => {
+ const name = typeof t === 'string' ? t : t.name;
+ const selected = trReflTagState.has(name) ? 'selected' : '';
+ return `${name}`;
+ }).join('')}
+
+
+ `).join('');
+}
+
+function trReflGroupTags(tags) {
+ const groups = {};
+ tags.forEach(t => {
+ const category = (typeof t === 'object' && t.category) ? t.category : '常用';
+ if (!groups[category]) groups[category] = [];
+ groups[category].push(t);
+ });
+ return groups;
+}
+
+function trReflToggleTag(tag) {
+ if (trReflTagState.has(tag)) trReflTagState.delete(tag);
+ else trReflTagState.add(tag);
+ trReflRenderTagPreset();
+}
+
+function trReflAddCustomTag() {
+ const input = document.getElementById('tr-refl-tag-input');
+ const val = input.value.trim();
+ if (!val) return;
+ trReflTagState.add(val);
+ input.value = '';
+ trReflRenderTagPreset();
+}
+
+async function trReflSaveTags() {
+ const pairId = parseInt(document.getElementById('tr-refl-tags-pair-id').value);
+ if (!pairId) return;
+
+ const currentTags = Array.from(trReflTagState);
+ const data = trReflData || { pairs: [] };
+ const pair = data.pairs.find(p => p.id === pairId);
+ const oldTags = (pair && pair.tags) || [];
+ const oldNames = new Set(oldTags.map(t => typeof t === 'string' ? t : t.name));
+
+ try {
+ for (const tag of currentTags) {
+ if (!oldNames.has(tag)) {
+ await fetch(`${TR_API_BASE}/trade-pairs/${pairId}/tags`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ tag }),
+ });
+ }
+ }
+ for (const tag of oldTags) {
+ const name = typeof tag === 'string' ? tag : tag.name;
+ if (!trReflTagState.has(name)) {
+ await fetch(`${TR_API_BASE}/trade-pairs/${pairId}/tags`, {
+ method: 'DELETE',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ tag: name }),
+ });
+ }
+ }
+ showToast('success', '保存成功', '标签已更新');
+ trReflCloseTagsModal();
+ trReflLoadData();
+ } catch (e) {
+ showToast('error', '保存失败', e.message);
+ }
+}
+
+function trReflCloseTagsModal() {
+ document.getElementById('tr-refl-tags-modal').classList.remove('show');
+ trReflCurrentPairId = null;
+}
+
+// ===== AI 分析结果 Modal =====
+async function trReflOpenAIModal(pairId) {
+ const data = trReflData || { pairs: [] };
+ const pair = data.pairs.find(p => p.id === pairId);
+ if (!pair) return;
+ trReflCurrentPairId = pairId;
+ trReflCurrentAIResult = pair.ai_analysis || null;
+
+ if (!trReflCurrentAIResult) {
+ // 如果没有缓存的 AI 结果,尝试请求分析接口
+ 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 && json.data) {
+ trReflCurrentAIResult = json.data;
+ }
+ } catch (e) { console.error('AI分析请求失败', e); }
+ }
+
+ const result = trReflCurrentAIResult || {};
+ document.getElementById('tr-refl-ai-pair-id').value = pairId;
+ document.getElementById('tr-refl-ai-overall').textContent = result.overall_evaluation || result.analysis || '暂无 AI 分析结果';
+ document.getElementById('tr-refl-ai-strengths').innerHTML = (result.strengths || []).map(s => `
${s}`).join('') || '
-';
+ document.getElementById('tr-refl-ai-weaknesses').innerHTML = (result.weaknesses || []).map(s => `
${s}`).join('') || '
-';
+ document.getElementById('tr-refl-ai-suggestion').textContent = result.suggestion || result.experience_suggestion || '暂无建议';
+
+ document.getElementById('tr-refl-ai-modal').classList.add('show');
+}
+
+async function trReflSaveExperience() {
+ const pairId = parseInt(document.getElementById('tr-refl-ai-pair-id').value);
+ const data = trReflData || { pairs: [] };
+ const pair = data.pairs.find(p => p.id === pairId);
+ if (!pair || !trReflCurrentAIResult) return;
+
+ const result = trReflCurrentAIResult;
+ const body = {
+ title: `${pair.symbol_name || pair.variety || pair.symbol} ${pair.direction === '多' ? '做多' : '做空'} 经验`,
+ content: result.overall_evaluation || result.analysis || '',
+ type: pair.net_pnl >= 0 ? 'insight' : 'lesson',
+ tags: ['AI分析', ...(pair.tags || [])].slice(0, 5),
+ source_trade_pair_id: pairId,
+ strengths: result.strengths || [],
+ weaknesses: result.weaknesses || [],
+ suggestion: result.suggestion || result.experience_suggestion || '',
+ };
+
+ try {
+ const res = await fetch(`${TR_API_BASE}/experiences`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body),
+ });
+ const json = await res.json();
+ if (json.success) {
+ showToast('success', '保存成功', '已保存到经验库');
+ trReflCloseAIModal();
+ } else {
+ showToast('error', '保存失败', json.message || '请重试');
+ }
+ } catch (e) {
+ showToast('error', '保存失败', e.message);
+ }
+}
+
+function trReflCloseAIModal() {
+ document.getElementById('tr-refl-ai-modal').classList.remove('show');
+ trReflCurrentPairId = null;
+ trReflCurrentAIResult = null;
+}
+
+// ===== 经验库 =====
+async function trExpLoad() {
+ try {
+ const res = await fetch(`${TR_API_BASE}/experiences`);
+ const json = await res.json();
+ trReflExperiences = json.success ? (json.data || []) : [];
+ trExpPopulateTagFilter();
+ trExpRender();
+ } catch (e) {
+ console.error('加载经验库失败', e);
+ trReflExperiences = [];
+ trExpRender();
+ }
+}
+
+function trExpPopulateTagFilter() {
+ const select = document.getElementById('tr-exp-tag-filter');
+ const allTags = new Set();
+ trReflExperiences.forEach(e => (e.tags || []).forEach(t => allTags.add(typeof t === 'string' ? t : t.name)));
+ const current = select.value;
+ select.innerHTML = '
' + Array.from(allTags).sort().map(t => `
`).join('');
+ select.value = current;
+}
+
+function trExpRender() {
+ const container = document.getElementById('tr-exp-container');
+ const empty = document.getElementById('tr-exp-empty');
+ const keyword = document.getElementById('tr-exp-search').value.toLowerCase();
+ const tag = document.getElementById('tr-exp-tag-filter').value;
+ const type = document.getElementById('tr-exp-type-filter').value;
+ const view = document.querySelector('.tr-exp-viewtab.active').dataset.view;
+
+ let list = trReflExperiences.filter(e => {
+ const matchKeyword = !keyword || (e.title || '').toLowerCase().includes(keyword) || (e.content || '').toLowerCase().includes(keyword);
+ const matchTag = !tag || (e.tags || []).some(t => (typeof t === 'string' ? t : t.name) === tag);
+ const matchType = !type || e.type === type;
+ return matchKeyword && matchTag && matchType;
+ });
+
+ if (!list.length) {
+ container.innerHTML = '';
+ container.className = 'tr-exp-grid';
+ empty.style.display = 'flex';
+ return;
+ }
+ empty.style.display = 'none';
+
+ if (view === 'timeline') {
+ container.className = '';
+ list = [...list].sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0));
+ container.innerHTML = `
${list.map(e => trExpRenderCard(e, true)).join('')}
`;
+ } else if (view === 'category') {
+ container.className = '';
+ const grouped = {};
+ list.forEach(e => {
+ const k = e.type || '其他';
+ if (!grouped[k]) grouped[k] = [];
+ grouped[k].push(e);
+ });
+ const typeNames = { lesson: '教训', insight: '心得', strategy: '策略', discipline: '纪律' };
+ container.innerHTML = Object.entries(grouped).map(([k, items]) => `
+
+
${typeNames[k] || k}
+
${items.map(e => trExpRenderCard(e)).join('')}
+
+ `).join('');
+ } else {
+ container.className = 'tr-exp-grid';
+ container.innerHTML = list.map(e => trExpRenderCard(e)).join('');
+ }
+}
+
+function trExpRenderCard(e, timeline = false) {
+ const typeNames = { lesson: '教训', insight: '心得', strategy: '策略', discipline: '纪律' };
+ const tags = (e.tags || []).map(t => `
${typeof t === 'string' ? t : t.name}`).join('');
+ const date = e.created_at ? e.created_at.slice(0, 10) : '';
+ return `
+
${e.title || '未命名经验'}
+
${typeNames[e.type] || e.type || '其他'} ${date ? '· ' + date : ''}
+
${(e.content || '').slice(0, 120)}${(e.content || '').length > 120 ? '...' : ''}
+
${tags}
+
`;
+}
+
+// ===== 星级评分辅助 =====
+function trReflUpdateStars(container, score) {
+ container.querySelectorAll('.tr-form-star').forEach(s => {
+ s.classList.toggle('active', parseInt(s.dataset.score) <= score);
+ });
+}
+
+// ===== 文本映射 =====
+const trReflTextMap = {
+ emotion: {
+ calm: '平静', confident: '自信', anxious: '焦虑', greedy: '贪婪',
+ fearful: '恐惧', impulsive: '冲动', tired: '疲惫'
+ },
+ overall: {
+ excellent: '优秀', good: '良好', average: '一般', poor: '较差', bad: '糟糕'
+ },
+ market: {
+ accurate: '准确', basically: '基本准确', deviated: '有所偏差', wrong: '错误'
+ },
+ timing: {
+ excellent: '优秀', good: '良好', average: '一般', poor: '较差', bad: '糟糕'
+ }
+};
+
// ==================== 交易复盘功能 ====================
const TR_API_BASE = '/api/v1/trade-review';
@@ -671,8 +1288,16 @@ function initTradeReview() {
// 批次管理
document.getElementById('tr-btn-batches').addEventListener('click', trShowBatchModal);
+ // 子 Tab 切换
+ document.querySelectorAll('#tr-subtabs .tr-subtab').forEach(btn => {
+ btn.addEventListener('click', () => trSwitchSubtab(btn.dataset.subtab));
+ });
+
// 初始加载
trInitDefaultDate();
+
+ // 初始化交易反思子系统
+ trReflInit();
}
async function trInitDefaultDate() {
@@ -879,18 +1504,19 @@ function trRenderDailyTable(daily) {
const closePnl = net + d.total_commission;
const netCls = net >= 0 ? 'tr-pnl-positive' : 'tr-pnl-negative';
const cpCls = closePnl >= 0 ? 'tr-pnl-positive' : 'tr-pnl-negative';
- return `
- | ${d.trade_date} |
- ${d.total_trades} |
- ${closePnl >= 0 ? '+' : ''}${closePnl.toFixed(2)} |
- ${d.total_commission.toFixed(2)} |
- ${net >= 0 ? '+' : ''}${net.toFixed(2)} |
- ${d.win_count} |
- ${d.loss_count} |
- ${d.win_rate}% |
- +${d.max_win.toFixed(2)} |
- ${d.max_loss.toFixed(2)} |
- ${d.variety_count} |
+ return `
+ | ${d.trade_date} |
+ ${d.total_trades} |
+ ${closePnl >= 0 ? '+' : ''}${closePnl.toFixed(2)} |
+ ${d.total_commission.toFixed(2)} |
+ ${net >= 0 ? '+' : ''}${net.toFixed(2)} |
+ ${d.win_count} |
+ ${d.loss_count} |
+ ${d.win_rate}% |
+ +${d.max_win.toFixed(2)} |
+ ${d.max_loss.toFixed(2)} |
+ ${d.variety_count} |
+ 进入反思 |
`;
}).join('');
}