Split journal order type from entry reason and fix review edit toggle in embed shell.

Adds order_type to journal uploads, removes legacy swing/trend labels from entry reason options, and ensures review-edit buttons sync when the records tab loads dynamically.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-09 09:27:03 +08:00
parent 4abe8a6ca9
commit d3a44b23db
17 changed files with 325 additions and 85 deletions
+44
View File
@@ -0,0 +1,44 @@
"""复盘表单:下单类型与开仓类型校验(三所共用)."""
from __future__ import annotations
from typing import Optional, Sequence, Tuple
from lib.strategy.strategy_trade_labels import (
JOURNAL_ORDER_TYPE_OPTIONS,
STRATEGY_ENTRY_REASON_OPTIONS,
normalize_journal_order_type,
)
from lib.trade.entry_model_lib import (
TRADE_STYLE_FALLBACK_ENTRY_REASONS,
normalize_review_entry_reason,
)
_LEGACY_JOURNAL_ENTRY_REASONS: Tuple[str, ...] = (
*TRADE_STYLE_FALLBACK_ENTRY_REASONS,
*STRATEGY_ENTRY_REASON_OPTIONS,
)
def normalize_journal_entry_reason(
raw: Optional[str],
allowed: Sequence[str],
*,
allow_legacy: bool = False,
) -> str:
s = normalize_review_entry_reason(raw, allowed)
if s:
return s
if not allow_legacy:
return ""
legacy = (raw or "").strip()
if legacy in _LEGACY_JOURNAL_ENTRY_REASONS:
return legacy
return ""
def journal_entry_reason_valid(raw: Optional[str], allowed: Sequence[str]) -> bool:
return bool(normalize_journal_entry_reason(raw, allowed, allow_legacy=False))
def journal_order_type_valid(raw: Optional[str]) -> bool:
return bool(normalize_journal_order_type(raw))
+27 -6
View File
@@ -1,5 +1,6 @@
<script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ORDER_TYPE_OPTIONS = {{ order_type_options | tojson }};
function reloadInstancePage(){
if(document.body && document.body.getAttribute("data-embed-shell") === "1" && window.InstanceEmbed){
@@ -12,6 +13,11 @@ function reloadInstancePage(){
function validateJournalEntryReason(){
const form = document.getElementById("journal-form");
if(!form) return true;
const orderSel = form.querySelector('[name="order_type"]');
if(!orderSel || !orderSel.value){
alert("请选择下单类型");
return false;
}
const sel = form.querySelector('[name="entry_reason"]');
if(!sel || !sel.value){
alert("请选择开仓类型");
@@ -147,6 +153,10 @@ function normalizeBeijingDatetimeString(v){
}
function toggleReviewMode(){
if(window.InstanceTheme && typeof InstanceTheme.syncReviewEditButtons === "function"){
InstanceTheme.syncReviewEditButtons();
return;
}
const on = !!(document.getElementById("review-mode-toggle") || {}).checked;
document.querySelectorAll(".review-edit-btn").forEach(btn=>{
btn.disabled = !on;
@@ -537,11 +547,18 @@ function fillJournalFromTrade(t){
setJournalField("early_exit_note", "");
const kst = String(t.key_signal_type || "").trim();
const mt = String(t.monitor_type || "").trim();
if(mt === "趋势回调" && JOURNAL_ENTRY_REASON_OPTIONS.includes("趋势回调")){
setJournalField("entry_reason", "趋势回调");
} else if(mt === "顺势加仓" && JOURNAL_ENTRY_REASON_OPTIONS.includes("顺势加仓")){
setJournalField("entry_reason", "顺势加仓");
} else if(t.effective_entry_reason && JOURNAL_ENTRY_REASON_OPTIONS.includes(t.effective_entry_reason)){
const orderType = (function(){
if(mt === "趋势回调" && JOURNAL_ORDER_TYPE_OPTIONS.includes("趋势回调")) return "趋势回调";
if(mt === "顺势加仓" && JOURNAL_ORDER_TYPE_OPTIONS.includes("顺势加仓")) return "顺势加仓";
if(mt === "关键位监控" || mt.includes("关键位")) return "关键位监控";
return "下单监控";
})();
if(JOURNAL_ORDER_TYPE_OPTIONS.includes(orderType)){
setJournalField("order_type", orderType);
} else {
setJournalField("order_type", "");
}
if(t.effective_entry_reason && JOURNAL_ENTRY_REASON_OPTIONS.includes(t.effective_entry_reason)){
setJournalField("entry_reason", t.effective_entry_reason);
} else {
const erFromKey = KEY_ENTRY_REASON_BY_SIGNAL[kst] || "";
@@ -554,7 +571,7 @@ function fillJournalFromTrade(t){
const er = String(t.result || "").trim();
const exitTrigMap = { 止盈: "止盈", 保本止盈: "保本止盈", 移动止盈: "移动止盈", 时间平仓: "时间平仓", 强制清仓: "强制清仓", 手动平仓: "手动平仓", 止损: "止损" };
if(exitTrigMap[er]) setJournalField("early_exit_trigger", exitTrigMap[er]);
const note = `来自交易记录自动填充:${t.symbol || "-"} ${t.direction || "-"} | 入场:${entryPx || "-"} 止损:${slPx || "-"} 止盈:${tpPx || "-"} | 类型:${t.monitor_type || "-"}`;
const note = `来自交易记录自动填充:${t.symbol || "-"} ${t.direction || "-"} | 入场:${entryPx || "-"} 止损:${slPx || "-"} 止盈:${tpPx || "-"} | 下单类型:${orderType || t.monitor_type || "-"}`;
setJournalField("note", note);
const form = document.getElementById("journal-form");
if(form && typeof form.scrollIntoView === "function"){
@@ -632,8 +649,12 @@ if(document.getElementById("review-list")) loadReviews();
const reviewToggle = document.getElementById("review-mode-toggle");
if(reviewToggle){
reviewToggle.addEventListener("change", toggleReviewMode);
reviewToggle.addEventListener("input", toggleReviewMode);
toggleReviewMode();
}
if(window.InstanceTheme && typeof InstanceTheme.initReviewEditModeSync === "function"){
InstanceTheme.initReviewEditModeSync();
}
const journalForm = document.getElementById("journal-form");
if(journalForm){
const pnlInput = journalForm.querySelector('[name="pnl"]');
@@ -227,7 +227,7 @@
</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>平仓时间(北京)</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 %}
@@ -309,7 +309,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) }}
{{ journal_form_fields(entry_reason_options, order_type_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">
+4 -4
View File
@@ -3,11 +3,11 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="/static/instance_theme.js?v=49"></script>
<script src="/static/instance_theme.js?v=50"></script>
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
<link rel="stylesheet" href="/static/instance_page.css?v=4">
<link rel="stylesheet" href="/static/instance_theme.css?v=75">
<link rel="stylesheet" href="/static/instance_theme.css?v=76">
<script src="/static/account_risk_badge.js?v=4"></script>
<meta name="theme-color" content="#0b0d14">
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
@@ -83,7 +83,7 @@
</div>
</div>
<script src="/static/instance_ui.js?v=7"></script>
<script src="/static/instance_ui.js?v=8"></script>
<script src="/static/journal_upload_slots.js?v=3"></script>
<script src="/static/instance_records_mobile.js?v=2"></script>
<script src="/static/time_close_ui.js?v=3"></script>
@@ -105,6 +105,6 @@ window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
</script>
<script src="/static/instance_settings_prefs.js?v=12"></script>
<script src="/static/instance_live.js?v=4"></script>
<script src="/static/instance_embed.js?v=21"></script>
<script src="/static/instance_embed.js?v=22"></script>
</body>
</html>
+32 -11
View File
@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="/static/instance_theme.js?v=49"></script>
<script src="/static/instance_theme.js?v=50"></script>
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
<script src="/static/account_risk_badge.js?v=4"></script>
@@ -17,7 +17,7 @@
<link rel="manifest" href="/static/icons/manifest.webmanifest">
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
<link rel="stylesheet" href="/static/instance_page.css?v=1">
<link rel="stylesheet" href="/static/instance_theme.css?v=75">
<link rel="stylesheet" href="/static/instance_theme.css?v=76">
</head>
<body
@@ -309,7 +309,7 @@
</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>平仓时间(北京)</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 %}
@@ -391,7 +391,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) }}
{{ journal_form_fields(entry_reason_options, order_type_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">
@@ -542,7 +542,7 @@
</div>
</div>
<script src="/static/instance_ui.js?v=7"></script>
<script src="/static/instance_ui.js?v=8"></script>
<script src="/static/journal_upload_slots.js?v=3"></script>
<script src="/static/instance_records_mobile.js?v=2"></script>
<script src="/static/time_close_ui.js?v=3"></script>
@@ -559,10 +559,16 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
<script src="/static/strategy_roll.js?v=6"></script>
<script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ORDER_TYPE_OPTIONS = {{ order_type_options | tojson }};
function validateJournalEntryReason(){
const form = document.getElementById("journal-form");
if(!form) return true;
const orderSel = form.querySelector('[name="order_type"]');
if(!orderSel || !orderSel.value){
alert("请选择下单类型");
return false;
}
const sel = form.querySelector('[name="entry_reason"]');
if(!sel || !sel.value){
alert("请选择开仓类型");
@@ -698,6 +704,10 @@ function normalizeBeijingDatetimeString(v){
}
function toggleReviewMode(){
if(window.InstanceTheme && typeof InstanceTheme.syncReviewEditButtons === "function"){
InstanceTheme.syncReviewEditButtons();
return;
}
const on = !!(document.getElementById("review-mode-toggle") || {}).checked;
document.querySelectorAll(".review-edit-btn").forEach(btn=>{
btn.disabled = !on;
@@ -1088,11 +1098,18 @@ function fillJournalFromTrade(t){
setJournalField("early_exit_note", "");
const kst = String(t.key_signal_type || "").trim();
const mt = String(t.monitor_type || "").trim();
if(mt === "趋势回调" && JOURNAL_ENTRY_REASON_OPTIONS.includes("趋势回调")){
setJournalField("entry_reason", "趋势回调");
} else if(mt === "顺势加仓" && JOURNAL_ENTRY_REASON_OPTIONS.includes("顺势加仓")){
setJournalField("entry_reason", "顺势加仓");
} else if(t.effective_entry_reason && JOURNAL_ENTRY_REASON_OPTIONS.includes(t.effective_entry_reason)){
const orderType = (function(){
if(mt === "趋势回调" && JOURNAL_ORDER_TYPE_OPTIONS.includes("趋势回调")) return "趋势回调";
if(mt === "顺势加仓" && JOURNAL_ORDER_TYPE_OPTIONS.includes("顺势加仓")) return "顺势加仓";
if(mt === "关键位监控" || mt.includes("关键位")) return "关键位监控";
return "下单监控";
})();
if(JOURNAL_ORDER_TYPE_OPTIONS.includes(orderType)){
setJournalField("order_type", orderType);
} else {
setJournalField("order_type", "");
}
if(t.effective_entry_reason && JOURNAL_ENTRY_REASON_OPTIONS.includes(t.effective_entry_reason)){
setJournalField("entry_reason", t.effective_entry_reason);
} else {
const erFromKey = KEY_ENTRY_REASON_BY_SIGNAL[kst] || "";
@@ -1105,7 +1122,7 @@ function fillJournalFromTrade(t){
const er = String(t.result || "").trim();
const exitTrigMap = { 止盈: "止盈", 保本止盈: "保本止盈", 移动止盈: "移动止盈", 时间平仓: "时间平仓", 强制清仓: "强制清仓", 手动平仓: "手动平仓", 止损: "止损" };
if(exitTrigMap[er]) setJournalField("early_exit_trigger", exitTrigMap[er]);
const note = `来自交易记录自动填充:${t.symbol || "-"} ${t.direction || "-"} | 入场:${entryPx || "-"} 止损:${slPx || "-"} 止盈:${tpPx || "-"} | 类型:${t.monitor_type || "-"}`;
const note = `来自交易记录自动填充:${t.symbol || "-"} ${t.direction || "-"} | 入场:${entryPx || "-"} 止损:${slPx || "-"} 止盈:${tpPx || "-"} | 下单类型:${orderType || t.monitor_type || "-"}`;
setJournalField("note", note);
const form = document.getElementById("journal-form");
if(form && typeof form.scrollIntoView === "function"){
@@ -1170,8 +1187,12 @@ if(document.getElementById("review-list")) loadReviews();
const reviewToggle = document.getElementById("review-mode-toggle");
if(reviewToggle){
reviewToggle.addEventListener("change", toggleReviewMode);
reviewToggle.addEventListener("input", toggleReviewMode);
toggleReviewMode();
}
if(window.InstanceTheme && typeof InstanceTheme.initReviewEditModeSync === "function"){
InstanceTheme.initReviewEditModeSync();
}
const journalForm = document.getElementById("journal-form");
if(journalForm){
const pnlInput = journalForm.querySelector('[name="pnl"]');