Auto-fill journal direction from trade records.
Add a required direction field on the review form, persist it, and set long/short when clicking 填入复盘. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -76,7 +76,7 @@ from lib.strategy.strategy_trade_labels import (
|
|||||||
trade_record_monitor_type as resolve_trade_record_monitor_type,
|
trade_record_monitor_type as resolve_trade_record_monitor_type,
|
||||||
trend_plan_id_from_monitor_row,
|
trend_plan_id_from_monitor_row,
|
||||||
)
|
)
|
||||||
from lib.instance.journal_form_lib import normalize_journal_entry_reason
|
from lib.instance.journal_form_lib import normalize_journal_direction, normalize_journal_entry_reason
|
||||||
from lib.instance.journal_images_lib import (
|
from lib.instance.journal_images_lib import (
|
||||||
collect_journal_slot_images,
|
collect_journal_slot_images,
|
||||||
enrich_journal_api_item,
|
enrich_journal_api_item,
|
||||||
@@ -1434,6 +1434,9 @@ def init_db():
|
|||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE journal_entries ADD COLUMN order_type TEXT")
|
c.execute("ALTER TABLE journal_entries ADD COLUMN order_type TEXT")
|
||||||
except: pass
|
except: pass
|
||||||
|
try:
|
||||||
|
c.execute("ALTER TABLE journal_entries ADD COLUMN direction TEXT")
|
||||||
|
except: pass
|
||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE key_monitors ADD COLUMN direction TEXT DEFAULT 'long'")
|
c.execute("ALTER TABLE key_monitors ADD COLUMN direction TEXT DEFAULT 'long'")
|
||||||
except: pass
|
except: pass
|
||||||
@@ -9281,6 +9284,10 @@ def add_journal():
|
|||||||
if not order_type_norm:
|
if not order_type_norm:
|
||||||
flash("请选择下单类型")
|
flash("请选择下单类型")
|
||||||
return _redirect_records()
|
return _redirect_records()
|
||||||
|
direction_norm = normalize_journal_direction(d.get("direction") or d.get("direction_hint"))
|
||||||
|
if not direction_norm:
|
||||||
|
flash("请选择方向")
|
||||||
|
return _redirect_records()
|
||||||
entry_reason_norm = normalize_journal_entry_reason(
|
entry_reason_norm = normalize_journal_entry_reason(
|
||||||
d.get("entry_reason"), ENTRY_REASON_OPTIONS, allow_legacy=False
|
d.get("entry_reason"), ENTRY_REASON_OPTIONS, allow_legacy=False
|
||||||
)
|
)
|
||||||
@@ -9376,11 +9383,11 @@ def add_journal():
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO journal_entries
|
"""INSERT INTO journal_entries
|
||||||
(id, open_datetime, close_datetime, hold_duration, coin, tf, pnl, order_type, entry_reason, exit_reason,
|
(id, open_datetime, close_datetime, hold_duration, coin, tf, direction, pnl, order_type, entry_reason, exit_reason,
|
||||||
expect_rr, real_rr, early_exit, early_exit_reason, early_exit_trigger, early_exit_note,
|
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,
|
mood_score, mood_ai_score, mood_ai_comment, mood_issues, post_breakeven_stare,
|
||||||
new_trade_while_occupied, note, image, images_json)
|
new_trade_while_occupied, note, image, images_json)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
(
|
(
|
||||||
entry_id,
|
entry_id,
|
||||||
normalize_bj_datetime_storage(d.get("open_datetime")),
|
normalize_bj_datetime_storage(d.get("open_datetime")),
|
||||||
@@ -9388,6 +9395,7 @@ def add_journal():
|
|||||||
hold_duration,
|
hold_duration,
|
||||||
d.get("coin"),
|
d.get("coin"),
|
||||||
d.get("tf"),
|
d.get("tf"),
|
||||||
|
direction_norm,
|
||||||
d.get("pnl"), order_type_norm, 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,
|
early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note,
|
||||||
None, None, None, mood_issues,
|
None, None, None, mood_issues,
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ from lib.strategy.strategy_trade_labels import (
|
|||||||
trade_record_monitor_type as resolve_trade_record_monitor_type,
|
trade_record_monitor_type as resolve_trade_record_monitor_type,
|
||||||
trend_plan_id_from_monitor_row,
|
trend_plan_id_from_monitor_row,
|
||||||
)
|
)
|
||||||
from lib.instance.journal_form_lib import normalize_journal_entry_reason
|
from lib.instance.journal_form_lib import normalize_journal_direction, normalize_journal_entry_reason
|
||||||
from lib.instance.journal_images_lib import (
|
from lib.instance.journal_images_lib import (
|
||||||
collect_journal_slot_images,
|
collect_journal_slot_images,
|
||||||
enrich_journal_api_item,
|
enrich_journal_api_item,
|
||||||
@@ -1430,6 +1430,9 @@ def init_db():
|
|||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE journal_entries ADD COLUMN order_type TEXT")
|
c.execute("ALTER TABLE journal_entries ADD COLUMN order_type TEXT")
|
||||||
except: pass
|
except: pass
|
||||||
|
try:
|
||||||
|
c.execute("ALTER TABLE journal_entries ADD COLUMN direction TEXT")
|
||||||
|
except: pass
|
||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE key_monitors ADD COLUMN direction TEXT DEFAULT 'long'")
|
c.execute("ALTER TABLE key_monitors ADD COLUMN direction TEXT DEFAULT 'long'")
|
||||||
except: pass
|
except: pass
|
||||||
@@ -9148,6 +9151,10 @@ def add_journal():
|
|||||||
if not order_type_norm:
|
if not order_type_norm:
|
||||||
flash("请选择下单类型")
|
flash("请选择下单类型")
|
||||||
return _redirect_records()
|
return _redirect_records()
|
||||||
|
direction_norm = normalize_journal_direction(d.get("direction") or d.get("direction_hint"))
|
||||||
|
if not direction_norm:
|
||||||
|
flash("请选择方向")
|
||||||
|
return _redirect_records()
|
||||||
entry_reason_norm = normalize_journal_entry_reason(
|
entry_reason_norm = normalize_journal_entry_reason(
|
||||||
d.get("entry_reason"), ENTRY_REASON_OPTIONS, allow_legacy=False
|
d.get("entry_reason"), ENTRY_REASON_OPTIONS, allow_legacy=False
|
||||||
)
|
)
|
||||||
@@ -9243,11 +9250,11 @@ def add_journal():
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO journal_entries
|
"""INSERT INTO journal_entries
|
||||||
(id, open_datetime, close_datetime, hold_duration, coin, tf, pnl, order_type, entry_reason, exit_reason,
|
(id, open_datetime, close_datetime, hold_duration, coin, tf, direction, pnl, order_type, entry_reason, exit_reason,
|
||||||
expect_rr, real_rr, early_exit, early_exit_reason, early_exit_trigger, early_exit_note,
|
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,
|
mood_score, mood_ai_score, mood_ai_comment, mood_issues, post_breakeven_stare,
|
||||||
new_trade_while_occupied, note, image, images_json)
|
new_trade_while_occupied, note, image, images_json)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
(
|
(
|
||||||
entry_id,
|
entry_id,
|
||||||
normalize_bj_datetime_storage(d.get("open_datetime")),
|
normalize_bj_datetime_storage(d.get("open_datetime")),
|
||||||
@@ -9255,6 +9262,7 @@ def add_journal():
|
|||||||
hold_duration,
|
hold_duration,
|
||||||
d.get("coin"),
|
d.get("coin"),
|
||||||
d.get("tf"),
|
d.get("tf"),
|
||||||
|
direction_norm,
|
||||||
d.get("pnl"), order_type_norm, 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,
|
early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note,
|
||||||
None, None, None, mood_issues,
|
None, None, None, mood_issues,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ from lib.strategy.strategy_trade_labels import (
|
|||||||
trade_record_monitor_type as resolve_trade_record_monitor_type,
|
trade_record_monitor_type as resolve_trade_record_monitor_type,
|
||||||
trend_plan_id_from_monitor_row,
|
trend_plan_id_from_monitor_row,
|
||||||
)
|
)
|
||||||
from lib.instance.journal_form_lib import normalize_journal_entry_reason
|
from lib.instance.journal_form_lib import normalize_journal_direction, normalize_journal_entry_reason
|
||||||
from lib.exchange.okx_orders_lib import cancel_okx_all_open_orders, fetch_okx_all_open_orders
|
from lib.exchange.okx_orders_lib import cancel_okx_all_open_orders, fetch_okx_all_open_orders
|
||||||
from lib.instance.journal_images_lib import (
|
from lib.instance.journal_images_lib import (
|
||||||
collect_journal_slot_images,
|
collect_journal_slot_images,
|
||||||
@@ -1435,6 +1435,9 @@ def init_db():
|
|||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE journal_entries ADD COLUMN order_type TEXT")
|
c.execute("ALTER TABLE journal_entries ADD COLUMN order_type TEXT")
|
||||||
except: pass
|
except: pass
|
||||||
|
try:
|
||||||
|
c.execute("ALTER TABLE journal_entries ADD COLUMN direction TEXT")
|
||||||
|
except: pass
|
||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE key_monitors ADD COLUMN direction TEXT DEFAULT 'long'")
|
c.execute("ALTER TABLE key_monitors ADD COLUMN direction TEXT DEFAULT 'long'")
|
||||||
except: pass
|
except: pass
|
||||||
@@ -8836,6 +8839,10 @@ def add_journal():
|
|||||||
if not order_type_norm:
|
if not order_type_norm:
|
||||||
flash("请选择下单类型")
|
flash("请选择下单类型")
|
||||||
return _redirect_records()
|
return _redirect_records()
|
||||||
|
direction_norm = normalize_journal_direction(d.get("direction") or d.get("direction_hint"))
|
||||||
|
if not direction_norm:
|
||||||
|
flash("请选择方向")
|
||||||
|
return _redirect_records()
|
||||||
entry_reason_norm = normalize_journal_entry_reason(
|
entry_reason_norm = normalize_journal_entry_reason(
|
||||||
d.get("entry_reason"), ENTRY_REASON_OPTIONS, allow_legacy=False
|
d.get("entry_reason"), ENTRY_REASON_OPTIONS, allow_legacy=False
|
||||||
)
|
)
|
||||||
@@ -8931,11 +8938,11 @@ def add_journal():
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO journal_entries
|
"""INSERT INTO journal_entries
|
||||||
(id, open_datetime, close_datetime, hold_duration, coin, tf, pnl, order_type, entry_reason, exit_reason,
|
(id, open_datetime, close_datetime, hold_duration, coin, tf, direction, pnl, order_type, entry_reason, exit_reason,
|
||||||
expect_rr, real_rr, early_exit, early_exit_reason, early_exit_trigger, early_exit_note,
|
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,
|
mood_score, mood_ai_score, mood_ai_comment, mood_issues, post_breakeven_stare,
|
||||||
new_trade_while_occupied, note, image, images_json)
|
new_trade_while_occupied, note, image, images_json)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
(
|
(
|
||||||
entry_id,
|
entry_id,
|
||||||
normalize_bj_datetime_storage(d.get("open_datetime")),
|
normalize_bj_datetime_storage(d.get("open_datetime")),
|
||||||
@@ -8943,6 +8950,7 @@ def add_journal():
|
|||||||
hold_duration,
|
hold_duration,
|
||||||
d.get("coin"),
|
d.get("coin"),
|
||||||
d.get("tf"),
|
d.get("tf"),
|
||||||
|
direction_norm,
|
||||||
d.get("pnl"), order_type_norm, 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,
|
early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note,
|
||||||
None, None, None, mood_issues,
|
None, None, None, mood_issues,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
typeof formatExitLine === "function" ? formatExitLine(o) : o.exit_reason || "无";
|
typeof formatExitLine === "function" ? formatExitLine(o) : o.exit_reason || "无";
|
||||||
const lines = [
|
const lines = [
|
||||||
`币种/周期:${escapeHtml(o.coin || "-")} ${escapeHtml(o.tf || "-")}`,
|
`币种/周期:${escapeHtml(o.coin || "-")} ${escapeHtml(o.tf || "-")}`,
|
||||||
|
`方向:${escapeHtml((function(){ const d = inferJournalDirection(o); return d ? d.text : "-"; })())}`,
|
||||||
`开仓时间:${escapeHtml(o.open_datetime || "-")}`,
|
`开仓时间:${escapeHtml(o.open_datetime || "-")}`,
|
||||||
`平仓时间:${escapeHtml(o.close_datetime || "-")}`,
|
`平仓时间:${escapeHtml(o.close_datetime || "-")}`,
|
||||||
`持仓时长:${escapeHtml(o.hold_duration || "-")}`,
|
`持仓时长:${escapeHtml(o.hold_duration || "-")}`,
|
||||||
|
|||||||
@@ -42,3 +42,13 @@ def journal_entry_reason_valid(raw: Optional[str], allowed: Sequence[str]) -> bo
|
|||||||
|
|
||||||
def journal_order_type_valid(raw: Optional[str]) -> bool:
|
def journal_order_type_valid(raw: Optional[str]) -> bool:
|
||||||
return bool(normalize_journal_order_type(raw))
|
return bool(normalize_journal_order_type(raw))
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_journal_direction(raw: Optional[str]) -> str:
|
||||||
|
s = (raw or "").strip().lower()
|
||||||
|
if s in ("long", "buy", "多", "做多"):
|
||||||
|
return "long"
|
||||||
|
if s in ("short", "sell", "空", "做空"):
|
||||||
|
return "short"
|
||||||
|
# 兼容旧隐藏字段 direction_hint
|
||||||
|
return ""
|
||||||
|
|||||||
@@ -545,6 +545,8 @@ function fillJournalFromTrade(t){
|
|||||||
if(stopHint){ stopHint.value = slPx; }
|
if(stopHint){ stopHint.value = slPx; }
|
||||||
const dirHint = document.getElementById("direction-hint");
|
const dirHint = document.getElementById("direction-hint");
|
||||||
if(dirHint){ dirHint.value = t.direction || "long"; }
|
if(dirHint){ dirHint.value = t.direction || "long"; }
|
||||||
|
const dirNorm = String(t.direction || "long").toLowerCase() === "short" ? "short" : "long";
|
||||||
|
setJournalField("direction", dirNorm);
|
||||||
setJournalField("early_exit_trigger", "");
|
setJournalField("early_exit_trigger", "");
|
||||||
setJournalField("early_exit_note", "");
|
setJournalField("early_exit_note", "");
|
||||||
const kst = String(t.key_signal_type || "").trim();
|
const kst = String(t.key_signal_type || "").trim();
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=9"></script>
|
<script src="/static/instance_ui.js?v=10"></script>
|
||||||
<script src="/static/journal_upload_slots.js?v=3"></script>
|
<script src="/static/journal_upload_slots.js?v=3"></script>
|
||||||
<script src="/static/instance_records_mobile.js?v=2"></script>
|
<script src="/static/instance_records_mobile.js?v=2"></script>
|
||||||
<script src="/static/time_close_ui.js?v=3"></script>
|
<script src="/static/time_close_ui.js?v=3"></script>
|
||||||
|
|||||||
@@ -447,7 +447,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=9"></script>
|
<script src="/static/instance_ui.js?v=10"></script>
|
||||||
<script src="/static/journal_upload_slots.js?v=3"></script>
|
<script src="/static/journal_upload_slots.js?v=3"></script>
|
||||||
<script src="/static/instance_records_mobile.js?v=2"></script>
|
<script src="/static/instance_records_mobile.js?v=2"></script>
|
||||||
<script src="/static/time_close_ui.js?v=3"></script>
|
<script src="/static/time_close_ui.js?v=3"></script>
|
||||||
@@ -1002,6 +1002,8 @@ function fillJournalFromTrade(t){
|
|||||||
if(stopHint){ stopHint.value = slPx; }
|
if(stopHint){ stopHint.value = slPx; }
|
||||||
const dirHint = document.getElementById("direction-hint");
|
const dirHint = document.getElementById("direction-hint");
|
||||||
if(dirHint){ dirHint.value = t.direction || "long"; }
|
if(dirHint){ dirHint.value = t.direction || "long"; }
|
||||||
|
const dirNorm = String(t.direction || "long").toLowerCase() === "short" ? "short" : "long";
|
||||||
|
setJournalField("direction", dirNorm);
|
||||||
setJournalField("early_exit_trigger", "");
|
setJournalField("early_exit_trigger", "");
|
||||||
setJournalField("early_exit_note", "");
|
setJournalField("early_exit_note", "");
|
||||||
const kst = String(t.key_signal_type || "").trim();
|
const kst = String(t.key_signal_type || "").trim();
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
<input name="real_rr" class="journal-field-num" placeholder="实际RR">
|
<input name="real_rr" class="journal-field-num" placeholder="实际RR">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-grid journal-form-row2">
|
<div class="form-grid journal-form-row2">
|
||||||
|
<select name="direction" id="journal-direction" class="journal-field-direction" required title="做多/做空">
|
||||||
|
<option value="">方向(必选)</option>
|
||||||
|
<option value="long">做多</option>
|
||||||
|
<option value="short">做空</option>
|
||||||
|
</select>
|
||||||
<select name="order_type" id="journal-order-type" class="journal-field-order-type" required title="下单来源:下单监控/关键位/趋势回调/顺势加仓">
|
<select name="order_type" id="journal-order-type" class="journal-field-order-type" required title="下单来源:下单监控/关键位/趋势回调/顺势加仓">
|
||||||
<option value="">下单类型(必选)</option>
|
<option value="">下单类型(必选)</option>
|
||||||
{% for ot in order_type_options %}
|
{% for ot in order_type_options %}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import unittest
|
|||||||
|
|
||||||
from lib.instance.journal_form_lib import (
|
from lib.instance.journal_form_lib import (
|
||||||
journal_entry_reason_valid,
|
journal_entry_reason_valid,
|
||||||
|
normalize_journal_direction,
|
||||||
normalize_journal_entry_reason,
|
normalize_journal_entry_reason,
|
||||||
)
|
)
|
||||||
from lib.strategy.strategy_trade_labels import (
|
from lib.strategy.strategy_trade_labels import (
|
||||||
@@ -58,6 +59,13 @@ class JournalFormLibTests(unittest.TestCase):
|
|||||||
self.assertTrue(journal_entry_reason_valid("顺势/大分歧A", opts))
|
self.assertTrue(journal_entry_reason_valid("顺势/大分歧A", opts))
|
||||||
self.assertFalse(journal_entry_reason_valid("趋势单", opts))
|
self.assertFalse(journal_entry_reason_valid("趋势单", opts))
|
||||||
|
|
||||||
|
def test_normalize_journal_direction(self):
|
||||||
|
self.assertEqual(normalize_journal_direction("short"), "short")
|
||||||
|
self.assertEqual(normalize_journal_direction("做空"), "short")
|
||||||
|
self.assertEqual(normalize_journal_direction("long"), "long")
|
||||||
|
self.assertEqual(normalize_journal_direction("做多"), "long")
|
||||||
|
self.assertEqual(normalize_journal_direction(""), "")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user