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 = ''; - 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 += '
'; + labels.forEach(function (lb) { + html += '' + lb + ''; + }); + 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 }}

-
-

开盘前制定,当日有效;下方为进行中计划。

-
-
-
- - - - - -
-
-
- - -
-
- - - - -
-
- -
-
- -
- {% for p in plans %} -
-
- {{ p.symbol_name or p.symbol }} - {{ '多' if p.direction == 'long' else '空' }} - {% if p.status == 'planned' %}待触发 - {% else %}已激活{% endif %} -
-
- 区间{{ p.zone_lower }}~{{ p.zone_upper }} - {% if p.decision_reason %} · {{ p.decision_reason }}{% endif %} - · 损{{ p.stop_loss }} 盈{{ p.take_profit }} -
- -
- {% else %} -
今日暂无进行中的计划
- {% endfor %} -
-
-
- -
-

历史计划

-
-
-
-
- - 重置 -
-
- - - - {% for p in history %} - - - - - - - - - {% else %} - - {% endfor %} - -
日期品种方向决策区间决策理由状态
{{ p.plan_date or '' }}{{ p.symbol_name or p.symbol }}{{ '多' if p.direction == 'long' else '空' }}{{ p.zone_lower }}~{{ p.zone_upper }}{{ p.decision_reason or '—' }} - {% if p.status == 'closed' %}完成 - {% elif p.status == 'expired' %}失效 - {% else %}{{ p.status }}{% endif %} -
暂无历史
-
-
-
-
-{% endblock %} +{% extends "base.html" %} +{% block title %}开单计划 - 国内期货监控系统{% endblock %} +{% block content %} +
+
+ +

今日计划 今日 {{ today }}

+
+

开盘前制定,当日有效;请先选择主力合约,下方为进行中计划。

+
+
+
+ + + + + +
+
+
+ + +
+
+ + + + + +
+
+ +
+ {% for p in plans %} +
+
+ {{ p.symbol_name or p.symbol }} + {{ '多' if p.direction == 'long' else '空' }} + {% if p.status == 'planned' %}待触发 + {% else %}已激活{% endif %} +
+
+ 区间{{ p.zone_lower }}~{{ p.zone_upper }} + {% if p.decision_reason %} · {{ p.decision_reason }}{% endif %} + · 损{{ p.stop_loss }} 盈{{ p.take_profit }} +
+ +
+ {% else %} +
今日暂无进行中的计划
+ {% endfor %} +
+
+
+ +
+

历史计划

+
+
+
+
+ + 重置 +
+
+ + + + {% for p in history %} + + + + + + + + + {% else %} + + {% endfor %} + +
日期品种方向决策区间决策理由状态
{{ p.plan_date or '' }}{{ p.symbol_name or p.symbol }}{{ '多' if p.direction == 'long' else '空' }}{{ p.zone_lower }}~{{ p.zone_upper }}{{ p.decision_reason or '—' }} + {% if p.status == 'closed' %}完成 + {% elif p.status == 'expired' %}失效 + {% else %}{{ p.status }}{% endif %} +
暂无历史
+
+
+
+
+{% 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 %} -
-
-

复盘上传

-
-
-
-
- - - - - -
-
-
- - - -
-
- - - - -
-
-
开仓时间
-
平仓时间
- - -
-
- - -
-
- - - - -
-
- - - - -
-
- - - - - -
-

勾选行为标签即为情绪单

-
- {% for tag in behavior_tags %} - - {% endfor %} -
-
-
-
- -
-

复盘历史

-
- - {% if preset == 'custom' or start or end %} -
- -
-
- -
- {% endif %} -
- - - - - - - - {% for r in reviews %} - - - - - - - - - - {% else %} - - {% endfor %} - -
平仓品种方向盈亏情绪单详情
{{ r.close_time[:16] if r.close_time else '' }}{{ r.symbol_name or r.symbol }}{{ '多' if r.direction == 'long' else '空' }} - {% if r.pnl and r.pnl > 0 %}{{ r.pnl }} - {% elif r.pnl and r.pnl < 0 %}{{ r.pnl }} - {% else %}{{ r.actual_pnl or '-' }}{% endif %} - {% if r.is_emotion %}情绪{% else %}-{% endif %} - -
暂无复盘记录
-
-
-
-
- - - -{% if auto_records %} -
-

系统自动记录(止盈/止损)

- - - - {% for r in auto_records %} - - - - - - - - - {% endfor %} - -
品种类型方向触发价结果时间
{{ 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 '' }}
-
-{% endif %} -{% endblock %} -{% block extra_js %} - -{% endblock %} +{% extends "base.html" %} +{% block title %}交易记录与复盘 - 国内期货监控系统{% endblock %} +{% block content %} +
+
+

复盘上传

+
+
+
+
+ + + + + +
+
+
+ + + +
+
+ + + + +
+
+
开仓时间
+
平仓时间
+ + +
+
+ + +
+
+ + + + +
+
+ + + + +
+
+ + + + + +
+

勾选行为标签即为情绪单

+
+ {% for tag in behavior_tags %} + + {% endfor %} +
+
+
+
+ +
+

复盘历史

+
+ + {% if preset == 'custom' or start or end %} +
+ +
+
+ +
+ {% endif %} +
+ + + + + + + + {% for r in reviews %} + + + + + + + + + + {% else %} + + {% endfor %} + +
平仓品种方向盈亏情绪单详情
{{ r.close_time[:16] if r.close_time else '' }}{{ r.symbol_name or r.symbol }}{{ '多' if r.direction == 'long' else '空' }} + {% if r.pnl and r.pnl > 0 %}{{ r.pnl }} + {% elif r.pnl and r.pnl < 0 %}{{ r.pnl }} + {% else %}{{ r.actual_pnl or '-' }}{% endif %} + {% if r.is_emotion %}情绪单{% else %}-{% endif %} + +
暂无复盘记录
+
+
+
+
+ + + +{% if auto_records %} +
+

系统自动记录(止盈/止损)

+ + + + {% for r in auto_records %} + + + + + + + + + {% endfor %} + +
品种类型方向触发价结果时间
{{ 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 '' }}
+
+{% endif %} +{% endblock %} +{% block extra_js %} + +{% endblock %}