diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index f28eb42..89f21a4 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -67,14 +67,16 @@ from lib.key_monitor.false_breakout_key_monitor_lib import ( storage_bounds_from_key_price, ) from lib.strategy.strategy_trade_labels import ( - STRATEGY_ENTRY_REASON_OPTIONS, + JOURNAL_ORDER_TYPE_OPTIONS, apply_order_monitor_source_labels, entry_reason_for_monitor_type, handoff_trade_miss_reason, + normalize_journal_order_type, order_monitor_source_type, trade_record_monitor_type as resolve_trade_record_monitor_type, trend_plan_id_from_monitor_row, ) +from lib.instance.journal_form_lib import normalize_journal_entry_reason from lib.instance.journal_images_lib import ( collect_journal_slot_images, enrich_journal_api_item, @@ -177,7 +179,7 @@ from lib.trade.position_sizing_lib import ( from lib.trade.trade_policy_lib import load_trade_policy from lib.trade.entry_model_lib import ( build_intraday_entry_reason_options, - build_trend_div_entry_reason_options, + build_journal_entry_reason_options, enrich_entry_model_display, hub_meta_entry_context, migrate_entry_model_columns, @@ -1123,8 +1125,8 @@ EARLY_EXIT_TRIGGERS = ( "其他", ) -# 趋势户:大分歧A/B/小分歧 + 策略(关键位本实例关闭) -ENTRY_REASON_OPTIONS = build_trend_div_entry_reason_options(STRATEGY_ENTRY_REASON_OPTIONS) +# 趋势户:复盘开仓类型仅 entry model;策略/风格项已拆至下单类型 +ENTRY_REASON_OPTIONS = build_journal_entry_reason_options() STATS_SEGMENT_DEFS = ( ("all", "全部交易", {"segment": "all"}), @@ -1137,9 +1139,8 @@ STATS_SEGMENT_DEFS = ( ("key_trigger", "关键位触价开仓", {"segment": "key_trigger"}), ) def normalize_entry_reason(raw, custom_text=None): - from lib.trade.entry_model_lib import normalize_review_entry_reason - - return normalize_review_entry_reason(raw, ENTRY_REASON_OPTIONS) + del custom_text + return normalize_journal_entry_reason(raw, ENTRY_REASON_OPTIONS, allow_legacy=True) def entry_reason_valid_for_storage(s): @@ -1429,6 +1430,9 @@ def init_db(): try: c.execute("ALTER TABLE journal_entries ADD COLUMN images_json TEXT") except: pass + try: + c.execute("ALTER TABLE journal_entries ADD COLUMN order_type TEXT") + except: pass try: c.execute("ALTER TABLE key_monitors ADD COLUMN direction TEXT DEFAULT 'long'") except: pass @@ -7326,6 +7330,7 @@ def render_main_page(page="trade", embed_mode=None): trend_manual_count=trend_manual_entry_reason_count(TRADE_POLICY), ) ), + order_type_options=list(JOURNAL_ORDER_TYPE_OPTIONS), key_auto_order_enabled=KEY_AUTO_ORDER_ENABLED, journal_chart_tf_choices=JOURNAL_CHART_TF_CHOICES, journal_chart_default_tf1=JOURNAL_CHART_DEFAULT_TF1, @@ -9256,7 +9261,13 @@ def del_order(id): @login_required def add_journal(): d = request.form - entry_reason_norm = normalize_entry_reason(d.get("entry_reason")) + order_type_norm = normalize_journal_order_type(d.get("order_type")) + if not order_type_norm: + flash("请选择下单类型") + return _redirect_records() + entry_reason_norm = normalize_journal_entry_reason( + d.get("entry_reason"), ENTRY_REASON_OPTIONS, allow_legacy=False + ) if not entry_reason_norm: flash("请选择开仓类型") return _redirect_records() @@ -9349,11 +9360,11 @@ def add_journal(): conn = get_db() conn.execute( """INSERT INTO journal_entries - (id, open_datetime, close_datetime, hold_duration, coin, tf, pnl, entry_reason, exit_reason, + (id, open_datetime, close_datetime, hold_duration, coin, tf, pnl, order_type, entry_reason, exit_reason, expect_rr, real_rr, early_exit, early_exit_reason, early_exit_trigger, early_exit_note, mood_score, mood_ai_score, mood_ai_comment, mood_issues, post_breakeven_stare, new_trade_while_occupied, note, image, images_json) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", ( entry_id, normalize_bj_datetime_storage(d.get("open_datetime")), @@ -9361,7 +9372,7 @@ def add_journal(): hold_duration, d.get("coin"), d.get("tf"), - d.get("pnl"), entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text, + d.get("pnl"), order_type_norm, entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text, early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note, None, None, None, mood_issues, d.get("post_breakeven_stare"), None, d.get("note"), image_filename, diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 6e9aa2a..a05193d 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -68,14 +68,17 @@ from lib.key_monitor.false_breakout_key_monitor_lib import ( storage_bounds_from_key_price, ) from lib.strategy.strategy_trade_labels import ( + JOURNAL_ORDER_TYPE_OPTIONS, STRATEGY_ENTRY_REASON_OPTIONS, apply_order_monitor_source_labels, entry_reason_for_monitor_type, handoff_trade_miss_reason, + normalize_journal_order_type, order_monitor_source_type, trade_record_monitor_type as resolve_trade_record_monitor_type, trend_plan_id_from_monitor_row, ) +from lib.instance.journal_form_lib import normalize_journal_entry_reason from lib.instance.journal_images_lib import ( collect_journal_slot_images, enrich_journal_api_item, @@ -1127,9 +1130,8 @@ STATS_SEGMENT_DEFS = ( ("key_trigger", "关键位触价开仓", {"segment": "key_trigger"}), ) def normalize_entry_reason(raw, custom_text=None): - from lib.trade.entry_model_lib import normalize_review_entry_reason - - return normalize_review_entry_reason(raw, ENTRY_REASON_OPTIONS) + del custom_text + return normalize_journal_entry_reason(raw, ENTRY_REASON_OPTIONS, allow_legacy=True) def entry_reason_valid_for_storage(s): @@ -1424,6 +1426,9 @@ def init_db(): try: c.execute("ALTER TABLE journal_entries ADD COLUMN images_json TEXT") except: pass + try: + c.execute("ALTER TABLE journal_entries ADD COLUMN order_type TEXT") + except: pass try: c.execute("ALTER TABLE key_monitors ADD COLUMN direction TEXT DEFAULT 'long'") except: pass @@ -7122,6 +7127,7 @@ def render_main_page(page="trade", embed_mode=None): trend_manual_count=trend_manual_entry_reason_count(TRADE_POLICY), ) ), + order_type_options=list(JOURNAL_ORDER_TYPE_OPTIONS), key_auto_order_enabled=KEY_AUTO_ORDER_ENABLED, journal_chart_tf_choices=JOURNAL_CHART_TF_CHOICES, journal_chart_default_tf1=JOURNAL_CHART_DEFAULT_TF1, @@ -9122,7 +9128,13 @@ def del_order(id): @login_required def add_journal(): d = request.form - entry_reason_norm = normalize_entry_reason(d.get("entry_reason")) + order_type_norm = normalize_journal_order_type(d.get("order_type")) + if not order_type_norm: + flash("请选择下单类型") + return _redirect_records() + entry_reason_norm = normalize_journal_entry_reason( + d.get("entry_reason"), ENTRY_REASON_OPTIONS, allow_legacy=False + ) if not entry_reason_norm: flash("请选择开仓类型") return _redirect_records() @@ -9215,11 +9227,11 @@ def add_journal(): conn = get_db() conn.execute( """INSERT INTO journal_entries - (id, open_datetime, close_datetime, hold_duration, coin, tf, pnl, entry_reason, exit_reason, + (id, open_datetime, close_datetime, hold_duration, coin, tf, pnl, order_type, entry_reason, exit_reason, expect_rr, real_rr, early_exit, early_exit_reason, early_exit_trigger, early_exit_note, mood_score, mood_ai_score, mood_ai_comment, mood_issues, post_breakeven_stare, new_trade_while_occupied, note, image, images_json) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", ( entry_id, normalize_bj_datetime_storage(d.get("open_datetime")), @@ -9227,7 +9239,7 @@ def add_journal(): hold_duration, d.get("coin"), d.get("tf"), - d.get("pnl"), entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text, + d.get("pnl"), order_type_norm, entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text, early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note, None, None, None, mood_issues, d.get("post_breakeven_stare"), None, d.get("note"), image_filename, diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index f1ccb86..a6e9471 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -67,14 +67,16 @@ from lib.key_monitor.false_breakout_key_monitor_lib import ( storage_bounds_from_key_price, ) from lib.strategy.strategy_trade_labels import ( - STRATEGY_ENTRY_REASON_OPTIONS, + JOURNAL_ORDER_TYPE_OPTIONS, apply_order_monitor_source_labels, entry_reason_for_monitor_type, handoff_trade_miss_reason, + normalize_journal_order_type, order_monitor_source_type, trade_record_monitor_type as resolve_trade_record_monitor_type, trend_plan_id_from_monitor_row, ) +from lib.instance.journal_form_lib import normalize_journal_entry_reason from lib.exchange.okx_orders_lib import cancel_okx_all_open_orders, fetch_okx_all_open_orders from lib.instance.journal_images_lib import ( collect_journal_slot_images, @@ -175,7 +177,7 @@ from lib.trade.position_sizing_lib import ( from lib.trade.trade_policy_lib import load_trade_policy from lib.trade.entry_model_lib import ( build_intraday_entry_reason_options, - build_trend_div_entry_reason_options, + build_journal_entry_reason_options, enrich_entry_model_display, hub_meta_entry_context, migrate_entry_model_columns, @@ -1134,8 +1136,8 @@ EARLY_EXIT_TRIGGERS = ( "其他", ) -# 趋势户:大分歧A/B/小分歧 + 策略(关键位本实例关闭) -ENTRY_REASON_OPTIONS = build_trend_div_entry_reason_options(STRATEGY_ENTRY_REASON_OPTIONS) +# 趋势户:复盘开仓类型仅 entry model;策略/风格项已拆至下单类型 +ENTRY_REASON_OPTIONS = build_journal_entry_reason_options() STATS_SEGMENT_DEFS = ( ("all", "全部交易", {"segment": "all"}), @@ -1148,9 +1150,8 @@ STATS_SEGMENT_DEFS = ( ("key_trigger", "关键位触价开仓", {"segment": "key_trigger"}), ) def normalize_entry_reason(raw, custom_text=None): - from lib.trade.entry_model_lib import normalize_review_entry_reason - - return normalize_review_entry_reason(raw, ENTRY_REASON_OPTIONS) + del custom_text + return normalize_journal_entry_reason(raw, ENTRY_REASON_OPTIONS, allow_legacy=True) def normalize_early_exit_trigger(raw): @@ -1430,6 +1431,9 @@ def init_db(): try: c.execute("ALTER TABLE journal_entries ADD COLUMN images_json TEXT") except: pass + try: + c.execute("ALTER TABLE journal_entries ADD COLUMN order_type TEXT") + except: pass try: c.execute("ALTER TABLE key_monitors ADD COLUMN direction TEXT DEFAULT 'long'") except: pass @@ -6722,6 +6726,7 @@ def render_main_page(page="trade", embed_mode=None): trend_manual_count=trend_manual_entry_reason_count(TRADE_POLICY), ) ), + order_type_options=list(JOURNAL_ORDER_TYPE_OPTIONS), key_auto_order_enabled=KEY_AUTO_ORDER_ENABLED, journal_chart_tf_choices=JOURNAL_CHART_TF_CHOICES, journal_chart_default_tf1=JOURNAL_CHART_DEFAULT_TF1, @@ -8776,7 +8781,13 @@ def del_order(id): @login_required def add_journal(): d = request.form - entry_reason_norm = normalize_entry_reason(d.get("entry_reason")) + order_type_norm = normalize_journal_order_type(d.get("order_type")) + if not order_type_norm: + flash("请选择下单类型") + return _redirect_records() + entry_reason_norm = normalize_journal_entry_reason( + d.get("entry_reason"), ENTRY_REASON_OPTIONS, allow_legacy=False + ) if not entry_reason_norm: flash("请选择开仓类型") return _redirect_records() @@ -8869,11 +8880,11 @@ def add_journal(): conn = get_db() conn.execute( """INSERT INTO journal_entries - (id, open_datetime, close_datetime, hold_duration, coin, tf, pnl, entry_reason, exit_reason, + (id, open_datetime, close_datetime, hold_duration, coin, tf, pnl, order_type, entry_reason, exit_reason, expect_rr, real_rr, early_exit, early_exit_reason, early_exit_trigger, early_exit_note, mood_score, mood_ai_score, mood_ai_comment, mood_issues, post_breakeven_stare, new_trade_while_occupied, note, image, images_json) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", ( entry_id, normalize_bj_datetime_storage(d.get("open_datetime")), @@ -8881,7 +8892,7 @@ def add_journal(): hold_duration, d.get("coin"), d.get("tf"), - d.get("pnl"), entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text, + d.get("pnl"), order_type_norm, entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text, early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note, None, None, None, mood_issues, d.get("post_breakeven_stare"), None, d.get("note"), image_filename, diff --git a/lib/common/static/instance_embed.js b/lib/common/static/instance_embed.js index 8b49187..c3efd8d 100644 --- a/lib/common/static/instance_embed.js +++ b/lib/common/static/instance_embed.js @@ -100,10 +100,14 @@ if (!revisit && tab === "strategy" && typeof global.initStrategyRollForm === "function") { global.initStrategyRollForm(); } - if (!revisit && tab === "records") { - if (typeof global.loadJournals === "function") global.loadJournals(); - if (typeof global.loadReviews === "function") global.loadReviews(); - if (typeof global.toggleReviewMode === "function") global.toggleReviewMode(); + if (tab === "records") { + if (!revisit && typeof global.loadJournals === "function") global.loadJournals(); + if (!revisit && typeof global.loadReviews === "function") global.loadReviews(); + if (global.InstanceTheme && typeof global.InstanceTheme.initReviewEditModeSync === "function") { + global.InstanceTheme.initReviewEditModeSync(); + } else if (typeof global.toggleReviewMode === "function") { + global.toggleReviewMode(); + } } if (!revisit && tab === "stats") { if (typeof global.initStatsSegmentFromUrl === "function") global.initStatsSegmentFromUrl(); diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index 37a23c8..5a446d8 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -1999,6 +1999,7 @@ html[data-theme="light"] .symbol-live-price--ok { .journal-card .journal-form-row2 { grid-template-columns: + minmax(6.5rem, 0.85fr) minmax(7.5rem, 1.15fr) minmax(7rem, 1fr) minmax(0, 1.35fr) @@ -2006,6 +2007,7 @@ html[data-theme="light"] .symbol-live-price--ok { margin-bottom: 8px; } +.journal-card .journal-form-row2 select[name="order_type"], .journal-card .journal-form-row2 select[name="entry_reason"] { font-size: 0.8rem; line-height: 1.35; diff --git a/lib/common/static/instance_theme.js b/lib/common/static/instance_theme.js index 1522277..c4b70af 100644 --- a/lib/common/static/instance_theme.js +++ b/lib/common/static/instance_theme.js @@ -290,7 +290,7 @@ apply(data.theme, { skipStore: true }); } - /** 交易记录页:核对开关与按钮 disabled 保持同步(iframe 软导航/表单恢复后不触发 change) */ + /** 交易记录页:核对开关与按钮 disabled 保持同步(含 iframe 软导航后动态挂载的 toggle) */ function syncReviewEditButtons() { const toggle = document.getElementById("review-mode-toggle"); if (!toggle) return; @@ -301,13 +301,17 @@ } function initReviewEditModeSync() { - const toggle = document.getElementById("review-mode-toggle"); - if (!toggle) return; - if (toggle.dataset.instReviewModeBound !== "1") { - toggle.dataset.instReviewModeBound = "1"; - toggle.addEventListener("input", () => { + if (!global.__instReviewModeBound) { + global.__instReviewModeBound = true; + const onToggle = () => { if (typeof global.toggleReviewMode === "function") global.toggleReviewMode(); else syncReviewEditButtons(); + }; + document.addEventListener("change", (ev) => { + if (ev.target && ev.target.id === "review-mode-toggle") onToggle(); + }); + document.addEventListener("input", (ev) => { + if (ev.target && ev.target.id === "review-mode-toggle") onToggle(); }); } const run = () => { diff --git a/lib/common/static/instance_ui.js b/lib/common/static/instance_ui.js index e8e418a..709f783 100644 --- a/lib/common/static/instance_ui.js +++ b/lib/common/static/instance_ui.js @@ -38,6 +38,7 @@ `平仓时间:${escapeHtml(o.close_datetime || "-")}`, `持仓时长:${escapeHtml(o.hold_duration || "-")}`, `盈亏:${formatPnlSpan(o.pnl)}`, + `下单类型:${escapeHtml(o.order_type || "无")}`, `开仓类型:${escapeHtml(o.entry_reason || "无")}`, `平仓/离场:${escapeHtml(exitText)}`, `预期RR:${escapeHtml(o.expect_rr || "-")}`, @@ -198,27 +199,28 @@ function parseTradeRecordRow(tr) { const cells = tr.querySelectorAll("td"); - if (cells.length < 14) return null; - const dirBadge = cells[2].querySelector(".badge"); + if (cells.length < 15) return null; + const dirBadge = cells[3].querySelector(".badge"); return { rowId: tr.id, symbol: cells[0].textContent.trim(), type: cells[1].textContent.trim(), - directionHtml: (dirBadge ? dirBadge.outerHTML : cells[2].innerHTML).trim(), - directionText: cells[2].textContent.trim(), - trigger: cells[3].textContent.trim(), - stopLoss: cells[4].textContent.trim(), - takeProfit: cells[5].textContent.trim(), - margin: cells[6].textContent.trim(), - leverage: cells[7].textContent.trim(), - holdMinutes: cells[8].textContent.trim(), - openedAt: cells[9].textContent.trim(), - closedAt: cells[10].textContent.trim(), - pnlHtml: cells[11].innerHTML.trim(), - pnlText: cells[11].textContent.trim(), - resultHtml: cells[12].innerHTML.trim(), - resultText: cells[12].textContent.trim(), - actionsHtml: cells[13].innerHTML, + entryReason: cells[2].textContent.trim(), + directionHtml: (dirBadge ? dirBadge.outerHTML : cells[3].innerHTML).trim(), + directionText: cells[3].textContent.trim(), + trigger: cells[4].textContent.trim(), + stopLoss: cells[5].textContent.trim(), + takeProfit: cells[6].textContent.trim(), + margin: cells[7].textContent.trim(), + leverage: cells[8].textContent.trim(), + holdMinutes: cells[9].textContent.trim(), + openedAt: cells[10].textContent.trim(), + closedAt: cells[11].textContent.trim(), + pnlHtml: cells[12].innerHTML.trim(), + pnlText: cells[12].textContent.trim(), + resultHtml: cells[13].innerHTML.trim(), + resultText: cells[13].textContent.trim(), + actionsHtml: cells[14].innerHTML, }; } @@ -240,7 +242,8 @@ function buildTradeRecordDetailHtml(row) { return `