diff --git a/app.py b/app.py
index b7a8e88..83efc23 100644
--- a/app.py
+++ b/app.py
@@ -174,6 +174,7 @@ def init_db():
"ALTER TABLE order_plans ADD COLUMN sina_code TEXT",
"ALTER TABLE order_plans ADD COLUMN market_code TEXT",
"ALTER TABLE key_monitors ADD COLUMN market_code TEXT",
+ "ALTER TABLE key_monitors ADD COLUMN sina_code TEXT",
"ALTER TABLE trade_records ADD COLUMN market_code TEXT",
"ALTER TABLE order_plans ADD COLUMN plan_date TEXT",
"ALTER TABLE order_plans ADD COLUMN decision_reason TEXT",
diff --git a/static/js/review.js b/static/js/review.js
index 6fb3592..c9900e8 100644
--- a/static/js/review.js
+++ b/static/js/review.js
@@ -1,113 +1,165 @@
-(function () {
- function parseNum(v) {
- var n = parseFloat(v);
- return isNaN(n) ? null : n;
- }
-
- function calcRR(direction, entry, stop, target) {
- if (!entry || !stop || !target) return '';
- var risk, reward;
- if (direction === 'long') {
- risk = entry - stop;
- reward = target - entry;
- } else if (direction === 'short') {
- risk = stop - entry;
- reward = entry - target;
- } else {
- return '';
- }
- if (risk <= 0) return '';
- return (reward / risk).toFixed(2);
- }
-
- function calcDuration(openVal, closeVal) {
- if (!openVal || !closeVal) return '';
- var o = new Date(openVal);
- var c = new Date(closeVal);
- var secs = Math.floor((c - o) / 1000);
- if (secs < 0) return '';
- var h = Math.floor(secs / 3600);
- var m = Math.floor((secs % 3600) / 60);
- return h ? h + '小时' + m + '分钟' : m + '分钟';
- }
-
- function recalc() {
- var form = document.getElementById('review-form');
- if (!form) return;
- var dir = form.querySelector('[name="direction"]').value;
- var entry = parseNum(form.querySelector('[name="entry_price"]').value);
- var sl = parseNum(form.querySelector('[name="stop_loss"]').value);
- var tp = parseNum(form.querySelector('[name="take_profit"]').value);
- var close = parseNum(form.querySelector('[name="close_price"]').value);
- var openT = form.querySelector('[name="open_time"]').value;
- var closeT = form.querySelector('[name="close_time"]').value;
-
- var hold = document.getElementById('holding_duration');
- var initR = document.getElementById('initial_rr');
- var actR = document.getElementById('actual_rr');
- if (hold) hold.value = calcDuration(openT, closeT);
- if (initR) initR.value = calcRR(dir, entry, sl, tp);
- if (actR) actR.value = calcRR(dir, entry, sl, close);
- }
-
- function bindForm() {
- var form = document.getElementById('review-form');
- if (!form) return;
- form.querySelectorAll('input, select').forEach(function (el) {
- el.addEventListener('input', recalc);
- el.addEventListener('change', recalc);
- });
- }
-
- function showModal(data) {
- var mask = document.getElementById('review-modal');
- var body = document.getElementById('review-modal-body');
- if (!mask || !body) return;
- var html = '
';
- var fields = [
- ['品种', data.symbol], ['方向', data.direction],
- ['成交价', data.entry_price], ['止损', data.stop_loss],
- ['止盈', data.take_profit], ['平仓价', data.close_price],
- ['张数', data.lots], ['开仓时间', data.open_time],
- ['平仓时间', data.close_time], ['持仓时长', data.holding_duration],
- ['初始盈亏比', data.initial_pnl], ['实际盈亏比', data.actual_pnl],
- ['盈亏金额', data.pnl], ['开仓类型', data.open_type],
- ['离场触发', data.exit_trigger], ['离场补充', data.exit_supplement],
- ['情绪单', data.is_emotion ? '是' : '否'],
- ['行为标签', data.behavior_tags], ['备注', data.notes]
- ];
- fields.forEach(function (pair) {
- html += '
' + (pair[1] || '-') + '
';
- });
- html += '
';
- if (data.screenshot) {
- html += '';
- }
- body.innerHTML = html;
- mask.classList.add('show');
- }
-
- function bindModal() {
- var mask = document.getElementById('review-modal');
- if (!mask) return;
- mask.querySelector('.modal-close').addEventListener('click', function () {
- mask.classList.remove('show');
- });
- mask.addEventListener('click', function (e) {
- if (e.target === mask) mask.classList.remove('show');
- });
- document.querySelectorAll('.review-view-btn').forEach(function (btn) {
- btn.addEventListener('click', function () {
- try {
- showModal(JSON.parse(btn.getAttribute('data-review')));
- } catch (e) { /* ignore */ }
- });
- });
- }
-
- document.addEventListener('DOMContentLoaded', function () {
- bindForm();
- bindModal();
- recalc();
- });
-})();
+(function () {
+ function parseNum(v) {
+ var n = parseFloat(v);
+ return isNaN(n) ? null : n;
+ }
+
+ function calcRR(direction, entry, stop, target) {
+ if (!entry || !stop || !target) return '';
+ var risk, reward;
+ if (direction === 'long') {
+ risk = entry - stop;
+ reward = target - entry;
+ } else if (direction === 'short') {
+ risk = stop - entry;
+ reward = entry - target;
+ } else {
+ return '';
+ }
+ if (risk <= 0) return '';
+ return (reward / risk).toFixed(2);
+ }
+
+ function calcDuration(openVal, closeVal) {
+ if (!openVal || !closeVal) return '';
+ var o = new Date(openVal);
+ var c = new Date(closeVal);
+ var secs = Math.floor((c - o) / 1000);
+ if (secs < 0) return '';
+ var h = Math.floor(secs / 3600);
+ var m = Math.floor((secs % 3600) / 60);
+ return h ? h + '小时' + m + '分钟' : m + '分钟';
+ }
+
+ function recalc() {
+ var form = document.getElementById('review-form');
+ if (!form) return;
+ var dir = form.querySelector('[name="direction"]').value;
+ var entry = parseNum(form.querySelector('[name="entry_price"]').value);
+ var sl = parseNum(form.querySelector('[name="stop_loss"]').value);
+ var tp = parseNum(form.querySelector('[name="take_profit"]').value);
+ var close = parseNum(form.querySelector('[name="close_price"]').value);
+ var openT = form.querySelector('[name="open_time"]').value;
+ var closeT = form.querySelector('[name="close_time"]').value;
+
+ var hold = document.getElementById('holding_duration');
+ var initR = document.getElementById('initial_rr');
+ var actR = document.getElementById('actual_rr');
+ if (hold) hold.value = calcDuration(openT, closeT);
+ if (initR) initR.value = calcRR(dir, entry, sl, tp);
+ if (actR) actR.value = calcRR(dir, entry, sl, close);
+ }
+
+ function bindForm() {
+ var form = document.getElementById('review-form');
+ if (!form) return;
+ form.querySelectorAll('input, select').forEach(function (el) {
+ el.addEventListener('input', recalc);
+ el.addEventListener('change', recalc);
+ });
+ }
+
+ function esc(v) {
+ if (v === null || v === undefined || v === '') return '-';
+ return String(v);
+ }
+
+ function fmtTime(v) {
+ if (!v) return '-';
+ return String(v).replace('T', ' ').slice(0, 16);
+ }
+
+ function fmtRR(data) {
+ var init = data.initial_pnl;
+ var act = data.actual_pnl;
+ if (init && act) return init + ' / ' + act;
+ return act || init || '-';
+ }
+
+ function fmtTags(data) {
+ var tags = data.behavior_tags || '';
+ if (data.is_emotion && tags.indexOf('情绪') === -1) {
+ return tags ? '情绪单 · ' + tags : '情绪单';
+ }
+ return tags || '-';
+ }
+
+ function showModal(data) {
+ var mask = document.getElementById('review-modal');
+ var body = document.getElementById('review-modal-body');
+ if (!mask || !body) return;
+
+ var labels = [
+ '品种', '合约', '方向', '张数', '周期',
+ '成交价', '止损', '止盈', '平仓价',
+ '开仓时间', '平仓时间', '持仓时长', '盈利金额', '盈亏比',
+ '开仓类型', '行为标签'
+ ];
+ var values = [
+ esc(data.symbol),
+ esc(data.symbol_code),
+ esc(data.direction),
+ esc(data.lots),
+ esc(data.timeframe),
+ esc(data.entry_price),
+ esc(data.stop_loss),
+ esc(data.take_profit),
+ esc(data.close_price),
+ fmtTime(data.open_time),
+ fmtTime(data.close_time),
+ esc(data.holding_duration),
+ esc(data.pnl),
+ fmtRR(data),
+ esc(data.open_type),
+ fmtTags(data)
+ ];
+
+ var html = '';
+ html += '
';
+ values.forEach(function (val, i) {
+ var cls = '';
+ if (i === 15 && data.is_emotion) cls = ' class="emotion-val"';
+ html += '' + val + '';
+ });
+ html += '
';
+
+ html += '';
+ if (data.screenshot) {
+ html += '

';
+ } else {
+ html += '
暂无截图 / K线图
';
+ }
+ html += '
';
+
+ body.innerHTML = html;
+ mask.classList.add('show');
+ }
+
+ function bindModal() {
+ var mask = document.getElementById('review-modal');
+ if (!mask) return;
+ mask.querySelector('.modal-close').addEventListener('click', function () {
+ mask.classList.remove('show');
+ });
+ mask.addEventListener('click', function (e) {
+ if (e.target === mask) mask.classList.remove('show');
+ });
+ document.querySelectorAll('.review-view-btn').forEach(function (btn) {
+ btn.addEventListener('click', function () {
+ try {
+ showModal(JSON.parse(btn.getAttribute('data-review')));
+ } catch (e) { /* ignore */ }
+ });
+ });
+ }
+
+ document.addEventListener('DOMContentLoaded', function () {
+ bindForm();
+ bindModal();
+ recalc();
+ });
+})();
diff --git a/templates/base.html b/templates/base.html
index 6fd31e2..ded7514 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -1,337 +1,378 @@
-
-
-
-
-
- {% block title %}国内期货监控系统{% endblock %}
-
-
- {% block extra_css %}{% endblock %}
-
-
-
-
-
- {% with msg=get_flashed_messages() %}{% if msg %}{{ msg[0] }}
{% endif %}{% endwith %}
- {% block content %}{% endblock %}
-
-
-
- {% block extra_js %}{% endblock %}
-
-
+
+
+
+
+
+ {% block title %}国内期货监控系统{% endblock %}
+
+
+ {% block extra_css %}{% endblock %}
+
+
+
+
+
+ {% with msg=get_flashed_messages() %}{% if msg %}{{ msg[0] }}
{% endif %}{% endwith %}
+ {% block content %}{% endblock %}
+
+
+
+ {% block extra_js %}{% endblock %}
+
+
diff --git a/templates/plans.html b/templates/plans.html
index e210203..d32b559 100644
--- a/templates/plans.html
+++ b/templates/plans.html
@@ -1,97 +1,95 @@
-{% extends "base.html" %}
-{% block title %}开单计划 - 国内期货监控系统{% endblock %}
-{% block content %}
-
-
-
-
今日计划 今日 {{ today }}
-
-
开盘前制定,当日有效;下方为进行中计划。
-
-
进行中
-
-
-
-
-
-
-{% endblock %}
+{% extends "base.html" %}
+{% block title %}开单计划 - 国内期货监控系统{% endblock %}
+{% block content %}
+
+
+
+
今日计划 今日 {{ today }}
+
+
开盘前制定,当日有效;请先选择主力合约,下方为进行中计划。
+
+
进行中
+
+
+
+
+
+
+{% endblock %}
diff --git a/templates/records.html b/templates/records.html
index 9001793..934823a 100644
--- a/templates/records.html
+++ b/templates/records.html
@@ -1,177 +1,181 @@
-{% extends "base.html" %}
-{% block title %}交易记录与复盘 - 国内期货监控系统{% endblock %}
-{% block content %}
-
-
-
-
-
复盘历史
-
-
- {% if preset == 'custom' or start or end %}
-
- {% endif %}
-
-
-
-
-
-
-
-{% if auto_records %}
-
-
系统自动记录(止盈/止损)
-
- | 品种 | 类型 | 方向 | 触发价 | 结果 | 时间 |
-
- {% for r in auto_records %}
-
- | {{ r.symbol_name or r.symbol }} |
- {{ r.monitor_type }} |
- {{ '多' if r.direction == 'long' else '空' }} |
- {{ r.trigger_price }} |
- {% if r.result == '止盈' %}止盈{% else %}止损{% endif %} |
- {{ r.created_at[:16] if r.created_at else '' }} |
-
- {% endfor %}
-
-
-
-{% endif %}
-{% endblock %}
-{% block extra_js %}
-
-{% endblock %}
+{% extends "base.html" %}
+{% block title %}交易记录与复盘 - 国内期货监控系统{% endblock %}
+{% block content %}
+
+
+
+
+
复盘历史
+
+
+ {% if preset == 'custom' or start or end %}
+
+ {% endif %}
+
+
+
+
+
+
+
+{% if auto_records %}
+
+
系统自动记录(止盈/止损)
+
+ | 品种 | 类型 | 方向 | 触发价 | 结果 | 时间 |
+
+ {% for r in auto_records %}
+
+ | {{ r.symbol_name or r.symbol }} |
+ {{ r.monitor_type }} |
+ {{ '多' if r.direction == 'long' else '空' }} |
+ {{ r.trigger_price }} |
+ {% if r.result == '止盈' %}止盈{% else %}止损{% endif %} |
+ {{ r.created_at[:16] if r.created_at else '' }} |
+
+ {% endfor %}
+
+
+
+{% endif %}
+{% endblock %}
+{% block extra_js %}
+
+{% endblock %}