diff --git a/crypto_monitor_gate/__pycache__/app.cpython-310.pyc b/crypto_monitor_gate/__pycache__/app.cpython-310.pyc new file mode 100644 index 0000000..eeb66ab Binary files /dev/null and b/crypto_monitor_gate/__pycache__/app.cpython-310.pyc differ diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 3b97cdc..be80b84 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -827,13 +827,30 @@ ENTRY_REASON_OPTIONS = ( "趋势空头:小分歧高吸入场(左侧),确认条件:二次探顶", "波段单:5m顺势突破,确认条件:2根k线+成交量放大+4h同向+日成交量前20", ) +# 复盘表单「其他」选项的 value(非入库值;自定义文本走 entry_reason_custom) +ENTRY_REASON_OTHER = "__OTHER__" -def normalize_entry_reason(raw): +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 + + def normalize_early_exit_trigger(raw): v = str(raw or "").strip() return v if v in EARLY_EXIT_TRIGGERS else "" @@ -865,7 +882,7 @@ def ai_extract_journal_from_image(image_b64): 2) 时间输出为 YYYY-MM-DDTHH:MM(用于 HTML datetime-local),无法识别填空字符串。 3) 不要猜测主观原因;early_exit_note(仅手工平仓)、note 默认留空,除非图中明确写出。 4) 允许字段为空。 -5) entry_reason 只能从下列完整字符串中选一个(一字不差;截图无法归类则填空字符串): +5) entry_reason:优先从下列完整字符串中选一个(一字不差);若无法归类则可将简述写入 entry_reason(保存时也可选表单「其他」手写): - 趋势多头:4h大结构突破前进场,确认条件:三次探顶,5m收敛不创新低 - 趋势空头:4h大结构突破前进场,确认条件:三次探底,5m收敛不创新高 - 趋势多头:小分歧低吸入场(左侧),确认条件:二次探底 @@ -3900,6 +3917,7 @@ def render_main_page(page="trade"): occupied_miss_total=occupied_miss_total, price_fmt=format_price_for_symbol, entry_reason_options=list(ENTRY_REASON_OPTIONS), + entry_reason_other_value=ENTRY_REASON_OTHER, exchange_display=EXCHANGE_DISPLAY_NAME, ) @@ -5031,9 +5049,9 @@ def add_miss(): @login_required def add_journal(): d = request.form - entry_reason_norm = normalize_entry_reason(d.get("entry_reason")) + entry_reason_norm = normalize_entry_reason(d.get("entry_reason"), d.get("entry_reason_custom")) 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() @@ -5356,8 +5374,8 @@ def api_trade_record_review_update(): reviewed_entry_reason_update = _MISSING_ER 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 + if s and not entry_reason_valid_for_storage(s): + return jsonify({"ok": False, "msg": "开仓类型须为五种固定整句之一、自定义说明(2000字内)或留空"}), 400 reviewed_entry_reason_update = s or None conn = get_db() diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index bcdb3ab..7d59cb9 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -36,6 +36,10 @@ font-size:.8rem; line-height:1.35; } + .journal-card .form-grid input[name="entry_reason_custom"]{ + grid-column:1/-1; + font-size:.8rem; + } input,select,button,textarea{padding:8px 10px;border-radius:8px;border:1px solid #2e2e45;background:#1a1a29;color:#fff;font-size:.88rem;outline:none} button{background:linear-gradient(90deg,#4285f4,#7b42ff);border:none;cursor:pointer} .list{display:flex;flex-direction:column;gap:8px;margin-top:8px;max-height:240px;overflow:auto} @@ -427,12 +431,14 @@ - {% for er in entry_reason_options %} {% endfor %} + +