Forráskód Böngészése

Add followthrough recovery tracking for dragon v2

erwin 5 napja
szülő
commit
fa4f4147ea
56 módosított fájl, 3912 hozzáadás és 179 törlés
  1. 7 0
      research/dragon/v2/MEMORY.md
  2. 1 0
      research/dragon/v2/USER.md
  3. 129 0
      research/dragon/v2/daily_reports/dragon_daily_rc1_manifest_2026-06-04.json
  4. 74 0
      research/dragon/v2/daily_reports/dragon_daily_signal_report_2026-06-04.md
  5. 4 3
      research/dragon/v2/dragon_branch_divergence_report.md
  6. 129 0
      research/dragon/v2/dragon_daily_rc1_manifest.json
  7. 48 0
      research/dragon/v2/dragon_daily_signal_report.html
  8. 31 31
      research/dragon/v2/dragon_daily_signal_report.md
  9. 28 0
      research/dragon/v2/dragon_followthrough_mid_exit_review.md
  10. 269 0
      research/dragon/v2/dragon_followthrough_mid_exit_review.py
  11. 36 0
      research/dragon/v2/dragon_followthrough_profit_loop_review.md
  12. 694 0
      research/dragon/v2/dragon_followthrough_profit_loop_review.py
  13. 104 104
      research/dragon/v2/dragon_forward_observation_state.json
  14. 48 0
      research/dragon/v2/dragon_forward_weekly_review.html
  15. 19 19
      research/dragon/v2/dragon_forward_weekly_review.md
  16. 76 0
      research/dragon/v2/dragon_glued_followthrough_confirmation.py
  17. 32 0
      research/dragon/v2/dragon_glued_followthrough_exit.py
  18. 1 1
      research/dragon/v2/dragon_historical_trade_details.html
  19. 1 1
      research/dragon/v2/dragon_monitor_health_report.md
  20. 57 0
      research/dragon/v2/dragon_reports_index.html
  21. 4 4
      research/dragon/v2/dragon_rollout_governance_report.md
  22. 1 1
      research/dragon/v2/dragon_rollout_rollback_runbook.md
  23. 3 3
      research/dragon/v2/dragon_rollout_state.json
  24. 68 12
      research/dragon/v2/dragon_signal_change_review.md
  25. 48 0
      research/dragon/v2/html_reports/dragon_daily_signal_report_2026-06-04.html
  26. 48 0
      research/dragon/v2/html_reports/dragon_forward_weekly_review_2026-06-04.html
  27. 373 0
      research/dragon/v2/html_reports/dragon_historical_trade_details_2026-06-04.html
  28. 57 0
      research/dragon/v2/html_reports/dragon_reports_index_2026-06-04.html
  29. 57 0
      research/dragon/v2/html_reports/index.html
  30. 51 0
      research/dragon/v2/memory/2026-04-07.md
  31. 224 0
      research/dragon/v2/memory/2026-04-08.md
  32. 205 0
      research/dragon/v2/memory/2026-04-10.md
  33. 72 0
      research/dragon/v2/memory/2026-04-11.md
  34. 12 0
      research/dragon/v2/memory/2026-06-04.md
  35. 3 0
      research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/.openspec.yaml
  36. 54 0
      research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/acceptance-summary.md
  37. 67 0
      research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/design.md
  38. 32 0
      research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/proposal.md
  39. 40 0
      research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/specs/followthrough-profit-loop-research/spec.md
  40. 22 0
      research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/tasks.md
  41. 3 0
      research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/.openspec.yaml
  42. 78 0
      research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/acceptance-summary.md
  43. 56 0
      research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/design.md
  44. 27 0
      research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/proposal.md
  45. 33 0
      research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/specs/followthrough-mid-exit-shadow-probe/spec.md
  46. 18 0
      research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/tasks.md
  47. 3 0
      research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/.openspec.yaml
  48. 51 0
      research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/acceptance-summary.md
  49. 69 0
      research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/design.md
  50. 32 0
      research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/proposal.md
  51. 50 0
      research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/specs/glued-followthrough-shadow-pending/spec.md
  52. 28 0
      research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/specs/shadow-branch-observation/spec.md
  53. 36 0
      research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/tasks.md
  54. 41 0
      research/dragon/v2/tests/test_followthrough_mid_exit_probe.py
  55. 43 0
      research/dragon/v2/tests/test_followthrough_profit_loop_review.py
  56. 115 0
      research/dragon/v2/tests/test_glued_followthrough_shadow_reentry.py

+ 7 - 0
research/dragon/v2/MEMORY.md

@@ -1185,3 +1185,10 @@
 - Current practical conclusion:
 - keep this as a shadow branch and continue observing
 - do not yet promote it to formal RC1 because the evidence still comes from one repaired-trend historical path
+
+- 2026-06-04 daily/forward refresh:
+- official source data refreshed through `2026-06-03`, then intraday snapshot appended for `2026-06-04`
+- latest evaluated close `4439.771`, `a1=0.0439`, `b1=0.0123`, `c1=87.74`
+- latest markers all false (`KDJ buy/sell=False`, `QL buy/sell=False`)
+- all tracked branches remain in position from `2026-05-06` `BUY` `dual_gold_resonance_buy`, open return about `+10.33%`
+- no latest-bar events; forward rollout decision `FORWARD_OK`, active branch `alpha_first_glued_refined_hot_cap`

+ 1 - 0
research/dragon/v2/USER.md

@@ -30,3 +30,4 @@
 - User instruction: set false-veto recovery as the current first priority and continue development autonomously without mid-task interruptions.
 - Current research direction after lean Phase 2 review: keep `ql_rebound_weak_followthrough` and `high_zone_weak_b1` in hard-block territory for now; if further optimization is attempted, focus narrowly on `mid_zone_very_weak_b1` execution timing and entry-specific exit treatment.
 - Current shadow-tracking direction after the next experiment: keep the new `alpha_first_glued_followthrough_mid_exit_probe` branch under observation as the only promising followthrough-exit variant; do not promote it yet because the evidence is still one repaired-trend path.
+- Latest operational state as of `2026-06-04`: default branch `alpha_first_glued_refined_hot_cap` remains active and in position from `2026-05-06` `dual_gold_resonance_buy`; no latest-bar event fired; rollout decision `FORWARD_OK`.

+ 129 - 0
research/dragon/v2/daily_reports/dragon_daily_rc1_manifest_2026-06-04.json

@@ -0,0 +1,129 @@
+{
+  "release_version": "RC1",
+  "branch": "alpha_first_glued_refined_hot_cap",
+  "config": {
+    "disabled_rules": [],
+    "post_exit_confirmation_window_days": 10,
+    "aux_sell_same_side_once_per_cycle": true,
+    "aux_sell_duplicate_cooldown_days": 5,
+    "aux_sell_high_zone_kdj_only_block_c1": 85.0,
+    "aux_sell_high_zone_kdj_only_block_b1": -0.02,
+    "aux_sell_high_zone_warning_c1": 80.0,
+    "aux_sell_strong_break_c1": 60.0,
+    "aux_sell_strong_break_b1": -0.05,
+    "aux_sell_stronger_c1_delta": 8.0,
+    "aux_sell_stronger_b1_delta": 0.05,
+    "aux_sell_high_zone_rearm_c1_delta": 2.0,
+    "state_crash_followthrough_window_days": 5,
+    "state_crash_followthrough_repeat_cooldown_days": 4,
+    "state_crash_followthrough_c1_max": 80.0,
+    "state_crash_followthrough_a1_max": 0.01,
+    "state_crash_followthrough_b1_max": -0.15,
+    "glued_high_weak_rebound_high_c1": 68.0,
+    "glued_high_weak_rebound_high_b1": -0.08,
+    "glued_high_weak_rebound_mid_c1": 50.0,
+    "glued_high_weak_rebound_mid_b1": -0.15,
+    "glued_high_weak_rebound_ql_c1_low": 35.0,
+    "glued_high_weak_rebound_ql_c1_high": 55.0,
+    "glued_high_weak_rebound_ql_b1": -0.06,
+    "glued_high_weak_rebound_ql_a1": -0.013,
+    "glued_followthrough_pending_enabled": false,
+    "glued_followthrough_confirm_window_bars": 3,
+    "glued_followthrough_allow_mid_zone_very_weak_b1": false,
+    "glued_followthrough_allow_high_zone_weak_b1": false,
+    "glued_followthrough_allow_ql_rebound_weak_followthrough": false,
+    "glued_followthrough_require_ql_buy_reconfirm": true,
+    "glued_followthrough_require_close_break_signal_close": true,
+    "glued_followthrough_require_b1_repair": true,
+    "glued_followthrough_b1_repair_min": 0.02,
+    "glued_followthrough_exit_hold_kdj_only_enabled": false,
+    "glued_followthrough_exit_hold_kdj_only_days_max": 12,
+    "glued_followthrough_exit_hold_kdj_only_c1_low": 55.0,
+    "glued_followthrough_exit_hold_kdj_only_c1_high": 60.0,
+    "glued_followthrough_exit_hold_kdj_only_a1_min": 0.0,
+    "glued_followthrough_exit_hold_kdj_only_b1_min": -0.02,
+    "glued_selective_hot_c1_min": 40.0,
+    "glued_selective_hot_c1_max": 75.0,
+    "glued_selective_hot_b1_min": 0.1,
+    "glued_selective_low_c1_min": 23.0,
+    "glued_selective_low_c1_max": 28.0,
+    "glued_selective_low_b1_max": 0.02,
+    "deep_oversold_filter1_c1_low": 13.0,
+    "deep_oversold_filter1_c1_high": 15.0,
+    "deep_oversold_filter1_a1_min": -0.04,
+    "deep_oversold_filter1_b1_max": -0.08,
+    "deep_oversold_filter2_c1_low": 13.0,
+    "deep_oversold_filter2_c1_high": 14.5,
+    "deep_oversold_filter2_a1_min": -0.04,
+    "deep_oversold_filter2_b1_min": -0.06,
+    "deep_oversold_entry_c1_max": 16.0,
+    "deep_oversold_entry_a1_min": -0.09,
+    "deep_oversold_entry_b1_min": -0.1,
+    "deep_oversold_shallow_ql_fallback": true,
+    "deep_oversold_positive_b1_fallback_a1_min": -0.02,
+    "deep_oversold_block_positive_b1_rebound": false,
+    "deep_oversold_block_shallow_false_start_without_ql": false,
+    "deep_oversold_confirm_weak_with_ql": false,
+    "deep_oversold_confirm_window_bars": 2,
+    "deep_oversold_selective_positive_b1_c1_max": 15.3,
+    "deep_oversold_selective_shallow_c1_min": 12.0,
+    "deep_oversold_selective_shallow_b1_min": -0.025,
+    "deep_oversold_selective_mixed_c1_max": 10.2,
+    "deep_oversold_selective_mixed_require_no_ql": true,
+    "oversold_recovery_c1_low": 18.0,
+    "oversold_recovery_c1_high": 22.0,
+    "oversold_recovery_a1_min": -0.03,
+    "oversold_recovery_a1_max": 0.0,
+    "oversold_recovery_b1_min": -0.02,
+    "oversold_reversal_after_ql_block_c1_low": 23.0,
+    "oversold_reversal_after_ql_block_c1_high": 26.0,
+    "oversold_reversal_after_ql_block_b1_min": -0.12,
+    "oversold_reversal_after_ql_block_a1_min": -0.035,
+    "oversold_reversal_after_ql_entry_c1_low": 20.0,
+    "oversold_reversal_after_ql_entry_c1_high": 26.0,
+    "oversold_reversal_after_ql_entry_a1_min": -0.04,
+    "oversold_reversal_after_ql_entry_a1_max": 0.0,
+    "oversold_reversal_after_ql_entry_b1_min": -0.22,
+    "oversold_reversal_after_ql_entry_b1_max": 0.0,
+    "post_sell_rebound_block_high_c1": 22.0,
+    "post_sell_rebound_block_high_a1_min": -0.035,
+    "post_sell_rebound_block_high_b1_max": -0.07,
+    "post_sell_rebound_block_low_c1": 15.0,
+    "post_sell_rebound_block_low_a1_min": -0.04,
+    "post_sell_rebound_block_low_b1_max": -0.095,
+    "post_sell_rebound_entry1_c1_low": 18.0,
+    "post_sell_rebound_entry1_c1_high": 30.0,
+    "post_sell_rebound_entry1_a1_min": -0.045,
+    "post_sell_rebound_entry1_a1_max": 0.0,
+    "post_sell_rebound_entry1_b1_low": -0.09,
+    "post_sell_rebound_entry1_b1_high": -0.04,
+    "post_sell_rebound_entry2_c1_high": 19.0,
+    "post_sell_rebound_entry2_a1_min": -0.04,
+    "post_sell_rebound_entry2_a1_max": 0.0,
+    "post_sell_rebound_entry2_b1_low": -0.13,
+    "post_sell_rebound_entry2_b1_high": -0.09,
+    "predictive_b1_break_short_holding_days_max": 2,
+    "predictive_b1_break_short_a1_min": -0.02,
+    "predictive_b1_break_short_a1_max": 0.0,
+    "predictive_b1_break_short_b1_max": -0.13,
+    "predictive_b1_break_short_c1_low": 50.0,
+    "predictive_b1_break_short_c1_high": 70.0,
+    "predictive_b1_break_long_holding_days_min": 40,
+    "predictive_b1_break_long_max_c1": 80.0,
+    "predictive_b1_break_long_max_a1": 0.15,
+    "predictive_b1_break_long_max_b1": 0.3,
+    "predictive_b1_break_long_ql_days_max": 7,
+    "predictive_b1_break_long_a1_min": -0.02,
+    "predictive_b1_break_long_a1_max": 0.0,
+    "predictive_b1_break_long_b1_max": -0.12,
+    "predictive_b1_break_long_c1_low": 60.0,
+    "predictive_b1_break_long_c1_high": 65.0,
+    "enable_knife_take_profit_2_wait_ql": true
+  },
+  "as_of_request_date": "2026-06-04",
+  "latest_bar_date": "2026-06-04",
+  "data_mode": "intraday_snapshot",
+  "snapshot_appended": true,
+  "snapshot_timestamp": "2026-06-04T15:12:30",
+  "historical_latest_bar_date": "2026-06-03"
+}

+ 74 - 0
research/dragon/v2/daily_reports/dragon_daily_signal_report_2026-06-04.md

@@ -0,0 +1,74 @@
+# Dragon Daily Signal Report
+
+- Request date: `2026-06-04`
+- Latest available market bar: `2026-06-04`
+- Data mode: `intraday_snapshot`
+- Historical latest official bar: `2026-06-03`
+- Snapshot timestamp: `2026-06-04T15:12:30`
+- Snapshot rule: `current market price is used as today's close for indicator and signal evaluation`
+- Instrument: `399673`
+- Forward default branch: `alpha_first_glued_refined_hot_cap`
+- Benchmark control branch: `alpha_first_selective_veto`
+
+## Latest Branch Status
+### workbook_preserving
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
+- latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
+- events on latest bar: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
+
+### alpha_first_selective_veto
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
+- latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
+- events on latest bar: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
+
+### alpha_first_glued_refined_hot_cap
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
+- latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
+- events on latest bar: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
+
+### alpha_first_glued_followthrough_probe
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
+- latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
+- events on latest bar: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
+
+### alpha_first_glued_followthrough_mid_exit_probe
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
+- latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
+- events on latest bar: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
+
+## Monitor Snapshot
+- warnings: `0`
+- hard breaches: `0`
+- missing data metrics: `0`
+- next_open avg_return delta vs control: `0.53%`
+- next_open PF delta vs control: `0.92`
+- next_open max_drawdown refined: `-13.19%`
+- next_open max loss streak refined: `8`
+- next_open + 20bps CAGR refined/control: `25.17%` / `22.07%`
+
+## Outputs
+- `dragon_daily_signal_snapshot.csv`
+- `dragon_daily_branch_status.csv`
+- `dragon_daily_monitor_snapshot.csv`
+- `dragon_historical_trade_details.csv`
+- `dragon_daily_rc1_manifest.json`

+ 4 - 3
research/dragon/v2/dragon_branch_divergence_report.md

@@ -1,11 +1,11 @@
 # Dragon Branch Divergence Report
 
-- latest_bar_date: `2026-04-10`
+- latest_bar_date: `2026-06-04`
 - divergence_level: `none`
 - same_position_flag: `True`
 - same_latest_real_event_flag: `True`
-- refined_latest_event: `2026-02-13|SELL|knife_take_profit_2_glued`
-- control_latest_event: `2026-02-13|SELL|knife_take_profit_2_glued`
+- refined_latest_event: `2026-05-06|BUY|dual_gold_resonance_buy`
+- control_latest_event: `2026-05-06|BUY|dual_gold_resonance_buy`
 - next_open_avg_return_delta: `0.53%`
 - next_open_pf_delta: `0.92`
 - warning_count: `0`
@@ -18,3 +18,4 @@
 - `2026-04-08`: level `none`, same_position `True`, same_event `True`
 - `2026-04-09`: level `none`, same_position `True`, same_event `True`
 - `2026-04-10`: level `none`, same_position `True`, same_event `True`
+- `2026-06-04`: level `none`, same_position `True`, same_event `True`

+ 129 - 0
research/dragon/v2/dragon_daily_rc1_manifest.json

@@ -0,0 +1,129 @@
+{
+  "release_version": "RC1",
+  "branch": "alpha_first_glued_refined_hot_cap",
+  "config": {
+    "disabled_rules": [],
+    "post_exit_confirmation_window_days": 10,
+    "aux_sell_same_side_once_per_cycle": true,
+    "aux_sell_duplicate_cooldown_days": 5,
+    "aux_sell_high_zone_kdj_only_block_c1": 85.0,
+    "aux_sell_high_zone_kdj_only_block_b1": -0.02,
+    "aux_sell_high_zone_warning_c1": 80.0,
+    "aux_sell_strong_break_c1": 60.0,
+    "aux_sell_strong_break_b1": -0.05,
+    "aux_sell_stronger_c1_delta": 8.0,
+    "aux_sell_stronger_b1_delta": 0.05,
+    "aux_sell_high_zone_rearm_c1_delta": 2.0,
+    "state_crash_followthrough_window_days": 5,
+    "state_crash_followthrough_repeat_cooldown_days": 4,
+    "state_crash_followthrough_c1_max": 80.0,
+    "state_crash_followthrough_a1_max": 0.01,
+    "state_crash_followthrough_b1_max": -0.15,
+    "glued_high_weak_rebound_high_c1": 68.0,
+    "glued_high_weak_rebound_high_b1": -0.08,
+    "glued_high_weak_rebound_mid_c1": 50.0,
+    "glued_high_weak_rebound_mid_b1": -0.15,
+    "glued_high_weak_rebound_ql_c1_low": 35.0,
+    "glued_high_weak_rebound_ql_c1_high": 55.0,
+    "glued_high_weak_rebound_ql_b1": -0.06,
+    "glued_high_weak_rebound_ql_a1": -0.013,
+    "glued_followthrough_pending_enabled": false,
+    "glued_followthrough_confirm_window_bars": 3,
+    "glued_followthrough_allow_mid_zone_very_weak_b1": false,
+    "glued_followthrough_allow_high_zone_weak_b1": false,
+    "glued_followthrough_allow_ql_rebound_weak_followthrough": false,
+    "glued_followthrough_require_ql_buy_reconfirm": true,
+    "glued_followthrough_require_close_break_signal_close": true,
+    "glued_followthrough_require_b1_repair": true,
+    "glued_followthrough_b1_repair_min": 0.02,
+    "glued_followthrough_exit_hold_kdj_only_enabled": false,
+    "glued_followthrough_exit_hold_kdj_only_days_max": 12,
+    "glued_followthrough_exit_hold_kdj_only_c1_low": 55.0,
+    "glued_followthrough_exit_hold_kdj_only_c1_high": 60.0,
+    "glued_followthrough_exit_hold_kdj_only_a1_min": 0.0,
+    "glued_followthrough_exit_hold_kdj_only_b1_min": -0.02,
+    "glued_selective_hot_c1_min": 40.0,
+    "glued_selective_hot_c1_max": 75.0,
+    "glued_selective_hot_b1_min": 0.1,
+    "glued_selective_low_c1_min": 23.0,
+    "glued_selective_low_c1_max": 28.0,
+    "glued_selective_low_b1_max": 0.02,
+    "deep_oversold_filter1_c1_low": 13.0,
+    "deep_oversold_filter1_c1_high": 15.0,
+    "deep_oversold_filter1_a1_min": -0.04,
+    "deep_oversold_filter1_b1_max": -0.08,
+    "deep_oversold_filter2_c1_low": 13.0,
+    "deep_oversold_filter2_c1_high": 14.5,
+    "deep_oversold_filter2_a1_min": -0.04,
+    "deep_oversold_filter2_b1_min": -0.06,
+    "deep_oversold_entry_c1_max": 16.0,
+    "deep_oversold_entry_a1_min": -0.09,
+    "deep_oversold_entry_b1_min": -0.1,
+    "deep_oversold_shallow_ql_fallback": true,
+    "deep_oversold_positive_b1_fallback_a1_min": -0.02,
+    "deep_oversold_block_positive_b1_rebound": false,
+    "deep_oversold_block_shallow_false_start_without_ql": false,
+    "deep_oversold_confirm_weak_with_ql": false,
+    "deep_oversold_confirm_window_bars": 2,
+    "deep_oversold_selective_positive_b1_c1_max": 15.3,
+    "deep_oversold_selective_shallow_c1_min": 12.0,
+    "deep_oversold_selective_shallow_b1_min": -0.025,
+    "deep_oversold_selective_mixed_c1_max": 10.2,
+    "deep_oversold_selective_mixed_require_no_ql": true,
+    "oversold_recovery_c1_low": 18.0,
+    "oversold_recovery_c1_high": 22.0,
+    "oversold_recovery_a1_min": -0.03,
+    "oversold_recovery_a1_max": 0.0,
+    "oversold_recovery_b1_min": -0.02,
+    "oversold_reversal_after_ql_block_c1_low": 23.0,
+    "oversold_reversal_after_ql_block_c1_high": 26.0,
+    "oversold_reversal_after_ql_block_b1_min": -0.12,
+    "oversold_reversal_after_ql_block_a1_min": -0.035,
+    "oversold_reversal_after_ql_entry_c1_low": 20.0,
+    "oversold_reversal_after_ql_entry_c1_high": 26.0,
+    "oversold_reversal_after_ql_entry_a1_min": -0.04,
+    "oversold_reversal_after_ql_entry_a1_max": 0.0,
+    "oversold_reversal_after_ql_entry_b1_min": -0.22,
+    "oversold_reversal_after_ql_entry_b1_max": 0.0,
+    "post_sell_rebound_block_high_c1": 22.0,
+    "post_sell_rebound_block_high_a1_min": -0.035,
+    "post_sell_rebound_block_high_b1_max": -0.07,
+    "post_sell_rebound_block_low_c1": 15.0,
+    "post_sell_rebound_block_low_a1_min": -0.04,
+    "post_sell_rebound_block_low_b1_max": -0.095,
+    "post_sell_rebound_entry1_c1_low": 18.0,
+    "post_sell_rebound_entry1_c1_high": 30.0,
+    "post_sell_rebound_entry1_a1_min": -0.045,
+    "post_sell_rebound_entry1_a1_max": 0.0,
+    "post_sell_rebound_entry1_b1_low": -0.09,
+    "post_sell_rebound_entry1_b1_high": -0.04,
+    "post_sell_rebound_entry2_c1_high": 19.0,
+    "post_sell_rebound_entry2_a1_min": -0.04,
+    "post_sell_rebound_entry2_a1_max": 0.0,
+    "post_sell_rebound_entry2_b1_low": -0.13,
+    "post_sell_rebound_entry2_b1_high": -0.09,
+    "predictive_b1_break_short_holding_days_max": 2,
+    "predictive_b1_break_short_a1_min": -0.02,
+    "predictive_b1_break_short_a1_max": 0.0,
+    "predictive_b1_break_short_b1_max": -0.13,
+    "predictive_b1_break_short_c1_low": 50.0,
+    "predictive_b1_break_short_c1_high": 70.0,
+    "predictive_b1_break_long_holding_days_min": 40,
+    "predictive_b1_break_long_max_c1": 80.0,
+    "predictive_b1_break_long_max_a1": 0.15,
+    "predictive_b1_break_long_max_b1": 0.3,
+    "predictive_b1_break_long_ql_days_max": 7,
+    "predictive_b1_break_long_a1_min": -0.02,
+    "predictive_b1_break_long_a1_max": 0.0,
+    "predictive_b1_break_long_b1_max": -0.12,
+    "predictive_b1_break_long_c1_low": 60.0,
+    "predictive_b1_break_long_c1_high": 65.0,
+    "enable_knife_take_profit_2_wait_ql": true
+  },
+  "as_of_request_date": "2026-06-04",
+  "latest_bar_date": "2026-06-04",
+  "data_mode": "intraday_snapshot",
+  "snapshot_appended": true,
+  "snapshot_timestamp": "2026-06-04T15:12:30",
+  "historical_latest_bar_date": "2026-06-03"
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 48 - 0
research/dragon/v2/dragon_daily_signal_report.html


+ 31 - 31
research/dragon/v2/dragon_daily_signal_report.md

@@ -1,60 +1,60 @@
 # Dragon Daily Signal Report
 
-- Request date: `2026-04-11`
-- Latest available market bar: `2026-04-10`
-- Data mode: `official_daily_bar`
-- Historical latest official bar: `2026-04-10`
-- Snapshot timestamp: `none`
-- Snapshot rule: `not used`
+- Request date: `2026-06-04`
+- Latest available market bar: `2026-06-04`
+- Data mode: `intraday_snapshot`
+- Historical latest official bar: `2026-06-03`
+- Snapshot timestamp: `2026-06-04T15:12:30`
+- Snapshot rule: `current market price is used as today's close for indicator and signal evaluation`
 - Instrument: `399673`
 - Forward default branch: `alpha_first_glued_refined_hot_cap`
 - Benchmark control branch: `alpha_first_selective_veto`
 
 ## Latest Branch Status
 ### workbook_preserving
-- evaluated_at `2026-04-10T00:00:00`
-- latest_close `3658.655` | a1 `0.0121` | b1 `0.0579` | c1 `55.00`
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
 - latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
-- latest real event: `2026-02-13` `SELL` `knife_take_profit_2_glued`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
 - events on latest bar: `none`
-- in_position: `False`
-- open trade: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
 
 ### alpha_first_selective_veto
-- evaluated_at `2026-04-10T00:00:00`
-- latest_close `3658.655` | a1 `0.0121` | b1 `0.0579` | c1 `55.00`
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
 - latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
-- latest real event: `2026-02-13` `SELL` `knife_take_profit_2_glued`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
 - events on latest bar: `none`
-- in_position: `False`
-- open trade: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
 
 ### alpha_first_glued_refined_hot_cap
-- evaluated_at `2026-04-10T00:00:00`
-- latest_close `3658.655` | a1 `0.0121` | b1 `0.0579` | c1 `55.00`
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
 - latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
-- latest real event: `2026-02-13` `SELL` `knife_take_profit_2_glued`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
 - events on latest bar: `none`
-- in_position: `False`
-- open trade: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
 
 ### alpha_first_glued_followthrough_probe
-- evaluated_at `2026-04-10T00:00:00`
-- latest_close `3658.655` | a1 `0.0121` | b1 `0.0579` | c1 `55.00`
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
 - latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
-- latest real event: `2026-02-13` `SELL` `knife_take_profit_2_glued`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
 - events on latest bar: `none`
-- in_position: `False`
-- open trade: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
 
 ### alpha_first_glued_followthrough_mid_exit_probe
-- evaluated_at `2026-04-10T00:00:00`
-- latest_close `3658.655` | a1 `0.0121` | b1 `0.0579` | c1 `55.00`
+- evaluated_at `2026-06-04T15:12:30`
+- latest_close `4439.771` | a1 `0.0439` | b1 `0.0123` | c1 `87.74`
 - latest markers: `KDJ buy=False` `KDJ sell=False` `QL buy=False` `QL sell=False`
-- latest real event: `2026-02-13` `SELL` `knife_take_profit_2_glued`
+- latest real event: `2026-05-06` `BUY` `dual_gold_resonance_buy`
 - events on latest bar: `none`
-- in_position: `False`
-- open trade: `none`
+- in_position: `True`
+- open trade: `2026-05-06` `dual_gold_resonance_buy` | holding `29`d | open_return `10.33%`
 
 ## Monitor Snapshot
 - warnings: `0`

+ 28 - 0
research/dragon/v2/dragon_followthrough_mid_exit_review.md

@@ -0,0 +1,28 @@
+# Dragon Followthrough Mid Exit Review
+
+## Scope
+- focus: shadow-only exit treatment for `mid_zone_very_weak_b1` followthrough reentry
+- objective: test whether the current `knife_take_profit_2_glued` path is clipping a valid repaired trend too early
+
+## Branch Summary
+- base same_close avg `3.42%` | PF `5.11` | comp `1424.12%`
+- mid_probe same_close avg `3.38%` | PF `5.09` | comp `1420.41%`
+- mid_exit_probe same_close avg `3.45%` | PF `5.15` | comp `1460.02%`
+- base next_open avg `3.31%` | PF `4.73` | comp `1295.54%`
+- mid_probe next_open avg `3.27%` | PF `4.70` | comp `1288.52%`
+- mid_exit_probe next_open avg `3.35%` | PF `4.77` | comp `1332.91%`
+
+## Reentry Trade Detail
+- `alpha_first_glued_followthrough_probe` | `2020-12-01` -> `2020-12-09` | same_close `-0.24%` | next_open `-0.50%` | sell `knife_take_profit_2_glued` | post_exit_max_10b `8.35%`
+- `alpha_first_glued_followthrough_mid_exit_probe` | `2020-12-01` -> `2021-02-19` | same_close `26.65%` | next_open `26.51%` | sell `high_regime_confirmed_exit:kdj_sell` | post_exit_max_10b `-4.89%`
+
+## Trade Path Diff Vs Mid Probe
+- `removed_vs_mid_probe` | `2020-12-01` -> `2020-12-09` | `glued_followthrough_reentry_buy:confirmed_mid_zone_very_weak_b1` -> `knife_take_profit_2_glued` | return `-0.24%`
+- `removed_vs_mid_probe` | `2020-12-15` -> `2021-02-19` | `glued_buy` -> `high_regime_confirmed_exit:kdj_sell` | return `23.73%`
+- `added_vs_mid_probe` | `2020-12-01` -> `2021-02-19` | `glued_followthrough_reentry_buy:confirmed_mid_zone_very_weak_b1` -> `high_regime_confirmed_exit:kdj_sell` | return `26.65%`
+
+## Judgment
+- The narrow mid-exit probe is materially better than the original mid probe on the only confirmed followthrough sample.
+- It converts the repaired-trend trade from a small loss into a long hold ending with a high-regime exit.
+- It also improves branch-level compounded return versus both the original mid probe and the current RC1 branch in this replay.
+- This is still a one-path result, so it should remain shadow-only for now, but it is strong enough to keep under daily observation.

+ 269 - 0
research/dragon/v2/dragon_followthrough_mid_exit_review.py

@@ -0,0 +1,269 @@
+from __future__ import annotations
+
+from pathlib import Path
+
+import pandas as pd
+
+from dragon_branch_configs import (
+    alpha_first_glued_followthrough_mid_exit_probe_config,
+    alpha_first_glued_followthrough_probe_config,
+    alpha_first_glued_refined_hot_cap_config,
+)
+from dragon_execution_common import apply_execution_model, summary
+from dragon_indicators import DragonIndicatorConfig, DragonIndicatorEngine
+from dragon_shared import END_DATE, START_DATE, format_num as _format_num, format_pct as _format_pct
+from dragon_strategy import DragonRuleEngine
+
+
+REENTRY_REASON_PREFIX = "glued_followthrough_reentry_buy:confirmed_"
+OUTPUT_BRANCH_SUMMARY = "dragon_followthrough_mid_exit_branch_summary.csv"
+OUTPUT_TRADE_DETAILS = "dragon_followthrough_mid_exit_trade_details.csv"
+OUTPUT_TRADE_DIFF = "dragon_followthrough_mid_exit_trade_diff.csv"
+OUTPUT_REVIEW = "dragon_followthrough_mid_exit_review.md"
+
+
+def _load_history() -> pd.DataFrame:
+    engine = DragonIndicatorEngine(DragonIndicatorConfig(start_date="2015-01-01", end_date=None))
+    raw = engine.fetch_daily_data(include_intraday_snapshot=False)
+    indicators = engine.compute(raw.reset_index(drop=False).rename(columns={"index": "date"}))
+    indicators["date"] = pd.to_datetime(indicators["date"])
+    return indicators.sort_values("date").reset_index(drop=True)
+
+
+def _release_window_trades(trades: pd.DataFrame) -> pd.DataFrame:
+    return trades[
+        (trades["buy_date"] >= START_DATE)
+        & (trades["buy_date"] <= END_DATE)
+        & (trades["sell_date"] >= START_DATE)
+        & (trades["sell_date"] <= END_DATE)
+    ].copy()
+
+
+def _add_execution_prices(trades: pd.DataFrame, indicators: pd.DataFrame) -> pd.DataFrame:
+    trades = trades.copy()
+    lookup = indicators.set_index(indicators["date"].dt.date)
+    next_by_date = {
+        indicators.iloc[idx]["date"].date().isoformat(): indicators.iloc[idx + 1]
+        for idx in range(len(indicators) - 1)
+    }
+
+    same_entry: list[float] = []
+    same_exit: list[float] = []
+    next_open_entry: list[float] = []
+    next_open_exit: list[float] = []
+
+    for _, trade in trades.iterrows():
+        buy_row = lookup.loc[pd.Timestamp(trade["buy_date"]).date()]
+        sell_row = lookup.loc[pd.Timestamp(trade["sell_date"]).date()]
+        buy_next = next_by_date.get(trade["buy_date"])
+        sell_next = next_by_date.get(trade["sell_date"])
+        same_entry.append(float(buy_row["close"]))
+        same_exit.append(float(sell_row["close"]))
+        next_open_entry.append(float("nan") if buy_next is None else float(buy_next["open"]))
+        next_open_exit.append(float("nan") if sell_next is None else float(sell_next["open"]))
+
+    trades["exec_same_close_entry"] = same_entry
+    trades["exec_same_close_exit"] = same_exit
+    trades["exec_next_open_entry"] = next_open_entry
+    trades["exec_next_open_exit"] = next_open_exit
+    return trades
+
+
+def _run_branch(indicators: pd.DataFrame, name: str, config) -> tuple[pd.DataFrame, pd.DataFrame]:
+    indexed = indicators.set_index("date", drop=False)
+    _, trades = DragonRuleEngine(config=config).run(indexed)
+    trades = _release_window_trades(trades)
+    trades.insert(0, "branch", name)
+    return indexed, trades
+
+
+def _branch_summary(indicators: pd.DataFrame, name: str, trades: pd.DataFrame) -> list[dict[str, object]]:
+    exec_trades = _add_execution_prices(trades, indicators)
+    same = summary(name, apply_execution_model(exec_trades, "same_close", 0.0))
+    nxt = summary(name, apply_execution_model(exec_trades, "next_open", 0.0))
+    return [
+        {
+            "branch": name,
+            "execution_model": "same_close",
+            "trades": int(same["trades"]),
+            "win_rate": float(same["win_rate"]),
+            "avg_return": float(same["avg_return"]),
+            "profit_factor": float(same["profit_factor"]),
+            "compounded_return": float(same["compounded_return"]),
+            "max_drawdown": float(same["max_drawdown"]),
+        },
+        {
+            "branch": name,
+            "execution_model": "next_open",
+            "trades": int(nxt["trades"]),
+            "win_rate": float(nxt["win_rate"]),
+            "avg_return": float(nxt["avg_return"]),
+            "profit_factor": float(nxt["profit_factor"]),
+            "compounded_return": float(nxt["compounded_return"]),
+            "max_drawdown": float(nxt["max_drawdown"]),
+        },
+    ]
+
+
+def _reentry_trade_details(indicators: pd.DataFrame, branch: str, trades: pd.DataFrame) -> pd.DataFrame:
+    details = trades[trades["buy_reason"].astype(str).str.startswith(REENTRY_REASON_PREFIX)].copy()
+    if details.empty:
+        return pd.DataFrame(
+            columns=[
+                "branch",
+                "buy_date",
+                "buy_reason",
+                "sell_date",
+                "sell_reason",
+                "holding_days",
+                "return_pct",
+                "next_open_return_pct",
+                "post_exit_max_close_return_10b",
+                "post_exit_min_close_return_10b",
+            ]
+        )
+
+    indicators = indicators.sort_values("date").reset_index(drop=True)
+    date_to_pos = {row.date().isoformat(): idx for idx, row in enumerate(indicators["date"])}
+    next_by_date = {
+        indicators.iloc[idx]["date"].date().isoformat(): indicators.iloc[idx + 1]
+        for idx in range(len(indicators) - 1)
+    }
+
+    rows: list[dict[str, object]] = []
+    for _, trade in details.iterrows():
+        sell_pos = date_to_pos[trade["sell_date"]]
+        post10 = indicators.iloc[sell_pos + 1 : sell_pos + 11].copy()
+        buy_next = next_by_date.get(trade["buy_date"])
+        sell_next = next_by_date.get(trade["sell_date"])
+        next_open_return = float("nan")
+        if buy_next is not None and sell_next is not None:
+            next_open_return = float(sell_next["open"]) / float(buy_next["open"]) - 1.0
+
+        rows.append(
+            {
+                "branch": branch,
+                "buy_date": trade["buy_date"],
+                "buy_reason": trade["buy_reason"],
+                "sell_date": trade["sell_date"],
+                "sell_reason": trade["sell_reason"],
+                "holding_days": int(trade["holding_days"]),
+                "return_pct": float(trade["return_pct"]),
+                "next_open_return_pct": next_open_return,
+                "post_exit_max_close_return_10b": float(post10["close"].max()) / float(trade["sell_price"]) - 1.0 if not post10.empty else float("nan"),
+                "post_exit_min_close_return_10b": float(post10["close"].min()) / float(trade["sell_price"]) - 1.0 if not post10.empty else float("nan"),
+            }
+        )
+    return pd.DataFrame(rows)
+
+
+def _trade_diff(mid_probe: pd.DataFrame, mid_exit_probe: pd.DataFrame) -> pd.DataFrame:
+    key_cols = ["buy_date", "sell_date", "buy_reason", "sell_reason"]
+    left = {
+        (row.buy_date, row.sell_date, row.buy_reason, row.sell_reason)
+        for row in mid_probe.itertuples()
+    }
+    right = {
+        (row.buy_date, row.sell_date, row.buy_reason, row.sell_reason)
+        for row in mid_exit_probe.itertuples()
+    }
+
+    rows: list[dict[str, object]] = []
+    for _, trade in mid_probe.iterrows():
+        key = tuple(trade[col] for col in key_cols)
+        if key not in right:
+            rows.append({"status": "removed_vs_mid_probe", **trade.to_dict()})
+    for _, trade in mid_exit_probe.iterrows():
+        key = tuple(trade[col] for col in key_cols)
+        if key not in left:
+            rows.append({"status": "added_vs_mid_probe", **trade.to_dict()})
+    return pd.DataFrame(rows)
+
+
+def _build_review(summary_df: pd.DataFrame, detail_df: pd.DataFrame, diff_df: pd.DataFrame) -> str:
+    same = summary_df[summary_df["execution_model"] == "same_close"].set_index("branch")
+    nxt = summary_df[summary_df["execution_model"] == "next_open"].set_index("branch")
+    mid_probe_key = "alpha_first_glued_followthrough_probe"
+    exit_probe_key = "alpha_first_glued_followthrough_mid_exit_probe"
+    base_key = "alpha_first_glued_refined_hot_cap"
+
+    lines = [
+        "# Dragon Followthrough Mid Exit Review",
+        "",
+        "## Scope",
+        "- focus: shadow-only exit treatment for `mid_zone_very_weak_b1` followthrough reentry",
+        "- objective: test whether the current `knife_take_profit_2_glued` path is clipping a valid repaired trend too early",
+        "",
+        "## Branch Summary",
+        f"- base same_close avg `{_format_pct(float(same.loc[base_key, 'avg_return']))}` | PF `{_format_num(float(same.loc[base_key, 'profit_factor']))}` | comp `{_format_pct(float(same.loc[base_key, 'compounded_return']))}`",
+        f"- mid_probe same_close avg `{_format_pct(float(same.loc[mid_probe_key, 'avg_return']))}` | PF `{_format_num(float(same.loc[mid_probe_key, 'profit_factor']))}` | comp `{_format_pct(float(same.loc[mid_probe_key, 'compounded_return']))}`",
+        f"- mid_exit_probe same_close avg `{_format_pct(float(same.loc[exit_probe_key, 'avg_return']))}` | PF `{_format_num(float(same.loc[exit_probe_key, 'profit_factor']))}` | comp `{_format_pct(float(same.loc[exit_probe_key, 'compounded_return']))}`",
+        f"- base next_open avg `{_format_pct(float(nxt.loc[base_key, 'avg_return']))}` | PF `{_format_num(float(nxt.loc[base_key, 'profit_factor']))}` | comp `{_format_pct(float(nxt.loc[base_key, 'compounded_return']))}`",
+        f"- mid_probe next_open avg `{_format_pct(float(nxt.loc[mid_probe_key, 'avg_return']))}` | PF `{_format_num(float(nxt.loc[mid_probe_key, 'profit_factor']))}` | comp `{_format_pct(float(nxt.loc[mid_probe_key, 'compounded_return']))}`",
+        f"- mid_exit_probe next_open avg `{_format_pct(float(nxt.loc[exit_probe_key, 'avg_return']))}` | PF `{_format_num(float(nxt.loc[exit_probe_key, 'profit_factor']))}` | comp `{_format_pct(float(nxt.loc[exit_probe_key, 'compounded_return']))}`",
+        "",
+        "## Reentry Trade Detail",
+    ]
+
+    for row in detail_df.itertuples(index=False):
+        lines.append(
+            f"- `{row.branch}` | `{row.buy_date}` -> `{row.sell_date}` | same_close `{_format_pct(float(row.return_pct))}` | "
+            f"next_open `{_format_pct(float(row.next_open_return_pct))}` | sell `{row.sell_reason}` | "
+            f"post_exit_max_10b `{_format_pct(float(row.post_exit_max_close_return_10b))}`"
+        )
+
+    lines.extend(["", "## Trade Path Diff Vs Mid Probe"])
+    for row in diff_df.itertuples(index=False):
+        lines.append(
+            f"- `{row.status}` | `{row.buy_date}` -> `{row.sell_date}` | `{row.buy_reason}` -> `{row.sell_reason}` | return `{_format_pct(float(row.return_pct))}`"
+        )
+
+    lines.extend(
+        [
+            "",
+            "## Judgment",
+            "- The narrow mid-exit probe is materially better than the original mid probe on the only confirmed followthrough sample.",
+            "- It converts the repaired-trend trade from a small loss into a long hold ending with a high-regime exit.",
+            "- It also improves branch-level compounded return versus both the original mid probe and the current RC1 branch in this replay.",
+            "- This is still a one-path result, so it should remain shadow-only for now, but it is strong enough to keep under daily observation.",
+        ]
+    )
+    return "\n".join(lines) + "\n"
+
+
+def main() -> None:
+    base_dir = Path(__file__).resolve().parent
+    indicators = _load_history()
+
+    branches = [
+        ("alpha_first_glued_refined_hot_cap", alpha_first_glued_refined_hot_cap_config()),
+        ("alpha_first_glued_followthrough_probe", alpha_first_glued_followthrough_probe_config()),
+        ("alpha_first_glued_followthrough_mid_exit_probe", alpha_first_glued_followthrough_mid_exit_probe_config()),
+    ]
+
+    summary_rows: list[dict[str, object]] = []
+    detail_rows: list[pd.DataFrame] = []
+    branch_trades: dict[str, pd.DataFrame] = {}
+
+    for name, config in branches:
+        _, trades = _run_branch(indicators, name, config)
+        branch_trades[name] = trades
+        summary_rows.extend(_branch_summary(indicators, name, trades))
+        detail_rows.append(_reentry_trade_details(indicators, name, trades))
+
+    summary_df = pd.DataFrame(summary_rows)
+    detail_df = pd.concat(detail_rows, ignore_index=True)
+    diff_df = _trade_diff(
+        branch_trades["alpha_first_glued_followthrough_probe"],
+        branch_trades["alpha_first_glued_followthrough_mid_exit_probe"],
+    )
+    review = _build_review(summary_df, detail_df, diff_df)
+
+    summary_df.to_csv(base_dir / OUTPUT_BRANCH_SUMMARY, index=False, encoding="utf-8-sig")
+    detail_df.to_csv(base_dir / OUTPUT_TRADE_DETAILS, index=False, encoding="utf-8-sig")
+    diff_df.to_csv(base_dir / OUTPUT_TRADE_DIFF, index=False, encoding="utf-8-sig")
+    (base_dir / OUTPUT_REVIEW).write_text(review, encoding="utf-8")
+
+
+if __name__ == "__main__":
+    main()

+ 36 - 0
research/dragon/v2/dragon_followthrough_profit_loop_review.md

@@ -0,0 +1,36 @@
+# Dragon Followthrough Profit Loop Review
+
+## Scope
+- objective: inspect whether false-veto glued weak rebounds deserve a fast trend reentry path
+- data_source: `fetch_daily_data+compute`
+- latest_bar: `2026-04-10`
+- row_count: `2737`
+
+## Subtype Readout
+- `mid_zone_very_weak_b1` | blocked_full `2` | shadow_confirmed `1` | confirm_like_3bar `50.00%` | next3_sell_cross_rate `0.00%` | avg_next20_max `7.04%`
+- `high_zone_weak_b1` | blocked_full `13` | shadow_confirmed `2` | confirm_like_3bar `15.38%` | next3_sell_cross_rate `38.46%` | avg_next20_max `2.97%`
+- `ql_rebound_weak_followthrough` | blocked_full `3` | shadow_confirmed `0` | confirm_like_3bar `0.00%` | next3_sell_cross_rate `100.00%` | avg_next20_max `0.04%`
+
+## Probe Readout
+- `probe_mid_zone` -> `mid_zone_very_weak_b1` | trades `1` | same_close_avg `-0.24%` | next_open_avg `-0.50%` | post_exit_max_10b `8.35%` | exit_reasons `knife_take_profit_2_glued:1`
+- `probe_high_zone` -> `high_zone_weak_b1` | trades `2` | same_close_avg `-3.52%` | next_open_avg `-5.03%` | post_exit_max_10b `-1.52%` | exit_reasons `knife_take_profit_2_glued:1 | good_to_take_profit_2:kdj_sell:1`
+- `probe_ql_rebound` -> `ql_rebound_weak_followthrough` | trades `0` | same_close_avg `NA` | next_open_avg `NA` | post_exit_max_10b `NA` | exit_reasons `none`
+
+## Latest Block Case
+- signal_date `2026-04-08` | subtype `ql_rebound_weak_followthrough` | regime `range`
+- observed bars after block `2/3` | ql_reconfirm_count `0` | sell_cross_count `0`
+- max_close_return so far `3.56%` | confirm_like_within_3 `False`
+
+## Judgment
+- `mid_zone_very_weak_b1` is the only subtype that still shows a non-zero delayed-reentry path; it stays the only practical followthrough research lane.
+- `high_zone_weak_b1` should not be promoted. Enabling pending there only created losing followthrough trades in this replay.
+- `ql_rebound_weak_followthrough` remains a hard-block family. The probe branch still produced zero confirmed reentries there.
+- execution timing remains a real question only for the mid-zone path: same_close `-0.24%` vs next_open `-0.50%`.
+- entry-specific exit treatment is worth a narrow check only if the mid-zone path keeps showing higher post-entry upside than realized return.
+- high-zone probe result is already weak enough to stop here: trades `2`, same_close_avg `-3.52%`.
+- ql rebound probe stays off the table for promotion: confirmed trades `0`.
+
+## Trade Detail Highlights
+- `probe_mid_zone` | origin `2020-11-30` -> buy `2020-12-01` -> sell `2020-12-09` | same_close `-0.24%` | next_open `-0.50%` | sell `knife_take_profit_2_glued` | post_exit_max_10b `8.35%`
+- `probe_high_zone` | origin `2016-04-29` -> buy `2016-05-03` -> sell `2016-05-06` | same_close `-4.31%` | next_open `-4.88%` | sell `knife_take_profit_2_glued` | post_exit_max_10b `-2.73%`
+- `probe_high_zone` | origin `2025-10-21` -> buy `2025-10-24` -> sell `2025-11-14` | same_close `-2.74%` | next_open `-5.17%` | sell `good_to_take_profit_2:kdj_sell` | post_exit_max_10b `-0.30%`

+ 694 - 0
research/dragon/v2/dragon_followthrough_profit_loop_review.py

@@ -0,0 +1,694 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+from pathlib import Path
+
+import pandas as pd
+
+from dragon_branch_configs import (
+    alpha_first_glued_followthrough_probe_config,
+    alpha_first_glued_refined_hot_cap_config,
+)
+from dragon_indicators import DragonIndicatorConfig, DragonIndicatorEngine
+from dragon_shared import END_DATE, START_DATE, format_pct as _format_pct
+from dragon_strategy import DragonRuleEngine
+from dragon_strategy_config import StrategyConfig
+
+
+REENTRY_REASON_PREFIX = "glued_followthrough_reentry_buy:confirmed_"
+OUTPUT_CANDIDATES = "dragon_followthrough_profit_loop_candidates.csv"
+OUTPUT_CANDIDATE_SUMMARY = "dragon_followthrough_profit_loop_candidate_summary.csv"
+OUTPUT_REENTRIES = "dragon_followthrough_profit_loop_reentries.csv"
+OUTPUT_REENTRY_SUMMARY = "dragon_followthrough_profit_loop_reentry_summary.csv"
+OUTPUT_REVIEW = "dragon_followthrough_profit_loop_review.md"
+
+
+@dataclass(frozen=True)
+class ProbeDefinition:
+    name: str
+    label: str
+    allowed_subtype: str
+    config: StrategyConfig
+
+
+def build_followthrough_probe_configs() -> dict[str, ProbeDefinition]:
+    base = alpha_first_glued_refined_hot_cap_config()
+    return {
+        "probe_mid_zone": ProbeDefinition(
+            name="probe_mid_zone",
+            label="pending only for mid_zone_very_weak_b1",
+            allowed_subtype="mid_zone_very_weak_b1",
+            config=alpha_first_glued_followthrough_probe_config(),
+        ),
+        "probe_high_zone": ProbeDefinition(
+            name="probe_high_zone",
+            label="pending only for high_zone_weak_b1",
+            allowed_subtype="high_zone_weak_b1",
+            config=base.with_updates(
+                glued_followthrough_pending_enabled=True,
+                glued_followthrough_allow_mid_zone_very_weak_b1=False,
+                glued_followthrough_allow_high_zone_weak_b1=True,
+                glued_followthrough_allow_ql_rebound_weak_followthrough=False,
+            ),
+        ),
+        "probe_ql_rebound": ProbeDefinition(
+            name="probe_ql_rebound",
+            label="pending only for ql_rebound_weak_followthrough",
+            allowed_subtype="ql_rebound_weak_followthrough",
+            config=base.with_updates(
+                glued_followthrough_pending_enabled=True,
+                glued_followthrough_allow_mid_zone_very_weak_b1=False,
+                glued_followthrough_allow_high_zone_weak_b1=False,
+                glued_followthrough_allow_ql_rebound_weak_followthrough=True,
+            ),
+        ),
+    }
+
+
+def prepare_indicator_history(df: pd.DataFrame) -> pd.DataFrame:
+    history = df.copy()
+    if "date" in history.columns and "date" in list(history.index.names):
+        history = history.reset_index(drop=True)
+    if "date" not in history.columns:
+        history = history.reset_index()
+        if "date" not in history.columns:
+            history = history.rename(columns={history.columns[0]: "date"})
+    history["date"] = pd.to_datetime(history["date"])
+    history = history.sort_values("date").reset_index(drop=True)
+
+    for column in ["open", "high", "low", "close", "a1", "b1", "c1"]:
+        if column in history.columns:
+            history[column] = history[column].astype(float)
+    for column in ["kdj_buy", "kdj_sell", "ql_buy", "ql_sell"]:
+        if column in history.columns:
+            history[column] = history[column].astype(bool)
+
+    history["ma20"] = history["close"].rolling(20).mean()
+    history["ma60"] = history["close"].rolling(60).mean()
+    history["regime"] = history.apply(_classify_regime, axis=1)
+    return history.set_index("date", drop=False)
+
+
+def collect_glued_block_candidates(
+    df: pd.DataFrame,
+    config: StrategyConfig,
+    *,
+    branch_name: str = "base_rc1",
+) -> pd.DataFrame:
+    indexed = prepare_indicator_history(df)
+    blocked, _ = _collect_branch_diagnostics(indexed, config, branch_name)
+    return blocked
+
+
+def collect_followthrough_reentries(
+    df: pd.DataFrame,
+    config: StrategyConfig,
+    *,
+    branch_name: str = "probe",
+) -> pd.DataFrame:
+    indexed = prepare_indicator_history(df)
+    _, reentries = _collect_branch_diagnostics(indexed, config, branch_name)
+    return reentries
+
+
+def _classify_regime(row: pd.Series) -> str:
+    close = float(row["close"])
+    ma20 = float(row["ma20"]) if pd.notna(row["ma20"]) else float("nan")
+    ma60 = float(row["ma60"]) if pd.notna(row["ma60"]) else float("nan")
+    if pd.isna(ma20) or pd.isna(ma60):
+        return "unknown"
+    if close >= ma20 >= ma60:
+        return "uptrend"
+    if close <= ma20 <= ma60:
+        return "downtrend"
+    return "range"
+
+
+def _in_release_window(value: str) -> bool:
+    return START_DATE <= value <= END_DATE
+
+
+def _collect_branch_diagnostics(
+    indexed: pd.DataFrame,
+    config: StrategyConfig,
+    branch_name: str,
+) -> tuple[pd.DataFrame, pd.DataFrame]:
+    engine = DragonRuleEngine(config=config)
+    blocked_rows: list[dict[str, object]] = []
+    reentry_rows: list[dict[str, object]] = []
+    prev_row = None
+
+    for _, row in indexed.iterrows():
+        engine._record_cross_counters(row)
+        engine._update_position_counters(row)
+        engine._update_pending_states(row)
+        just_bought = False
+        pending_snapshot = {
+            "origin_date": (
+                engine.context.pending_glued_followthrough_origin_date.isoformat()
+                if engine.context.pending_glued_followthrough_origin_date
+                else ""
+            ),
+            "subtype": engine.context.pending_glued_followthrough_subtype,
+            "bars_waited": int(engine.context.pending_glued_followthrough_bars_waited),
+            "signal_close": float(engine.context.pending_glued_followthrough_signal_close or 0.0),
+            "signal_a1": float(engine.context.pending_glued_followthrough_a1 or 0.0),
+            "signal_b1": float(engine.context.pending_glued_followthrough_b1 or 0.0),
+            "signal_c1": float(engine.context.pending_glued_followthrough_c1 or 0.0),
+        }
+
+        if (not engine.context.in_position) or bool(row["kdj_buy"] or row["ql_buy"]):
+            action, reason = engine._buy_decision(row, prev_row)
+            if action == "BLOCK" and reason == "buy_block_glued_high_weak_rebound":
+                subtype = engine._glued_high_weak_rebound_subtype(row)
+                blocked_rows.append(
+                    {
+                        "branch": branch_name,
+                        "signal_date": row.name.date().isoformat(),
+                        "subtype": subtype,
+                        "signal_close": float(row["close"]),
+                        "signal_a1": float(row["a1"]),
+                        "signal_b1": float(row["b1"]),
+                        "signal_c1": float(row["c1"]),
+                        "signal_kdj_buy": bool(row["kdj_buy"]),
+                        "signal_ql_buy": bool(row["ql_buy"]),
+                        "in_release_window": _in_release_window(row.name.date().isoformat()),
+                    }
+                )
+            elif action == "BUY":
+                if reason.startswith(REENTRY_REASON_PREFIX):
+                    reentry_rows.append(
+                        {
+                            "branch": branch_name,
+                            "buy_date": row.name.date().isoformat(),
+                            "buy_reason": reason,
+                            "origin_date": pending_snapshot["origin_date"],
+                            "subtype": pending_snapshot["subtype"],
+                            "bars_waited": pending_snapshot["bars_waited"],
+                            "signal_close": pending_snapshot["signal_close"],
+                            "signal_a1": pending_snapshot["signal_a1"],
+                            "signal_b1": pending_snapshot["signal_b1"],
+                            "signal_c1": pending_snapshot["signal_c1"],
+                            "buy_close": float(row["close"]),
+                            "buy_a1": float(row["a1"]),
+                            "buy_b1": float(row["b1"]),
+                            "buy_c1": float(row["c1"]),
+                            "buy_kdj_buy": bool(row["kdj_buy"]),
+                            "buy_ql_buy": bool(row["ql_buy"]),
+                            "b1_repair": float(row["b1"]) - pending_snapshot["signal_b1"],
+                            "in_release_window": _in_release_window(row.name.date().isoformat()),
+                        }
+                    )
+                engine._post_real_buy(row, reason)
+                just_bought = True
+            elif action == "AUX_BUY":
+                engine._post_aux_buy(row)
+
+        state_aux_sell_candidate = (not engine.context.in_position) and engine._should_emit_state_aux_sell(row)
+        if not just_bought and (engine.context.in_position or bool(row["kdj_sell"] or row["ql_sell"]) or state_aux_sell_candidate):
+            if engine.context.in_position and bool(row["kdj_sell"] or row["ql_sell"]):
+                engine.context.sell_signal_count += 1
+                if bool(row["kdj_sell"]):
+                    engine.context.kdj_sell_signal_count += 1
+                if bool(row["ql_sell"]):
+                    engine.context.ql_sell_signal_count += 1
+                if float(row["b1"]) < 0:
+                    engine.context.b1_negative_sell_count += 1
+
+            action, reason = engine._sell_decision(row, prev_row)
+            if action == "SELL":
+                engine.context.first_exit_checked = True
+                engine._post_real_sell(row, reason)
+            elif action == "AUX_SELL":
+                if engine.context.in_position:
+                    engine.context.first_exit_checked = True
+                engine._post_aux_sell(row, reason)
+
+        prev_row = row
+
+    blocked_df = pd.DataFrame(blocked_rows)
+    reentry_df = pd.DataFrame(reentry_rows)
+    return blocked_df, reentry_df
+
+
+def _load_full_history() -> tuple[pd.DataFrame, dict[str, object]]:
+    indicator_engine = DragonIndicatorEngine(DragonIndicatorConfig(start_date="2015-01-01", end_date=None))
+    raw = indicator_engine.fetch_daily_data(include_intraday_snapshot=False)
+    prepared = indicator_engine.compute(raw.reset_index(drop=False).rename(columns={"index": "date"}))
+    history = prepare_indicator_history(prepared)
+    latest_bar = history["date"].max().date().isoformat()
+    meta = {
+        "data_source": "fetch_daily_data+compute",
+        "latest_bar": latest_bar,
+        "row_count": int(len(history)),
+    }
+    return history, meta
+
+
+def _enrich_block_candidates(
+    blocked: pd.DataFrame,
+    history: pd.DataFrame,
+    config: StrategyConfig,
+) -> pd.DataFrame:
+    if blocked.empty:
+        return blocked.copy()
+
+    date_to_pos = {row.date().isoformat(): idx for idx, row in enumerate(history["date"])}
+    enriched_rows: list[dict[str, object]] = []
+
+    for row in blocked.itertuples(index=False):
+        pos = date_to_pos[str(row.signal_date)]
+        signal_row = history.iloc[pos]
+        next3 = history.iloc[pos + 1 : pos + 4].copy()
+        next10 = history.iloc[pos + 1 : pos + 11].copy()
+        next20 = history.iloc[pos + 1 : pos + 21].copy()
+
+        next3_sell_cross = (next3["kdj_sell"] | next3["ql_sell"]) if not next3.empty else pd.Series(dtype=bool)
+        confirm_like_bar = _first_confirm_like_bar(
+            next3=next3,
+            signal_close=float(row.signal_close),
+            signal_b1=float(row.signal_b1),
+            config=config,
+        )
+
+        record = {
+            "branch": row.branch,
+            "signal_date": row.signal_date,
+            "subtype": row.subtype,
+            "signal_close": float(row.signal_close),
+            "signal_a1": float(row.signal_a1),
+            "signal_b1": float(row.signal_b1),
+            "signal_c1": float(row.signal_c1),
+            "signal_kdj_buy": bool(row.signal_kdj_buy),
+            "signal_ql_buy": bool(row.signal_ql_buy),
+            "regime": str(signal_row["regime"]),
+            "ma20": float(signal_row["ma20"]) if pd.notna(signal_row["ma20"]) else float("nan"),
+            "ma60": float(signal_row["ma60"]) if pd.notna(signal_row["ma60"]) else float("nan"),
+            "in_release_window": bool(row.in_release_window),
+            "bars_available_3": int(len(next3)),
+            "bars_available_10": int(len(next10)),
+            "bars_available_20": int(len(next20)),
+            "next3_any_ql_buy": bool(next3["ql_buy"].any()) if not next3.empty else False,
+            "next3_ql_buy_count": int(next3["ql_buy"].sum()) if not next3.empty else 0,
+            "next3_any_sell_cross": bool(next3_sell_cross.any()) if not next3.empty else False,
+            "next3_sell_cross_count": int(next3_sell_cross.sum()) if not next3.empty else 0,
+            "confirm_like_within_3": confirm_like_bar > 0,
+            "confirm_like_bar": int(confirm_like_bar) if confirm_like_bar > 0 else 0,
+            "next3_max_close_return": _window_max_return(next3, float(row.signal_close)),
+            "next3_min_close_return": _window_min_return(next3, float(row.signal_close)),
+            "next10_close_return": _window_close_return(next10, float(row.signal_close), 10),
+            "next20_close_return": _window_close_return(next20, float(row.signal_close), 20),
+            "next20_max_close_return": _window_max_return(next20, float(row.signal_close)),
+            "next20_min_close_return": _window_min_return(next20, float(row.signal_close)),
+            "next20_max_close_date": _window_extreme_date(next20, "close", "max"),
+        }
+        enriched_rows.append(record)
+
+    return pd.DataFrame(enriched_rows)
+
+
+def _first_confirm_like_bar(next3: pd.DataFrame, signal_close: float, signal_b1: float, config: StrategyConfig) -> int:
+    for offset, (_, row) in enumerate(next3.iterrows(), start=1):
+        if bool(row["kdj_sell"]) or bool(row["ql_sell"]):
+            continue
+        if config.glued_followthrough_require_ql_buy_reconfirm and not bool(row["ql_buy"]):
+            continue
+        if config.glued_followthrough_require_close_break_signal_close and float(row["close"]) <= signal_close:
+            continue
+        if config.glued_followthrough_require_b1_repair and (float(row["b1"]) - signal_b1) < config.glued_followthrough_b1_repair_min:
+            continue
+        return offset
+    return 0
+
+
+def _window_close_return(window: pd.DataFrame, base_price: float, required_bars: int) -> float:
+    if len(window) < required_bars:
+        return float("nan")
+    return float(window.iloc[required_bars - 1]["close"]) / base_price - 1.0
+
+
+def _window_max_return(window: pd.DataFrame, base_price: float) -> float:
+    if window.empty:
+        return float("nan")
+    return float(window["close"].max()) / base_price - 1.0
+
+
+def _window_min_return(window: pd.DataFrame, base_price: float) -> float:
+    if window.empty:
+        return float("nan")
+    return float(window["close"].min()) / base_price - 1.0
+
+
+def _window_extreme_date(window: pd.DataFrame, column: str, mode: str) -> str:
+    if window.empty:
+        return ""
+    idx = window[column].idxmax() if mode == "max" else window[column].idxmin()
+    return pd.Timestamp(idx).date().isoformat()
+
+
+def _collect_reentry_trade_details(
+    history: pd.DataFrame,
+    probes: dict[str, ProbeDefinition],
+) -> pd.DataFrame:
+    indexed = history.set_index("date", drop=False)
+    rows: list[pd.DataFrame] = []
+
+    for probe in probes.values():
+        mapped = collect_followthrough_reentries(indexed, probe.config, branch_name=probe.name)
+        _, trades = DragonRuleEngine(config=probe.config).run(indexed)
+        trades = trades[trades["buy_reason"].astype(str).str.startswith(REENTRY_REASON_PREFIX)].copy()
+        if trades.empty:
+            rows.append(
+                pd.DataFrame(
+                    columns=[
+                        "branch",
+                        "buy_date",
+                        "buy_reason",
+                        "origin_date",
+                        "subtype",
+                        "bars_waited",
+                        "signal_close",
+                        "signal_a1",
+                        "signal_b1",
+                        "signal_c1",
+                        "buy_close",
+                        "buy_a1",
+                        "buy_b1",
+                        "buy_c1",
+                        "buy_kdj_buy",
+                        "buy_ql_buy",
+                        "b1_repair",
+                        "in_release_window",
+                        "buy_price",
+                        "sell_date",
+                        "sell_price",
+                        "sell_reason",
+                        "holding_days",
+                        "return_pct",
+                    ]
+                )
+            )
+            continue
+
+        trades.insert(0, "branch", probe.name)
+        merged = mapped.merge(
+            trades[
+                [
+                    "branch",
+                    "buy_date",
+                    "buy_price",
+                    "buy_reason",
+                    "sell_date",
+                    "sell_price",
+                    "sell_reason",
+                    "holding_days",
+                    "return_pct",
+                ]
+            ],
+            on=["branch", "buy_date", "buy_reason"],
+            how="left",
+            validate="one_to_one",
+        )
+        rows.append(merged)
+
+    details = pd.concat(rows, ignore_index=True) if rows else pd.DataFrame()
+    if details.empty:
+        return details
+    return _enrich_reentry_trade_details(details, history)
+
+
+def _enrich_reentry_trade_details(details: pd.DataFrame, history: pd.DataFrame) -> pd.DataFrame:
+    if details.empty:
+        return details.copy()
+
+    if "date" in history.columns and "date" in list(history.index.names):
+        history = history.reset_index(drop=True)
+    history = history.sort_values("date").reset_index(drop=True)
+    date_to_pos = {row.date().isoformat(): idx for idx, row in enumerate(history["date"])}
+    open_available = "open" in history.columns and history["open"].notna().any()
+
+    enriched_rows: list[dict[str, object]] = []
+    for row in details.itertuples(index=False):
+        buy_pos = date_to_pos[str(row.buy_date)]
+        sell_pos = date_to_pos[str(row.sell_date)]
+        buy_row = history.iloc[buy_pos]
+        buy_next = history.iloc[buy_pos + 1] if (buy_pos + 1) < len(history) else None
+        sell_next = history.iloc[sell_pos + 1] if (sell_pos + 1) < len(history) else None
+        post_exit_5 = history.iloc[sell_pos + 1 : sell_pos + 6]
+        post_exit_10 = history.iloc[sell_pos + 1 : sell_pos + 11]
+        entry_10 = history.iloc[buy_pos + 1 : buy_pos + 11]
+        entry_20 = history.iloc[buy_pos + 1 : buy_pos + 21]
+
+        next_open_return = float("nan")
+        if open_available and buy_next is not None and sell_next is not None:
+            next_open_return = float(sell_next["open"]) / float(buy_next["open"]) - 1.0
+
+        enriched_rows.append(
+            {
+                "branch": row.branch,
+                "subtype": row.subtype,
+                "origin_date": row.origin_date,
+                "origin_regime": str(history.iloc[date_to_pos[str(row.origin_date)]]["regime"]) if str(row.origin_date) in date_to_pos else "",
+                "buy_date": row.buy_date,
+                "buy_regime": str(buy_row["regime"]),
+                "buy_reason": row.buy_reason,
+                "bars_waited": int(row.bars_waited),
+                "signal_close": float(row.signal_close),
+                "signal_a1": float(row.signal_a1),
+                "signal_b1": float(row.signal_b1),
+                "signal_c1": float(row.signal_c1),
+                "buy_price": float(row.buy_price),
+                "buy_close": float(row.buy_close),
+                "buy_a1": float(row.buy_a1),
+                "buy_b1": float(row.buy_b1),
+                "buy_c1": float(row.buy_c1),
+                "b1_repair": float(row.b1_repair),
+                "sell_date": row.sell_date,
+                "sell_price": float(row.sell_price),
+                "sell_reason": row.sell_reason,
+                "holding_days": int(row.holding_days),
+                "return_pct": float(row.return_pct),
+                "next_open_return_pct": next_open_return,
+                "next_open_minus_same_close_pct": next_open_return - float(row.return_pct) if pd.notna(next_open_return) else float("nan"),
+                "entry_max_close_return_10b": _window_max_return(entry_10, float(row.buy_price)),
+                "entry_max_close_return_20b": _window_max_return(entry_20, float(row.buy_price)),
+                "post_exit_max_close_return_5b": _window_max_return(post_exit_5, float(row.sell_price)),
+                "post_exit_max_close_return_10b": _window_max_return(post_exit_10, float(row.sell_price)),
+                "post_exit_min_close_return_5b": _window_min_return(post_exit_5, float(row.sell_price)),
+                "in_release_window": bool(row.in_release_window and _in_release_window(str(row.sell_date))),
+            }
+        )
+
+    return pd.DataFrame(enriched_rows)
+
+
+def _summarize_candidates(candidates: pd.DataFrame, reentries: pd.DataFrame) -> pd.DataFrame:
+    rows: list[dict[str, object]] = []
+    subtype_order = {
+        "mid_zone_very_weak_b1": 0,
+        "high_zone_weak_b1": 1,
+        "ql_rebound_weak_followthrough": 2,
+    }
+
+    for subtype, group in candidates.groupby("subtype"):
+        full_group = group.copy()
+        complete_3 = group[group["bars_available_3"] >= 3].copy()
+        complete_10 = group[group["bars_available_10"] >= 10].copy()
+        complete_20 = group[group["bars_available_20"] >= 20].copy()
+        subtype_reentries = reentries[reentries["subtype"] == subtype].copy()
+        rows.append(
+            {
+                "subtype": subtype,
+                "sort_key": subtype_order.get(subtype, 99),
+                "blocked_count_full": int(len(full_group)),
+                "blocked_count_release": int(full_group["in_release_window"].sum()),
+                "shadow_confirmed_trade_count": int(len(subtype_reentries)),
+                "complete_3bar_count": int(len(complete_3)),
+                "complete_10bar_count": int(len(complete_10)),
+                "complete_20bar_count": int(len(complete_20)),
+                "uptrend_count": int((full_group["regime"] == "uptrend").sum()),
+                "range_count": int((full_group["regime"] == "range").sum()),
+                "downtrend_count": int((full_group["regime"] == "downtrend").sum()),
+                "confirm_like_3bar_rate": float(complete_3["confirm_like_within_3"].mean()) if not complete_3.empty else float("nan"),
+                "next3_ql_reconfirm_rate": float(complete_3["next3_any_ql_buy"].mean()) if not complete_3.empty else float("nan"),
+                "next3_sell_cross_rate": float(complete_3["next3_any_sell_cross"].mean()) if not complete_3.empty else float("nan"),
+                "avg_next3_max_close_return": float(complete_3["next3_max_close_return"].mean()) if not complete_3.empty else float("nan"),
+                "avg_next10_close_return": float(complete_10["next10_close_return"].mean()) if not complete_10.empty else float("nan"),
+                "avg_next20_close_return": float(complete_20["next20_close_return"].mean()) if not complete_20.empty else float("nan"),
+                "avg_next20_max_close_return": float(complete_20["next20_max_close_return"].mean()) if not complete_20.empty else float("nan"),
+            }
+        )
+
+    summary = pd.DataFrame(rows)
+    if summary.empty:
+        return summary
+    return summary.sort_values(["sort_key", "subtype"]).drop(columns=["sort_key"]).reset_index(drop=True)
+
+
+def _summarize_reentries(reentries: pd.DataFrame, probes: dict[str, ProbeDefinition]) -> pd.DataFrame:
+    rows: list[dict[str, object]] = []
+    for probe in probes.values():
+        group = reentries[reentries["branch"] == probe.name].copy()
+        rows.append(
+            {
+                "branch": probe.name,
+                "allowed_subtype": probe.allowed_subtype,
+                "trades": int(len(group)),
+                "release_window_trades": int(group["in_release_window"].sum()) if not group.empty else 0,
+                "avg_bars_waited": float(group["bars_waited"].mean()) if not group.empty else float("nan"),
+                "same_close_win_rate": float((group["return_pct"] > 0).mean()) if not group.empty else float("nan"),
+                "same_close_avg_return": float(group["return_pct"].mean()) if not group.empty else float("nan"),
+                "next_open_win_rate": float((group["next_open_return_pct"] > 0).mean()) if not group.empty else float("nan"),
+                "next_open_avg_return": float(group["next_open_return_pct"].mean()) if not group.empty else float("nan"),
+                "avg_next_open_minus_same_close": float(group["next_open_minus_same_close_pct"].mean()) if not group.empty else float("nan"),
+                "avg_holding_days": float(group["holding_days"].mean()) if not group.empty else float("nan"),
+                "avg_entry_max_close_return_10b": float(group["entry_max_close_return_10b"].mean()) if not group.empty else float("nan"),
+                "avg_entry_max_close_return_20b": float(group["entry_max_close_return_20b"].mean()) if not group.empty else float("nan"),
+                "avg_post_exit_max_close_return_5b": float(group["post_exit_max_close_return_5b"].mean()) if not group.empty else float("nan"),
+                "avg_post_exit_max_close_return_10b": float(group["post_exit_max_close_return_10b"].mean()) if not group.empty else float("nan"),
+                "exit_reason_distribution": _value_counts_string(group["sell_reason"]) if not group.empty else "",
+            }
+        )
+    return pd.DataFrame(rows)
+
+
+def _value_counts_string(series: pd.Series) -> str:
+    if series.empty:
+        return ""
+    counts = series.astype(str).value_counts()
+    return " | ".join(f"{idx}:{int(val)}" for idx, val in counts.items())
+
+
+def _latest_case_review(candidates: pd.DataFrame, latest_signal_date: str) -> list[str]:
+    case = candidates[candidates["signal_date"] == latest_signal_date].copy()
+    if case.empty:
+        return []
+    row = case.iloc[0]
+    return [
+        "## Latest Block Case",
+        f"- signal_date `{row['signal_date']}` | subtype `{row['subtype']}` | regime `{row['regime']}`",
+        f"- observed bars after block `{int(row['bars_available_3'])}/3` | ql_reconfirm_count `{int(row['next3_ql_buy_count'])}` | sell_cross_count `{int(row['next3_sell_cross_count'])}`",
+        f"- max_close_return so far `{_format_pct(float(row['next3_max_close_return']))}` | confirm_like_within_3 `{bool(row['confirm_like_within_3'])}`",
+        "",
+    ]
+
+
+def _build_review_markdown(
+    *,
+    meta: dict[str, object],
+    candidates: pd.DataFrame,
+    candidate_summary: pd.DataFrame,
+    reentries: pd.DataFrame,
+    reentry_summary: pd.DataFrame,
+) -> str:
+    lines = [
+        "# Dragon Followthrough Profit Loop Review",
+        "",
+        "## Scope",
+        "- objective: inspect whether false-veto glued weak rebounds deserve a fast trend reentry path",
+        f"- data_source: `{meta['data_source']}`",
+        f"- latest_bar: `{meta['latest_bar']}`",
+        f"- row_count: `{int(meta['row_count'])}`",
+        "",
+        "## Subtype Readout",
+    ]
+
+    for row in candidate_summary.itertuples(index=False):
+        lines.append(
+            f"- `{row.subtype}` | blocked_full `{int(row.blocked_count_full)}` | shadow_confirmed `{int(row.shadow_confirmed_trade_count)}` | "
+            f"confirm_like_3bar `{_format_pct(float(row.confirm_like_3bar_rate))}` | "
+            f"next3_sell_cross_rate `{_format_pct(float(row.next3_sell_cross_rate))}` | "
+            f"avg_next20_max `{_format_pct(float(row.avg_next20_max_close_return))}`"
+        )
+
+    lines.extend(["", "## Probe Readout"])
+    for row in reentry_summary.itertuples(index=False):
+        lines.append(
+            f"- `{row.branch}` -> `{row.allowed_subtype}` | trades `{int(row.trades)}` | "
+            f"same_close_avg `{_format_pct(float(row.same_close_avg_return))}` | "
+            f"next_open_avg `{_format_pct(float(row.next_open_avg_return))}` | "
+            f"post_exit_max_10b `{_format_pct(float(row.avg_post_exit_max_close_return_10b))}` | "
+            f"exit_reasons `{row.exit_reason_distribution or 'none'}`"
+        )
+
+    latest_signal_date = ""
+    if not candidates.empty:
+        latest_signal_date = str(candidates["signal_date"].max())
+    lines.extend([""] + _latest_case_review(candidates, latest_signal_date))
+
+    mid_probe = reentry_summary[reentry_summary["branch"] == "probe_mid_zone"]
+    high_probe = reentry_summary[reentry_summary["branch"] == "probe_high_zone"]
+    ql_probe = reentry_summary[reentry_summary["branch"] == "probe_ql_rebound"]
+
+    lines.extend(
+        [
+            "## Judgment",
+            "- `mid_zone_very_weak_b1` is the only subtype that still shows a non-zero delayed-reentry path; it stays the only practical followthrough research lane.",
+            "- `high_zone_weak_b1` should not be promoted. Enabling pending there only created losing followthrough trades in this replay.",
+            "- `ql_rebound_weak_followthrough` remains a hard-block family. The probe branch still produced zero confirmed reentries there.",
+        ]
+    )
+
+    if not mid_probe.empty:
+        row = mid_probe.iloc[0]
+        lines.append(
+            f"- execution timing remains a real question only for the mid-zone path: same_close `{_format_pct(float(row['same_close_avg_return']))}` vs "
+            f"next_open `{_format_pct(float(row['next_open_avg_return']))}`."
+        )
+        lines.append(
+            "- entry-specific exit treatment is worth a narrow check only if the mid-zone path keeps showing higher post-entry upside than realized return."
+        )
+    if not high_probe.empty:
+        row = high_probe.iloc[0]
+        lines.append(
+            f"- high-zone probe result is already weak enough to stop here: trades `{int(row['trades'])}`, same_close_avg `{_format_pct(float(row['same_close_avg_return']))}`."
+        )
+    if not ql_probe.empty:
+        row = ql_probe.iloc[0]
+        lines.append(
+            f"- ql rebound probe stays off the table for promotion: confirmed trades `{int(row['trades'])}`."
+        )
+
+    if not reentries.empty:
+        lines.extend(["", "## Trade Detail Highlights"])
+        for row in reentries.itertuples(index=False):
+            lines.append(
+                f"- `{row.branch}` | origin `{row.origin_date}` -> buy `{row.buy_date}` -> sell `{row.sell_date}` | "
+                f"same_close `{_format_pct(float(row.return_pct))}` | next_open `{_format_pct(float(row.next_open_return_pct))}` | "
+                f"sell `{row.sell_reason}` | post_exit_max_10b `{_format_pct(float(row.post_exit_max_close_return_10b))}`"
+            )
+
+    return "\n".join(lines) + "\n"
+
+
+def main() -> None:
+    base_dir = Path(__file__).resolve().parent
+    history, meta = _load_full_history()
+
+    base_config = alpha_first_glued_refined_hot_cap_config()
+    probes = build_followthrough_probe_configs()
+
+    normalized_history = history.reset_index(drop=True) if "date" in history.columns and "date" in list(history.index.names) else history.copy()
+    candidates = collect_glued_block_candidates(history, base_config, branch_name="base_rc1")
+    candidates = _enrich_block_candidates(candidates, normalized_history, base_config)
+    reentries = _collect_reentry_trade_details(history, probes)
+
+    candidate_summary = _summarize_candidates(candidates, reentries)
+    reentry_summary = _summarize_reentries(reentries, probes)
+    review = _build_review_markdown(
+        meta=meta,
+        candidates=candidates,
+        candidate_summary=candidate_summary,
+        reentries=reentries,
+        reentry_summary=reentry_summary,
+    )
+
+    candidates.to_csv(base_dir / OUTPUT_CANDIDATES, index=False, encoding="utf-8-sig")
+    candidate_summary.to_csv(base_dir / OUTPUT_CANDIDATE_SUMMARY, index=False, encoding="utf-8-sig")
+    reentries.to_csv(base_dir / OUTPUT_REENTRIES, index=False, encoding="utf-8-sig")
+    reentry_summary.to_csv(base_dir / OUTPUT_REENTRY_SUMMARY, index=False, encoding="utf-8-sig")
+    (base_dir / OUTPUT_REVIEW).write_text(review, encoding="utf-8")
+
+
+if __name__ == "__main__":
+    main()

+ 104 - 104
research/dragon/v2/dragon_forward_observation_state.json

@@ -1,147 +1,147 @@
 {
-  "last_run_timestamp": "2026-04-11T17:59:34",
-  "request_date": "2026-04-11",
-  "latest_bar_date": "2026-04-10",
+  "last_run_timestamp": "2026-06-04T15:12:33",
+  "request_date": "2026-06-04",
+  "latest_bar_date": "2026-06-04",
   "branches": {
     "workbook_preserving": {
       "branch": "workbook_preserving",
-      "as_of_date": "2026-04-10",
-      "as_of_timestamp": "2026-04-10T00:00:00",
-      "latest_close": 3658.655,
-      "latest_a1": 0.0121389450166323,
-      "latest_b1": 0.0579459927132818,
-      "latest_c1": 54.99602155815423,
+      "as_of_date": "2026-06-04",
+      "as_of_timestamp": "2026-06-04T15:12:30",
+      "latest_close": 4439.771,
+      "latest_a1": 0.043934266823287,
+      "latest_b1": 0.0122537870421238,
+      "latest_c1": 87.7357856828774,
       "latest_kdj_buy": false,
       "latest_kdj_sell": false,
       "latest_ql_buy": false,
       "latest_ql_sell": false,
-      "latest_real_event_date": "2026-02-13",
-      "latest_real_event_side": "SELL",
-      "latest_real_event_reason": "knife_take_profit_2_glued",
+      "latest_real_event_date": "2026-05-06",
+      "latest_real_event_side": "BUY",
+      "latest_real_event_reason": "dual_gold_resonance_buy",
       "events_today_count": 0,
       "events_today": null,
-      "in_position": false,
-      "open_entry_date": null,
-      "open_entry_reason": null,
-      "open_entry_price": null,
-      "open_holding_days": null,
-      "open_return_pct": null,
-      "data_mode": "official_daily_bar",
-      "snapshot_appended": false,
-      "snapshot_timestamp": null,
-      "historical_latest_bar_date": "2026-04-10"
+      "in_position": true,
+      "open_entry_date": "2026-05-06",
+      "open_entry_reason": "dual_gold_resonance_buy",
+      "open_entry_price": 4023.96,
+      "open_holding_days": 29,
+      "open_return_pct": 0.1033337806538832,
+      "data_mode": "intraday_snapshot",
+      "snapshot_appended": true,
+      "snapshot_timestamp": "2026-06-04T15:12:30",
+      "historical_latest_bar_date": "2026-06-03"
     },
     "alpha_first_selective_veto": {
       "branch": "alpha_first_selective_veto",
-      "as_of_date": "2026-04-10",
-      "as_of_timestamp": "2026-04-10T00:00:00",
-      "latest_close": 3658.655,
-      "latest_a1": 0.0121389450166323,
-      "latest_b1": 0.0579459927132818,
-      "latest_c1": 54.99602155815423,
+      "as_of_date": "2026-06-04",
+      "as_of_timestamp": "2026-06-04T15:12:30",
+      "latest_close": 4439.771,
+      "latest_a1": 0.043934266823287,
+      "latest_b1": 0.0122537870421238,
+      "latest_c1": 87.7357856828774,
       "latest_kdj_buy": false,
       "latest_kdj_sell": false,
       "latest_ql_buy": false,
       "latest_ql_sell": false,
-      "latest_real_event_date": "2026-02-13",
-      "latest_real_event_side": "SELL",
-      "latest_real_event_reason": "knife_take_profit_2_glued",
+      "latest_real_event_date": "2026-05-06",
+      "latest_real_event_side": "BUY",
+      "latest_real_event_reason": "dual_gold_resonance_buy",
       "events_today_count": 0,
       "events_today": null,
-      "in_position": false,
-      "open_entry_date": null,
-      "open_entry_reason": null,
-      "open_entry_price": null,
-      "open_holding_days": null,
-      "open_return_pct": null,
-      "data_mode": "official_daily_bar",
-      "snapshot_appended": false,
-      "snapshot_timestamp": null,
-      "historical_latest_bar_date": "2026-04-10"
+      "in_position": true,
+      "open_entry_date": "2026-05-06",
+      "open_entry_reason": "dual_gold_resonance_buy",
+      "open_entry_price": 4023.96,
+      "open_holding_days": 29,
+      "open_return_pct": 0.1033337806538832,
+      "data_mode": "intraday_snapshot",
+      "snapshot_appended": true,
+      "snapshot_timestamp": "2026-06-04T15:12:30",
+      "historical_latest_bar_date": "2026-06-03"
     },
     "alpha_first_glued_refined_hot_cap": {
       "branch": "alpha_first_glued_refined_hot_cap",
-      "as_of_date": "2026-04-10",
-      "as_of_timestamp": "2026-04-10T00:00:00",
-      "latest_close": 3658.655,
-      "latest_a1": 0.0121389450166323,
-      "latest_b1": 0.0579459927132818,
-      "latest_c1": 54.99602155815423,
+      "as_of_date": "2026-06-04",
+      "as_of_timestamp": "2026-06-04T15:12:30",
+      "latest_close": 4439.771,
+      "latest_a1": 0.043934266823287,
+      "latest_b1": 0.0122537870421238,
+      "latest_c1": 87.7357856828774,
       "latest_kdj_buy": false,
       "latest_kdj_sell": false,
       "latest_ql_buy": false,
       "latest_ql_sell": false,
-      "latest_real_event_date": "2026-02-13",
-      "latest_real_event_side": "SELL",
-      "latest_real_event_reason": "knife_take_profit_2_glued",
+      "latest_real_event_date": "2026-05-06",
+      "latest_real_event_side": "BUY",
+      "latest_real_event_reason": "dual_gold_resonance_buy",
       "events_today_count": 0,
       "events_today": null,
-      "in_position": false,
-      "open_entry_date": null,
-      "open_entry_reason": null,
-      "open_entry_price": null,
-      "open_holding_days": null,
-      "open_return_pct": null,
-      "data_mode": "official_daily_bar",
-      "snapshot_appended": false,
-      "snapshot_timestamp": null,
-      "historical_latest_bar_date": "2026-04-10"
+      "in_position": true,
+      "open_entry_date": "2026-05-06",
+      "open_entry_reason": "dual_gold_resonance_buy",
+      "open_entry_price": 4023.96,
+      "open_holding_days": 29,
+      "open_return_pct": 0.1033337806538832,
+      "data_mode": "intraday_snapshot",
+      "snapshot_appended": true,
+      "snapshot_timestamp": "2026-06-04T15:12:30",
+      "historical_latest_bar_date": "2026-06-03"
     },
     "alpha_first_glued_followthrough_probe": {
       "branch": "alpha_first_glued_followthrough_probe",
-      "as_of_date": "2026-04-10",
-      "as_of_timestamp": "2026-04-10T00:00:00",
-      "latest_close": 3658.655,
-      "latest_a1": 0.0121389450166323,
-      "latest_b1": 0.0579459927132818,
-      "latest_c1": 54.99602155815423,
+      "as_of_date": "2026-06-04",
+      "as_of_timestamp": "2026-06-04T15:12:30",
+      "latest_close": 4439.771,
+      "latest_a1": 0.043934266823287,
+      "latest_b1": 0.0122537870421238,
+      "latest_c1": 87.7357856828774,
       "latest_kdj_buy": false,
       "latest_kdj_sell": false,
       "latest_ql_buy": false,
       "latest_ql_sell": false,
-      "latest_real_event_date": "2026-02-13",
-      "latest_real_event_side": "SELL",
-      "latest_real_event_reason": "knife_take_profit_2_glued",
+      "latest_real_event_date": "2026-05-06",
+      "latest_real_event_side": "BUY",
+      "latest_real_event_reason": "dual_gold_resonance_buy",
       "events_today_count": 0,
       "events_today": null,
-      "in_position": false,
-      "open_entry_date": null,
-      "open_entry_reason": null,
-      "open_entry_price": null,
-      "open_holding_days": null,
-      "open_return_pct": null,
-      "data_mode": "official_daily_bar",
-      "snapshot_appended": false,
-      "snapshot_timestamp": null,
-      "historical_latest_bar_date": "2026-04-10"
+      "in_position": true,
+      "open_entry_date": "2026-05-06",
+      "open_entry_reason": "dual_gold_resonance_buy",
+      "open_entry_price": 4023.96,
+      "open_holding_days": 29,
+      "open_return_pct": 0.1033337806538832,
+      "data_mode": "intraday_snapshot",
+      "snapshot_appended": true,
+      "snapshot_timestamp": "2026-06-04T15:12:30",
+      "historical_latest_bar_date": "2026-06-03"
     },
     "alpha_first_glued_followthrough_mid_exit_probe": {
       "branch": "alpha_first_glued_followthrough_mid_exit_probe",
-      "as_of_date": "2026-04-10",
-      "as_of_timestamp": "2026-04-10T00:00:00",
-      "latest_close": 3658.655,
-      "latest_a1": 0.0121389450166323,
-      "latest_b1": 0.0579459927132818,
-      "latest_c1": 54.99602155815423,
+      "as_of_date": "2026-06-04",
+      "as_of_timestamp": "2026-06-04T15:12:30",
+      "latest_close": 4439.771,
+      "latest_a1": 0.043934266823287,
+      "latest_b1": 0.0122537870421238,
+      "latest_c1": 87.7357856828774,
       "latest_kdj_buy": false,
       "latest_kdj_sell": false,
       "latest_ql_buy": false,
       "latest_ql_sell": false,
-      "latest_real_event_date": "2026-02-13",
-      "latest_real_event_side": "SELL",
-      "latest_real_event_reason": "knife_take_profit_2_glued",
+      "latest_real_event_date": "2026-05-06",
+      "latest_real_event_side": "BUY",
+      "latest_real_event_reason": "dual_gold_resonance_buy",
       "events_today_count": 0,
       "events_today": null,
-      "in_position": false,
-      "open_entry_date": null,
-      "open_entry_reason": null,
-      "open_entry_price": null,
-      "open_holding_days": null,
-      "open_return_pct": null,
-      "data_mode": "official_daily_bar",
-      "snapshot_appended": false,
-      "snapshot_timestamp": null,
-      "historical_latest_bar_date": "2026-04-10"
+      "in_position": true,
+      "open_entry_date": "2026-05-06",
+      "open_entry_reason": "dual_gold_resonance_buy",
+      "open_entry_price": 4023.96,
+      "open_holding_days": 29,
+      "open_return_pct": 0.1033337806538832,
+      "data_mode": "intraday_snapshot",
+      "snapshot_appended": true,
+      "snapshot_timestamp": "2026-06-04T15:12:30",
+      "historical_latest_bar_date": "2026-06-03"
     }
   },
   "monitor_summary": {
@@ -153,14 +153,14 @@
     "hard_breach_metrics": []
   },
   "divergence": {
-    "latest_bar_date": "2026-04-10",
-    "request_date": "2026-04-11",
+    "latest_bar_date": "2026-06-04",
+    "request_date": "2026-06-04",
     "same_position_flag": true,
     "same_latest_real_event_flag": true,
-    "refined_in_position": false,
-    "control_in_position": false,
-    "refined_latest_event": "2026-02-13|SELL|knife_take_profit_2_glued",
-    "control_latest_event": "2026-02-13|SELL|knife_take_profit_2_glued",
+    "refined_in_position": true,
+    "control_in_position": true,
+    "refined_latest_event": "2026-05-06|BUY|dual_gold_resonance_buy",
+    "control_latest_event": "2026-05-06|BUY|dual_gold_resonance_buy",
     "next_open_avg_return_delta": 0.0052846983279676,
     "next_open_pf_delta": 0.9170407302181012,
     "next_open_max_drawdown_refined": -0.1319253592544694,

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 48 - 0
research/dragon/v2/dragon_forward_weekly_review.html


+ 19 - 19
research/dragon/v2/dragon_forward_weekly_review.md

@@ -1,59 +1,59 @@
 # Dragon Forward Weekly Review
 
-- latest_window_end: `2026-04-10`
+- latest_window_end: `2026-06-04`
 
 ## alpha_first_glued_followthrough_mid_exit_probe
-- window: `2026-04-03` -> `2026-04-10`
-- observation_days: `1`
-- days_in_position: `0`
-- latest_real_event_changed_count: `0`
+- window: `2026-04-07` -> `2026-06-04`
+- observation_days: `2`
+- days_in_position: `1`
+- latest_real_event_changed_count: `1`
 - new_event_days: `0`
 - warning_days: `0`
 - hard_breach_days: `0`
 - material_divergence_days: `0`
 
 ## alpha_first_glued_followthrough_probe
-- window: `2026-04-03` -> `2026-04-10`
-- observation_days: `1`
-- days_in_position: `0`
-- latest_real_event_changed_count: `0`
+- window: `2026-04-07` -> `2026-06-04`
+- observation_days: `2`
+- days_in_position: `1`
+- latest_real_event_changed_count: `1`
 - new_event_days: `0`
 - warning_days: `0`
 - hard_breach_days: `0`
 - material_divergence_days: `0`
 
 ## alpha_first_glued_refined_hot_cap
-- window: `2026-04-03` -> `2026-04-10`
+- window: `2026-04-07` -> `2026-06-04`
 - observation_days: `5`
-- days_in_position: `0`
-- latest_real_event_changed_count: `0`
+- days_in_position: `1`
+- latest_real_event_changed_count: `1`
 - new_event_days: `0`
 - warning_days: `0`
 - hard_breach_days: `0`
 - material_divergence_days: `0`
 
 ## alpha_first_selective_veto
-- window: `2026-04-03` -> `2026-04-10`
+- window: `2026-04-07` -> `2026-06-04`
 - observation_days: `5`
-- days_in_position: `0`
-- latest_real_event_changed_count: `0`
+- days_in_position: `1`
+- latest_real_event_changed_count: `1`
 - new_event_days: `0`
 - warning_days: `0`
 - hard_breach_days: `0`
 - material_divergence_days: `0`
 
 ## workbook_preserving
-- window: `2026-04-03` -> `2026-04-10`
+- window: `2026-04-07` -> `2026-06-04`
 - observation_days: `5`
-- days_in_position: `0`
-- latest_real_event_changed_count: `0`
+- days_in_position: `1`
+- latest_real_event_changed_count: `1`
 - new_event_days: `0`
 - warning_days: `0`
 - hard_breach_days: `0`
 - material_divergence_days: `0`
 
 ## system_monitor
-- window: `2026-04-03` -> `2026-04-10`
+- window: `2026-04-07` -> `2026-06-04`
 - observation_days: `5`
 - days_in_position: `0`
 - latest_real_event_changed_count: `0`

+ 76 - 0
research/dragon/v2/dragon_glued_followthrough_confirmation.py

@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+from dragon_strategy_config import StrategyConfig
+
+
+def glued_high_weak_rebound_subtype(
+    *,
+    a1: float,
+    b1: float,
+    c1: float,
+    ql_buy: bool,
+    config: StrategyConfig,
+) -> str:
+    if c1 > config.glued_high_weak_rebound_high_c1 and b1 < config.glued_high_weak_rebound_high_b1:
+        return "high_zone_weak_b1"
+    if c1 > config.glued_high_weak_rebound_mid_c1 and b1 < config.glued_high_weak_rebound_mid_b1:
+        return "mid_zone_very_weak_b1"
+    if (
+        ql_buy
+        and config.glued_high_weak_rebound_ql_c1_low < c1 < config.glued_high_weak_rebound_ql_c1_high
+        and b1 < config.glued_high_weak_rebound_ql_b1
+        and a1 > config.glued_high_weak_rebound_ql_a1
+    ):
+        return "ql_rebound_weak_followthrough"
+    return ""
+
+
+def glued_followthrough_pending_allowed(
+    *,
+    subtype: str,
+    config: StrategyConfig,
+) -> bool:
+    if not config.glued_followthrough_pending_enabled:
+        return False
+    if subtype == "mid_zone_very_weak_b1":
+        return config.glued_followthrough_allow_mid_zone_very_weak_b1
+    if subtype == "high_zone_weak_b1":
+        return config.glued_followthrough_allow_high_zone_weak_b1
+    if subtype == "ql_rebound_weak_followthrough":
+        return config.glued_followthrough_allow_ql_rebound_weak_followthrough
+    return False
+
+
+def evaluate_glued_followthrough_confirmation(
+    *,
+    active: bool,
+    subtype: str,
+    in_position: bool,
+    kdj_sell: bool,
+    ql_sell: bool,
+    bars_waited: int,
+    ql_buy: bool,
+    close: float,
+    signal_close: float,
+    b1: float,
+    signal_b1: float,
+    config: StrategyConfig,
+) -> tuple[str, str, bool]:
+    if not active or not subtype:
+        return "NONE", "", False
+    if in_position:
+        return "NONE", "", True
+    if kdj_sell or ql_sell:
+        return "NONE", "", True
+    if bars_waited > config.glued_followthrough_confirm_window_bars:
+        return "NONE", "", True
+    if bars_waited < 1:
+        return "NONE", "", False
+    if config.glued_followthrough_require_ql_buy_reconfirm and not ql_buy:
+        return "NONE", "", False
+    if config.glued_followthrough_require_close_break_signal_close and close <= signal_close:
+        return "NONE", "", False
+    if config.glued_followthrough_require_b1_repair and (b1 - signal_b1) < config.glued_followthrough_b1_repair_min:
+        return "NONE", "", False
+    return "BUY", f"glued_followthrough_reentry_buy:confirmed_{subtype}", True
+

+ 32 - 0
research/dragon/v2/dragon_glued_followthrough_exit.py

@@ -0,0 +1,32 @@
+from __future__ import annotations
+
+from dragon_strategy_config import StrategyConfig
+
+
+def should_hold_glued_followthrough_reentry_kdj_only(
+    *,
+    enabled: bool,
+    entry_reason_code: str,
+    kdj_sell: bool,
+    ql_sell: bool,
+    holding_days: int,
+    a1: float,
+    b1: float,
+    c1: float,
+    config: StrategyConfig,
+) -> bool:
+    if not enabled:
+        return False
+    if entry_reason_code != "entry_glued_followthrough_reentry":
+        return False
+    if not kdj_sell or ql_sell:
+        return False
+    if holding_days > config.glued_followthrough_exit_hold_kdj_only_days_max:
+        return False
+    if not (config.glued_followthrough_exit_hold_kdj_only_c1_low < c1 < config.glued_followthrough_exit_hold_kdj_only_c1_high):
+        return False
+    if a1 <= config.glued_followthrough_exit_hold_kdj_only_a1_min:
+        return False
+    if b1 <= config.glued_followthrough_exit_hold_kdj_only_b1_min:
+        return False
+    return True

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
research/dragon/v2/dragon_historical_trade_details.html


+ 1 - 1
research/dragon/v2/dragon_monitor_health_report.md

@@ -1,6 +1,6 @@
 # Dragon Monitor Health Report
 
-- latest_bar_date: `2026-04-10`
+- latest_bar_date: `2026-06-04`
 - warning_count: `0`
 - hard_breach_count: `0`
 - missing_data_count: `0`

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 57 - 0
research/dragon/v2/dragon_reports_index.html


+ 4 - 4
research/dragon/v2/dragon_rollout_governance_report.md

@@ -1,8 +1,8 @@
 # Dragon Rollout Governance Report
 
-- generated_at: `2026-04-11T17:59:34`
-- request_date: `2026-04-11`
-- latest_bar_date: `2026-04-10`
+- generated_at: `2026-06-04T15:12:33`
+- request_date: `2026-06-04`
+- latest_bar_date: `2026-06-04`
 - candidate_branch: `alpha_first_glued_refined_hot_cap`
 - fallback_branch: `alpha_first_selective_veto`
 - decision: `FORWARD_OK`
@@ -16,7 +16,7 @@
 ## Gate Details
 ### data_freshness
 - status: `ok`
-- value: `manifest=2026-04-10,monitor=2026-04-10,divergence=2026-04-10`
+- value: `manifest=2026-06-04,monitor=2026-06-04,divergence=2026-06-04`
 - threshold: `all dates aligned`
 - detail: Daily manifest, monitor history, and divergence log must point to the same latest bar.
 - action: none

+ 1 - 1
research/dragon/v2/dragon_rollout_rollback_runbook.md

@@ -22,7 +22,7 @@
 4. Regenerate rollout report and ensure decision is `FORWARD_OK` before reactivating candidate branch.
 
 ## Current Snapshot
-- latest_bar_date: `2026-04-10`
+- latest_bar_date: `2026-06-04`
 - decision: `FORWARD_OK`
 - recommended_active_branch: `alpha_first_glued_refined_hot_cap`
 - fallback_branch: `alpha_first_selective_veto`

+ 3 - 3
research/dragon/v2/dragon_rollout_state.json

@@ -1,7 +1,7 @@
 {
-  "generated_at": "2026-04-11T17:59:34",
-  "request_date": "2026-04-11",
-  "latest_bar_date": "2026-04-10",
+  "generated_at": "2026-06-04T15:12:33",
+  "request_date": "2026-06-04",
+  "latest_bar_date": "2026-06-04",
   "release_version": "RC1",
   "candidate_branch": "alpha_first_glued_refined_hot_cap",
   "fallback_branch": "alpha_first_selective_veto",

+ 68 - 12
research/dragon/v2/dragon_signal_change_review.md

@@ -1,19 +1,75 @@
 # Dragon Signal Change Review
 
-- latest_bar_date: `2026-04-10`
-- change_count: `2`
+- latest_bar_date: `2026-06-04`
+- change_count: `10`
 
-## alpha_first_glued_followthrough_mid_exit_probe / branch_initialized
-- old: `nan`
-- new: `2026-02-13|SELL|knife_take_profit_2_glued`
-- reason: first forward-observation snapshot for this branch
+## alpha_first_glued_followthrough_mid_exit_probe / latest_real_event_changed
+- old: `2026-02-13|SELL|knife_take_profit_2_glued`
+- new: `2026-05-06|BUY|dual_gold_resonance_buy`
+- reason: latest real-trade event changed
 - event_context: `none`
-- indicator_context: `close=3658.655,a1=0.0121,b1=0.0579,c1=55.00`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
 
-## alpha_first_glued_followthrough_probe / branch_initialized
-- old: `nan`
-- new: `2026-02-13|SELL|knife_take_profit_2_glued`
-- reason: first forward-observation snapshot for this branch
+## alpha_first_glued_followthrough_mid_exit_probe / position_changed
+- old: `False`
+- new: `True`
+- reason: position state changed between forward snapshots
 - event_context: `none`
-- indicator_context: `close=3658.655,a1=0.0121,b1=0.0579,c1=55.00`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
+
+## alpha_first_glued_followthrough_probe / latest_real_event_changed
+- old: `2026-02-13|SELL|knife_take_profit_2_glued`
+- new: `2026-05-06|BUY|dual_gold_resonance_buy`
+- reason: latest real-trade event changed
+- event_context: `none`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
+
+## alpha_first_glued_followthrough_probe / position_changed
+- old: `False`
+- new: `True`
+- reason: position state changed between forward snapshots
+- event_context: `none`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
+
+## alpha_first_glued_refined_hot_cap / latest_real_event_changed
+- old: `2026-02-13|SELL|knife_take_profit_2_glued`
+- new: `2026-05-06|BUY|dual_gold_resonance_buy`
+- reason: latest real-trade event changed
+- event_context: `none`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
+
+## alpha_first_glued_refined_hot_cap / position_changed
+- old: `False`
+- new: `True`
+- reason: position state changed between forward snapshots
+- event_context: `none`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
+
+## alpha_first_selective_veto / latest_real_event_changed
+- old: `2026-02-13|SELL|knife_take_profit_2_glued`
+- new: `2026-05-06|BUY|dual_gold_resonance_buy`
+- reason: latest real-trade event changed
+- event_context: `none`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
+
+## alpha_first_selective_veto / position_changed
+- old: `False`
+- new: `True`
+- reason: position state changed between forward snapshots
+- event_context: `none`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
+
+## workbook_preserving / latest_real_event_changed
+- old: `2026-02-13|SELL|knife_take_profit_2_glued`
+- new: `2026-05-06|BUY|dual_gold_resonance_buy`
+- reason: latest real-trade event changed
+- event_context: `none`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
+
+## workbook_preserving / position_changed
+- old: `False`
+- new: `True`
+- reason: position state changed between forward snapshots
+- event_context: `none`
+- indicator_context: `close=4439.771,a1=0.0439,b1=0.0123,c1=87.74`
 

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 48 - 0
research/dragon/v2/html_reports/dragon_daily_signal_report_2026-06-04.html


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 48 - 0
research/dragon/v2/html_reports/dragon_forward_weekly_review_2026-06-04.html


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 373 - 0
research/dragon/v2/html_reports/dragon_historical_trade_details_2026-06-04.html


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 57 - 0
research/dragon/v2/html_reports/dragon_reports_index_2026-06-04.html


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 57 - 0
research/dragon/v2/html_reports/index.html


+ 51 - 0
research/dragon/v2/memory/2026-04-07.md

@@ -0,0 +1,51 @@
+## 2026-04-07
+
+- Added `dragon_excel_export.py` to export v2 strategy results into an Excel workbook styled off the original `龙泉回测20260109.data.xlsx`.
+- Export output file: `dragon_v2_compare_export.xlsx`.
+- Export workbook keeps the original sheets and adds three v2 comparison sheets:
+- `龙泉_v2_workbook_preserving`
+- `龙泉_v2_alpha`
+- `龙泉_v2_RC1`
+- Data source uses freshly fetched `399673` daily history through the latest available market bar and recomputes indicators plus branch events locally.
+- Export sheet layout intentionally stays close to the original workbook:
+- original header rows retained
+- KDJ / QL signal columns retained
+- real / auxiliary strategy operations written into the same operation-direction column
+- in-position floating PnL rows and yearly summary rows are also generated
+- Current generated workbook run used latest available bar `2026-04-03`.
+
+- User then requested an overfitting-risk inspection on dragon v2.
+- Current judgment:
+- not a trivial black-box overfit, because walk-forward / local sensitivity / execution-latency stress all remain directionally supportive
+- but still carries medium-to-high overfitting risk because the strategy tree is complex, the parameter surface is wide, and multiple weak families remain sample-dependent
+- key hard risk buckets:
+- `deep_oversold_*` family
+- `predictive_b1_break_*` bridge logic
+- broad historical iterative tuning path dependence around workbook alignment
+
+- serious code-based overfitting review completed on 2026-04-07.
+- verdict: not a fake strategy, but still medium-to-high overfitting risk due to high rule-tree complexity, 102-field config surface, and several low-sample specialized families.
+- strongest stable core: glued_buy and its refined selective filter family.
+- main fragile families: deep_oversold_* and predictive_b1_break_* bridge logic; also one-off reentry/exit rules should be treated as provisional rather than core alpha.
+
+- Added `dragon_overfit_trade_map.py` and generated `dragon_overfit_trade_map.html`.
+- This new HTML visualizes full historical trades against the price curve, supports branch switching, and explicitly highlights trades judged as medium/high overfitting risk.
+- Current first-pass counts from the generated report:
+- `RC1`: total trades `94`, high-risk `10`, medium-risk `12`
+- `alpha_first_selective_veto`: total trades `105`, high-risk `10`, medium-risk `14`
+- `workbook_preserving`: total trades `109`, high-risk `8`, medium-risk `20`
+
+- Built an external consultation bundle for `dragon/v2`:
+- folder: `dragon_v2_consult_2026-04-07/`
+- zip: `dragon_v2_consult_2026-04-07.zip`
+- bundle contents:
+- Chinese bottleneck/risk memo
+- suggested ChatGPT Pro prompt
+- core source files
+- branch / RC1 parameter snapshots
+- key evidence reports (`rule_ablation`, `threshold_perturbation`, `walk_forward`, `deep_oversold`, `predictive_break`)
+- minimal supporting data and workbook exports
+- Main consulting judgment written into the bundle:
+- strongest stable core remains `glued_buy`
+- biggest architecture bottleneck is the large order-sensitive rule tree in `dragon_strategy.py`
+- biggest quant fragility still concentrates in `deep_oversold_*` and `predictive_b1_break_*`

+ 224 - 0
research/dragon/v2/memory/2026-04-08.md

@@ -0,0 +1,224 @@
+## 2026-04-08
+
+- Ran `dragon_daily_signal_pipeline.py` on request date `2026-04-08`.
+- Latest available market bar resolved to `2026-04-07`.
+- Latest `399673` snapshot on `2026-04-07`:
+- close `3325.910`
+- `a1 = -0.0182`
+- `b1 = -0.1651`
+- `c1 = 44.35`
+- `KDJ buy/sell = False/False`
+- `QL buy/sell = False/False`
+- No branch fired any new event on the latest bar.
+- `workbook_preserving`, `alpha_first_selective_veto`, and `alpha_first_glued_refined_hot_cap` all remain flat.
+- Latest real event for all three branches is still `2026-02-13 SELL knife_take_profit_2_glued`.
+- User-facing answer for this run: no buy trigger on the latest available bar.
+
+- Also ran `dragon_forward_observation_pipeline.py` to persist the daily check.
+- Forward-observation result on `2026-04-07`:
+- signal-change count `0`
+- divergence level `none`
+- warnings `0`
+- hard breaches `0`
+- missing data metrics `0`
+
+- Later on `2026-04-08`, upgraded the live-signal path to support intraday realtime triggering.
+- Implementation:
+- `dragon/data_fetcher_v2.py` now supports realtime snapshot fetch and conditional current-day snapshot append.
+- `dragon_indicators.py` now supports `fetch_daily_data(include_intraday_snapshot=True)` and exposes fetch metadata.
+- `dragon_daily_signal_pipeline.py` now uses intraday snapshot mode by default for same-day checks and reports:
+- `data_mode`
+- `historical_latest_bar_date`
+- `snapshot_timestamp`
+- explicit note that current market price is used as today's provisional `close`
+
+- Verified live run after the upgrade:
+- latest evaluated bar became `2026-04-08` via realtime snapshot
+- historical latest official bar remained `2026-04-07`
+- snapshot timestamp example: `2026-04-08T14:47:31`
+- live price snapshot close around `3524.990`
+- latest markers on all three branches:
+- `KDJ buy=True`
+- `QL buy=True`
+- but no real `BUY` event fired
+- local decision check on the last bar shows all three branches were blocked by:
+- `buy_block_glued_high_weak_rebound`
+
+- Verified `dragon_forward_observation_pipeline.py` still runs successfully after the schema change.
+
+- Analyzed `buy_block_glued_high_weak_rebound` on `2026-04-08`.
+- Current rule location:
+- `dragon_strategy.py` `_buy_filter_glued_high_weak_rebound(...)`
+- official daily-bar replay through `2026-04-07` shows the same historical block count on all three tracked branches:
+- full history block count `17`
+- release-window (`2016-01-01 -> 2025-12-31`) block count `15`
+- subtype split in the release window:
+- `high_zone_weak_b1`: `11`
+- `mid_zone_very_weak_b1`: `2`
+- `ql_rebound_weak_followthrough`: `2`
+- release-window block dates:
+- `2016-04-29`, `2017-09-27`, `2018-04-18`, `2019-04-24`, `2019-10-10`, `2020-08-18`, `2020-08-24`, `2020-11-30`, `2021-07-29`, `2021-12-09`, `2022-08-01`, `2024-06-04`, `2024-06-17`, `2025-10-21`, `2025-11-13`
+- including the intraday realtime snapshot on `2026-04-08`, RC1 block count becomes `18` total; the current live block belongs to the `ql_rebound_weak_followthrough` clause.
+
+- Effect check by disabling only this filter while keeping the rest of the strategy unchanged:
+- `workbook_preserving`: trades `106 -> 121`, avg_return `2.57% -> 2.13%`, profit_factor `3.37 -> 2.90`
+- `alpha_first_selective_veto`: trades `102 -> 117`, avg_return `2.86% -> 2.36%`, profit_factor `4.04 -> 3.38`
+- `alpha_first_glued_refined_hot_cap` / RC1: trades `91 -> 106`, avg_return `3.42% -> 2.80%`, profit_factor `5.11 -> 4.08`
+- In all three branches the filter removal adds `15` trades and removes `0`.
+- Added-trade quality is weak:
+- avg_return about `-0.99%`
+- win_rate about `26.7%`
+- Therefore current judgment: this filter is materially useful and should be treated as a real quality filter, not noise.
+
+- Added detailed blocked-trade artifacts:
+- `dragon_glued_high_weak_rebound_block_review.md`
+- `dragon_glued_high_weak_rebound_blocked_trades.csv`
+- RC1 blocked-trade detail sorted by realized return shows the worst candidates were:
+- `2020-08-18 -> 2020-08-19` `-3.91%`
+- `2019-04-24 -> 2019-04-25` `-3.42%`
+- `2018-04-18 -> 2018-04-20` `-3.10%`
+- best completed blocked candidates were:
+- `2020-11-30 -> 2020-12-09` `+2.44%`
+- `2017-09-27 -> 2017-10-16` `+1.60%`
+- `2020-08-24 -> 2020-08-25` `+0.90%`
+- one extra blocked date `2025-11-13` is present as a candidate date but its corresponding exit falls outside the release-window trade summary, so it has no completed in-window trade row.
+
+- Checked the original reconstruction branch (`workbook_preserving`) on the intraday snapshot later on `2026-04-08`.
+- Snapshot example:
+- evaluated timestamp `2026-04-08T15:56:41`
+- close `3532.798`
+- `a1 = -0.0073`
+- `b1 = -0.0777`
+- `c1 = 47.86`
+- `KDJ buy = True`
+- `QL buy = True`
+- original branch decision was still:
+- `BLOCK buy_block_glued_high_weak_rebound`
+
+- User later requested the 2026 RC1 buy/sell points.
+- Extracted real-trade RC1 points for calendar year 2026:
+- `2026-01-05 BUY dual_gold_resonance_buy @ 3488.230`
+- `2026-01-20 SELL ql_mid_zone_take_profit @ 3419.621`
+- `2026-01-27 BUY glued_buy @ 3476.169`
+- `2026-02-02 SELL knife_take_profit_2_glued @ 3432.873`
+- `2026-02-09 BUY glued_buy @ 3493.591`
+- `2026-02-13 SELL knife_take_profit_2_glued @ 3423.045`
+- As of the intraday snapshot on `2026-04-08`, RC1 remains flat and no new real event has been added after `2026-02-13`.
+
+- Later analysis of why no intraday buy fired on `2026-04-08`:
+- the live snapshot did create raw buy markers (`KDJ buy=True`, `QL buy=True`) and satisfied glued-state entry context (`abs(a1) < 0.02`)
+- but the final buy decision was blocked by `buy_block_glued_high_weak_rebound`
+- exact subtype was `ql_rebound_weak_followthrough`
+- snapshot values around `2026-04-08T14:48:51`:
+- `a1 = -0.00779`
+- `b1 = -0.08157`
+- `c1 = 47.62`
+- this matched the veto window:
+- `35 < c1 < 55`
+- `b1 < -0.06`
+- `a1 > -0.013`
+- `ql_buy = True`
+- the move was therefore interpreted as a fast intraday rebound with still-weak followthrough rather than a confirmed real buy
+
+- Follow-up judgment on whether this may miss a real rally or reflect overfitting:
+- yes, any veto rule can miss an individual future rally
+- but current evidence does not support calling this specific filter an obvious overfit blocker
+- disabling `buy_block_glued_high_weak_rebound` adds `15` release-window trades and weakens all three tracked branches
+- RC1 added-trade set stats if allowed:
+- win rate about `26.7%`
+- avg return about `-0.99%`
+- best completed blocked trade only about `+2.44%`
+- today's exact subtype `ql_rebound_weak_followthrough` has only two historical daily examples and both were losers (`-2.20%`, `-1.04%`)
+- nuanced conclusion:
+- strategy-level overfitting risk remains medium-to-high overall because of rule-tree complexity
+- but today's specific veto is more likely a useful quality filter than a clear case of overfit signal suppression
+- remaining uncertainty is concentrated in the sparse `ql_rebound_weak_followthrough` subclause because its sample is still small
+
+- Direct advisory judgment for the user:
+- if forced to decide on the live `2026-04-08` snapshot, the correct call is still `do not buy yet`
+- do not weaken the global `buy_block_glued_high_weak_rebound` rule because of this one signal
+- safer future research path, if needed, is a subtype-specific second-chance release after stronger followthrough repair, not removal of the veto itself
+
+- Later nuance after the user suggested this may be the start of a big trend:
+- that concern is legitimate; broad neighborhood analysis around today's indicator shape is not obviously bearish
+- among the 12 nearest historical RC1 buy-shape neighbors to the `2026-04-08` snapshot, avg return was about `+2.09%`, median about `+1.67%`, win rate about `66.7%`, with `3` trades above `+5%` and `1` above `+10%`
+- however, the exact vetoed subclause neighborhood (`ql_rebound_weak_followthrough`) still has only 2 historical blocked daily examples and both were losers
+- operational interpretation:
+- there is a non-trivial chance today is an early stage of a larger move
+- but current evidence still does not justify overriding RC1 on this bar alone
+- if followthrough continues and either `B1` repairs above the current veto threshold or the setup migrates out of this weak-followthrough window, the bull case should be re-evaluated quickly rather than dismissed
+
+- Later projected tomorrow's `B1` using the latest available intraday snapshot around `2026-04-08T18:25:09` with `close = 3532.798`.
+- Using the current `Y2/Y3` recursion and assuming no new 38-day extreme beyond the projected close:
+- if tomorrow closes flat near `3532.798`, projected `B1` is about `-0.0229`
+- if tomorrow closes `-1%`, projected `B1` is about `-0.0421`
+- if tomorrow closes `-2%`, projected `B1` is about `-0.0614`
+- approximate threshold for `B1 >= -0.06` is tomorrow close around `3464.7`, about `-1.93%` vs the latest snapshot close
+- practical takeaway:
+- yes, under the current snapshot baseline, “tomorrow not falling” should place `B1` above `-0.06`
+- but that only means the current `ql_rebound_weak_followthrough` veto likely stops applying
+- it does not guarantee a real buy tomorrow because `KDJ buy` / `QL buy` are cross-event flags and may not re-fire on the next bar
+
+- Later reviewed the external recommendation file `dragon_v2_rc1_restructuring_recommendations_for_codex_CN.md` and wrote a local implementation proposal `dragon_v2_rc1_restructuring_proposal_cn.md`.
+- Main judgment on that external recommendation:
+- directionally correct on freezing RC1, splitting core / secondary / bridge, downgrading predictive logic, and adding golden tests
+- but too aggressive as a direct big-bang refactor for the current repo
+- key verified repo facts used in the proposal:
+- `dragon_strategy.py` has `1297` lines
+- `StrategyContext` has `50` fields
+- `_buy_decision()` about `125` lines
+- `_sell_decision()` about `303` lines
+- `StrategyConfig` has `102` fields
+- current repo still has no `tests/` directory
+- `reason` strings are already a de facto interface across strategy, daily pipeline, path trace, and html reporting
+- proposal conclusion:
+- do not directly rewrite RC1 or remove reason strings
+- first add structured decision metadata with legacy reason compatibility
+- first add golden baselines and layer attribution
+- only then isolate weak families (`deep_oversold`, `predictive`, `post_sell_rebound`) into separate modules on a research path
+
+- Executed the first implementation pass for proposal Phase 0-2 without changing RC1 live behavior.
+- Added governance and scaffolding modules:
+- `dragon_rc1_frozen_policy.md`
+- `dragon_reason_types.py`
+- `dragon_decision_types.py`
+- `dragon_rule_catalog.py`
+- Added executable scripts:
+- `dragon_rc1_golden_baseline.py`
+- `dragon_rule_layer_attribution.py`
+- Generated new baseline artifact:
+- `dragon_rc1_golden_manifest.json`
+- generated_at `2026-04-08T23:43:07`
+- evaluation window `2016-01-01 -> 2025-12-31`
+- RC1 trade_count `91`, event_count `272`, win_rate `52.75%`, avg_return `3.42%`
+- indicator source used: `dragon_indicator_snapshot_full.csv`
+- Generated layer-attribution report:
+- `dragon_rule_layer_attribution.md`
+- key entry-layer read:
+- `core/glued` remains dominant (`50` trades, avg_return `4.92%`, PF `7.15`)
+- `secondary/deep_oversold` remains weak (avg_return `-0.19%`, PF `0.80`)
+- `bridge/predictive_break` count still low (`1` entry trade in-window)
+- key exit-layer read:
+- `secondary/post_sell_rebound` exits show weak quality (avg_return `-2.00%`, PF `0.02`)
+- `core/high_regime` exits remain strong (avg_return `11.63%`, PF `107.38`)
+- mapping audit is now clean in the generated markdown (`no unknown reason mapping`)
+- Important repo note:
+- generated `.csv` artifacts from these scripts are covered by `.gitignore` (`*.csv`) and are not tracked by default
+
+- Later answered the user's question about what profitable indicator profiles look like in RC1.
+- Main conclusion:
+- the best-performing trades are not primarily deep-oversold knife catches
+- profitable trades are concentrated in `glued_buy`, plus smaller contributions from `early_crash_probe_buy` / `oversold_recovery_buy`
+- holding duration matters strongly:
+- `00-05d` avg return about `-2.03%`
+- `06-10d` avg return about `-0.81%`
+- `21-40d` avg return about `+6.33%`
+- `41d+` avg return about `+24.13%`
+- top-profit trades (`>=10%`) mostly look like:
+- `A1` near zero / mild glue rather than deeply negative
+- `B1` around zero to mildly positive more often than the full sample
+- `C1` mainly in the `20-60` middle zone rather than extreme `<20` oversold
+- `KDJ buy` is almost always present
+- `QL buy` is helpful but not mandatory
+- strongest actionable read:
+- large profits mostly come from entries that can transition into long trend holds and exit through regime/trend management (`prewarning_reduction_exit`, `crash_protection_exit`, high-regime exits), not from short rebound scalps

+ 205 - 0
research/dragon/v2/memory/2026-04-10.md

@@ -0,0 +1,205 @@
+## 2026-04-10
+
+- User asked: `统计下4.8号那天是什么信号过滤了开单`.
+- Verified from `daily_reports/dragon_daily_signal_report_2026-04-08.md` and branch status CSV:
+- 2026-04-08 had `KDJ buy=True`, `QL buy=True`, `a1=-0.0073`, `b1=-0.0777`, `c1=47.86`.
+- No buy event fired (`events_today_count=0`) on all three branches.
+- The blocking rule is `buy_block_glued_high_weak_rebound`, exact subclause `ql_rebound_weak_followthrough`.
+- Matching condition set:
+- `ql_buy=True`
+- `35 < c1 < 55` -> `47.86`
+- `b1 < -0.06` -> `-0.0777`
+- `a1 > -0.013` -> `-0.0073`
+
+- User asked historical return stats for `buy_block_glued_high_weak_rebound`.
+- Based on `dragon_glued_high_weak_rebound_blocked_trades.csv` current snapshot:
+- total blocked rows `15`, completed added trades `14`, one pending/unclosed row (`2025-11-13`).
+- completed sample stats:
+- win rate `28.57%` (`4/14`)
+- avg return `-0.98%`
+- median return `-0.94%`
+- best/worst `+2.44% / -3.91%`
+- avg holding `6.93` days
+- compounded return if all allowed `-13.04%`
+- subtype stats:
+- `high_zone_weak_b1`: `10` trades, win rate `20%`, avg `-1.38%`
+- `mid_zone_very_weak_b1`: `2` trades, win rate `100%`, avg `+1.67%`
+- `ql_rebound_weak_followthrough`: `2` trades, win rate `0%`, avg `-1.62%`
+
+- User asked to further split by market regime and list each trade detail.
+- Added regime classification at buy date using close/MA20/MA60 from `dragon_indicator_snapshot_full.csv`:
+- uptrend: `close >= MA20 >= MA60`
+- downtrend: `close <= MA20 <= MA60`
+- range: otherwise
+- Generated files:
+- `dragon_glued_high_weak_rebound_regime_stats.csv`
+- `dragon_glued_high_weak_rebound_year_regime_stats.csv`
+- `dragon_glued_high_weak_rebound_trade_details_with_regime.csv`
+- Completed-trade regime stats (`14` samples):
+- downtrend `1` trade, win rate `0%`, avg `-2.20%`
+- uptrend `3` trades, win rate `0%`, avg `-0.94%`
+- range `10` trades, win rate `40%`, avg `-0.87%`
+
+- User asked to list trades with the same block reason as 2026-04-08 (`ql_rebound_weak_followthrough` under `buy_block_glued_high_weak_rebound`).
+- Matching historical blocked trades:
+- `2024-06-04 -> 2024-06-06`, return `-1.04%`, hold `2`d, sell reason `early_failed_rebound_exit`.
+- `2024-06-17 -> 2024-06-20`, return `-2.20%`, hold `3`d, sell reason `knife_take_profit_2_glued`.
+- Summary of this subtype (`2` completed samples): win rate `0%`, avg return `-1.62%`.
+- Reconfirmed on user follow-up: historical blocked trades matching 2026-04-08 same reason remain exactly two rows in `dragon_glued_high_weak_rebound_blocked_trades.csv` (2024-06-04 and 2024-06-17).
+- Clarified count confusion with user:
+- large count refers to top-level rule `buy_block_glued_high_weak_rebound` (historically 15 in release window; 17 full-history on 2026-04-08 analysis; 18 including 2026-04-08 realtime snapshot),
+- while 2026-04-08 exact matching subclause `ql_rebound_weak_followthrough` has only 2 historical rows (2024-06-04, 2024-06-17).
+- User asked for conceptual difference between the two signals.
+- Clarification:
+- `buy_block_glued_high_weak_rebound` is the parent veto rule (OR of three clauses).
+- `ql_rebound_weak_followthrough` is only one of the three clauses (requires `ql_buy=True`, `35<c1<55`, `b1<-0.06`, `a1>-0.013`), so naturally has much fewer historical hits.
+- User asked to list all child veto clauses under the parent rule.
+- Confirmed from strategy code: exactly three OR clauses:
+- `high_zone_weak_b1`
+- `mid_zone_very_weak_b1`
+- `ql_rebound_weak_followthrough`
+- User requested all interception details for `buy_block_glued_high_weak_rebound`.
+- Re-listed full historical blocked rows from `dragon_glued_high_weak_rebound_blocked_trades.csv`:
+- total `15`, completed `14`, pending `1` (`2025-11-13`).
+- subtype counts: `high_zone_weak_b1=11`, `mid_zone_very_weak_b1=2`, `ql_rebound_weak_followthrough=2`.
+
+- User requested packaging the full strategy for GPT-Pro consultation.
+- Created new consultation bundle:
+- folder: `dragon_v2_consult_2026-04-10_rc1/`
+- zip: `dragon_v2_consult_2026-04-10_rc1.zip`
+- Bundle includes:
+- updated core source (layered engine, runtime decoupling, governance, daily/forward pipelines)
+- parameter snapshots and manifests
+- key reports (ablation/sensitivity/stability/rollout/weak-rebound block review)
+- recent daily reports (`2026-04-08`, `2026-04-09`)
+- blocked-trade detail datasets including regime breakdown
+- OpenSpec change packs for recent restructuring/governance work
+- Added bundle docs:
+- `README_CN.md`
+- `consult_context_CN.md`
+- `chatgpt_pro_prompt_CN.md`
+
+- User asked to study detailed answers under `gpt-pro/`.
+- Reviewed package:
+- `gpt-pro/README_CN.md`
+- `gpt-pro/CODEX_STRICT_EXECUTION_PROMPT_CN.md`
+- `gpt-pro/PATCH_PLAN_FILE_BY_FILE_CN.md`
+- `gpt-pro/dragon_glued_followthrough_codex_pack_2026-04-10/IMPLEMENTATION_SPEC_CN.md`
+- `.../PHASED_DELIVERY_AND_ACCEPTANCE_CN.md`
+- `.../RESEARCH_NOTES_AND_EXPECTED_BEHAVIOR_CN.md`
+- `.../preview_results/preview_validation.json`
+- Core proposal recognized:
+- Keep `buy_block_glued_high_weak_rebound` hard block in default RC1.
+- Add shadow branch `alpha_first_glued_followthrough_probe` with pending/confirmation scaffold.
+- Only allow subtype `mid_zone_very_weak_b1` into pending in phase-1; keep `ql_rebound_weak_followthrough` hard-blocked.
+- Important compatibility note:
+- GPT-Pro preview baseline hashes/counts (`events=296`, `trades=98`, hashes `85ca.../7af4...`) do not match this repo's current RC1 golden (`events=272`, `trades=91`, hashes `8965.../1298...`).
+- Their patch paths assume `source/` layout, while current repo uses root-level python files.
+- Conclusion: strategy direction is reasonable as shadow-research scaffold, but must be adapted to current repo baseline/tests before any landing.
+
+- User approved: execute full GPT-Pro recommendation in OpenSpec mode.
+- Implemented new OpenSpec change:
+- `openspec/changes/glued-followthrough-shadow-reentry/`
+- includes `.openspec.yaml`, `proposal.md`, `design.md`, `tasks.md`, two spec files, and `acceptance-summary.md`.
+- Code implementation completed (current repo layout adaptation):
+- new module `dragon_glued_followthrough_confirmation.py`
+- added glued followthrough config flags in `dragon_strategy_config.py` (default conservative/off)
+- added branch config `alpha_first_glued_followthrough_probe_config()` in `dragon_branch_configs.py`
+- strategy wiring in `dragon_strategy.py`:
+- added glued pending state fields
+- queue/clear helpers
+- pending lifecycle update + invalidation
+- pending confirmation decision path
+- block-path queue hook under subtype-gated branch config
+- clear pending on real buy/sell
+- added rule catalog mapping in `dragon_rule_catalog.py` for `glued_followthrough_reentry_buy:*`
+- added shadow branch to daily pipeline in `dragon_daily_signal_pipeline.py`
+- Added test:
+- `tests/test_glued_followthrough_shadow_reentry.py`
+- Validation results:
+- `py -3 -m py_compile ...` pass
+- `py -3 -m unittest discover -s tests -v` pass (`22` tests)
+- `py -3 dragon_daily_signal_pipeline.py` pass
+- `py -3 dragon_forward_observation_pipeline.py` pass
+- Compatibility outcomes:
+- release-window RC1 hashes unchanged (`events 8965d1...a331`, `trades 1298be...cb97`)
+- probe branch release-window adds exactly `1` extra trade:
+- `2020-12-01 -> 2020-12-09`
+- `glued_followthrough_reentry_buy:confirmed_mid_zone_very_weak_b1`
+- return `-0.0024350630226196435`
+
+- User asked what GPT-Pro suggestions remain not implemented.
+- Cross-check result:
+- Phase 1 mandatory scope is implemented.
+- Remaining not-done items are GPT-Pro Phase 2 research suggestions:
+- add block-after-followthrough observation metrics
+- run independent shadow toggle experiment for `high_zone_weak_b1`
+- run execution timing study (`same-close` vs `next-open`) for followthrough reentry path
+- evaluate entry-specific exit treatment / exemption for followthrough reentry
+
+- User clarified the core target explicitly:
+- the strategy is meant to make money by following trends.
+- the biggest real problem is how to rejoin the trend quickly after a false veto.
+- future optimization ordering should focus on:
+- `false-veto detection`
+- `timely followthrough reentry`
+- `trend profit capture`
+- User further clarified execution style preference:
+- do not expand this into a large research project.
+- keep future work lean, regime-focused, and practical for a small team / ~1M capital setup.
+
+- User pointed out that GPT-Pro had already stated this direction explicitly.
+- Reconfirmed from GPT-Pro docs:
+- `IMPLEMENTATION_SPEC_CN.md` states block subtype should enter pending and attempt delayed reentry within `1~3` bars after stronger confirmation.
+- `RESEARCH_NOTES_AND_EXPECTED_BEHAVIOR_CN.md` prioritizes `execution timing` first and `entry-specific exit treatment` second.
+- After re-reading GPT-Pro docs, one implementation detail was found missing and then fixed:
+- added full-snapshot local hash pinning to `tests/test_glued_followthrough_shadow_reentry.py`
+- full baseline pinned: events `296`, trades `98`, hashes `5636adc7...` / `1d419aaf...`
+- User then made the priority explicit again:
+- set false-veto recovery as the first priority and continue developing autonomously without mid-task interruptions.
+
+- Implemented a lean Phase 2 OpenSpec change:
+- `openspec/changes/followthrough-lean-profit-loop/`
+- Added:
+- `dragon_followthrough_profit_loop_review.py`
+- `tests/test_followthrough_profit_loop_review.py`
+- `openspec/changes/followthrough-lean-profit-loop/acceptance-summary.md`
+- Ran:
+- `py -3 -m py_compile dragon_followthrough_profit_loop_review.py tests/test_followthrough_profit_loop_review.py`
+- `py -3 -m unittest tests.test_followthrough_profit_loop_review -v`
+- `py -3 dragon_followthrough_profit_loop_review.py`
+- `py -3 -m unittest discover -s tests -v`
+- Final validation: full suite `25` tests passed.
+
+- Lean Phase 2 findings:
+- `mid_zone_very_weak_b1`:
+- blocked_full `2`
+- shadow_confirmed `1`
+- confirm_like_3bar `50%`
+- avg_next20_max `+7.04%`
+- probe trade `2020-11-30 -> 2020-12-01 -> 2020-12-09`
+- same_close `-0.24%`
+- next_open `-0.50%`
+- post_exit_max_10b `+8.35%`
+- interpretation: the only subtype still worth a next-step experiment, and the bottleneck now looks more like execution/exit handling than whether any followthrough exists at all
+
+- `high_zone_weak_b1`:
+- blocked_full `13`
+- shadow_confirmed `2`
+- same_close avg `-3.52%`
+- next_open avg `-5.03%`
+- interpretation: do not widen this family; followthrough confirmation here still loses
+
+- `ql_rebound_weak_followthrough`:
+- blocked_full `3` (includes current full-history latest case)
+- shadow_confirmed `0`
+- completed historical windows: confirm_like_3bar `0%`, next3_sell_cross_rate `100%`
+- interpretation: hard block remains justified
+
+- Latest live-relevant blocked case review:
+- `2026-04-08` subtype is still `ql_rebound_weak_followthrough`
+- by `2026-04-10` only `2/3` followthrough bars have elapsed
+- ql_reconfirm_count `0`
+- sell_cross_count `0`
+- max_close_return_so_far `+3.56%`
+- still no confirm-like delayed reentry signal under current mid-zone/q l confirmation logic

+ 72 - 0
research/dragon/v2/memory/2026-04-11.md

@@ -0,0 +1,72 @@
+## 2026-04-11
+
+- User asked to continue autonomously after the lean Phase 2 result.
+- Follow-up work stayed narrow and only targeted the remaining promising lane:
+- `mid_zone_very_weak_b1`
+- goal: test whether the current problem is early exit treatment after followthrough reentry
+
+- Implemented new OpenSpec change:
+- `openspec/changes/followthrough-mid-exit-shadow-probe/`
+- Added:
+- `dragon_glued_followthrough_exit.py`
+- `dragon_followthrough_mid_exit_review.py`
+- `tests/test_followthrough_mid_exit_probe.py`
+- `openspec/changes/followthrough-mid-exit-shadow-probe/acceptance-summary.md`
+
+- Strategy change is shadow-only:
+- new config gates in `dragon_strategy_config.py` are default-off
+- new branch config `alpha_first_glued_followthrough_mid_exit_probe_config()` lives in `dragon_branch_configs.py`
+- `dragon_strategy.py` now has a branch-gated hold for:
+- entry code `entry_glued_followthrough_reentry`
+- `kdj_sell=True`
+- `ql_sell=False`
+- early holding window
+- mild positive repair state
+- this prevents immediate `knife_take_profit_2_glued` only in the new shadow branch
+
+- Added the new branch to `dragon_daily_signal_pipeline.py` so it is visible in daily and forward observation outputs.
+
+- Historical result from the new branch:
+- original mid probe:
+- `2020-12-01 -> 2020-12-09`
+- sell reason `knife_take_profit_2_glued`
+- return `-0.24%`
+- new mid exit probe:
+- `2020-12-01 -> 2021-02-19`
+- sell reason `high_regime_confirmed_exit:kdj_sell`
+- holding `80` days
+- return `+26.65%`
+
+- Trade-path difference versus the old mid probe:
+- removed:
+- `2020-12-01 -> 2020-12-09` followthrough reentry loss
+- `2020-12-15 -> 2021-02-19` later glued re-buy
+- added:
+- one merged trend trade `2020-12-01 -> 2021-02-19`
+- interpretation:
+- the repaired trend was real
+- the old branch was cutting it too early and then buying back later
+
+- Review artifact conclusions from `dragon_followthrough_mid_exit_review.md`:
+- same_close compounded return:
+- base `1424.12%`
+- mid_probe `1420.41%`
+- mid_exit_probe `1460.02%`
+- next_open compounded return:
+- base `1295.54%`
+- mid_probe `1288.52%`
+- mid_exit_probe `1332.91%`
+- this makes `alpha_first_glued_followthrough_mid_exit_probe` the only currently promising shadow extension worth monitoring
+- but promotion is still not justified because the evidence is still one repaired-trend path
+
+- Validation:
+- `py -3 -m py_compile dragon_glued_followthrough_exit.py dragon_followthrough_mid_exit_review.py tests/test_followthrough_mid_exit_probe.py dragon_daily_signal_pipeline.py`
+- `py -3 -m unittest tests.test_followthrough_mid_exit_probe -v`
+- `py -3 dragon_followthrough_mid_exit_review.py`
+- `py -3 -m unittest discover -s tests -v` -> `26` tests passed
+- `py -3 dragon_daily_signal_pipeline.py` passed
+- `py -3 dragon_forward_observation_pipeline.py` passed
+
+- Also fixed a weekend-sensitive test failure unrelated to the strategy logic:
+- updated `tests/test_data_fetcher_tday_cache.py` to use an effective business day when testing snapshot-merge writeback
+- this keeps the full suite green even when current date falls on a weekend

+ 12 - 0
research/dragon/v2/memory/2026-06-04.md

@@ -0,0 +1,12 @@
+## 2026-06-04
+
+- Ran `py -3 dragon_daily_signal_pipeline.py` for request date `2026-06-04`.
+- Source official daily data refreshed through `2026-06-03`; the pipeline appended an intraday snapshot for `2026-06-04`.
+- Latest evaluated bar is `2026-06-04`, data mode `intraday_snapshot`, snapshot timestamp `2026-06-04T15:12:04`.
+- Latest 399673 snapshot close is `4439.771`; indicators: `a1=0.0439`, `b1=0.0123`, `c1=87.74`.
+- Latest markers: `KDJ buy=False`, `KDJ sell=False`, `QL buy=False`, `QL sell=False`.
+- Default branch `alpha_first_glued_refined_hot_cap` remains in position from `2026-05-06` `BUY` reason `dual_gold_resonance_buy`; open return about `+10.33%`, holding `29` days.
+- No branch generated a new event on the latest bar; control, default, and followthrough shadow branches are aligned.
+- Ran `py -3 dragon_forward_observation_pipeline.py`; rollout governance decision is `FORWARD_OK`, active branch remains `alpha_first_glued_refined_hot_cap`, all `8` gates are `ok`.
+- User asked for recent two-month buy/sell points. Recomputed events through `2026-06-04` snapshot for all tracked branches.
+- Window `2026-04-04 -> 2026-06-04`: all tracked branches are aligned. Real events only include `2026-05-06 BUY dual_gold_resonance_buy`; no real SELL in the window. Aux events: `2026-04-23 SELL bearish_signal_after_exit:kdj_sell`, `2026-04-27 SELL bearish_signal_after_exit:ql_sell`, `2026-05-25 BUY bullish_signal_while_holding`.

+ 3 - 0
research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/.openspec.yaml

@@ -0,0 +1,3 @@
+schema: spec-driven
+created: 2026-04-10
+

+ 54 - 0
research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/acceptance-summary.md

@@ -0,0 +1,54 @@
+## Acceptance Summary
+
+### Delivered
+
+- Added research script:
+- `dragon_followthrough_profit_loop_review.py`
+- Added focused regression test:
+- `tests/test_followthrough_profit_loop_review.py`
+- Added research artifacts:
+- `dragon_followthrough_profit_loop_candidates.csv`
+- `dragon_followthrough_profit_loop_candidate_summary.csv`
+- `dragon_followthrough_profit_loop_reentries.csv`
+- `dragon_followthrough_profit_loop_reentry_summary.csv`
+- `dragon_followthrough_profit_loop_review.md`
+
+### Validation
+
+- `py -3 -m py_compile dragon_followthrough_profit_loop_review.py tests/test_followthrough_profit_loop_review.py`
+- `py -3 -m unittest tests.test_followthrough_profit_loop_review -v`
+- `py -3 dragon_followthrough_profit_loop_review.py`
+- `py -3 -m unittest discover -s tests -v`
+
+Observed final test count: `25` passing.
+
+### Key Findings
+
+- `mid_zone_very_weak_b1`
+- blocked_full `2`
+- shadow_confirmed `1`
+- confirm_like_3bar `50.00%`
+- avg_next20_max `7.04%`
+- probe_mid same_close `-0.24%`
+- probe_mid next_open `-0.50%`
+- probe_mid post_exit_max_10b `8.35%`
+
+- `high_zone_weak_b1`
+- blocked_full `13`
+- shadow_confirmed `2`
+- confirm_like_3bar `15.38%`
+- probe_high same_close `-3.52%`
+- probe_high next_open `-5.03%`
+
+- `ql_rebound_weak_followthrough`
+- blocked_full `3`
+- shadow_confirmed `0`
+- confirm_like_3bar `0.00%`
+- next3_sell_cross_rate `100.00%` on completed historical windows
+
+### Operational Reading
+
+- Default RC1 path was not modified.
+- `mid_zone_very_weak_b1` remains the only subtype worth further trend-reentry study.
+- `high_zone_weak_b1` does not justify promotion.
+- `ql_rebound_weak_followthrough` remains a hard-block family under current evidence.

+ 67 - 0
research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/design.md

@@ -0,0 +1,67 @@
+## Context
+
+The user clarified the real priority: make money by following trends, especially by recovering quickly when a hard veto blocks a move that later repairs into a valid trend.
+
+GPT-Pro also already narrowed the next worthwhile work:
+- execution timing
+- entry-specific exit treatment
+- optional high-zone shadow observation
+
+The practical constraint is equally clear:
+- keep it small-team sized
+- do not build a broad research platform
+- do not change RC1 production behavior before the evidence is stronger
+
+## Goals / Non-Goals
+
+**Goals**
+- Turn blocked glued weak rebounds into a reproducible observation dataset.
+- Compare delayed reentry paths by subtype in a narrow shadow scope.
+- Show whether the pain point is:
+- no real followthrough
+- execution timing slippage
+- exit family clipping the reentry too early
+- Keep outputs lightweight and easy to rerun.
+
+**Non-Goals**
+- Promote any followthrough branch to formal alpha in this phase.
+- Relax default hard blocks in RC1.
+- Introduce a new online monitoring subsystem.
+- Rework all sell families.
+
+## Decisions
+
+### 1. Research stays outside the RC1 path
+This phase adds a standalone script and report only. No change to default branch config, governance, or forward pipeline behavior.
+
+### 2. Candidate observation is rebuilt from actual strategy logic
+Instead of inferring from static thresholds alone, the script replays the strategy state machine and captures every `buy_block_glued_high_weak_rebound` decision directly.
+
+### 3. Reentry analysis stays subtype-specific
+The script evaluates three isolated probe lanes:
+- `mid_zone_very_weak_b1`
+- `high_zone_weak_b1`
+- `ql_rebound_weak_followthrough`
+
+This keeps evidence clean and avoids mixed-lane conclusions.
+
+### 4. Exit-treatment evidence is observational first
+Before touching sell logic, the script measures:
+- realized reentry return
+- next-open vs same-close execution delta
+- post-entry max-close opportunity
+- post-exit followthrough left on the table
+
+## Risks / Trade-offs
+
+- [Small sample sizes] accepted; the goal is to avoid broad, unjustified strategy edits.
+- [Fetched raw history may change with new market data] accepted; this is a living research report, not a golden baseline.
+- [Internal strategy hooks are reused] accepted; this keeps the review aligned with the real engine path.
+
+## Migration Plan
+
+1. Add the lean review script and helper functions.
+2. Add a focused regression test for known block/reentry mapping.
+3. Generate outputs and publish the review report.
+4. Record findings in OpenSpec acceptance and workspace memory.
+

+ 32 - 0
research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/proposal.md

@@ -0,0 +1,32 @@
+## Why
+
+Phase 1 already proved the shadow pending scaffold can be added without drifting RC1. That did not answer the only question that actually matters for this strategy now: after a false veto, which glued weak-rebound paths deserve a fast trend reentry, and is the bottleneck entry timing or the post-entry exit treatment?
+
+The next step should stay lean. We do not need a new platform. We need a repeatable review pack that turns this question into concrete subtype-level evidence.
+
+## What Changes
+
+- Add a dedicated review script for glued followthrough profit-loop analysis.
+- Reconstruct blocked-candidate observations directly from strategy logic.
+- Compare probe reentry trades across:
+- subtype lane
+- same-close vs next-open execution
+- post-entry upside and post-exit followthrough
+- Publish CSV + Markdown outputs.
+- Keep RC1 default strategy behavior unchanged.
+
+## Capabilities
+
+### New Capabilities
+- `followthrough-profit-loop-research`: reproducible blocked-candidate and reentry-trade review focused on false-veto recovery.
+
+### Unchanged Capabilities
+- `glued-followthrough-shadow-pending`: existing Phase 1 pending scaffold remains intact.
+- `shadow-branch-observation`: RC1 default governance path remains unchanged.
+
+## Impact
+
+- Affected code: new review script, new tests, new OpenSpec change pack.
+- Runtime impact: offline research/reporting only.
+- Strategy impact: none to RC1 default branch.
+

+ 40 - 0
research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/specs/followthrough-profit-loop-research/spec.md

@@ -0,0 +1,40 @@
+## ADDED Requirements
+
+### Requirement: Blocked glued weak rebounds SHALL be reviewable as a concrete dataset
+The workspace SHALL provide a repeatable way to replay the strategy and collect every `buy_block_glued_high_weak_rebound` candidate with subtype and forward observation metrics.
+
+#### Scenario: Blocked-candidate replay
+- **WHEN** the review script replays the official strategy on the current history
+- **THEN** each blocked glued weak rebound is recorded with:
+- signal date
+- subtype
+- signal indicators
+- regime snapshot
+- short-window followthrough metrics
+
+### Requirement: Followthrough probe lanes SHALL be analyzable in isolation
+The workspace SHALL provide subtype-isolated probe outputs for delayed glued followthrough reentries.
+
+#### Scenario: Probe-lane comparison
+- **WHEN** the review script runs subtype-isolated probe configs
+- **THEN** it publishes reentry-trade outputs for each lane independently
+- **AND** lanes do not need to be mixed to produce conclusions
+
+### Requirement: Timing and exit-treatment evidence SHALL be published before strategy changes
+The review SHALL compare timing sensitivity and exit-side aftermath for followthrough reentries.
+
+#### Scenario: Reentry trade review
+- **WHEN** a followthrough probe trade exists
+- **THEN** the review records:
+- same-close return
+- next-open return
+- post-entry max-close opportunity
+- post-exit followthrough left on the table
+
+### Requirement: RC1 default behavior SHALL remain unchanged
+This change SHALL NOT alter RC1 default strategy outputs.
+
+#### Scenario: Research-only delivery
+- **WHEN** the change is applied
+- **THEN** no default RC1 branch or governance path is modified
+- **AND** the change is limited to research/reporting artifacts

+ 22 - 0
research/dragon/v2/openspec/changes/followthrough-lean-profit-loop/tasks.md

@@ -0,0 +1,22 @@
+## 1. Research Script
+
+- [x] 1.1 Add a standalone followthrough profit-loop review script.
+- [x] 1.2 Capture blocked glued weak-rebound candidates directly from strategy replay.
+- [x] 1.3 Capture probe reentry origin mapping for subtype-isolated followthrough branches.
+- [x] 1.4 Add execution-timing and post-exit observation metrics to reentry trades.
+
+## 2. Outputs
+
+- [x] 2.1 Publish blocked-candidate detail CSV.
+- [x] 2.2 Publish blocked-candidate subtype summary CSV.
+- [x] 2.3 Publish reentry-trade detail CSV.
+- [x] 2.4 Publish reentry summary CSV.
+- [x] 2.5 Publish Markdown review with conclusions.
+
+## 3. Validation
+
+- [x] 3.1 Add a focused regression test for known mid-zone mapping.
+- [x] 3.2 Run the new review script.
+- [x] 3.3 Run the full unittest suite.
+- [x] 3.4 Publish acceptance summary.
+

+ 3 - 0
research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/.openspec.yaml

@@ -0,0 +1,3 @@
+schema: spec-driven
+created: 2026-04-11
+

+ 78 - 0
research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/acceptance-summary.md

@@ -0,0 +1,78 @@
+## Acceptance Summary
+
+### Delivered
+
+- Added entry-specific exit helper:
+- `dragon_glued_followthrough_exit.py`
+- Added default-off config gates in:
+- `dragon_strategy_config.py`
+- Added new shadow branch config:
+- `alpha_first_glued_followthrough_mid_exit_probe_config()`
+- Wired branch-gated hold into:
+- `dragon_strategy.py`
+- Exposed branch in:
+- `dragon_daily_signal_pipeline.py`
+- Added review/report artifacts:
+- `dragon_followthrough_mid_exit_review.py`
+- `dragon_followthrough_mid_exit_branch_summary.csv`
+- `dragon_followthrough_mid_exit_trade_details.csv`
+- `dragon_followthrough_mid_exit_trade_diff.csv`
+- `dragon_followthrough_mid_exit_review.md`
+- Added regression test:
+- `tests/test_followthrough_mid_exit_probe.py`
+
+### Validation
+
+- `py -3 -m py_compile dragon_glued_followthrough_exit.py dragon_followthrough_mid_exit_review.py tests/test_followthrough_mid_exit_probe.py dragon_daily_signal_pipeline.py`
+- `py -3 -m unittest tests.test_followthrough_mid_exit_probe -v`
+- `py -3 dragon_followthrough_mid_exit_review.py`
+- `py -3 -m unittest discover -s tests -v`
+- `py -3 dragon_daily_signal_pipeline.py`
+- `py -3 dragon_forward_observation_pipeline.py`
+
+Final validation status:
+- full suite `26` tests passed
+- daily pipeline smoke passed
+- forward observation pipeline smoke passed
+
+### Key Historical Fixture
+
+- branch: `alpha_first_glued_followthrough_mid_exit_probe`
+- trade count in release window: `91`
+- followthrough reentry trade:
+- buy date `2020-12-01`
+- buy reason `glued_followthrough_reentry_buy:confirmed_mid_zone_very_weak_b1`
+- sell date `2021-02-19`
+- sell reason `high_regime_confirmed_exit:kdj_sell`
+- holding days `80`
+- return `26.648684%`
+
+### Comparative Findings
+
+- original mid probe:
+- reentry path `2020-12-01 -> 2020-12-09`
+- same_close `-0.24%`
+- next_open `-0.50%`
+- sell `knife_take_profit_2_glued`
+
+- mid exit probe:
+- reentry path `2020-12-01 -> 2021-02-19`
+- same_close `+26.65%`
+- next_open `+26.51%`
+- sell `high_regime_confirmed_exit:kdj_sell`
+
+- branch-level metrics:
+- same_close compounded return:
+- base `1424.12%`
+- mid_probe `1420.41%`
+- mid_exit_probe `1460.02%`
+- next_open compounded return:
+- base `1295.54%`
+- mid_probe `1288.52%`
+- mid_exit_probe `1332.91%`
+
+### Operational Reading
+
+- RC1 default path remains unchanged.
+- The new exit treatment is promising enough to keep as a shadow observation branch.
+- Evidence is still only one repaired-trend path, so promotion is not justified yet.

+ 56 - 0
research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/design.md

@@ -0,0 +1,56 @@
+## Context
+
+The prior shadow scaffold proved that delayed mid-zone reentry can happen, but the observed sample was sold at:
+- `2020-12-09`
+- reason `knife_take_profit_2_glued`
+- return about `-0.24%`
+
+The same review also showed strong unrealized continuation after that exit. This suggests the engineering bottleneck may now be exit treatment, not signal discovery.
+
+## Goals / Non-Goals
+
+**Goals**
+- Keep RC1 default behavior unchanged.
+- Add one narrow exit probe only for:
+- entry code `entry_glued_followthrough_reentry`
+- `kdj_sell=True`
+- `ql_sell=False`
+- mild positive repair state
+- Make the result observable as a shadow branch.
+
+**Non-Goals**
+- Promote the branch to production alpha.
+- Relax `ql_rebound_weak_followthrough` or `high_zone_weak_b1`.
+- Rewrite the general glued sell family.
+- Add multi-factor scoring or large experiment grids.
+
+## Decisions
+
+### 1. Exit treatment is branch-gated and entry-specific
+The new hold only applies when the entry itself is a glued followthrough reentry. Regular glued trades are not changed.
+
+### 2. The hold only protects the first mild `kdj_only` wobble
+The probe requires:
+- `kdj_sell=True`
+- `ql_sell=False`
+- holding days still early
+- `a1` positive
+- `b1` only mildly weak
+- `c1` still in the mid-zone repair band
+
+### 3. Observation is more important than immediate promotion
+Even if the historical result looks strong, this stays a shadow branch because the sample count is still one repaired-trend path.
+
+## Risks / Trade-offs
+
+- [Single-path evidence] accepted; this is exactly why the branch remains shadow-only.
+- [Overfitting] mitigated by narrow scope and explicit shadow governance.
+- [Default drift] mitigated by default-off config plus existing RC1 hash tests.
+
+## Migration Plan
+
+1. Add config gates and exit helper.
+2. Add branch config and daily-pipeline registration.
+3. Add review script and fixture test.
+4. Run review + full validation and publish acceptance summary.
+

+ 27 - 0
research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/proposal.md

@@ -0,0 +1,27 @@
+## Why
+
+The lean profit-loop review showed one clear next question: the only subtype worth pursuing is `mid_zone_very_weak_b1`, and its current probe path likely fails because the reentry is clipped too early by the default glued take-profit logic.
+
+We do not need a broad sell-system rewrite. We need one narrow, shadow-only exit experiment that asks:
+- if a repaired mid-zone followthrough reentry is still structurally healthy,
+- should it be allowed to survive the first mild `kdj_sell` instead of being cut by `knife_take_profit_2_glued`?
+
+## What Changes
+
+- Add a narrow helper for followthrough-reentry-specific `kdj_only` exit hold.
+- Add config gates for this exit treatment, all default-off.
+- Add a new shadow branch:
+- `alpha_first_glued_followthrough_mid_exit_probe`
+- Add a focused review script comparing:
+- RC1 base
+- original mid followthrough probe
+- new mid exit probe
+- Add a regression test pinning the new branch fixture.
+- Expose the new branch in daily pipeline outputs only.
+
+## Impact
+
+- Affected code: config, branch config, strategy sell path, daily pipeline, new review script, new test, new OpenSpec pack.
+- Runtime impact: negligible.
+- Governance impact: none to RC1 default path.
+

+ 33 - 0
research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/specs/followthrough-mid-exit-shadow-probe/spec.md

@@ -0,0 +1,33 @@
+## ADDED Requirements
+
+### Requirement: Mid followthrough exit hold SHALL be branch-gated and entry-specific
+The strategy SHALL support a shadow-only exit hold for glued followthrough reentry trades without changing default RC1 behavior.
+
+#### Scenario: Mild `kdj_only` wobble after followthrough reentry
+- **WHEN** the current position was opened by `glued_followthrough_reentry_buy:*`
+- **AND** probe config enables mid exit hold
+- **AND** `kdj_sell=True`
+- **AND** `ql_sell=False`
+- **AND** holding state is still within the configured mild-repair window
+- **THEN** strategy returns `HOLD`
+- **AND** does not emit `knife_take_profit_2_glued` on that bar
+
+### Requirement: New exit-probe branch SHALL remain shadow-only
+The new branch SHALL be observable in reports but SHALL NOT replace the RC1 default branch.
+
+#### Scenario: Daily branch registration
+- **WHEN** daily pipeline builds shadow branch runs
+- **THEN** `alpha_first_glued_followthrough_mid_exit_probe` appears in outputs
+- **AND** RC1 default branch remains `alpha_first_glued_refined_hot_cap`
+
+### Requirement: Historical fixture SHALL be reproducible
+The new branch SHALL reproduce the known historical repaired-trend path.
+
+#### Scenario: Mid exit probe fixture
+- **WHEN** the strategy replays the current local snapshot with `alpha_first_glued_followthrough_mid_exit_probe`
+- **THEN** the branch contains a followthrough reentry trade:
+- buy date `2020-12-01`
+- sell date `2021-02-19`
+- sell reason `high_regime_confirmed_exit:kdj_sell`
+- holding days `80`
+

+ 18 - 0
research/dragon/v2/openspec/changes/followthrough-mid-exit-shadow-probe/tasks.md

@@ -0,0 +1,18 @@
+## 1. Exit Probe
+
+- [x] 1.1 Add a narrow followthrough-reentry exit helper.
+- [x] 1.2 Add default-off config gates for the exit probe.
+- [x] 1.3 Wire the hold into the sell path for the followthrough reentry entry code only.
+
+## 2. Branch and Observation
+
+- [x] 2.1 Add `alpha_first_glued_followthrough_mid_exit_probe_config()`.
+- [x] 2.2 Register the new branch in daily pipeline outputs.
+
+## 3. Reporting and Validation
+
+- [x] 3.1 Add a focused review script for the mid exit probe.
+- [x] 3.2 Add a regression test pinning the historical fixture.
+- [x] 3.3 Run the review script.
+- [x] 3.4 Run full validation and publish acceptance summary.
+

+ 3 - 0
research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/.openspec.yaml

@@ -0,0 +1,3 @@
+schema: spec-driven
+created: 2026-04-10
+

+ 51 - 0
research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/acceptance-summary.md

@@ -0,0 +1,51 @@
+## Acceptance Summary
+
+Implementation completed with current-repo compatibility adaptation.
+
+## Validation Commands
+
+```powershell
+py -3 -m py_compile dragon_glued_followthrough_confirmation.py dragon_strategy_config.py dragon_branch_configs.py dragon_rule_catalog.py dragon_daily_signal_pipeline.py dragon_strategy.py tests/test_glued_followthrough_shadow_reentry.py
+py -3 -m unittest discover -s tests -v
+py -3 dragon_daily_signal_pipeline.py
+py -3 dragon_forward_observation_pipeline.py
+```
+
+## Validation Results
+
+- `py_compile`: pass.
+- `unittest discover`: pass (`22` tests).
+- Daily pipeline smoke: pass.
+- Forward observation pipeline smoke: pass.
+- Shadow reentry test now pins both full-snapshot local hashes and release-window RC1 hashes.
+
+## Baseline Compatibility
+
+- Release-window core hashes (RC1 default) remain unchanged:
+- events core sha256: `8965d1b539a998d7d0aff04432aa2a47cf30ee40df013b9d8b7eb66a3d50a331`
+- trades core sha256: `1298be56b0898266b0b854d62a979c00c20b01629393c82bb8c804faf852cb97`
+- counts: events `272`, trades `91`
+
+- Full local snapshot baseline (for shadow research reference):
+- events core sha256: `5636adc78212aad094ac57e45d44ecb230e59287a58569d94d2a82ddd148b600`
+- trades core sha256: `1d419aafd7cbb4a88091b4a1b217d9e3ea806607c73f34650fd3b686790093ff`
+- counts: events `296`, trades `98`
+
+## Probe Fixture
+
+- Branch: `alpha_first_glued_followthrough_probe`
+- Release-window trades: `92` vs base `91`
+- Extra trades vs base: `1`
+- Extra fixture:
+- `buy_date=2020-12-01`
+- `buy_reason=glued_followthrough_reentry_buy:confirmed_mid_zone_very_weak_b1`
+- `sell_date=2020-12-09`
+- `sell_reason=knife_take_profit_2_glued`
+- `holding_days=8`
+- `return_pct=-0.0024350630226196435`
+
+## Governance Boundary Check
+
+- Forward default branch remains `alpha_first_glued_refined_hot_cap`.
+- Control branch logic remains `alpha_first_selective_veto`.
+- `ql_rebound_weak_followthrough` remains hard-blocked in default config.

+ 69 - 0
research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/design.md

@@ -0,0 +1,69 @@
+## Context
+
+RC1 currently uses `buy_block_glued_high_weak_rebound` as a quality-preserving hard veto. Historical evidence does not support globally relaxing this block, especially for `ql_rebound_weak_followthrough`.
+
+The requirement is to add a **minimal, isolated, reversible** research path:
+- day-0 remains blocked
+- selected subtype can be queued as pending in a probe branch
+- delayed reentry is allowed only after explicit confirmation
+
+## Goals / Non-Goals
+
+**Goals**
+- Preserve RC1 default branch behavior exactly.
+- Keep `buy_block_glued_high_weak_rebound` hard block in default path.
+- Keep `ql_rebound_weak_followthrough` hard-blocked by default.
+- Add a branch-gated pending scaffold for followthrough research.
+- Make behavior locally testable.
+
+**Non-Goals**
+- Promote probe behavior to formal alpha.
+- Relax global veto in RC1.
+- Rework sell families in this phase.
+- Introduce a broad scoring framework.
+
+## Decisions
+
+### 1. Base path hard-veto remains unchanged
+Blocked signals still return `buy_block_glued_high_weak_rebound` on the current bar. Pending only records a candidate in probe branch.
+
+### 2. Phase-1 pending subtype scope is narrow
+Only `mid_zone_very_weak_b1` is enabled in the probe branch by default.  
+`high_zone_weak_b1` and `ql_rebound_weak_followthrough` remain disabled.
+
+### 3. Confirmation requires explicit repair
+Probe reentry requires all of:
+- at least one bar waited
+- no sell cross
+- `ql_buy=True`
+- `close > signal_close`
+- `b1 - signal_b1 >= threshold`
+
+### 4. Keep pending implementation isolated
+New glued pending logic is isolated in a dedicated module and dedicated strategy-context fields; no merge with deep-oversold module.
+
+## Risks / Trade-offs
+
+- [No immediate performance gain] acceptable; this phase targets structure and observability first.
+- [Default-path drift] mitigated by hash regression and golden regression tests.
+- [Probe path overreach] mitigated by strict branch-level config gating.
+
+## Migration Plan
+
+1. Add config flags + probe branch config.
+2. Add glued followthrough confirmation module.
+3. Wire pending lifecycle in strategy facade (queue/confirm/clear).
+4. Register new reason family mapping.
+5. Add shadow branch to daily pipeline list.
+6. Add tests and run full validation.
+
+## Rollback Plan
+
+If any RC1 default regression appears, revert:
+- probe branch config entry
+- glued pending fields and strategy hooks
+- glued followthrough module
+- daily pipeline shadow branch registration
+
+RC1 default behavior must remain unchanged after rollback.
+

+ 32 - 0
research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/proposal.md

@@ -0,0 +1,32 @@
+## Why
+
+`buy_block_glued_high_weak_rebound` currently acts as a hard veto with no structured "blocked now, re-confirm later" path. This is good for RC1 stability, but it also means we cannot observe delayed followthrough behavior in a controlled, testable way.
+
+Recent review suggests the correct direction is **not** to relax RC1 hard-veto defaults. The correct direction is to add a shadow-only research scaffold that can evaluate delayed reentry after explicit confirmation.
+
+## What Changes
+
+- Add a dedicated glued followthrough confirmation module.
+- Add branch-gated pending lifecycle for blocked glued weak-rebound signals.
+- Add a shadow research branch: `alpha_first_glued_followthrough_probe`.
+- Keep RC1 default branch behavior unchanged.
+- Add regression tests:
+- default local baseline hashes unchanged
+- probe branch fixture behavior pinned
+- Wire shadow branch into daily signal pipeline output only.
+
+## Capabilities
+
+### New Capabilities
+- `glued-followthrough-shadow-pending`: subtype-aware pending and confirmation evaluation for blocked glued weak rebounds.
+- `shadow-branch-observation`: expose probe branch in daily pipeline without changing forward default governance path.
+
+### Modified Capabilities
+- `dragon-strategy-compatibility-facade`: supports optional glued pending lifecycle in branch-gated mode.
+
+## Impact
+
+- Affected code: `dragon_strategy.py`, `dragon_strategy_config.py`, `dragon_branch_configs.py`, `dragon_rule_catalog.py`, `dragon_daily_signal_pipeline.py`, new glued followthrough module, new tests.
+- Runtime impact: negligible.
+- Governance impact: none to RC1 default branch; this is shadow research only.
+

+ 50 - 0
research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/specs/glued-followthrough-shadow-pending/spec.md

@@ -0,0 +1,50 @@
+## ADDED Requirements
+
+### Requirement: Glued weak-rebound block subtype SHALL be explicitly classifiable
+The strategy facade SHALL classify `buy_block_glued_high_weak_rebound` into explicit subtype labels using the existing threshold set.
+
+#### Scenario: Weak-rebound subtype evaluation
+- **WHEN** strategy evaluates a glued buy candidate
+- **THEN** weak-rebound subtype is resolved as one of:
+- `high_zone_weak_b1`
+- `mid_zone_very_weak_b1`
+- `ql_rebound_weak_followthrough`
+- **AND** empty subtype indicates no weak-rebound block hit
+
+### Requirement: Probe-gated pending lifecycle SHALL be supported
+The strategy SHALL support a probe-gated pending lifecycle for blocked glued weak-rebound candidates, without changing day-0 block behavior.
+
+#### Scenario: Candidate bar is blocked but queueable in probe branch
+- **WHEN** weak-rebound subtype is hit on a glued entry bar
+- **AND** probe config enables pending for that subtype
+- **AND** no active glued pending exists
+- **THEN** pending state is queued
+- **AND** current bar still returns `buy_block_glued_high_weak_rebound`
+
+### Requirement: Pending confirmation SHALL require explicit followthrough repair
+Pending glued followthrough confirmation SHALL require explicit followthrough conditions before delayed buy can fire.
+
+#### Scenario: Pending candidate gets confirmed
+- **WHEN** pending is active
+- **AND** bars waited >= 1 and <= confirmation window
+- **AND** no `kdj_sell` and no `ql_sell`
+- **AND** `ql_buy=True`
+- **AND** `close > signal_close`
+- **AND** `b1 - signal_b1 >= config threshold`
+- **THEN** strategy emits `BUY`
+- **AND** reason prefix is `glued_followthrough_reentry_buy:confirmed_`
+- **AND** pending state is cleared
+
+### Requirement: Pending state SHALL be cleared on terminal conditions
+Pending glued followthrough state SHALL clear on terminal conditions.
+
+#### Scenario: Pending invalidation
+- **WHEN** pending is active
+- **AND** any of:
+- in position
+- `kdj_sell=True` or `ql_sell=True`
+- bars waited exceeds confirmation window
+- real buy triggered
+- real sell triggered
+- **THEN** pending state is cleared
+

+ 28 - 0
research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/specs/shadow-branch-observation/spec.md

@@ -0,0 +1,28 @@
+## ADDED Requirements
+
+### Requirement: RC1 default branch behavior SHALL remain unchanged
+Adding shadow followthrough research SHALL NOT change RC1 default branch behavior.
+
+#### Scenario: RC1 default branch regression check
+- **WHEN** RC1 default branch is replayed on current local baseline dataset
+- **THEN** baseline core event/trade hashes remain unchanged
+
+### Requirement: Probe branch SHALL be isolated from default governance path
+The followthrough probe branch SHALL be available for observation but SHALL NOT become default governance path.
+
+#### Scenario: Daily pipeline branch registration
+- **WHEN** daily pipeline builds branch runs
+- **THEN** probe branch appears in branch status outputs
+- **AND** default branch remains `alpha_first_glued_refined_hot_cap`
+- **AND** control branch logic remains `alpha_first_selective_veto`
+
+### Requirement: Probe branch SHALL use conservative subtype scope by default
+Probe branch default config SHALL only enable pending queue for `mid_zone_very_weak_b1`.
+
+#### Scenario: Probe config defaults
+- **WHEN** `alpha_first_glued_followthrough_probe_config()` is loaded
+- **THEN** `glued_followthrough_pending_enabled=True`
+- **AND** `glued_followthrough_allow_mid_zone_very_weak_b1=True`
+- **AND** `glued_followthrough_allow_high_zone_weak_b1=False`
+- **AND** `glued_followthrough_allow_ql_rebound_weak_followthrough=False`
+

+ 36 - 0
research/dragon/v2/openspec/changes/glued-followthrough-shadow-reentry/tasks.md

@@ -0,0 +1,36 @@
+## 1. Config and Branch
+
+- [x] 1.1 Add glued followthrough config flags to `dragon_strategy_config.py`.
+- [x] 1.2 Add `alpha_first_glued_followthrough_probe_config()` to `dragon_branch_configs.py`.
+
+## 2. Module Isolation
+
+- [x] 2.1 Add `dragon_glued_followthrough_confirmation.py`.
+- [x] 2.2 Implement subtype classification reusing existing weak-rebound thresholds.
+- [x] 2.3 Implement pending eligibility and confirmation evaluator.
+
+## 3. Strategy Wiring
+
+- [x] 3.1 Extend `StrategyContext` with glued pending fields.
+- [x] 3.2 Add queue / clear helpers in `dragon_strategy.py`.
+- [x] 3.3 Add pending lifecycle updates and clear conditions.
+- [x] 3.4 Queue pending on blocked weak rebound in probe-enabled branch only.
+- [x] 3.5 Resolve pending confirmation into `glued_followthrough_reentry_buy:*`.
+- [x] 3.6 Clear glued pending on real buy / real sell.
+
+## 4. Classification and Pipeline
+
+- [x] 4.1 Register `glued_followthrough_reentry_buy:*` in `dragon_rule_catalog.py`.
+- [x] 4.2 Add shadow branch to `dragon_daily_signal_pipeline.py` branch list.
+
+## 5. Tests and Validation
+
+- [x] 5.1 Add `tests/test_glued_followthrough_shadow_reentry.py`.
+- [x] 5.2 Run full unittest suite.
+- [x] 5.3 Run RC1 golden regression test.
+- [x] 5.4 Run daily signal pipeline smoke.
+
+## 6. Acceptance
+
+- [x] 6.1 Update this task list to completed.
+- [x] 6.2 Publish acceptance summary with observed hashes and probe fixture.

+ 41 - 0
research/dragon/v2/tests/test_followthrough_mid_exit_probe.py

@@ -0,0 +1,41 @@
+from __future__ import annotations
+
+import unittest
+from pathlib import Path
+
+from dragon_branch_configs import alpha_first_glued_followthrough_mid_exit_probe_config
+from dragon_rc1_golden_baseline import _load_indicator_snapshot
+from dragon_shared import END_DATE, START_DATE
+from dragon_strategy import DragonRuleEngine
+
+
+class TestFollowthroughMidExitProbe(unittest.TestCase):
+    def setUp(self) -> None:
+        self.base_dir = Path(__file__).resolve().parents[1]
+        self.indexed, _ = _load_indicator_snapshot(self.base_dir)
+
+    def test_mid_exit_probe_promotes_single_reentry_trade_to_late_high_regime_exit(self) -> None:
+        _, trades = DragonRuleEngine(config=alpha_first_glued_followthrough_mid_exit_probe_config()).run(self.indexed)
+        trades = trades[
+            (trades["buy_date"] >= START_DATE)
+            & (trades["buy_date"] <= END_DATE)
+            & (trades["sell_date"] >= START_DATE)
+            & (trades["sell_date"] <= END_DATE)
+        ].copy()
+
+        target = trades[
+            trades["buy_reason"].astype(str).str.startswith("glued_followthrough_reentry_buy:confirmed_mid_zone_very_weak_b1")
+        ].copy()
+        self.assertEqual(len(trades), 91)
+        self.assertEqual(len(target), 1)
+
+        row = target.iloc[0]
+        self.assertEqual(row["buy_date"], "2020-12-01")
+        self.assertEqual(row["sell_date"], "2021-02-19")
+        self.assertEqual(row["sell_reason"], "high_regime_confirmed_exit:kdj_sell")
+        self.assertEqual(int(row["holding_days"]), 80)
+        self.assertAlmostEqual(float(row["return_pct"]), 0.266486841392223, places=12)
+
+
+if __name__ == "__main__":
+    unittest.main()

+ 43 - 0
research/dragon/v2/tests/test_followthrough_profit_loop_review.py

@@ -0,0 +1,43 @@
+from __future__ import annotations
+
+import unittest
+from pathlib import Path
+
+from dragon_branch_configs import alpha_first_glued_followthrough_probe_config, alpha_first_glued_refined_hot_cap_config
+from dragon_followthrough_profit_loop_review import collect_followthrough_reentries, collect_glued_block_candidates
+from dragon_rc1_golden_baseline import _load_indicator_snapshot
+
+
+class TestFollowthroughProfitLoopReview(unittest.TestCase):
+    def setUp(self) -> None:
+        self.base_dir = Path(__file__).resolve().parents[1]
+        self.indexed, _ = _load_indicator_snapshot(self.base_dir)
+
+    def test_collects_known_mid_zone_block_candidate(self) -> None:
+        blocked = collect_glued_block_candidates(
+            self.indexed,
+            alpha_first_glued_refined_hot_cap_config(),
+            branch_name="base_rc1",
+        )
+        target = blocked[
+            (blocked["signal_date"] == "2020-11-30")
+            & (blocked["subtype"] == "mid_zone_very_weak_b1")
+        ].copy()
+        self.assertEqual(len(target), 1)
+
+    def test_maps_probe_mid_followthrough_origin(self) -> None:
+        reentries = collect_followthrough_reentries(
+            self.indexed,
+            alpha_first_glued_followthrough_probe_config(),
+            branch_name="probe_mid_zone",
+        )
+        self.assertEqual(len(reentries), 1)
+        row = reentries.iloc[0]
+        self.assertEqual(row["origin_date"], "2020-11-30")
+        self.assertEqual(row["buy_date"], "2020-12-01")
+        self.assertEqual(row["subtype"], "mid_zone_very_weak_b1")
+        self.assertEqual(int(row["bars_waited"]), 1)
+
+
+if __name__ == "__main__":
+    unittest.main()

+ 115 - 0
research/dragon/v2/tests/test_glued_followthrough_shadow_reentry.py

@@ -0,0 +1,115 @@
+from __future__ import annotations
+
+import json
+import unittest
+from pathlib import Path
+
+import pandas as pd
+
+from dragon_branch_configs import (
+    alpha_first_glued_followthrough_probe_config,
+    alpha_first_glued_refined_hot_cap_config,
+)
+from dragon_rc1_golden_baseline import (
+    EVENTS_CORE_COLUMNS,
+    MANIFEST_OUTPUT,
+    TRADES_CORE_COLUMNS,
+    _df_sha256,
+    _load_indicator_snapshot,
+)
+from dragon_shared import END_DATE, START_DATE
+from dragon_strategy import DragonRuleEngine
+
+
+class TestGluedFollowthroughShadowReentry(unittest.TestCase):
+    def setUp(self) -> None:
+        self.base_dir = Path(__file__).resolve().parents[1]
+        self.manifest_path = self.base_dir / MANIFEST_OUTPUT
+        self.manifest = json.loads(self.manifest_path.read_text(encoding="utf-8"))
+        indexed, _ = _load_indicator_snapshot(self.base_dir)
+        self.indexed = indexed
+
+    def _release_window_trades(self, trades: pd.DataFrame) -> pd.DataFrame:
+        return trades[
+            (trades["buy_date"] >= START_DATE)
+            & (trades["buy_date"] <= END_DATE)
+            & (trades["sell_date"] >= START_DATE)
+            & (trades["sell_date"] <= END_DATE)
+        ].copy()
+
+    def test_default_branch_full_snapshot_hashes_stay_stable(self) -> None:
+        events, trades = DragonRuleEngine(config=alpha_first_glued_refined_hot_cap_config()).run(self.indexed)
+
+        events.sort_values(["date", "side", "layer", "reason"], inplace=True)
+        trades.sort_values(["buy_date", "sell_date", "buy_reason", "sell_reason"], inplace=True)
+
+        self.assertEqual(len(events), 296)
+        self.assertEqual(len(trades), 98)
+        self.assertEqual(
+            _df_sha256(events[EVENTS_CORE_COLUMNS]),
+            "5636adc78212aad094ac57e45d44ecb230e59287a58569d94d2a82ddd148b600",
+        )
+        self.assertEqual(
+            _df_sha256(trades[TRADES_CORE_COLUMNS]),
+            "1d419aafd7cbb4a88091b4a1b217d9e3ea806607c73f34650fd3b686790093ff",
+        )
+
+    def test_default_branch_release_hashes_stay_stable(self) -> None:
+        events, trades = DragonRuleEngine(config=alpha_first_glued_refined_hot_cap_config()).run(self.indexed)
+        events = events[(events["date"] >= START_DATE) & (events["date"] <= END_DATE)].copy()
+        trades = self._release_window_trades(trades)
+
+        events.sort_values(["date", "side", "layer", "reason"], inplace=True)
+        trades.sort_values(["buy_date", "sell_date", "buy_reason", "sell_reason"], inplace=True)
+
+        self.assertEqual(
+            _df_sha256(events[EVENTS_CORE_COLUMNS]),
+            self.manifest["artifacts"]["events"]["core_sha256"],
+        )
+        self.assertEqual(
+            _df_sha256(trades[TRADES_CORE_COLUMNS]),
+            self.manifest["artifacts"]["trades"]["core_sha256"],
+        )
+
+    def test_probe_branch_adds_expected_single_trade_fixture(self) -> None:
+        _, base_trades = DragonRuleEngine(config=alpha_first_glued_refined_hot_cap_config()).run(self.indexed)
+        _, probe_trades = DragonRuleEngine(config=alpha_first_glued_followthrough_probe_config()).run(self.indexed)
+
+        base_trades = self._release_window_trades(base_trades)
+        probe_trades = self._release_window_trades(probe_trades)
+
+        base_keys = {
+            (row.buy_date, row.sell_date, row.buy_reason, row.sell_reason)
+            for row in base_trades.itertuples()
+        }
+        extra = probe_trades[
+            ~probe_trades.apply(
+                lambda row: (
+                    row["buy_date"],
+                    row["sell_date"],
+                    row["buy_reason"],
+                    row["sell_reason"],
+                )
+                in base_keys,
+                axis=1,
+            )
+        ].copy()
+
+        self.assertEqual(len(base_trades), 91)
+        self.assertEqual(len(probe_trades), 92)
+        self.assertEqual(len(extra), 1)
+
+        row = extra.iloc[0]
+        self.assertEqual(row["buy_date"], "2020-12-01")
+        self.assertEqual(
+            row["buy_reason"],
+            "glued_followthrough_reentry_buy:confirmed_mid_zone_very_weak_b1",
+        )
+        self.assertEqual(row["sell_date"], "2020-12-09")
+        self.assertEqual(row["sell_reason"], "knife_take_profit_2_glued")
+        self.assertEqual(int(row["holding_days"]), 8)
+        self.assertAlmostEqual(float(row["return_pct"]), -0.0024350630226196435, places=12)
+
+
+if __name__ == "__main__":
+    unittest.main()