feat: show entry type in trade records, remove journal custom entry reason

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-06 00:49:21 +08:00
parent 5137d5ed17
commit fa9e6be36d
9 changed files with 36 additions and 183 deletions
+4 -17
View File
@@ -1120,28 +1120,16 @@ STATS_SEGMENT_DEFS = (
("key_false_breakout", "关键位假突破", {"segment": "key_false_breakout"}),
("key_trigger", "关键位触价开仓", {"segment": "key_trigger"}),
)
# 复盘表单「其他」选项的 value(非入库值;自定义文本走 entry_reason_custom
ENTRY_REASON_OTHER = "__OTHER__"
def normalize_entry_reason(raw, custom_text=None):
v = str(raw or "").strip()
if v == ENTRY_REASON_OTHER:
c = str(custom_text or "").strip()
return c[:2000] if c else ""
return v if v in ENTRY_REASON_OPTIONS else ""
def entry_reason_valid_for_storage(s):
"""允许五种固定整句、或自定义短文本(不含未解析的 __OTHER__ 占位)。"""
t = str(s or "").strip()
if not t:
return True
if t == ENTRY_REASON_OTHER:
return False
if t in ENTRY_REASON_OPTIONS:
return True
return 1 <= len(t) <= 2000
return t in ENTRY_REASON_OPTIONS
def normalize_early_exit_trigger(raw):
@@ -7276,7 +7264,6 @@ def render_main_page(page="trade", embed_mode=None):
trend_manual_count=trend_manual_entry_reason_count(TRADE_POLICY),
)
),
entry_reason_other_value=ENTRY_REASON_OTHER,
key_auto_order_enabled=KEY_AUTO_ORDER_ENABLED,
journal_chart_tf_choices=JOURNAL_CHART_TF_CHOICES,
journal_chart_default_tf1=JOURNAL_CHART_DEFAULT_TF1,
@@ -9115,9 +9102,9 @@ def del_order(id):
@login_required
def add_journal():
d = request.form
entry_reason_norm = normalize_entry_reason(d.get("entry_reason"), d.get("entry_reason_custom"))
entry_reason_norm = normalize_entry_reason(d.get("entry_reason"))
if not entry_reason_norm:
flash("请选择开仓类型;若选「其他」请在下方填写自定义说明")
flash("请选择开仓类型")
return _redirect_records()
early_exit_trigger = normalize_early_exit_trigger(d.get("early_exit_trigger"))
early_exit_note = str(d.get("early_exit_note") or "").strip()
@@ -9495,7 +9482,7 @@ def api_trade_record_review_update():
if "reviewed_entry_reason" in payload:
s = str(payload.get("reviewed_entry_reason") or "").strip()
if s and not entry_reason_valid_for_storage(s):
return jsonify({"ok": False, "msg": "开仓类型须为五种固定整句之一、自定义说明(2000字内)或留空"}), 400
return jsonify({"ok": False, "msg": "开仓类型须为下拉选项之一或留空"}), 400
reviewed_entry_reason_update = s or None
conn = get_db()
+6 -34
View File
@@ -570,12 +570,13 @@
</div>
<div class="table-wrap">
<table>
<tr><th>品种</th><th>类型</th><th>方向</th><th>成交</th><th>止损(开仓)</th><th>止盈</th><th>基数</th><th>杠杆</th><th>持仓分钟</th><th>开仓时间(北京)</th><th>平仓时间(北京)</th><th>盈亏U</th><th>结果</th><th>操作</th></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>盈亏U</th><th>结果</th><th>操作</th></tr>
{% for r in record %}
<tr id="trade-row-{{ r.id }}">
{% set pnl_val = (r.pnl_amount or 0)|float %}
<td>{{ r.symbol }}</td>
<td>{{ r.monitor_type }}{% if r.key_signal_type %} · {{ r.key_signal_type }}{% endif %}</td>
<td>{{ r.effective_entry_reason or '-' }}</td>
<td><span class="badge {{ 'direction-long' if r.direction == 'long' else 'direction-short' }}">{{ '做多' if r.direction == 'long' else '做空' }}</span></td>
<td>{{ price_fmt(r.symbol, r.trigger_price) }}</td>
{% set stop_show = r.display_open_stop_loss or r.initial_stop_loss or r.stop_loss %}
@@ -613,7 +614,8 @@
"closed_at": r.effective_closed_at,
"pnl_amount": r.effective_pnl_amount,
"result": r.effective_result,
"risk_amount": r.risk_amount
"risk_amount": r.risk_amount,
"effective_entry_reason": r.effective_entry_reason or ""
}|tojson|safe }})'
>填入复盘</button>
<button
@@ -650,7 +652,7 @@
<input type="hidden" name="exit_price_hint" id="exit-price-hint">
<input type="hidden" name="direction_hint" id="direction-hint">
{% from 'journal_form_fields.html' import journal_form_fields %}
{{ journal_form_fields(entry_reason_options, entry_reason_other_value) }}
{{ journal_form_fields(entry_reason_options) }}
{% from 'journal_upload_slots.html' import journal_upload_slots %}
{{ journal_upload_slots() }}
<div class="form-row journal-chart-options" style="flex-wrap:wrap;align-items:center">
@@ -802,40 +804,15 @@
<script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
function syncJournalEntryReasonOtherUi(){
const form = document.getElementById("journal-form");
if(!form) return;
const sel = form.querySelector('[name="entry_reason"]');
const box = form.querySelector('[name="entry_reason_custom"]');
if(!sel || !box) return;
if(sel.value === JOURNAL_ENTRY_REASON_OTHER){
box.style.display = "";
box.required = true;
} else {
box.style.display = "none";
box.required = false;
box.value = "";
}
}
function validateJournalEntryReason(){
const form = document.getElementById("journal-form");
if(!form) return true;
const sel = form.querySelector('[name="entry_reason"]');
const box = form.querySelector('[name="entry_reason_custom"]');
if(!sel || !sel.value){
alert("请选择开仓类型");
return false;
}
if(sel.value === JOURNAL_ENTRY_REASON_OTHER){
const t = (box && box.value || "").trim();
if(!t){
alert("选择「其他」时请填写自定义开仓类型说明");
return false;
}
}
return true;
}
function showImage(src){document.getElementById("bigImg").src=src;document.getElementById("imgModal").style.display="flex";}
@@ -987,7 +964,7 @@ function editTradeRecordReview(t){
const result = prompt("结果(止盈/止损/保本止盈/移动止盈/手动平仓/时间平仓)", String(t.result || ""));
if(result === null) return;
const note = prompt("备注(可空)", String(t.miss_reason || "")) ?? "";
const entryHint = "开仓类型:五种固定整句、或自定义说明(2000字内;与复盘表单一致;留空=本次不改该项)";
const entryHint = "开仓类型(下拉选项之一,留空=不改该项";
const entryIn = prompt(entryHint, String(t.effective_entry_reason || ""));
if(entryIn === null) return;
const payload = {
@@ -1363,8 +1340,6 @@ function fillJournalFromTrade(t){
setJournalField("entry_reason", "");
}
}
setJournalField("entry_reason_custom", "");
syncJournalEntryReasonOtherUi();
const er = String(t.result || "").trim();
const exitTrigMap = { 止盈: "止盈", 保本止盈: "保本止盈", 移动止盈: "移动止盈", 时间平仓: "时间平仓", 手动平仓: "手动平仓", 止损: "止损" };
if(exitTrigMap[er]) setJournalField("early_exit_trigger", exitTrigMap[er]);
@@ -1969,9 +1944,6 @@ if(_journalFormEl){
_journalFormEl.addEventListener("submit", function(ev){
if(!validateJournalEntryReason()) ev.preventDefault();
});
const _jErSel = _journalFormEl.querySelector('[name="entry_reason"]');
if(_jErSel) _jErSel.addEventListener("change", syncJournalEntryReasonOtherUi);
syncJournalEntryReasonOtherUi();
}
const addOrderForm = document.getElementById("add-order-form");
+4 -17
View File
@@ -1108,28 +1108,16 @@ STATS_SEGMENT_DEFS = (
("key_false_breakout", "关键位假突破", {"segment": "key_false_breakout"}),
("key_trigger", "关键位触价开仓", {"segment": "key_trigger"}),
)
# 复盘表单「其他」选项的 value(非入库值;自定义文本走 entry_reason_custom
ENTRY_REASON_OTHER = "__OTHER__"
def normalize_entry_reason(raw, custom_text=None):
v = str(raw or "").strip()
if v == ENTRY_REASON_OTHER:
c = str(custom_text or "").strip()
return c[:2000] if c else ""
return v if v in ENTRY_REASON_OPTIONS else ""
def entry_reason_valid_for_storage(s):
"""允许五种固定整句、或自定义短文本(不含未解析的 __OTHER__ 占位)。"""
t = str(s or "").strip()
if not t:
return True
if t == ENTRY_REASON_OTHER:
return False
if t in ENTRY_REASON_OPTIONS:
return True
return 1 <= len(t) <= 2000
return t in ENTRY_REASON_OPTIONS
def normalize_early_exit_trigger(raw):
@@ -7059,7 +7047,6 @@ def render_main_page(page="trade", embed_mode=None):
trend_manual_count=trend_manual_entry_reason_count(TRADE_POLICY),
)
),
entry_reason_other_value=ENTRY_REASON_OTHER,
key_auto_order_enabled=KEY_AUTO_ORDER_ENABLED,
journal_chart_tf_choices=JOURNAL_CHART_TF_CHOICES,
journal_chart_default_tf1=JOURNAL_CHART_DEFAULT_TF1,
@@ -8941,9 +8928,9 @@ def del_order(id):
@login_required
def add_journal():
d = request.form
entry_reason_norm = normalize_entry_reason(d.get("entry_reason"), d.get("entry_reason_custom"))
entry_reason_norm = normalize_entry_reason(d.get("entry_reason"))
if not entry_reason_norm:
flash("请选择开仓类型;若选「其他」请在下方填写自定义说明")
flash("请选择开仓类型")
return _redirect_records()
early_exit_trigger = normalize_early_exit_trigger(d.get("early_exit_trigger"))
early_exit_note = str(d.get("early_exit_note") or "").strip()
@@ -9321,7 +9308,7 @@ def api_trade_record_review_update():
if "reviewed_entry_reason" in payload:
s = str(payload.get("reviewed_entry_reason") or "").strip()
if s and not entry_reason_valid_for_storage(s):
return jsonify({"ok": False, "msg": "开仓类型须为五种固定整句之一、自定义说明(2000字内)或留空"}), 400
return jsonify({"ok": False, "msg": "开仓类型须为下拉选项之一或留空"}), 400
reviewed_entry_reason_update = s or None
conn = get_db()
+6 -34
View File
@@ -536,12 +536,13 @@
</div>
<div class="table-wrap">
<table>
<tr><th>品种</th><th>类型</th><th>方向</th><th>成交</th><th>止损(开仓)</th><th>止盈</th><th>基数</th><th>杠杆</th><th>持仓分钟</th><th>开仓时间(北京)</th><th>平仓时间(北京)</th><th>盈亏U</th><th>结果</th><th>操作</th></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>盈亏U</th><th>结果</th><th>操作</th></tr>
{% for r in record %}
<tr id="trade-row-{{ r.id }}">
{% set pnl_val = (r.pnl_amount or 0)|float %}
<td>{{ r.symbol }}</td>
<td>{{ r.monitor_type }}{% if r.key_signal_type %} · {{ r.key_signal_type }}{% endif %}</td>
<td>{{ r.effective_entry_reason or '-' }}</td>
<td><span class="badge {{ 'direction-long' if r.direction == 'long' else 'direction-short' }}">{{ '做多' if r.direction == 'long' else '做空' }}</span></td>
<td>{{ price_fmt(r.symbol, r.trigger_price) }}</td>
{% set stop_show = r.display_open_stop_loss or r.initial_stop_loss or r.stop_loss %}
@@ -579,7 +580,8 @@
"closed_at": r.effective_closed_at,
"pnl_amount": r.effective_pnl_amount,
"result": r.effective_result,
"risk_amount": r.risk_amount
"risk_amount": r.risk_amount,
"effective_entry_reason": r.effective_entry_reason or ""
}|tojson|safe }})'
>填入复盘</button>
<button
@@ -616,7 +618,7 @@
<input type="hidden" name="exit_price_hint" id="exit-price-hint">
<input type="hidden" name="direction_hint" id="direction-hint">
{% from 'journal_form_fields.html' import journal_form_fields %}
{{ journal_form_fields(entry_reason_options, entry_reason_other_value) }}
{{ journal_form_fields(entry_reason_options) }}
{% from 'journal_upload_slots.html' import journal_upload_slots %}
{{ journal_upload_slots() }}
<div class="form-row journal-chart-options" style="flex-wrap:wrap;align-items:center">
@@ -768,40 +770,15 @@
<script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
function syncJournalEntryReasonOtherUi(){
const form = document.getElementById("journal-form");
if(!form) return;
const sel = form.querySelector('[name="entry_reason"]');
const box = form.querySelector('[name="entry_reason_custom"]');
if(!sel || !box) return;
if(sel.value === JOURNAL_ENTRY_REASON_OTHER){
box.style.display = "";
box.required = true;
} else {
box.style.display = "none";
box.required = false;
box.value = "";
}
}
function validateJournalEntryReason(){
const form = document.getElementById("journal-form");
if(!form) return true;
const sel = form.querySelector('[name="entry_reason"]');
const box = form.querySelector('[name="entry_reason_custom"]');
if(!sel || !sel.value){
alert("请选择开仓类型");
return false;
}
if(sel.value === JOURNAL_ENTRY_REASON_OTHER){
const t = (box && box.value || "").trim();
if(!t){
alert("选择「其他」时请填写自定义开仓类型说明");
return false;
}
}
return true;
}
function showImage(src){document.getElementById("bigImg").src=src;document.getElementById("imgModal").style.display="flex";}
@@ -953,7 +930,7 @@ function editTradeRecordReview(t){
const result = prompt("结果(止盈/止损/保本止盈/移动止盈/手动平仓/时间平仓)", String(t.result || ""));
if(result === null) return;
const note = prompt("备注(可空)", String(t.miss_reason || "")) ?? "";
const entryHint = "开仓类型:五种固定整句、或自定义说明(2000字内;与复盘表单一致;留空=本次不改该项)";
const entryHint = "开仓类型(下拉选项之一,留空=不改该项";
const entryIn = prompt(entryHint, String(t.effective_entry_reason || ""));
if(entryIn === null) return;
const payload = {
@@ -1329,8 +1306,6 @@ function fillJournalFromTrade(t){
setJournalField("entry_reason", "");
}
}
setJournalField("entry_reason_custom", "");
syncJournalEntryReasonOtherUi();
const er = String(t.result || "").trim();
const exitTrigMap = { 止盈: "止盈", 保本止盈: "保本止盈", 移动止盈: "移动止盈", 时间平仓: "时间平仓", 手动平仓: "手动平仓", 止损: "止损" };
if(exitTrigMap[er]) setJournalField("early_exit_trigger", exitTrigMap[er]);
@@ -1894,9 +1869,6 @@ if(_journalFormEl){
_journalFormEl.addEventListener("submit", function(ev){
if(!validateJournalEntryReason()) ev.preventDefault();
});
const _jErSel = _journalFormEl.querySelector('[name="entry_reason"]');
if(_jErSel) _jErSel.addEventListener("change", syncJournalEntryReasonOtherUi);
syncJournalEntryReasonOtherUi();
}
const addOrderForm = document.getElementById("add-order-form");
+2 -9
View File
@@ -1110,14 +1110,8 @@ STATS_SEGMENT_DEFS = (
("key_false_breakout", "关键位假突破", {"segment": "key_false_breakout"}),
("key_trigger", "关键位触价开仓", {"segment": "key_trigger"}),
)
ENTRY_REASON_OTHER = "__OTHER__"
def normalize_entry_reason(raw, custom_text=None):
v = str(raw or "").strip()
if v == ENTRY_REASON_OTHER:
c = str(custom_text or "").strip()
return c[:2000] if c else ""
return v if v in ENTRY_REASON_OPTIONS else ""
@@ -6613,7 +6607,6 @@ def render_main_page(page="trade", embed_mode=None):
trend_manual_count=trend_manual_entry_reason_count(TRADE_POLICY),
)
),
entry_reason_other_value=ENTRY_REASON_OTHER,
key_auto_order_enabled=KEY_AUTO_ORDER_ENABLED,
journal_chart_tf_choices=JOURNAL_CHART_TF_CHOICES,
journal_chart_default_tf1=JOURNAL_CHART_DEFAULT_TF1,
@@ -8486,7 +8479,7 @@ def del_order(id):
@login_required
def add_journal():
d = request.form
entry_reason_norm = normalize_entry_reason(d.get("entry_reason"), d.get("entry_reason_custom"))
entry_reason_norm = normalize_entry_reason(d.get("entry_reason"))
if not entry_reason_norm:
flash("请选择开仓类型")
return _redirect_records()
@@ -8866,7 +8859,7 @@ def api_trade_record_review_update():
if "reviewed_entry_reason" in payload:
s = str(payload.get("reviewed_entry_reason") or "").strip()
if s and not normalize_entry_reason(s):
return jsonify({"ok": False, "msg": "开仓类型须为五种固定枚举整句之一或留空"}), 400
return jsonify({"ok": False, "msg": "开仓类型须为下拉选项之一或留空"}), 400
reviewed_entry_reason_update = s or None
conn = get_db()
+6 -34
View File
@@ -565,12 +565,13 @@
</div>
<div class="table-wrap">
<table>
<tr><th>品种</th><th>类型</th><th>方向</th><th>成交</th><th>止损(开仓)</th><th>止盈</th><th>基数</th><th>杠杆</th><th>持仓分钟</th><th>开仓时间(北京)</th><th>平仓时间(北京)</th><th>盈亏U</th><th>结果</th><th>操作</th></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>盈亏U</th><th>结果</th><th>操作</th></tr>
{% for r in record %}
<tr id="trade-row-{{ r.id }}">
{% set pnl_val = (r.pnl_amount or 0)|float %}
<td>{{ r.symbol }}</td>
<td>{{ r.monitor_type }}{% if r.key_signal_type %} · {{ r.key_signal_type }}{% endif %}</td>
<td>{{ r.effective_entry_reason or '-' }}</td>
<td><span class="badge {{ 'direction-long' if r.direction == 'long' else 'direction-short' }}">{{ '做多' if r.direction == 'long' else '做空' }}</span></td>
<td>{{ price_fmt(r.symbol, r.trigger_price) }}</td>
{% set stop_show = r.display_open_stop_loss or r.initial_stop_loss or r.stop_loss %}
@@ -608,7 +609,8 @@
"closed_at": r.effective_closed_at,
"pnl_amount": r.effective_pnl_amount,
"result": r.effective_result,
"risk_amount": r.risk_amount
"risk_amount": r.risk_amount,
"effective_entry_reason": r.effective_entry_reason or ""
}|tojson|safe }})'
>填入复盘</button>
<button
@@ -645,7 +647,7 @@
<input type="hidden" name="exit_price_hint" id="exit-price-hint">
<input type="hidden" name="direction_hint" id="direction-hint">
{% from 'journal_form_fields.html' import journal_form_fields %}
{{ journal_form_fields(entry_reason_options, entry_reason_other_value) }}
{{ journal_form_fields(entry_reason_options) }}
{% from 'journal_upload_slots.html' import journal_upload_slots %}
{{ journal_upload_slots() }}
<div class="form-row journal-chart-options" style="flex-wrap:wrap;align-items:center">
@@ -797,40 +799,15 @@
<script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
function syncJournalEntryReasonOtherUi(){
const form = document.getElementById("journal-form");
if(!form) return;
const sel = form.querySelector('[name="entry_reason"]');
const box = form.querySelector('[name="entry_reason_custom"]');
if(!sel || !box) return;
if(sel.value === JOURNAL_ENTRY_REASON_OTHER){
box.style.display = "";
box.required = true;
} else {
box.style.display = "none";
box.required = false;
box.value = "";
}
}
function validateJournalEntryReason(){
const form = document.getElementById("journal-form");
if(!form) return true;
const sel = form.querySelector('[name="entry_reason"]');
const box = form.querySelector('[name="entry_reason_custom"]');
if(!sel || !sel.value){
alert("请选择开仓类型");
return false;
}
if(sel.value === JOURNAL_ENTRY_REASON_OTHER){
const t = (box && box.value || "").trim();
if(!t){
alert("选择「其他」时请填写自定义开仓类型说明");
return false;
}
}
return true;
}
function showImage(src){document.getElementById("bigImg").src=src;document.getElementById("imgModal").style.display="flex";}
@@ -982,7 +959,7 @@ function editTradeRecordReview(t){
const result = prompt("结果(止盈/止损/保本止盈/移动止盈/手动平仓/时间平仓)", String(t.result || ""));
if(result === null) return;
const note = prompt("备注(可空)", String(t.miss_reason || "")) ?? "";
const entryHint = "开仓类型:五种固定整句、或自定义说明(2000字内;与复盘表单一致;留空=本次不改该项)";
const entryHint = "开仓类型(下拉选项之一,留空=不改该项";
const entryIn = prompt(entryHint, String(t.effective_entry_reason || ""));
if(entryIn === null) return;
const payload = {
@@ -1358,8 +1335,6 @@ function fillJournalFromTrade(t){
setJournalField("entry_reason", "");
}
}
setJournalField("entry_reason_custom", "");
syncJournalEntryReasonOtherUi();
const er = String(t.result || "").trim();
const exitTrigMap = { 止盈: "止盈", 保本止盈: "保本止盈", 移动止盈: "移动止盈", 时间平仓: "时间平仓", 手动平仓: "手动平仓", 止损: "止损" };
if(exitTrigMap[er]) setJournalField("early_exit_trigger", exitTrigMap[er]);
@@ -1946,9 +1921,6 @@ if(_journalFormEl){
_journalFormEl.addEventListener("submit", function(ev){
if(!validateJournalEntryReason()) ev.preventDefault();
});
const _jErSel = _journalFormEl.querySelector('[name="entry_reason"]');
if(_jErSel) _jErSel.addEventListener("change", syncJournalEntryReasonOtherUi);
syncJournalEntryReasonOtherUi();
}
const addOrderForm = document.getElementById("add-order-form");
+1 -31
View File
@@ -1,7 +1,6 @@
<script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
function tradeStyleForEntryModelSelect(sel){
if(!sel || !sel.value) return "trend";
@@ -57,38 +56,14 @@ function reloadInstancePage(){
window.location.href = `${window.location.pathname}?_ts=${Date.now()}`;
}
function syncJournalEntryReasonOtherUi(){
const form = document.getElementById("journal-form");
if(!form) return;
const sel = form.querySelector('[name="entry_reason"]');
const box = form.querySelector('[name="entry_reason_custom"]');
if(!sel || !box) return;
if(sel.value === JOURNAL_ENTRY_REASON_OTHER){
box.style.display = "";
box.required = true;
} else {
box.style.display = "none";
box.required = false;
box.value = "";
}
}
function validateJournalEntryReason(){
const form = document.getElementById("journal-form");
if(!form) return true;
const sel = form.querySelector('[name="entry_reason"]');
const box = form.querySelector('[name="entry_reason_custom"]');
if(!sel || !sel.value){
alert("请选择开仓类型");
return false;
}
if(sel.value === JOURNAL_ENTRY_REASON_OTHER){
const t = (box && box.value || "").trim();
if(!t){
alert("选择「其他」时请填写自定义开仓类型说明");
return false;
}
}
return true;
}
function showImage(src){document.getElementById("bigImg").src=src;document.getElementById("imgModal").style.display="flex";}
@@ -240,7 +215,7 @@ function editTradeRecordReview(t){
const result = prompt("结果(止盈/止损/保本止盈/移动止盈/手动平仓/时间平仓)", String(t.result || ""));
if(result === null) return;
const note = prompt("备注(可空)", String(t.miss_reason || "")) ?? "";
const entryHint = "开仓类型:五种固定整句、或自定义说明(2000字内;与复盘表单一致;留空=本次不改该项)";
const entryHint = "开仓类型(下拉选项之一,留空=不改该项";
const entryIn = prompt(entryHint, String(t.effective_entry_reason || ""));
if(entryIn === null) return;
const payload = {
@@ -616,8 +591,6 @@ function fillJournalFromTrade(t){
setJournalField("entry_reason", "");
}
}
setJournalField("entry_reason_custom", "");
syncJournalEntryReasonOtherUi();
const er = String(t.result || "").trim();
const exitTrigMap = { 止盈: "止盈", 保本止盈: "保本止盈", 移动止盈: "移动止盈", 时间平仓: "时间平仓", 手动平仓: "手动平仓", 止损: "止损" };
if(exitTrigMap[er]) setJournalField("early_exit_trigger", exitTrigMap[er]);
@@ -1193,9 +1166,6 @@ if(_journalFormEl){
_journalFormEl.addEventListener("submit", function(ev){
if(!validateJournalEntryReason()) ev.preventDefault();
});
const _jErSel = _journalFormEl.querySelector('[name="entry_reason"]');
if(_jErSel) _jErSel.addEventListener("change", syncJournalEntryReasonOtherUi);
syncJournalEntryReasonOtherUi();
}
const addOrderForm = document.getElementById("add-order-form");
@@ -213,12 +213,13 @@
</div>
<div class="table-wrap">
<table>
<tr><th>品种</th><th>类型</th><th>方向</th><th>成交</th><th>止损(开仓)</th><th>止盈</th><th>基数</th><th>杠杆</th><th>持仓分钟</th><th>开仓时间(北京)</th><th>平仓时间(北京)</th><th>盈亏U</th><th>结果</th><th>操作</th></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>盈亏U</th><th>结果</th><th>操作</th></tr>
{% for r in record %}
<tr id="trade-row-{{ r.id }}">
{% set pnl_val = (r.pnl_amount or 0)|float %}
<td>{{ r.symbol }}</td>
<td>{{ r.monitor_type }}{% if r.key_signal_type %} · {{ r.key_signal_type }}{% endif %}</td>
<td>{{ r.effective_entry_reason or '-' }}</td>
<td><span class="badge {{ 'direction-long' if r.direction == 'long' else 'direction-short' }}">{{ '做多' if r.direction == 'long' else '做空' }}</span></td>
<td>{{ price_fmt(r.symbol, r.trigger_price) }}</td>
{% set stop_show = r.display_open_stop_loss or r.initial_stop_loss or r.stop_loss %}
@@ -256,7 +257,8 @@
"closed_at": r.effective_closed_at,
"pnl_amount": r.effective_pnl_amount,
"result": r.effective_result,
"risk_amount": r.risk_amount
"risk_amount": r.risk_amount,
"effective_entry_reason": r.effective_entry_reason or ""
}|tojson|safe }})'
>填入复盘</button>
<button
@@ -293,7 +295,7 @@
<input type="hidden" name="exit_price_hint" id="exit-price-hint">
<input type="hidden" name="direction_hint" id="direction-hint">
{% from 'journal_form_fields.html' import journal_form_fields %}
{{ journal_form_fields(entry_reason_options, entry_reason_other_value) }}
{{ journal_form_fields(entry_reason_options) }}
{% from 'journal_upload_slots.html' import journal_upload_slots %}
{{ journal_upload_slots() }}
<div class="form-row journal-chart-options" style="flex-wrap:wrap;align-items:center">
@@ -1,5 +1,5 @@
{# 复盘表单字段两行布局 #}
{% macro journal_form_fields(entry_reason_options, entry_reason_other_value) -%}
{% macro journal_form_fields(entry_reason_options) -%}
<div class="form-grid journal-form-row1">
<input type="datetime-local" name="open_datetime" required>
<input type="datetime-local" name="close_datetime" required>
@@ -10,12 +10,11 @@
<input name="real_rr" placeholder="实际RR">
</div>
<div class="form-grid journal-form-row2">
<select name="entry_reason" id="journal-entry-reason" required title="大分歧A/B/小分歧或策略项;选其他可手写">
<select name="entry_reason" id="journal-entry-reason" required title="大分歧A/B/小分歧或策略项">
<option value="">开仓类型(必选)</option>
{% for er in entry_reason_options %}
<option value="{{ er }}">{{ er }}</option>
{% endfor %}
<option value="{{ entry_reason_other_value }}">其他(自定义,见下方说明框)</option>
</select>
<select name="early_exit_trigger" required title="平仓如何触发">
<option value="">离场触发(必选)</option>
@@ -30,5 +29,4 @@
<input name="early_exit_note" id="early-exit-note" placeholder="离场补充(仅手工平仓必填)">
<select name="post_breakeven_stare"><option value="否">保本后盯盘:否</option><option value="是">保本后盯盘:是</option></select>
</div>
<input type="text" name="entry_reason_custom" id="journal-entry-reason-custom" maxlength="2000" placeholder="选「其他」时在此填写开仓类型说明" autocomplete="off" style="display:none;width:100%;margin-top:4px;font-size:.8rem">
{%- endmacro %}