双栏布局:开单计划/关键位/复盘;复盘字段与情绪单
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import sqlite3
|
|||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
import requests
|
import requests
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
@@ -44,6 +44,46 @@ def today_str() -> str:
|
|||||||
return datetime.now(TZ).date().isoformat()
|
return datetime.now(TZ).date().isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def calc_holding_duration(open_time: str, close_time: str) -> str:
|
||||||
|
try:
|
||||||
|
o = datetime.fromisoformat(open_time.strip())
|
||||||
|
c = datetime.fromisoformat(close_time.strip())
|
||||||
|
delta = c - o
|
||||||
|
if delta.total_seconds() < 0:
|
||||||
|
return ""
|
||||||
|
secs = int(delta.total_seconds())
|
||||||
|
h, rem = divmod(secs, 3600)
|
||||||
|
m, _ = divmod(rem, 60)
|
||||||
|
if h:
|
||||||
|
return f"{h}小时{m}分钟"
|
||||||
|
return f"{m}分钟"
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def calc_theoretical_pnl(direction: str, entry: float, target: float, lots: float) -> Optional[float]:
|
||||||
|
if entry is None or target is None or lots is None:
|
||||||
|
return None
|
||||||
|
if direction == "long":
|
||||||
|
return round((target - entry) * lots, 2)
|
||||||
|
if direction == "short":
|
||||||
|
return round((entry - target) * lots, 2)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_review_date_filter(preset: str, start: str, end: str) -> tuple[str, str]:
|
||||||
|
today = datetime.now(TZ).date()
|
||||||
|
if preset == "today":
|
||||||
|
s = today.isoformat()
|
||||||
|
return s, s
|
||||||
|
if preset == "week":
|
||||||
|
monday = today - timedelta(days=today.weekday())
|
||||||
|
return monday.isoformat(), today.isoformat()
|
||||||
|
if preset == "month":
|
||||||
|
return today.replace(day=1).isoformat(), today.isoformat()
|
||||||
|
return start.strip(), end.strip()
|
||||||
|
|
||||||
|
|
||||||
def expire_old_plans():
|
def expire_old_plans():
|
||||||
"""当日结束后计划自动失效,保留历史。"""
|
"""当日结束后计划自动失效,保留历史。"""
|
||||||
today = today_str()
|
today = today_str()
|
||||||
@@ -118,6 +158,18 @@ def init_db():
|
|||||||
"ALTER TABLE key_monitors ADD COLUMN market_code TEXT",
|
"ALTER TABLE key_monitors ADD COLUMN market_code TEXT",
|
||||||
"ALTER TABLE trade_records ADD COLUMN market_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 plan_date TEXT",
|
||||||
|
"ALTER TABLE key_monitors ADD COLUMN status TEXT DEFAULT 'active'",
|
||||||
|
"ALTER TABLE key_monitors ADD COLUMN archived_at TEXT",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN direction TEXT",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN entry_price REAL",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN stop_loss REAL",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN take_profit REAL",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN close_price REAL",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN lots REAL",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN holding_duration TEXT",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN initial_pnl REAL",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN actual_pnl REAL",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN is_emotion INTEGER DEFAULT 0",
|
||||||
]
|
]
|
||||||
for sql in migrations:
|
for sql in migrations:
|
||||||
try:
|
try:
|
||||||
@@ -309,7 +361,9 @@ def check_order_plans():
|
|||||||
|
|
||||||
def check_key_monitors():
|
def check_key_monitors():
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
rows = conn.execute("SELECT * FROM key_monitors").fetchall()
|
rows = conn.execute(
|
||||||
|
"SELECT * FROM key_monitors WHERE status='active' OR status IS NULL"
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
for r in rows:
|
for r in rows:
|
||||||
sym = r["symbol"]
|
sym = r["symbol"]
|
||||||
@@ -493,9 +547,14 @@ def del_plan(pid):
|
|||||||
@login_required
|
@login_required
|
||||||
def keys():
|
def keys():
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
key_list = conn.execute("SELECT * FROM key_monitors ORDER BY id DESC").fetchall()
|
key_list = conn.execute(
|
||||||
|
"SELECT * FROM key_monitors WHERE status='active' OR status IS NULL ORDER BY id DESC"
|
||||||
|
).fetchall()
|
||||||
|
history = conn.execute(
|
||||||
|
"SELECT * FROM key_monitors WHERE status='archived' ORDER BY archived_at DESC LIMIT 100"
|
||||||
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
return render_template("keys.html", keys=key_list)
|
return render_template("keys.html", keys=key_list, history=history)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/add_key", methods=["POST"])
|
@app.route("/add_key", methods=["POST"])
|
||||||
@@ -530,18 +589,24 @@ def add_key():
|
|||||||
@login_required
|
@login_required
|
||||||
def del_key(pid):
|
def del_key(pid):
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute("DELETE FROM key_monitors WHERE id=?", (pid,))
|
conn.execute(
|
||||||
|
"UPDATE key_monitors SET status='archived', archived_at=? WHERE id=?",
|
||||||
|
(datetime.now(TZ).isoformat(), pid),
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
flash("已删除")
|
flash("已移入监控历史")
|
||||||
return redirect(url_for("keys"))
|
return redirect(url_for("keys"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/records")
|
@app.route("/records")
|
||||||
@login_required
|
@login_required
|
||||||
def records():
|
def records():
|
||||||
|
preset = request.args.get("preset", "")
|
||||||
start = request.args.get("start", "")
|
start = request.args.get("start", "")
|
||||||
end = request.args.get("end", "")
|
end = request.args.get("end", "")
|
||||||
|
if preset:
|
||||||
|
start, end = parse_review_date_filter(preset, start, end)
|
||||||
|
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
sql = "SELECT * FROM review_records WHERE 1=1"
|
sql = "SELECT * FROM review_records WHERE 1=1"
|
||||||
@@ -556,7 +621,7 @@ def records():
|
|||||||
review_list = conn.execute(sql, params).fetchall()
|
review_list = conn.execute(sql, params).fetchall()
|
||||||
|
|
||||||
auto_list = conn.execute(
|
auto_list = conn.execute(
|
||||||
"SELECT * FROM trade_records ORDER BY id DESC LIMIT 50"
|
"SELECT * FROM trade_records ORDER BY id DESC LIMIT 30"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -564,6 +629,7 @@ def records():
|
|||||||
"records.html",
|
"records.html",
|
||||||
reviews=review_list,
|
reviews=review_list,
|
||||||
auto_records=auto_list,
|
auto_records=auto_list,
|
||||||
|
preset=preset,
|
||||||
start=start,
|
start=start,
|
||||||
end=end,
|
end=end,
|
||||||
open_types=OPEN_TYPES,
|
open_types=OPEN_TYPES,
|
||||||
@@ -596,6 +662,7 @@ def add_review():
|
|||||||
f.save(os.path.join(UPLOAD_DIR, screenshot))
|
f.save(os.path.join(UPLOAD_DIR, screenshot))
|
||||||
|
|
||||||
tags = [t for t in BEHAVIOR_TAGS if d.get(f"tag_{t}")]
|
tags = [t for t in BEHAVIOR_TAGS if d.get(f"tag_{t}")]
|
||||||
|
is_emotion = 1 if tags else 0
|
||||||
|
|
||||||
def num(key: str) -> Optional[float]:
|
def num(key: str) -> Optional[float]:
|
||||||
v = d.get(key, "").strip()
|
v = d.get(key, "").strip()
|
||||||
@@ -603,21 +670,37 @@ def add_review():
|
|||||||
return None
|
return None
|
||||||
return float(v)
|
return float(v)
|
||||||
|
|
||||||
|
open_time = d.get("open_time", "").strip()
|
||||||
|
close_time = d.get("close_time", "").strip()
|
||||||
|
direction = d.get("direction", "").strip()
|
||||||
|
entry_price = num("entry_price")
|
||||||
|
stop_loss = num("stop_loss")
|
||||||
|
take_profit = num("take_profit")
|
||||||
|
close_price = num("close_price")
|
||||||
|
lots = num("lots") or 1.0
|
||||||
|
|
||||||
|
holding = calc_holding_duration(open_time, close_time)
|
||||||
|
initial_pnl = calc_theoretical_pnl(direction, entry_price, take_profit, lots)
|
||||||
|
actual_pnl = calc_theoretical_pnl(direction, entry_price, close_price, lots)
|
||||||
|
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO review_records
|
"""INSERT INTO review_records
|
||||||
(open_time, close_time, symbol, timeframe, pnl,
|
(open_time, close_time, symbol, timeframe, direction,
|
||||||
|
entry_price, stop_loss, take_profit, close_price, lots,
|
||||||
|
holding_duration, initial_pnl, actual_pnl, pnl,
|
||||||
open_type, expected_rr, actual_rr, exit_trigger, exit_supplement,
|
open_type, expected_rr, actual_rr, exit_trigger, exit_supplement,
|
||||||
watch_after_breakeven, new_position_while_occupied, screenshot,
|
watch_after_breakeven, new_position_while_occupied, screenshot,
|
||||||
auto_kline, kline_period1, kline_period2, kline_count, kline_cutoff,
|
auto_kline, kline_period1, kline_period2, kline_count, kline_cutoff,
|
||||||
behavior_tags, notes)
|
behavior_tags, is_emotion, notes)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
(
|
(
|
||||||
d.get("open_time", "").strip(),
|
open_time, close_time,
|
||||||
d.get("close_time", "").strip(),
|
|
||||||
d.get("symbol", "").strip(),
|
d.get("symbol", "").strip(),
|
||||||
d.get("timeframe", "").strip(),
|
d.get("timeframe", "").strip(),
|
||||||
num("pnl"),
|
direction,
|
||||||
|
entry_price, stop_loss, take_profit, close_price, lots,
|
||||||
|
holding, initial_pnl, actual_pnl, num("pnl"),
|
||||||
open_type,
|
open_type,
|
||||||
num("expected_rr"),
|
num("expected_rr"),
|
||||||
num("actual_rr"),
|
num("actual_rr"),
|
||||||
@@ -632,6 +715,7 @@ def add_review():
|
|||||||
int(d.get("kline_count") or 300),
|
int(d.get("kline_count") or 300),
|
||||||
d.get("kline_cutoff", "平仓时间"),
|
d.get("kline_cutoff", "平仓时间"),
|
||||||
",".join(tags),
|
",".join(tags),
|
||||||
|
is_emotion,
|
||||||
d.get("notes", "").strip(),
|
d.get("notes", "").strip(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
(function () {
|
||||||
|
function parseNum(v) {
|
||||||
|
var n = parseFloat(v);
|
||||||
|
return isNaN(n) ? null : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcPnl(direction, entry, target, lots) {
|
||||||
|
if (!entry || !target || !lots) return '';
|
||||||
|
if (direction === 'long') return ((target - entry) * lots).toFixed(2);
|
||||||
|
if (direction === 'short') return ((entry - target) * lots).toFixed(2);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 lots = parseNum(form.querySelector('[name="lots"]').value) || 1;
|
||||||
|
var openT = form.querySelector('[name="open_time"]').value;
|
||||||
|
var closeT = form.querySelector('[name="close_time"]').value;
|
||||||
|
|
||||||
|
var hold = document.getElementById('holding_duration');
|
||||||
|
var initP = document.getElementById('initial_pnl');
|
||||||
|
var actP = document.getElementById('actual_pnl');
|
||||||
|
if (hold) hold.value = calcDuration(openT, closeT);
|
||||||
|
if (initP) initP.value = calcPnl(dir, entry, tp, lots);
|
||||||
|
if (actP) actP.value = calcPnl(dir, entry, close, lots);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = '<div class="modal-grid">';
|
||||||
|
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],
|
||||||
|
['预期RR', data.expected_rr], ['实际RR', data.actual_rr],
|
||||||
|
['离场触发', data.exit_trigger], ['离场补充', data.exit_supplement],
|
||||||
|
['情绪单', data.is_emotion ? '是' : '否'],
|
||||||
|
['行为标签', data.behavior_tags], ['备注', data.notes]
|
||||||
|
];
|
||||||
|
fields.forEach(function (pair) {
|
||||||
|
html += '<div class="item"><label>' + pair[0] + '</label><div>' + (pair[1] || '-') + '</div></div>';
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
if (data.screenshot) {
|
||||||
|
html += '<div style="margin-top:1rem"><img src="/uploads/' + data.screenshot + '" style="max-width:100%;border-radius:8px"></div>';
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -63,6 +63,28 @@
|
|||||||
.filter-row{display:flex;gap:.75rem;flex-wrap:wrap;align-items:flex-end;margin-bottom:1rem}
|
.filter-row{display:flex;gap:.75rem;flex-wrap:wrap;align-items:flex-end;margin-bottom:1rem}
|
||||||
.filter-row .field{width:auto;min-width:140px}
|
.filter-row .field{width:auto;min-width:140px}
|
||||||
.filter-row button{width:auto}
|
.filter-row button{width:auto}
|
||||||
|
.split-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;align-items:stretch;margin-bottom:1.5rem}
|
||||||
|
.split-grid .card{margin-bottom:0;height:100%;min-height:560px;display:flex;flex-direction:column}
|
||||||
|
.split-grid .card-body{flex:1;overflow:auto}
|
||||||
|
.card-scroll{max-height:420px;overflow-y:auto}
|
||||||
|
.preset-tabs{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}
|
||||||
|
.preset-tabs a{padding:.45rem .85rem;border-radius:8px;border:1px solid #2e2e45;color:#a9a9c4;text-decoration:none;font-size:.85rem}
|
||||||
|
.preset-tabs a.active,.preset-tabs a:hover{background:#1e2533;color:#4cc2ff;border-color:#4cc2ff}
|
||||||
|
.btn-link{color:#4cc2ff;cursor:pointer;font-size:.85rem;background:none;border:none;padding:0}
|
||||||
|
.btn-link:hover{text-decoration:underline}
|
||||||
|
.modal-mask{position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:1000;display:none;align-items:center;justify-content:center;padding:1rem}
|
||||||
|
.modal-mask.show{display:flex}
|
||||||
|
.modal-box{background:#12121a;border:1px solid #242435;border-radius:16px;max-width:900px;width:100%;max-height:90vh;overflow:auto;padding:1.5rem}
|
||||||
|
.modal-box h3{margin-bottom:1rem;color:#c4c4ff}
|
||||||
|
.modal-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.75rem;font-size:.9rem}
|
||||||
|
.modal-grid .item label{color:#888;font-size:.75rem;display:block}
|
||||||
|
.modal-grid .item div{margin-top:.2rem}
|
||||||
|
.modal-close{float:right;color:#888;cursor:pointer;font-size:1.2rem}
|
||||||
|
.calc-readonly{background:#161625;color:#4cc2ff}
|
||||||
|
@media(max-width:1100px){
|
||||||
|
.split-grid{grid-template-columns:1fr}
|
||||||
|
.split-grid .card{min-height:auto}
|
||||||
|
}
|
||||||
@media(max-width:768px){
|
@media(max-width:768px){
|
||||||
.topbar-inner{flex-wrap:wrap;height:auto;padding:.75rem 0}
|
.topbar-inner{flex-wrap:wrap;height:auto;padding:.75rem 0}
|
||||||
.nav{order:3;width:100%}
|
.nav{order:3;width:100%}
|
||||||
|
|||||||
+36
-13
@@ -3,11 +3,13 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="page-title">关键位监控</h1>
|
<h1 class="page-title">关键位监控</h1>
|
||||||
|
|
||||||
|
<div class="split-grid">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>新增监控</h2>
|
<h2>新增监控</h2>
|
||||||
|
<div class="card-body">
|
||||||
<form action="{{ url_for('add_key') }}" method="post" class="form-row">
|
<form action="{{ url_for('add_key') }}" method="post" class="form-row">
|
||||||
<div class="symbol-wrap">
|
<div class="symbol-wrap" style="min-width:180px;flex:1">
|
||||||
<input type="text" class="symbol-input" placeholder="输入中文名或同花顺代码" autocomplete="off" required>
|
<input type="text" class="symbol-input" placeholder="中文名或同花顺代码" autocomplete="off" required>
|
||||||
<input type="hidden" name="symbol" required>
|
<input type="hidden" name="symbol" required>
|
||||||
<input type="hidden" name="symbol_name">
|
<input type="hidden" name="symbol_name">
|
||||||
<input type="hidden" name="market_code" required>
|
<input type="hidden" name="market_code" required>
|
||||||
@@ -22,32 +24,53 @@
|
|||||||
<option value="关键支撑位">关键支撑位</option>
|
<option value="关键支撑位">关键支撑位</option>
|
||||||
</select>
|
</select>
|
||||||
<select name="direction" required>
|
<select name="direction" required>
|
||||||
<option value="">选择方向</option>
|
<option value="">方向</option>
|
||||||
<option value="long">做多</option>
|
<option value="long">做多</option>
|
||||||
<option value="short">做空</option>
|
<option value="short">做空</option>
|
||||||
</select>
|
</select>
|
||||||
<input name="upper" type="number" step="0.0001" placeholder="上沿/阻力" required>
|
<input name="upper" type="number" step="0.0001" placeholder="上沿/阻力" required>
|
||||||
<input name="lower" type="number" step="0.0001" placeholder="下沿/支撑" required>
|
<input name="lower" type="number" step="0.0001" placeholder="下沿/支撑" required>
|
||||||
<button type="submit" class="btn-primary">添加监控</button>
|
<button type="submit" class="btn-primary">添加</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
<h3 style="font-size:.95rem;color:#a9a9ff;margin:1rem 0 .75rem">监控列表</h3>
|
||||||
|
<div class="list card-scroll">
|
||||||
<div class="card">
|
|
||||||
<h2>监控列表</h2>
|
|
||||||
<div class="list">
|
|
||||||
{% for k in keys %}
|
{% for k in keys %}
|
||||||
<div class="list-item">
|
<div class="list-item">
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ k.symbol_name or k.symbol }}</strong> | {{ k.monitor_type }}
|
<strong>{{ k.symbol_name or k.symbol }}</strong> | {{ k.monitor_type }}
|
||||||
<span class="badge dir">{{ '做多' if k.direction == 'long' else '做空' }}</span>
|
<span class="badge dir">{{ '做多' if k.direction == 'long' else '做空' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>上: {{ k.upper }} | 下: {{ k.lower }}</div>
|
<div>上 {{ k.upper }} | 下 {{ k.lower }}</div>
|
||||||
<div style="font-size:.8rem;color:#888">同花顺: {{ k.symbol }}</div>
|
<a href="{{ url_for('del_key', pid=k.id) }}" class="btn-del" onclick="return confirm('移入历史?')">删除</a>
|
||||||
<a href="{{ url_for('del_key', pid=k.id) }}" class="btn-del" onclick="return confirm('删除?')">删除</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div style="color:#888;padding:1rem">暂无关键位监控</div>
|
<div style="color:#888;padding:1rem">暂无监控</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>监控历史</h2>
|
||||||
|
<div class="card-body card-scroll">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>品种</th><th>类型</th><th>方向</th><th>上沿</th><th>下沿</th><th>归档时间</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for k in history %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ k.symbol_name or k.symbol }}</td>
|
||||||
|
<td>{{ k.monitor_type }}</td>
|
||||||
|
<td><span class="badge dir">{{ '多' if k.direction == 'long' else '空' }}</span></td>
|
||||||
|
<td>{{ k.upper }}</td>
|
||||||
|
<td>{{ k.lower }}</td>
|
||||||
|
<td>{{ k.archived_at[:16] if k.archived_at else '' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr><td colspan="6" style="color:#888">暂无历史</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+33
-53
@@ -3,11 +3,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="page-title">开单计划 <span style="font-size:.9rem;color:#888;font-weight:normal">今日 {{ today }}</span></h1>
|
<h1 class="page-title">开单计划 <span style="font-size:.9rem;color:#888;font-weight:normal">今日 {{ today }}</span></h1>
|
||||||
|
|
||||||
|
<div class="split-grid">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>今日计划(开盘前制定,当日有效)</h2>
|
<h2>今日计划</h2>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="hint" style="margin-bottom:1rem">开盘前制定,当日有效;下方为进行中计划。</p>
|
||||||
<form action="{{ url_for('add_plan') }}" method="post" class="form-row">
|
<form action="{{ url_for('add_plan') }}" method="post" class="form-row">
|
||||||
<div class="symbol-wrap" style="min-width:200px">
|
<div class="symbol-wrap" style="min-width:180px;flex:1">
|
||||||
<input type="text" class="symbol-input" placeholder="输入中文名或同花顺代码" autocomplete="off" required>
|
<input type="text" class="symbol-input" placeholder="中文名或同花顺代码" autocomplete="off" required>
|
||||||
<input type="hidden" name="symbol" required>
|
<input type="hidden" name="symbol" required>
|
||||||
<input type="hidden" name="symbol_name">
|
<input type="hidden" name="symbol_name">
|
||||||
<input type="hidden" name="market_code" required>
|
<input type="hidden" name="market_code" required>
|
||||||
@@ -16,91 +19,68 @@
|
|||||||
<div class="symbol-selected"></div>
|
<div class="symbol-selected"></div>
|
||||||
</div>
|
</div>
|
||||||
<select name="direction" required>
|
<select name="direction" required>
|
||||||
<option value="">选择方向</option>
|
<option value="">方向</option>
|
||||||
<option value="long">做多</option>
|
<option value="long">做多</option>
|
||||||
<option value="short">做空</option>
|
<option value="short">做空</option>
|
||||||
</select>
|
</select>
|
||||||
<input name="zone_lower" type="number" step="0.0001" placeholder="决策区间下限" required>
|
<input name="zone_lower" type="number" step="0.0001" placeholder="区间下限" required>
|
||||||
<input name="zone_upper" type="number" step="0.0001" placeholder="决策区间上限" required>
|
<input name="zone_upper" type="number" step="0.0001" placeholder="区间上限" required>
|
||||||
<input name="stop_loss" type="number" step="0.0001" placeholder="止损价位" required>
|
<input name="stop_loss" type="number" step="0.0001" placeholder="止损" required>
|
||||||
<input name="take_profit" type="number" step="0.0001" placeholder="止盈价位" required>
|
<input name="take_profit" type="number" step="0.0001" placeholder="止盈" required>
|
||||||
<button type="submit" class="btn-primary">添加今日计划</button>
|
<button type="submit" class="btn-primary">添加</button>
|
||||||
</form>
|
</form>
|
||||||
<p class="hint">计划仅当日有效,次日 0 点自动失效并归入历史;触发止盈/止损后标记为已完成。</p>
|
<h3 style="font-size:.95rem;color:#a9a9ff;margin:1rem 0 .75rem">进行中</h3>
|
||||||
</div>
|
<div class="list card-scroll">
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2>今日进行中</h2>
|
|
||||||
<div class="list">
|
|
||||||
{% for p in plans %}
|
{% for p in plans %}
|
||||||
<div class="list-item">
|
<div class="list-item">
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ p.symbol_name or p.symbol }}</strong>
|
<strong>{{ p.symbol_name or p.symbol }}</strong>
|
||||||
<span class="badge dir">{{ '做多' if p.direction == 'long' else '做空' }}</span>
|
<span class="badge dir">{{ '做多' if p.direction == 'long' else '做空' }}</span>
|
||||||
{% if p.status == 'planned' %}
|
{% if p.status == 'planned' %}<span class="badge planned">待触发</span>
|
||||||
<span class="badge planned">待触发</span>
|
{% else %}<span class="badge active">已激活</span>{% endif %}
|
||||||
{% else %}
|
|
||||||
<span class="badge active">已激活</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div>区间: {{ p.zone_lower }} ~ {{ p.zone_upper }}</div>
|
<div>区间 {{ p.zone_lower }}~{{ p.zone_upper }} | 损{{ p.stop_loss }} 盈{{ p.take_profit }}</div>
|
||||||
<div>止损: {{ p.stop_loss }} | 止盈: {{ p.take_profit }}</div>
|
<a href="{{ url_for('del_plan', pid=p.id) }}" class="btn-del" onclick="return confirm('删除?')">删除</a>
|
||||||
<div style="font-size:.8rem;color:#888">同花顺: {{ p.symbol }}</div>
|
|
||||||
<a href="{{ url_for('del_plan', pid=p.id) }}" class="btn-del" onclick="return confirm('删除此计划?')">删除</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div style="color:#888;padding:1rem">今日暂无开单计划</div>
|
<div style="color:#888;padding:1rem">今日暂无进行中的计划</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>历史计划</h2>
|
<h2>历史计划</h2>
|
||||||
|
<div class="card-body">
|
||||||
<form method="get" class="filter-row">
|
<form method="get" class="filter-row">
|
||||||
<div class="field">
|
<div class="field"><label>开始</label><input type="date" name="start" value="{{ start }}"></div>
|
||||||
<label>开始日期</label>
|
<div class="field"><label>结束</label><input type="date" name="end" value="{{ end }}"></div>
|
||||||
<input type="date" name="start" value="{{ start }}">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>结束日期</label>
|
|
||||||
<input type="date" name="end" value="{{ end }}">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn-primary">筛选</button>
|
<button type="submit" class="btn-primary">筛选</button>
|
||||||
<a href="{{ url_for('plans') }}" style="color:#888;font-size:.85rem;padding:.7rem">重置</a>
|
<a href="{{ url_for('plans') }}" style="color:#888;font-size:.85rem;padding:.7rem">重置</a>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="card-scroll">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead><tr><th>日期</th><th>品种</th><th>方向</th><th>区间</th><th>状态</th></tr></thead>
|
||||||
<tr>
|
|
||||||
<th>日期</th>
|
|
||||||
<th>品种</th>
|
|
||||||
<th>方向</th>
|
|
||||||
<th>区间</th>
|
|
||||||
<th>止损</th>
|
|
||||||
<th>止盈</th>
|
|
||||||
<th>状态</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for p in history %}
|
{% for p in history %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ p.plan_date or '' }}</td>
|
<td>{{ p.plan_date or '' }}</td>
|
||||||
<td>{{ p.symbol_name or p.symbol }}</td>
|
<td>{{ p.symbol_name or p.symbol }}</td>
|
||||||
<td><span class="badge dir">{{ '做多' if p.direction == 'long' else '做空' }}</span></td>
|
<td><span class="badge dir">{{ '多' if p.direction == 'long' else '空' }}</span></td>
|
||||||
<td>{{ p.zone_lower }}~{{ p.zone_upper }}</td>
|
<td>{{ p.zone_lower }}~{{ p.zone_upper }}</td>
|
||||||
<td>{{ p.stop_loss }}</td>
|
|
||||||
<td>{{ p.take_profit }}</td>
|
|
||||||
<td>
|
<td>
|
||||||
{% if p.status == 'closed' %}<span class="badge profit">已完成</span>
|
{% if p.status == 'closed' %}<span class="badge profit">完成</span>
|
||||||
{% elif p.status == 'expired' %}<span class="badge expired">已失效</span>
|
{% elif p.status == 'expired' %}<span class="badge expired">失效</span>
|
||||||
{% else %}<span class="badge">{{ p.status }}</span>{% endif %}
|
{% else %}{{ p.status }}{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td><a href="{{ url_for('del_plan', pid=p.id) }}" class="btn-del" onclick="return confirm('删除?')">删</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="8" style="color:#888">暂无历史记录</td></tr>
|
<tr><td colspan="5" style="color:#888">暂无历史</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+88
-130
@@ -1,211 +1,166 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}交易记录与复盘 - 国内期货监控系统{% endblock %}
|
{% block title %}交易记录与复盘 - 国内期货监控系统{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="page-title">交易复盘记录上传(含截图)</h1>
|
<h1 class="page-title">交易记录与复盘</h1>
|
||||||
|
|
||||||
|
<div class="split-grid">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<form action="{{ url_for('add_review') }}" method="post" enctype="multipart/form-data">
|
<h2>交易复盘记录上传(含截图)</h2>
|
||||||
|
<div class="card-body card-scroll">
|
||||||
|
<form id="review-form" action="{{ url_for('add_review') }}" method="post" enctype="multipart/form-data">
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
|
<div class="field"><label>品种</label><input name="symbol" placeholder="ag2608" required></div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>开仓时间</label>
|
<label>方向</label>
|
||||||
<input type="datetime-local" name="open_time" required>
|
<select name="direction" required>
|
||||||
</div>
|
<option value="">请选择</option>
|
||||||
<div class="field">
|
<option value="long">做多</option>
|
||||||
<label>平仓时间</label>
|
<option value="short">做空</option>
|
||||||
<input type="datetime-local" name="close_time" required>
|
</select>
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>品种</label>
|
|
||||||
<input name="symbol" placeholder="如 ag2608" required>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>周期</label>
|
|
||||||
<input name="timeframe" placeholder="5m" value="5m">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>盈亏(U)</label>
|
|
||||||
<input name="pnl" type="number" step="0.01" placeholder="盈亏金额">
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field"><label>成交价</label><input name="entry_price" type="number" step="0.0001" required></div>
|
||||||
|
<div class="field"><label>止损</label><input name="stop_loss" type="number" step="0.0001" required></div>
|
||||||
|
<div class="field"><label>止盈</label><input name="take_profit" type="number" step="0.0001" required></div>
|
||||||
|
<div class="field"><label>平仓价格</label><input name="close_price" type="number" step="0.0001" required></div>
|
||||||
|
<div class="field"><label>张数</label><input name="lots" type="number" step="1" min="1" value="1" required></div>
|
||||||
|
<div class="field"><label>开仓时间</label><input type="datetime-local" name="open_time" required></div>
|
||||||
|
<div class="field"><label>平仓时间</label><input type="datetime-local" name="close_time" required></div>
|
||||||
|
<div class="field"><label>持仓时长(自动)</label><input id="holding_duration" type="text" readonly class="calc-readonly" placeholder="自动计算"></div>
|
||||||
|
<div class="field"><label>初始盈亏(自动)</label><input id="initial_pnl" type="text" readonly class="calc-readonly" placeholder="按止盈测算"></div>
|
||||||
|
<div class="field"><label>实际盈亏(自动)</label><input id="actual_pnl" type="text" readonly class="calc-readonly" placeholder="按平仓价测算"></div>
|
||||||
|
<div class="field"><label>盈亏金额(手动)</label><input name="pnl" type="number" step="0.01" placeholder="实际盈亏金额"></div>
|
||||||
|
<div class="field"><label>周期</label><input name="timeframe" value="5m"></div>
|
||||||
<div class="field full">
|
<div class="field full">
|
||||||
<label>开仓类型(必选)</label>
|
<label>开仓类型(必选)</label>
|
||||||
<select name="open_type" required>
|
<select name="open_type" required>
|
||||||
<option value="">请选择</option>
|
<option value="">请选择</option>
|
||||||
{% for t in open_types %}
|
{% for t in open_types %}<option value="{{ t }}">{{ t }}</option>{% endfor %}
|
||||||
<option value="{{ t }}">{{ t }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field"><label>预期 RR</label><input name="expected_rr" type="number" step="0.01"></div>
|
||||||
<label>预期 RR</label>
|
<div class="field"><label>实际 RR</label><input name="actual_rr" type="number" step="0.01"></div>
|
||||||
<input name="expected_rr" type="number" step="0.01" placeholder="预期盈亏比">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>实际 RR</label>
|
|
||||||
<input name="actual_rr" type="number" step="0.01" placeholder="实际盈亏比">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>离场触发(必选)</label>
|
<label>离场触发(必选)</label>
|
||||||
<select name="exit_trigger" required>
|
<select name="exit_trigger" required>
|
||||||
<option value="">请选择</option>
|
<option value="">请选择</option>
|
||||||
{% for t in exit_triggers %}
|
{% for t in exit_triggers %}<option value="{{ t }}">{{ t }}</option>{% endfor %}
|
||||||
<option value="{{ t }}">{{ t }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field"><label>离场补充</label><input name="exit_supplement" placeholder="手工平仓说明"></div>
|
||||||
<label>离场补充(仅手工平仓必填)</label>
|
|
||||||
<input name="exit_supplement" placeholder="手工平仓说明">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>保本后盯盘</label>
|
<label>保本后盯盘</label>
|
||||||
<select name="watch_after_breakeven">
|
<select name="watch_after_breakeven"><option value="否">否</option><option value="是">是</option></select>
|
||||||
<option value="否">否</option>
|
|
||||||
<option value="是">是</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>占用时新开仓</label>
|
<label>占用时新开仓</label>
|
||||||
<select name="new_position_while_occupied">
|
<select name="new_position_while_occupied"><option value="否">否</option><option value="是">是</option></select>
|
||||||
<option value="否">否</option>
|
|
||||||
<option value="是">是</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>截图上传</label>
|
|
||||||
<input type="file" name="screenshot" accept="image/*">
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field"><label>截图上传</label><input type="file" name="screenshot" accept="image/*"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="check-row">
|
<div class="check-row"><label><input type="checkbox" name="auto_kline" value="1"> 保存时自动生成 K 线图并作为截图</label></div>
|
||||||
<label><input type="checkbox" name="auto_kline" value="1"> 保存时自动生成 K 线图并作为截图</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="field">
|
<div class="field"><label>周期1</label><select name="kline_period1">{% for p in kline_periods %}<option value="{{ p }}" {% if p=='15m' %}selected{% endif %}>{{ p }}</option>{% endfor %}</select></div>
|
||||||
<label>周期1</label>
|
<div class="field"><label>周期2</label><select name="kline_period2">{% for p in kline_periods %}<option value="{{ p }}" {% if p=='1h' %}selected{% endif %}>{{ p }}</option>{% endfor %}</select></div>
|
||||||
<select name="kline_period1">
|
<div class="field"><label>K线数</label><input name="kline_count" type="number" value="300"></div>
|
||||||
{% for p in kline_periods %}
|
<div class="field"><label>K线截止</label><select name="kline_cutoff">{% for c in kline_cutoffs %}<option value="{{ c }}">{{ c }}</option>{% endfor %}</select></div>
|
||||||
<option value="{{ p }}" {% if p == '15m' %}selected{% endif %}>{{ p }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
|
||||||
<label>周期2</label>
|
|
||||||
<select name="kline_period2">
|
|
||||||
{% for p in kline_periods %}
|
|
||||||
<option value="{{ p }}" {% if p == '1h' %}selected{% endif %}>{{ p }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>K线数</label>
|
|
||||||
<input name="kline_count" type="number" value="300" min="50" max="1000">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>K线截止</label>
|
|
||||||
<select name="kline_cutoff">
|
|
||||||
{% for c in kline_cutoffs %}
|
|
||||||
<option value="{{ c }}">{{ c }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="hint">勾选自动生成时,将按所选周期上下排列 K 线图;截止时间为平仓/开仓/当前,用于截取行情片段。</p>
|
|
||||||
|
|
||||||
|
<p class="hint">下方勾选行为标签的均为<strong>情绪单</strong></p>
|
||||||
<div class="check-row">
|
<div class="check-row">
|
||||||
{% for tag in behavior_tags %}
|
{% for tag in behavior_tags %}
|
||||||
<label><input type="checkbox" name="tag_{{ tag }}" value="1"> {{ tag }}</label>
|
<label><input type="checkbox" name="tag_{{ tag }}" value="1"> {{ tag }}</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field" style="margin-top:.75rem"><label>备注</label><textarea name="notes"></textarea></div>
|
||||||
<div class="field" style="margin-top:1rem">
|
|
||||||
<label>备注</label>
|
|
||||||
<textarea name="notes" placeholder="复盘备注"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn-primary" style="margin-top:1rem">保存复盘记录</button>
|
<button type="submit" class="btn-primary" style="margin-top:1rem">保存复盘记录</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>复盘历史</h2>
|
<h2>复盘历史</h2>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="preset-tabs">
|
||||||
|
<a href="{{ url_for('records', preset='today') }}" class="{% if preset=='today' %}active{% endif %}">本日</a>
|
||||||
|
<a href="{{ url_for('records', preset='week') }}" class="{% if preset=='week' %}active{% endif %}">本周</a>
|
||||||
|
<a href="{{ url_for('records', preset='month') }}" class="{% if preset=='month' %}active{% endif %}">本月</a>
|
||||||
|
<a href="{{ url_for('records', preset='custom') }}" class="{% if preset=='custom' %}active{% endif %}">自定义</a>
|
||||||
|
</div>
|
||||||
|
{% if preset == 'custom' or start or end %}
|
||||||
<form method="get" class="filter-row">
|
<form method="get" class="filter-row">
|
||||||
<div class="field">
|
<input type="hidden" name="preset" value="custom">
|
||||||
<label>开始日期</label>
|
<div class="field"><label>开始</label><input type="date" name="start" value="{{ start }}"></div>
|
||||||
<input type="date" name="start" value="{{ start }}">
|
<div class="field"><label>结束</label><input type="date" name="end" value="{{ end }}"></div>
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>结束日期</label>
|
|
||||||
<input type="date" name="end" value="{{ end }}">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn-primary">筛选</button>
|
<button type="submit" class="btn-primary">筛选</button>
|
||||||
<a href="{{ url_for('records') }}" style="color:#888;font-size:.85rem;padding:.7rem">重置</a>
|
|
||||||
</form>
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
<div class="card-scroll">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>开仓</th>
|
<th>平仓</th><th>品种</th><th>方向</th><th>盈亏</th><th>情绪单</th><th>详情</th><th></th>
|
||||||
<th>平仓</th>
|
|
||||||
<th>品种</th>
|
|
||||||
<th>周期</th>
|
|
||||||
<th>盈亏</th>
|
|
||||||
<th>开仓类型</th>
|
|
||||||
<th>预期RR</th>
|
|
||||||
<th>实际RR</th>
|
|
||||||
<th>离场</th>
|
|
||||||
<th>行为标签</th>
|
|
||||||
<th>截图</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for r in reviews %}
|
{% for r in reviews %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ r.open_time[:16] if r.open_time else '' }}</td>
|
|
||||||
<td>{{ r.close_time[:16] if r.close_time else '' }}</td>
|
<td>{{ r.close_time[:16] if r.close_time else '' }}</td>
|
||||||
<td>{{ r.symbol }}</td>
|
<td>{{ r.symbol }}</td>
|
||||||
<td>{{ r.timeframe }}</td>
|
<td><span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span></td>
|
||||||
<td>
|
<td>
|
||||||
{% if r.pnl and r.pnl > 0 %}<span class="badge profit">{{ r.pnl }}</span>
|
{% if r.pnl and r.pnl > 0 %}<span class="badge profit">{{ r.pnl }}</span>
|
||||||
{% elif r.pnl and r.pnl < 0 %}<span class="badge loss">{{ r.pnl }}</span>
|
{% elif r.pnl and r.pnl < 0 %}<span class="badge loss">{{ r.pnl }}</span>
|
||||||
{% else %}{{ r.pnl or '-' }}{% endif %}
|
{% else %}{{ r.actual_pnl or '-' }}{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ r.open_type }}</td>
|
<td>{% if r.is_emotion %}<span class="badge loss">情绪</span>{% else %}-{% endif %}</td>
|
||||||
<td>{{ r.expected_rr or '-' }}</td>
|
|
||||||
<td>{{ r.actual_rr or '-' }}</td>
|
|
||||||
<td>{{ r.exit_trigger }}</td>
|
|
||||||
<td style="font-size:.8rem">{{ r.behavior_tags or '-' }}</td>
|
|
||||||
<td>
|
<td>
|
||||||
{% if r.screenshot %}
|
<button type="button" class="btn-link review-view-btn" data-review='{{ {
|
||||||
<a href="{{ url_for('uploaded_file', filename=r.screenshot) }}" target="_blank" style="color:#4cc2ff">查看</a>
|
"symbol": r.symbol, "direction": "做多" if r.direction=="long" else "做空",
|
||||||
{% else %}-{% endif %}
|
"entry_price": r.entry_price, "stop_loss": r.stop_loss, "take_profit": r.take_profit,
|
||||||
|
"close_price": r.close_price, "lots": r.lots,
|
||||||
|
"open_time": r.open_time, "close_time": r.close_time,
|
||||||
|
"holding_duration": r.holding_duration, "initial_pnl": r.initial_pnl,
|
||||||
|
"actual_pnl": r.actual_pnl, "pnl": r.pnl,
|
||||||
|
"open_type": r.open_type, "expected_rr": r.expected_rr, "actual_rr": r.actual_rr,
|
||||||
|
"exit_trigger": r.exit_trigger, "exit_supplement": r.exit_supplement,
|
||||||
|
"is_emotion": r.is_emotion, "behavior_tags": r.behavior_tags,
|
||||||
|
"notes": r.notes, "screenshot": r.screenshot
|
||||||
|
} | tojson }}'>放大查看</button>
|
||||||
</td>
|
</td>
|
||||||
<td><a href="{{ url_for('del_review', rid=r.id) }}" class="btn-del" onclick="return confirm('删除?')">删</a></td>
|
<td><a href="{{ url_for('del_review', rid=r.id) }}" class="btn-del" onclick="return confirm('删除?')">删</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="12" style="color:#888">暂无复盘记录</td></tr>
|
<tr><td colspan="7" style="color:#888">暂无复盘记录</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="review-modal" class="modal-mask">
|
||||||
|
<div class="modal-box">
|
||||||
|
<span class="modal-close">✕</span>
|
||||||
|
<h3>复盘详情</h3>
|
||||||
|
<div id="review-modal-body"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if auto_records %}
|
{% if auto_records %}
|
||||||
<div class="card">
|
<div class="card" style="margin-top:1.5rem">
|
||||||
<h2>系统自动记录(止盈/止损)</h2>
|
<h2>系统自动记录(止盈/止损)</h2>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead><tr><th>品种</th><th>类型</th><th>方向</th><th>触发价</th><th>结果</th><th>时间</th></tr></thead>
|
||||||
<tr><th>品种</th><th>类型</th><th>方向</th><th>触发价</th><th>结果</th><th>时间</th></tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for r in auto_records %}
|
{% for r in auto_records %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ r.symbol_name or r.symbol }}</td>
|
<td>{{ r.symbol_name or r.symbol }}</td>
|
||||||
<td>{{ r.monitor_type }}</td>
|
<td>{{ r.monitor_type }}</td>
|
||||||
<td><span class="badge dir">{{ '做多' if r.direction == 'long' else '做空' }}</span></td>
|
<td><span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span></td>
|
||||||
<td>{{ r.trigger_price }}</td>
|
<td>{{ r.trigger_price }}</td>
|
||||||
<td>
|
<td>{% if r.result == '止盈' %}<span class="badge profit">止盈</span>{% else %}<span class="badge loss">止损</span>{% endif %}</td>
|
||||||
{% if r.result == '止盈' %}<span class="badge profit">止盈</span>
|
|
||||||
{% else %}<span class="badge loss">止损</span>{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ r.created_at[:16] if r.created_at else '' }}</td>
|
<td>{{ r.created_at[:16] if r.created_at else '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -214,3 +169,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block extra_js %}
|
||||||
|
<script src="{{ url_for('static', filename='js/review.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user