feat(quant-engine): 10개 고전 기술전략 갭분석 후 7개 보조신호 채택

사용자 제시 10개 고전 기술전략(골든크로스/모멘텀/52주신고가/연속상승하락/이격도/돌파실패/
강한종가/변동성확장/평균회귀/추세필터)을 기존 엔진과 대조한 갭분석 결과:
- 이미 구현됨: 모멘텀(VELOCITY_V1/RS_MOMENTUM_V1), 이격도·평균회귀(MEAN_REVERSION_GATE_V1)
- 신규 채택 7개: GOLDEN_CROSS_SIGNAL_V1, STRONG_CLOSE_SIGNAL_V1,
  VOLATILITY_EXPANSION_BREAKOUT_V1, FIFTY_TWO_WEEK_HIGH_TRIGGER_V1, CONSECUTIVE_STREAK_V1,
  BREAKOUT_FAILURE_STOP_V1, TREND_FILTER_GATE_V1

AGENTS.md 하드룰(추격매수 방지, anti-late-entry gate 필수통과)에 따라 BUY 방향 신호 전부를
STRATEGY_SCORING의 보조신호로만 편입 — BREAKOUT_QUALITY_GATE_V2/FOLLOW_THROUGH_DAY_CONFIRM_V1/
ANTI_LATE_ENTRY_GATE_V2 게이트 체인을 우회하는 독립 BUY 트리거로는 사용하지 않음.

검증: validate_specs/validate_golden_coverage_100(100%)/validate_calibration_registry_v1/
validate_schema_model_generation_v1/validate_agents_shrink_v1 전부 PASS. golden test 22/22 PASS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 00:29:18 +09:00
parent aedabdd37b
commit 782fe74578
43 changed files with 1709 additions and 4 deletions
@@ -0,0 +1,69 @@
schema_version: technical_signals_p4_adoption_plan.v1
meta:
title: technical_signals_p4_adoption_plan
source: 사용자 제시 10개 고전 기술적 전략 목록 (2026-06-18)
decision_basis: >
10개 전략 중 기존 엔진 커버리지를 분석한 결과 3개는 이미 구현됨(모멘텀=VELOCITY_V1/RS_MOMENTUM_V1,
이격도/평균회귀=MEAN_REVERSION_GATE_V1), 4개는 부분구현, 3개는 완전 공백.
AGENTS.md 하드룰(추격매수 방지, anti-late-entry gate 필수통과)과 충돌하지 않도록
이 7개를 독립 매수 트리거가 아니라 STRATEGY_SCORING 보조신호 또는 기존 게이트 체인 내부
조건으로만 편입한다.
gap_analysis:
already_covered:
"02_모멘텀": VELOCITY_V1, RS_MOMENTUM_V1
"05_이격도": MEAN_REVERSION_GATE_V1
"09_평균회귀": MEAN_REVERSION_GATE_V1
partial_extend:
"03_52주신고가": FIFTY_TWO_WEEK_HIGH_TRIGGER_V1
"04_연속상승하락": CONSECUTIVE_STREAK_V1
"06_돌파실패손절": BREAKOUT_FAILURE_STOP_V1
"10_추세필터": TREND_FILTER_GATE_V1
new_signal:
"01_골든크로스": GOLDEN_CROSS_SIGNAL_V1
"07_강한종가": STRONG_CLOSE_SIGNAL_V1
"08_변동성확장돌파": VOLATILITY_EXPANSION_BREAKOUT_V1
hard_constraint: >
이 7개 공식 중 BUY 방향 신호는 모두 BREAKOUT_QUALITY_GATE_V2 / FOLLOW_THROUGH_DAY_CONFIRM_V1 /
ANTI_LATE_ENTRY_GATE_V2 체인을 우회할 수 없다. 단독 BUY 트리거로 사용 금지 — STRATEGY_SCORING의
component_scores 보조 입력 또는 게이트 조건으로만 사용한다.
tasks:
- id: P4-1
title: GOLDEN_CROSS_SIGNAL_V1
output_file: spec/formulas/domains/entry.yaml
implementation: tools/build_golden_cross_signal_v1.py
- id: P4-2
title: STRONG_CLOSE_SIGNAL_V1
output_file: spec/formulas/domains/entry.yaml
implementation: tools/build_strong_close_signal_v1.py
- id: P4-3
title: VOLATILITY_EXPANSION_BREAKOUT_V1
output_file: spec/formulas/domains/entry.yaml
detail: bb_width 신규 필드 추가. BREAKOUT_QUALITY_GATE_V2 통과 필수 조건 명시.
implementation: tools/build_volatility_expansion_breakout_v1.py
- id: P4-4
title: FIFTY_TWO_WEEK_HIGH_TRIGGER_V1
output_file: spec/formulas/domains/entry.yaml
implementation: tools/build_fifty_two_week_high_trigger_v1.py
- id: P4-5
title: CONSECUTIVE_STREAK_V1
output_file: spec/formulas/domains/entry.yaml
detail: up_streak/down_streak 대칭 공식화 (down_streak는 기존 필드, up_streak 신규).
implementation: tools/build_consecutive_streak_v1.py
- id: P4-6
title: BREAKOUT_FAILURE_STOP_V1
output_file: spec/formulas/domains/exit.yaml
implementation: tools/build_breakout_failure_stop_v1.py
- id: P4-7
title: TREND_FILTER_GATE_V1
output_file: spec/formulas/domains/entry.yaml
implementation: tools/build_trend_filter_gate_v1.py
- id: P4-8
title: decision_flow/manifest 배선 + 전체 검증
depends_on: [P4-1, P4-2, P4-3, P4-4, P4-5, P4-6, P4-7]
command: |
python tools/validate_specs.py
python tools/validate_golden_coverage_100.py
python tools/validate_calibration_registry_v1.py
python tools/validate_schema_model_generation_v1.py
python tools/validate_agents_shrink_v1.py
+28
View File
@@ -18,6 +18,13 @@ source_precedence:
- model_governance_kill_switch_v1 - model_governance_kill_switch_v1
- state_vector_constructor_v1 - state_vector_constructor_v1
- weekly_legacy_transfer_plan_v1 - weekly_legacy_transfer_plan_v1
- golden_cross_signal_v1
- strong_close_signal_v1
- volatility_expansion_breakout_v1
- fifty_two_week_high_trigger_v1
- consecutive_streak_v1
- breakout_failure_stop_v1
- trend_filter_gate_v1
- portfolio_transition_optimizer_v1 - portfolio_transition_optimizer_v1
- walk_forward_bootstrap_v1 - walk_forward_bootstrap_v1
- transition_set_enumerator_v1 - transition_set_enumerator_v1
@@ -90,3 +97,24 @@ manifest_rows:
- formula_id: weekly_legacy_transfer_plan_v1 - formula_id: weekly_legacy_transfer_plan_v1
active_artifact: Temp/weekly_legacy_transfer_plan_v1.json active_artifact: Temp/weekly_legacy_transfer_plan_v1.json
value: 0.0 value: 0.0
- formula_id: golden_cross_signal_v1
active_artifact: Temp/golden_cross_signal_v1.json
value: null
- formula_id: strong_close_signal_v1
active_artifact: Temp/strong_close_signal_v1.json
value: null
- formula_id: volatility_expansion_breakout_v1
active_artifact: Temp/volatility_expansion_breakout_v1.json
value: null
- formula_id: fifty_two_week_high_trigger_v1
active_artifact: Temp/fifty_two_week_high_trigger_v1.json
value: null
- formula_id: consecutive_streak_v1
active_artifact: Temp/consecutive_streak_v1.json
value: null
- formula_id: breakout_failure_stop_v1
active_artifact: Temp/breakout_failure_stop_v1.json
value: null
- formula_id: trend_filter_gate_v1
active_artifact: Temp/trend_filter_gate_v1.json
value: null
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/BREAKOUT_FAILURE_STOP_V1",
"title": "BREAKOUT_FAILURE_STOP_V1",
"type": "object",
"properties": {
"formula_id": { "const": "BREAKOUT_FAILURE_STOP_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["prior_high", "close_price", "days_since_breakout"],
"x_formula_outputs": ["breakout_failure"]
}
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CONSECUTIVE_STREAK_V1",
"title": "CONSECUTIVE_STREAK_V1",
"type": "object",
"properties": {
"formula_id": { "const": "CONSECUTIVE_STREAK_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["daily_close_changes"],
"x_formula_outputs": ["up_streak", "down_streak"]
}
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/FIFTY_TWO_WEEK_HIGH_TRIGGER_V1",
"title": "FIFTY_TWO_WEEK_HIGH_TRIGGER_V1",
"type": "object",
"properties": {
"formula_id": { "const": "FIFTY_TWO_WEEK_HIGH_TRIGGER_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["close_price", "high52w"],
"x_formula_outputs": ["fifty_two_week_high_breakout"]
}
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/GOLDEN_CROSS_SIGNAL_V1",
"title": "GOLDEN_CROSS_SIGNAL_V1",
"type": "object",
"properties": {
"formula_id": { "const": "GOLDEN_CROSS_SIGNAL_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["ma20", "ma20_prev", "ma60", "ma60_prev"],
"x_formula_outputs": ["golden_cross_today"]
}
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/STRONG_CLOSE_SIGNAL_V1",
"title": "STRONG_CLOSE_SIGNAL_V1",
"type": "object",
"properties": {
"formula_id": { "const": "STRONG_CLOSE_SIGNAL_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["close_price", "high_price", "low_price"],
"x_formula_outputs": ["strong_close", "close_position_pct"]
}
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/TREND_FILTER_GATE_V1",
"title": "TREND_FILTER_GATE_V1",
"type": "object",
"properties": {
"formula_id": { "const": "TREND_FILTER_GATE_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["close_price", "ma120", "ma120_prev"],
"x_formula_outputs": ["trend_filter_pass"]
}
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/VOLATILITY_EXPANSION_BREAKOUT_V1",
"title": "VOLATILITY_EXPANSION_BREAKOUT_V1",
"type": "object",
"properties": {
"formula_id": { "const": "VOLATILITY_EXPANSION_BREAKOUT_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["bb_width", "bb_width_20d_percentile", "ret_1d"],
"x_formula_outputs": ["volatility_expansion_breakout"]
}
+18 -4
View File
@@ -106,13 +106,24 @@ decision_flow:
pass_condition: "market regime classified or marked UNKNOWN" pass_condition: "market regime classified or marked UNKNOWN"
fail_state: "INSUFFICIENT_DATA" fail_state: "INSUFFICIENT_DATA"
STRATEGY_SCORING: STRATEGY_SCORING:
purpose: "섹터/종목/수급/유동성/실적/밸류 점수 산출" purpose: >
섹터/종목/수급/유동성/실적/밸류 점수 산출. GOLDEN_CROSS_SIGNAL_V1, STRONG_CLOSE_SIGNAL_V1,
VOLATILITY_EXPANSION_BREAKOUT_V1, FIFTY_TWO_WEEK_HIGH_TRIGGER_V1, CONSECUTIVE_STREAK_V1,
TREND_FILTER_GATE_V1은 component_scores의 보조신호로만 포함되며, 단독으로 BUY를
허가하지 않는다(BREAKOUT_QUALITY_GATE_V2/FOLLOW_THROUGH_DAY_CONFIRM_V1/ANTI_LATE_ENTRY_GATE_V2
체인 우회 금지).
required_refs: required_refs:
- "spec/08_scoring_rules.yaml:strategy_score" - "spec/08_scoring_rules.yaml:strategy_score"
- "spec/strategy/sector_model.yaml:sector_model" - "spec/strategy/sector_model.yaml:sector_model"
- "spec/13_formula_registry.yaml:formula_registry.formulas.FLOW_CREDIT_V1" - "spec/13_formula_registry.yaml:formula_registry.formulas.FLOW_CREDIT_V1"
- "spec/formulas/domains/entry.yaml:GOLDEN_CROSS_SIGNAL_V1"
- "spec/formulas/domains/entry.yaml:STRONG_CLOSE_SIGNAL_V1"
- "spec/formulas/domains/entry.yaml:VOLATILITY_EXPANSION_BREAKOUT_V1"
- "spec/formulas/domains/entry.yaml:FIFTY_TWO_WEEK_HIGH_TRIGGER_V1"
- "spec/formulas/domains/entry.yaml:CONSECUTIVE_STREAK_V1"
- "spec/formulas/domains/entry.yaml:TREND_FILTER_GATE_V1"
required_inputs: ["close_price", "ma20", "flow_ok", "flow_rows", "frg_5d_sh", "inst_5d_sh", "avg_trade_value_5d"] required_inputs: ["close_price", "ma20", "flow_ok", "flow_rows", "frg_5d_sh", "inst_5d_sh", "avg_trade_value_5d"]
computed_outputs: ["flow_credit", "strategy_score", "component_scores", "grade_candidate"] computed_outputs: ["flow_credit", "strategy_score", "component_scores", "grade_candidate", "golden_cross_today", "strong_close", "volatility_expansion_breakout", "fifty_two_week_high_breakout", "up_streak", "down_streak", "trend_filter_pass"]
pass_condition: "strategy_score calculated or missing fields listed" pass_condition: "strategy_score calculated or missing fields listed"
fail_state: "INSUFFICIENT_DATA" fail_state: "INSUFFICIENT_DATA"
PORTFOLIO_CONSTRAINT_CHECK: PORTFOLIO_CONSTRAINT_CHECK:
@@ -179,14 +190,17 @@ decision_flow:
pass_condition: "integer quantity calculated, or NO_QUANTITY reason emitted" pass_condition: "integer quantity calculated, or NO_QUANTITY reason emitted"
fail_state: "INSUFFICIENT_DATA" fail_state: "INSUFFICIENT_DATA"
EXIT_POLICY_CHECK: EXIT_POLICY_CHECK:
purpose: "손절/익절/trailing/보유주 점검 규칙 적용" purpose: >
손절/익절/trailing/보유주 점검 규칙 적용. 돌파 매수로 진입한 포지션에는
BREAKOUT_FAILURE_STOP_V1을 적용해 전고점 재이탈을 ANTI_WHIPSAW_GATE_V1과 별도로 포착한다.
required_refs: required_refs:
- "spec/exit/stop_loss.yaml:stop_loss" - "spec/exit/stop_loss.yaml:stop_loss"
- "spec/exit/take_profit.yaml:take_profit" - "spec/exit/take_profit.yaml:take_profit"
- "spec/exit/position_review.yaml:position_review_cycle" - "spec/exit/position_review.yaml:position_review_cycle"
- "spec/13_formula_registry.yaml:formula_registry.formulas.EXPECTED_EDGE_V1" - "spec/13_formula_registry.yaml:formula_registry.formulas.EXPECTED_EDGE_V1"
- "spec/formulas/domains/exit.yaml:BREAKOUT_FAILURE_STOP_V1"
required_inputs: ["entry_price", "stop_price", "target_price", "final_quantity"] required_inputs: ["entry_price", "stop_price", "target_price", "final_quantity"]
computed_outputs: ["expected_edge", "stop_order", "take_profit_order", "invalidation_conditions"] computed_outputs: ["expected_edge", "stop_order", "take_profit_order", "invalidation_conditions", "breakout_failure"]
pass_condition: "exit or hold policy evaluated" pass_condition: "exit or hold policy evaluated"
fail_state: "INSUFFICIENT_DATA" fail_state: "INSUFFICIENT_DATA"
proactive_exit_radar_check: # [2026-05-19_PROACTIVE_RADAR_V1] proactive_exit_radar_check: # [2026-05-19_PROACTIVE_RADAR_V1]
+110
View File
@@ -2672,6 +2672,116 @@ field_dictionary:
unit: "KRW" unit: "KRW"
aliases: ["TRANSFER_CONFIRMED_AMOUNT_KRW"] aliases: ["TRANSFER_CONFIRMED_AMOUNT_KRW"]
note: "WEEKLY_LEGACY_TRANSFER_PLAN_V1 입력 — transfer_confirmed=true일 때만 값 존재." note: "WEEKLY_LEGACY_TRANSFER_PLAN_V1 입력 — transfer_confirmed=true일 때만 값 존재."
# ── [2026-06-18_TECHNICAL_SIGNALS_P4] 10개 고전 기술전략 갭분석 채택 신규 필드 ──
ma20_prev:
canonical_name: "ma20_prev"
type: "number_or_null"
unit: "KRW_per_share"
aliases: ["MA20_PREV"]
note: "GOLDEN_CROSS_SIGNAL_V1 입력 — 전일 ma20."
ma60_prev:
canonical_name: "ma60_prev"
type: "number_or_null"
unit: "KRW_per_share"
aliases: ["MA60_PREV"]
note: "GOLDEN_CROSS_SIGNAL_V1 입력 — 전일 ma60."
ma120:
canonical_name: "ma120"
type: "number_or_null"
unit: "KRW_per_share"
aliases: ["MA120", "120일선"]
note: "TREND_FILTER_GATE_V1 입력 — 120일 이동평균."
ma120_prev:
canonical_name: "ma120_prev"
type: "number_or_null"
unit: "KRW_per_share"
aliases: ["MA120_PREV"]
note: "TREND_FILTER_GATE_V1 입력 — 전일 ma120."
high_price:
canonical_name: "high_price"
type: "number"
unit: "KRW_per_share"
aliases: ["High", "고가", "high"]
note: "STRONG_CLOSE_SIGNAL_V1 입력 — 당일 고가."
low_price:
canonical_name: "low_price"
type: "number"
unit: "KRW_per_share"
aliases: ["Low", "저가", "low"]
note: "STRONG_CLOSE_SIGNAL_V1 입력 — 당일 저가."
bb_width:
canonical_name: "bb_width"
type: "number_or_null"
unit: "percent"
aliases: ["BB_WIDTH"]
note: "VOLATILITY_EXPANSION_BREAKOUT_V1 입력 — 20일 볼린저밴드 폭."
bb_width_20d_percentile:
canonical_name: "bb_width_20d_percentile"
type: "number_or_null"
unit: "percent"
aliases: ["BB_WIDTH_20D_PERCENTILE"]
note: "VOLATILITY_EXPANSION_BREAKOUT_V1 입력 — 최근 20일 분포 내 bb_width 백분위. 낮을수록 squeeze."
daily_close_changes:
canonical_name: "daily_close_changes"
type: "list_or_null"
unit: "list_of_percent"
aliases: ["DAILY_CLOSE_CHANGES"]
note: "CONSECUTIVE_STREAK_V1 입력 — 최근 N거래일 일별 종가 변화율(%) 리스트, 최신값이 마지막."
prior_high:
canonical_name: "prior_high"
type: "number_or_null"
unit: "KRW_per_share"
aliases: ["PRIOR_HIGH"]
note: "BREAKOUT_FAILURE_STOP_V1 입력 — 진입 당시 돌파 기준 전고점."
golden_cross_today:
canonical_name: "golden_cross_today"
type: "boolean_or_null"
unit: "none"
aliases: ["GOLDEN_CROSS_TODAY"]
note: "GOLDEN_CROSS_SIGNAL_V1 산출 — STRATEGY_SCORING 보조신호. 단독 BUY 트리거 금지."
strong_close:
canonical_name: "strong_close"
type: "boolean_or_null"
unit: "none"
aliases: ["STRONG_CLOSE"]
note: "STRONG_CLOSE_SIGNAL_V1 산출."
close_position_pct:
canonical_name: "close_position_pct"
type: "number_or_null"
unit: "percent"
aliases: ["CLOSE_POSITION_PCT"]
note: "STRONG_CLOSE_SIGNAL_V1 산출 — (close-low)/(high-low)*100."
volatility_expansion_breakout:
canonical_name: "volatility_expansion_breakout"
type: "boolean_or_null"
unit: "none"
aliases: ["VOLATILITY_EXPANSION_BREAKOUT"]
note: "VOLATILITY_EXPANSION_BREAKOUT_V1 산출 — BREAKOUT_QUALITY_GATE_V2 통과 전제."
fifty_two_week_high_breakout:
canonical_name: "fifty_two_week_high_breakout"
type: "boolean_or_null"
unit: "none"
aliases: ["FIFTY_TWO_WEEK_HIGH_BREAKOUT"]
note: "FIFTY_TWO_WEEK_HIGH_TRIGGER_V1 산출 — BREAKOUT_QUALITY_GATE_V2 입력 전용."
up_streak:
canonical_name: "up_streak"
type: "integer_or_null"
unit: "count"
aliases: ["UP_STREAK"]
note: "CONSECUTIVE_STREAK_V1 산출 — 연속 상승 일수."
trend_filter_pass:
canonical_name: "trend_filter_pass"
type: "boolean_or_null"
unit: "none"
aliases: ["TREND_FILTER_PASS"]
note: "TREND_FILTER_GATE_V1 산출 — close>ma120 AND ma120 상승 중."
breakout_failure:
canonical_name: "breakout_failure"
type: "boolean_or_null"
unit: "none"
aliases: ["BREAKOUT_FAILURE"]
note: "BREAKOUT_FAILURE_STOP_V1 산출 — true이면 SELL_RISK_EXIT_REVIEW."
deployable_cash_contribution_krw: deployable_cash_contribution_krw:
canonical_name: "deployable_cash_contribution_krw" canonical_name: "deployable_cash_contribution_krw"
type: "number" type: "number"
+143
View File
@@ -112,6 +112,13 @@ formula_registry:
- REBALANCE_CADENCE_GATE_V1 - REBALANCE_CADENCE_GATE_V1
- WALK_FORWARD_BOOTSTRAP_V1 - WALK_FORWARD_BOOTSTRAP_V1
- WEEKLY_LEGACY_TRANSFER_PLAN_V1 - WEEKLY_LEGACY_TRANSFER_PLAN_V1
- GOLDEN_CROSS_SIGNAL_V1
- STRONG_CLOSE_SIGNAL_V1
- VOLATILITY_EXPANSION_BREAKOUT_V1
- FIFTY_TWO_WEEK_HIGH_TRIGGER_V1
- CONSECUTIVE_STREAK_V1
- BREAKOUT_FAILURE_STOP_V1
- TREND_FILTER_GATE_V1
implementation_map: implementation_map:
REGIME_CONDITIONAL_MACRO_FACTOR_V1: tools/build_predictive_alpha_dialectic_engine_v2.py:NF1 REGIME_CONDITIONAL_MACRO_FACTOR_V1: tools/build_predictive_alpha_dialectic_engine_v2.py:NF1
REBOUND_CAPTURE_THESIS_FACTOR_V1: tools/build_predictive_alpha_dialectic_engine_v2.py:NF2 REBOUND_CAPTURE_THESIS_FACTOR_V1: tools/build_predictive_alpha_dialectic_engine_v2.py:NF2
@@ -151,6 +158,13 @@ formula_registry:
REBALANCE_CADENCE_GATE_V1: tools/build_rebalance_cadence_gate_v1.py REBALANCE_CADENCE_GATE_V1: tools/build_rebalance_cadence_gate_v1.py
WALK_FORWARD_BOOTSTRAP_V1: tools/build_walk_forward_bootstrap_v1.py WALK_FORWARD_BOOTSTRAP_V1: tools/build_walk_forward_bootstrap_v1.py
WEEKLY_LEGACY_TRANSFER_PLAN_V1: tools/build_weekly_legacy_transfer_plan_v1.py WEEKLY_LEGACY_TRANSFER_PLAN_V1: tools/build_weekly_legacy_transfer_plan_v1.py
GOLDEN_CROSS_SIGNAL_V1: tools/build_golden_cross_signal_v1.py
STRONG_CLOSE_SIGNAL_V1: tools/build_strong_close_signal_v1.py
VOLATILITY_EXPANSION_BREAKOUT_V1: tools/build_volatility_expansion_breakout_v1.py
FIFTY_TWO_WEEK_HIGH_TRIGGER_V1: tools/build_fifty_two_week_high_trigger_v1.py
CONSECUTIVE_STREAK_V1: tools/build_consecutive_streak_v1.py
BREAKOUT_FAILURE_STOP_V1: tools/build_breakout_failure_stop_v1.py
TREND_FILTER_GATE_V1: tools/build_trend_filter_gate_v1.py
formulas: formulas:
FLOW_CREDIT_V1: FLOW_CREDIT_V1:
purpose: 가격·거래량·5D 수급 품질을 0~1 점수로 계산 purpose: 가격·거래량·5D 수급 품질을 0~1 점수로 계산
@@ -3162,6 +3176,135 @@ formula_registry:
canonical_ref: spec/risk/portfolio_exposure.yaml:cash_floor canonical_ref: spec/risk/portfolio_exposure.yaml:cash_floor
implementation: tools/build_weekly_legacy_transfer_plan_v1.py implementation: tools/build_weekly_legacy_transfer_plan_v1.py
version: 2026-06-17_P3_v8_9_adoption version: 2026-06-17_P3_v8_9_adoption
GOLDEN_CROSS_SIGNAL_V1:
purpose: >
단기 이동평균(ma20)이 장기 이동평균(ma60)을 상향 돌파하는 골든크로스를 정량 판정한다.
STRATEGY_SCORING 보조신호로만 사용 — 단독 BUY 트리거 금지.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-1)
inputs:
- field: ma20
unit: KRW_per_share
- field: ma20_prev
unit: KRW_per_share
- field: ma60
unit: KRW_per_share
- field: ma60_prev
unit: KRW_per_share
expression: "golden_cross_today = (ma20_prev <= ma60_prev) AND (ma20 > ma60)"
output:
field: golden_cross_today
unit: boolean
missing_policy: ma20_prev/ma60_prev 결측 시 null.
canonical_ref: spec/formulas/domains/entry.yaml:GOLDEN_CROSS_SIGNAL_V1
implementation: tools/build_golden_cross_signal_v1.py
version: 2026-06-18_technical_signals_p4
STRONG_CLOSE_SIGNAL_V1:
purpose: >
종가가 당일 고가-저가 범위 중 고가 근처에서 마감하는지 판정한다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-2)
inputs:
- field: close_price
unit: KRW_per_share
- field: high_price
unit: KRW_per_share
- field: low_price
unit: KRW_per_share
expression: "close_position_pct = (close_price-low_price)/(high_price-low_price)*100; strong_close = close_position_pct >= 80"
output:
field: strong_close
unit: boolean
missing_policy: high_price==low_price면 null.
canonical_ref: spec/formulas/domains/entry.yaml:STRONG_CLOSE_SIGNAL_V1
implementation: tools/build_strong_close_signal_v1.py
version: 2026-06-18_technical_signals_p4
VOLATILITY_EXPANSION_BREAKOUT_V1:
purpose: >
bb_width 수축(squeeze) 후 급등하는 패턴을 판정한다. BREAKOUT_QUALITY_GATE_V2 통과가 전제조건.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-3)
inputs:
- field: bb_width
unit: percent
- field: bb_width_20d_percentile
unit: percent
- field: ret_1d
unit: percent
expression: "squeeze_detected = bb_width_20d_percentile <= 20; volatility_expansion_breakout = squeeze_detected_previous_day AND ret_1d >= 3.0"
output:
field: volatility_expansion_breakout
unit: boolean
missing_policy: bb_width_20d_percentile 결측 시 null.
canonical_ref: spec/formulas/domains/entry.yaml:VOLATILITY_EXPANSION_BREAKOUT_V1
implementation: tools/build_volatility_expansion_breakout_v1.py
version: 2026-06-18_technical_signals_p4
FIFTY_TWO_WEEK_HIGH_TRIGGER_V1:
purpose: >
종가가 52주 최고가(high52w)를 갱신하는지 판정해 BREAKOUT_QUALITY_GATE_V2 입력으로 공급한다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-4)
inputs:
- field: close_price
unit: KRW_per_share
- field: high52w
unit: KRW_per_share
expression: "fifty_two_week_high_breakout = close_price >= high52w"
output:
field: fifty_two_week_high_breakout
unit: boolean
missing_policy: high52w 결측 시 null.
canonical_ref: spec/formulas/domains/entry.yaml:FIFTY_TWO_WEEK_HIGH_TRIGGER_V1
implementation: tools/build_fifty_two_week_high_trigger_v1.py
version: 2026-06-18_technical_signals_p4
CONSECUTIVE_STREAK_V1:
purpose: >
N일 연속 상승(up_streak)/하락(down_streak)을 대칭적으로 공식화한다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-5)
inputs:
- field: daily_close_changes
unit: list_of_percent
output:
field: up_streak
unit: count
missing_policy: daily_close_changes 비어있으면 null.
canonical_ref: spec/formulas/domains/entry.yaml:CONSECUTIVE_STREAK_V1
implementation: tools/build_consecutive_streak_v1.py
version: 2026-06-18_technical_signals_p4
BREAKOUT_FAILURE_STOP_V1:
purpose: >
전고점 돌파 후 7거래일 이내 재이탈하면 SELL_RISK_EXIT_REVIEW를 발동한다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-6)
inputs:
- field: prior_high
unit: KRW_per_share
- field: close_price
unit: KRW_per_share
- field: days_since_breakout
unit: trading_days
expression: "breakout_failure = (days_since_breakout <= 7) AND (close_price < prior_high)"
output:
field: breakout_failure
unit: boolean
missing_policy: prior_high 결측 시 null.
canonical_ref: spec/formulas/domains/exit.yaml:BREAKOUT_FAILURE_STOP_V1
implementation: tools/build_breakout_failure_stop_v1.py
version: 2026-06-18_technical_signals_p4
TREND_FILTER_GATE_V1:
purpose: >
종가가 ma120 위에 있고 ma120이 상승 중인지 단일 게이트로 판정한다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-7)
inputs:
- field: close_price
unit: KRW_per_share
- field: ma120
unit: KRW_per_share
- field: ma120_prev
unit: KRW_per_share
expression: "trend_filter_pass = (close_price > ma120) AND (ma120 > ma120_prev)"
output:
field: trend_filter_pass
unit: boolean
missing_policy: ma120/ma120_prev 결측 시 null.
canonical_ref: spec/strategy/entry_core.yaml:entry_timing_guardrails.regime_based_entry
implementation: tools/build_trend_filter_gate_v1.py
version: 2026-06-18_technical_signals_p4
SELL_EXECUTION_TIMING_V1: SELL_EXECUTION_TIMING_V1:
purpose: '장중 가격 움직임에 따라 매도 주문 유형과 타이밍을 결정론적으로 판정. 장초반 패닉 매도, 반등 직전 저점 투매 방지. purpose: '장중 가격 움직임에 따라 매도 주문 유형과 타이밍을 결정론적으로 판정. 장초반 패닉 매도, 반등 직전 저점 투매 방지.
+67
View File
@@ -254,6 +254,73 @@ golden_cases:
input: {weekly_legacy_to_cma_transfer_plan_krw: 4000000, transfer_confirmed: true, transfer_confirmed_amount_krw: 3800000} input: {weekly_legacy_to_cma_transfer_plan_krw: 4000000, transfer_confirmed: true, transfer_confirmed_amount_krw: 3800000}
expected: {deployable_cash_contribution_krw: 3800000.0, plan_status: CONFIRMED_DEPLOYABLE} expected: {deployable_cash_contribution_krw: 3800000.0, plan_status: CONFIRMED_DEPLOYABLE}
# ── 기술적 신호 7종 채택 (governance/todo/technical_signals_p4_adoption_plan.yaml) ──
- formula_id: GOLDEN_CROSS_SIGNAL_V1
id: GV4_GCS_001
name: ma20가 ma60을 상향 돌파하면 golden_cross_today=true
input: {ma20_prev: 98, ma60_prev: 99, ma20: 105, ma60: 100}
expected: {golden_cross_today: true}
- formula_id: GOLDEN_CROSS_SIGNAL_V1
id: GV4_GCS_002
name: 전일값 결측 시 null(DATA_MISSING), false로 추정 금지
input: {ma20_prev: null}
expected: {golden_cross_today: null}
- formula_id: STRONG_CLOSE_SIGNAL_V1
id: GV4_SCS_001
name: 종가가 고가 근처(90%)면 strong_close=true
input: {close: 99, high: 100, low: 90}
expected: {strong_close: true, close_position_pct: 90.0}
- formula_id: STRONG_CLOSE_SIGNAL_V1
id: GV4_SCS_002
name: high==low(거래정지 등) 시 null
input: {high: 100, low: 100}
expected: {strong_close: null}
- formula_id: VOLATILITY_EXPANSION_BREAKOUT_V1
id: GV4_VEB_001
name: squeeze(저백분위) 후 급등 시 volatility_expansion_breakout=true, BREAKOUT_QUALITY_GATE_V2 별도 통과 필요
input: {bb_width_20d_percentile_prev: 10, ret_1d: 4.5}
expected: {volatility_expansion_breakout: true, hard_constraint: requires_BREAKOUT_QUALITY_GATE_V2_pass_separately}
- formula_id: FIFTY_TWO_WEEK_HIGH_TRIGGER_V1
id: GV4_FTW_001
name: 종가가 52주 최고가 이상이면 breakout=true, 단독 매수 트리거 아님
input: {close: 105, high52w: 100}
expected: {fifty_two_week_high_breakout: true, hard_constraint: feeds_BREAKOUT_QUALITY_GATE_V2_only_not_buy_trigger}
- formula_id: CONSECUTIVE_STREAK_V1
id: GV4_CST_001
name: 최근 3일 연속 상승이면 up_streak=3, down_streak=0
input: {daily_close_changes: [1, 2, -1, 1, 2, 3]}
expected: {up_streak: 3, down_streak: 0}
- formula_id: BREAKOUT_FAILURE_STOP_V1
id: GV4_BFS_001
name: 돌파 후 7일 이내 전고점 아래로 재이탈하면 SELL_RISK_EXIT_REVIEW
input: {prior_high: 100, close: 95, days_since_breakout: 3}
expected: {breakout_failure: true, gate: SELL_RISK_EXIT_REVIEW}
- formula_id: BREAKOUT_FAILURE_STOP_V1
id: GV4_BFS_002
name: 7일 초과 후 재이탈은 breakout_failure 규칙 미적용(false)
input: {prior_high: 100, close: 95, days_since_breakout: 10}
expected: {breakout_failure: false}
- formula_id: TREND_FILTER_GATE_V1
id: GV4_TFG_001
name: 종가가 ma120 위 + ma120 상승 중이면 trend_filter_pass=true
input: {close: 105, ma120: 100, ma120_prev: 99}
expected: {trend_filter_pass: true}
- formula_id: TREND_FILTER_GATE_V1
id: GV4_TFG_002
name: 종가가 ma120 아래면 trend_filter_pass=false
input: {close: 95, ma120: 100, ma120_prev: 99}
expected: {trend_filter_pass: false}
# ── STOP_BREACH_V1: profit_pct < -20% 경계값 3 케이스 ───────────────────── # ── STOP_BREACH_V1: profit_pct < -20% 경계값 3 케이스 ─────────────────────
- formula_id: STOP_BREACH_V1 - formula_id: STOP_BREACH_V1
id: GV4_STOP_001 id: GV4_STOP_001
+220
View File
@@ -852,3 +852,223 @@ formulas:
activation_threshold: activation_threshold:
min_t20_sample: 30 min_t20_sample: 30
retirement_condition: performance_degradation retirement_condition: performance_degradation
GOLDEN_CROSS_SIGNAL_V1:
purpose: >
단기 이동평균(ma20)이 장기 이동평균(ma60)을 상향 돌파하는 골든크로스를 정량 판정한다.
독립 BUY 트리거가 아니라 STRATEGY_SCORING의 component_scores 보조신호로만 사용하며,
BREAKOUT_QUALITY_GATE_V2/ANTI_LATE_ENTRY_GATE_V2를 우회하지 않는다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-1, 사용자 제시 전략 01_골든크로스)
applicable: STRATEGY_SCORING 단계. 단독으로 BUY 의사결정 금지.
inputs:
- field: ma20
unit: KRW_per_share
- field: ma20_prev
unit: KRW_per_share
note: 전일 ma20 값.
- field: ma60
unit: KRW_per_share
- field: ma60_prev
unit: KRW_per_share
note: 전일 ma60 값.
expression: >
golden_cross_today = (ma20_prev <= ma60_prev) AND (ma20 > ma60)
output:
field: golden_cross_today
unit: boolean
hard_constraint: golden_cross_today=true는 STRATEGY_SCORING 보조점수 가산 입력일 뿐이며 BUY 게이트 체인을 대체하지 않는다.
missing_policy: ma20_prev 또는 ma60_prev 결측 시 golden_cross_today=null(DATA_MISSING). false로 추정하지 않는다.
canonical_ref: spec/13_formula_registry.yaml:formula_registry.formulas.FLOW_CREDIT_V1
implementation: tools/build_golden_cross_signal_v1.py
owner: quant_team
lifecycle_state: shadow
input_fields:
- ma20
- ma20_prev
- ma60
- ma60_prev
output_fields:
- golden_cross_today
golden_cases:
- golden_cross_basic_detection
activation_threshold:
min_t20_sample: 30
retirement_condition: performance_degradation
STRONG_CLOSE_SIGNAL_V1:
purpose: >
종가가 당일 가격 범위(고가-저가) 중 고가 근처에서 마감하는지(강한 종가)를 정량 판정한다.
모멘텀 지속 가능성의 보조신호로만 사용한다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-2, 사용자 제시 전략 07_강한종가)
applicable: STRATEGY_SCORING 단계.
inputs:
- field: close_price
unit: KRW_per_share
- field: high_price
unit: KRW_per_share
- field: low_price
unit: KRW_per_share
expression: >
close_position_pct = (close_price - low_price) / (high_price - low_price) * 100 (high==low면 null)
strong_close = close_position_pct >= 80
output:
field: strong_close
unit: boolean
additional_outputs:
- close_position_pct
missing_policy: high_price==low_price(거래정지 등)면 close_position_pct=null, strong_close=null.
canonical_ref: spec/13_formula_registry.yaml:formula_registry.formulas.FLOW_CREDIT_V1
implementation: tools/build_strong_close_signal_v1.py
owner: quant_team
lifecycle_state: shadow
input_fields:
- close_price
- high_price
- low_price
output_fields:
- strong_close
- close_position_pct
golden_cases:
- strong_close_near_high
activation_threshold:
min_t20_sample: 30
retirement_condition: performance_degradation
VOLATILITY_EXPANSION_BREAKOUT_V1:
purpose: >
변동성이 수축(bb_width 축소)된 뒤 급등(변동성 확장)하는 패턴을 판정한다. 신규 필드 bb_width
도입. 이 신호 자체는 BUY를 허가하지 않으며 BREAKOUT_QUALITY_GATE_V2 통과를 전제조건으로 한다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-3, 사용자 제시 전략 08_변동성확장돌파)
applicable: STRATEGY_SCORING 단계. BREAKOUT_QUALITY_GATE_V2 PASS 후에만 보조신호로 채택.
inputs:
- field: bb_width
unit: percent
note: (상단밴드-하단밴드)/중심선 * 100. 20일 볼린저밴드 기준.
- field: bb_width_20d_percentile
unit: percent
note: 최근 20일 분포 내 현재 bb_width의 백분위. 낮을수록 수축(squeeze) 상태.
- field: ret_1d
unit: percent
expression: >
squeeze_detected = bb_width_20d_percentile <= 20
volatility_expansion_breakout = squeeze_detected_previous_day AND ret_1d >= 3.0
output:
field: volatility_expansion_breakout
unit: boolean
hard_constraint: volatility_expansion_breakout=true이어도 BREAKOUT_QUALITY_GATE_V2 != BLOCKED_LATE_CHASE 조건을 별도로 통과해야 BUY 후보 자격이 생긴다.
missing_policy: bb_width 또는 bb_width_20d_percentile 결측 시 squeeze_detected=null. false로 추정하지 않는다.
canonical_ref: spec/13_formula_registry.yaml:formula_registry.formulas.BREAKOUT_QUALITY_GATE_V2
implementation: tools/build_volatility_expansion_breakout_v1.py
owner: quant_team
lifecycle_state: shadow
input_fields:
- bb_width
- bb_width_20d_percentile
- ret_1d
output_fields:
- volatility_expansion_breakout
golden_cases:
- squeeze_then_expansion
activation_threshold:
min_t20_sample: 30
retirement_condition: performance_degradation
FIFTY_TWO_WEEK_HIGH_TRIGGER_V1:
purpose: >
종가가 52주 최고가(High52W)를 갱신하는지 판정해 BREAKOUT_QUALITY_GATE_V2/FOLLOW_THROUGH_DAY_CONFIRM_V1
체인의 입력 신호로 공급한다(기존 필드 High52W를 트리거로 명시적으로 연결).
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-4, 사용자 제시 전략 03_52주신고가)
applicable: STRATEGY_SCORING 단계. 단독 BUY 트리거 금지 — BREAKOUT_QUALITY_GATE_V2 입력으로만 전달.
inputs:
- field: close_price
unit: KRW_per_share
- field: high52w
unit: KRW_per_share
source: spec/12_field_dictionary.yaml:high52w (alias High52W)
expression: >
fifty_two_week_high_breakout = close_price >= high52w
output:
field: fifty_two_week_high_breakout
unit: boolean
hard_constraint: fifty_two_week_high_breakout=true는 BREAKOUT_QUALITY_GATE_V2 입력으로만 전달되며 그 자체로 BUY를 허가하지 않는다.
missing_policy: high52w 결측 시 fifty_two_week_high_breakout=null.
canonical_ref: spec/13_formula_registry.yaml:formula_registry.formulas.BREAKOUT_QUALITY_GATE_V2
implementation: tools/build_fifty_two_week_high_trigger_v1.py
owner: quant_team
lifecycle_state: shadow
input_fields:
- close_price
- high52w
output_fields:
- fifty_two_week_high_breakout
golden_cases:
- high52w_breakout_detection
activation_threshold:
min_t20_sample: 30
retirement_condition: performance_degradation
CONSECUTIVE_STREAK_V1:
purpose: >
N일 연속 상승(up_streak)/연속 하락(down_streak)을 대칭적으로 공식화한다.
down_streak는 REBOUND_CAPTURE_THESIS_FACTOR_V1에 하위조건으로 이미 존재하나 up_streak
대응이 없어 비대칭이었다. 이 공식이 단일 출처가 된다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-5, 사용자 제시 전략 04_연속상승하락)
applicable: STRATEGY_SCORING 단계, REBOUND_CAPTURE_THESIS_FACTOR_V1 down_streak 입력의 canonical source.
inputs:
- field: daily_close_changes
unit: list_of_percent
note: 최근 N거래일의 일별 종가 변화율(%) 리스트. 최신값이 마지막.
expression: >
up_streak = 마지막 값부터 역순으로 연속 양수(>0)인 일수
down_streak = 마지막 값부터 역순으로 연속 음수(<0)인 일수
output:
field: up_streak
unit: count
additional_outputs:
- down_streak
missing_policy: daily_close_changes 비어있으면 up_streak=down_streak=null.
canonical_ref: spec/13_formula_registry.yaml:formula_registry.formulas.REBOUND_CAPTURE_THESIS_FACTOR_V1
implementation: tools/build_consecutive_streak_v1.py
owner: quant_team
lifecycle_state: shadow
input_fields:
- daily_close_changes
output_fields:
- up_streak
- down_streak
golden_cases:
- up_streak_and_down_streak_symmetry
activation_threshold:
min_t20_sample: 30
retirement_condition: performance_degradation
TREND_FILTER_GATE_V1:
purpose: >
종가가 장기 이동평균(ma120) 위에 있고 ma120 자체가 상승 중인지를 단일 게이트로 공식화한다.
entry_core.yaml:regime_based_entry, ANTI_LATE_ENTRY_GATE_V2의 암묵적 추세 조건을 명시적
단일 공식으로 통합해 LLM이 추세 판정을 임의로 서술하지 않게 한다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-7, 사용자 제시 전략 10_추세필터)
applicable: STRATEGY_SCORING 및 PORTFOLIO_CONSTRAINT_CHECK 입력. 단독으로 BUY 허가하지 않음 — HOLD/AVOID 보조 게이트.
inputs:
- field: close_price
unit: KRW_per_share
- field: ma120
unit: KRW_per_share
- field: ma120_prev
unit: KRW_per_share
note: 전일 ma120 값. 상승 여부 판정용.
expression: >
trend_filter_pass = (close_price > ma120) AND (ma120 > ma120_prev)
output:
field: trend_filter_pass
unit: boolean
missing_policy: ma120 또는 ma120_prev 결측 시 trend_filter_pass=null(DATA_MISSING).
canonical_ref: spec/strategy/entry_core.yaml:entry_timing_guardrails.regime_based_entry
implementation: tools/build_trend_filter_gate_v1.py
owner: quant_team
lifecycle_state: shadow
input_fields:
- close_price
- ma120
- ma120_prev
output_fields:
- trend_filter_pass
golden_cases:
- trend_filter_above_rising_ma120
activation_threshold:
min_t20_sample: 30
retirement_condition: performance_degradation
+40
View File
@@ -1544,3 +1544,43 @@ formulas:
activation_threshold: activation_threshold:
min_t20_sample: 30 min_t20_sample: 30
retirement_condition: performance_degradation retirement_condition: performance_degradation
BREAKOUT_FAILURE_STOP_V1:
purpose: >
전고점(prior_high)을 돌파한 종목이 며칠 내 다시 그 아래로 이탈하면("돌파 실패") 전용 손절을
발동한다. ANTI_WHIPSAW_GATE_V1보다 좁고 구체적인 "돌파 후 재이탈" 패턴 전용 규칙이다.
(governance/todo/technical_signals_p4_adoption_plan.yaml P4-6, 사용자 제시 전략 06_돌파실패손절)
applicable: EXIT_POLICY_CHECK 단계. 보유 포지션이 돌파 매수로 진입한 경우에만 적용.
inputs:
- field: prior_high
unit: KRW_per_share
note: 진입 당시 돌파 기준이 된 전고점.
- field: close_price
unit: KRW_per_share
- field: days_since_breakout
unit: trading_days
source: spec/13_formula_registry.yaml:formula_registry.formulas.FOLLOW_THROUGH_DAY_CONFIRM_V1
expression: >
breakout_failure = (days_since_breakout <= 7) AND (close_price < prior_high)
output:
field: breakout_failure
unit: boolean
gates:
- if: breakout_failure == true
action: SELL_RISK_EXIT_REVIEW
reason_code: breakout_failure_stop
missing_policy: prior_high 또는 days_since_breakout 결측 시 breakout_failure=null. 손절 신호를 임의 추정하지 않는다.
canonical_ref: spec/13_formula_registry.yaml:formula_registry.formulas.ANTI_WHIPSAW_GATE_V1
implementation: tools/build_breakout_failure_stop_v1.py
owner: quant_team
lifecycle_state: shadow
input_fields:
- prior_high
- close_price
- days_since_breakout
output_fields:
- breakout_failure
golden_cases:
- breakout_then_reentry_below_prior_high
activation_threshold:
min_t20_sample: 30
retirement_condition: performance_degradation
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'BREAKOUT_FAILURE_STOP_V1'
SCHEMA_ID = 'schema://formula/BREAKOUT_FAILURE_STOP_V1'
SCHEMA_PATH = 'schemas/generated/breakout_failure_stop_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/BREAKOUT_FAILURE_STOP_V1",
"title": "BREAKOUT_FAILURE_STOP_V1",
"type": "object",
"properties": {
"formula_id": { "const": "BREAKOUT_FAILURE_STOP_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["prior_high", "close_price", "days_since_breakout"],
"x_formula_outputs": ["breakout_failure"]
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'CONSECUTIVE_STREAK_V1'
SCHEMA_ID = 'schema://formula/CONSECUTIVE_STREAK_V1'
SCHEMA_PATH = 'schemas/generated/consecutive_streak_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/CONSECUTIVE_STREAK_V1",
"title": "CONSECUTIVE_STREAK_V1",
"type": "object",
"properties": {
"formula_id": { "const": "CONSECUTIVE_STREAK_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["daily_close_changes"],
"x_formula_outputs": ["up_streak", "down_streak"]
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'FIFTY_TWO_WEEK_HIGH_TRIGGER_V1'
SCHEMA_ID = 'schema://formula/FIFTY_TWO_WEEK_HIGH_TRIGGER_V1'
SCHEMA_PATH = 'schemas/generated/fifty_two_week_high_trigger_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/FIFTY_TWO_WEEK_HIGH_TRIGGER_V1",
"title": "FIFTY_TWO_WEEK_HIGH_TRIGGER_V1",
"type": "object",
"properties": {
"formula_id": { "const": "FIFTY_TWO_WEEK_HIGH_TRIGGER_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["close_price", "high52w"],
"x_formula_outputs": ["fifty_two_week_high_breakout"]
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'GOLDEN_CROSS_SIGNAL_V1'
SCHEMA_ID = 'schema://formula/GOLDEN_CROSS_SIGNAL_V1'
SCHEMA_PATH = 'schemas/generated/golden_cross_signal_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/GOLDEN_CROSS_SIGNAL_V1",
"title": "GOLDEN_CROSS_SIGNAL_V1",
"type": "object",
"properties": {
"formula_id": { "const": "GOLDEN_CROSS_SIGNAL_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["ma20", "ma20_prev", "ma60", "ma60_prev"],
"x_formula_outputs": ["golden_cross_today"]
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'STRONG_CLOSE_SIGNAL_V1'
SCHEMA_ID = 'schema://formula/STRONG_CLOSE_SIGNAL_V1'
SCHEMA_PATH = 'schemas/generated/strong_close_signal_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/STRONG_CLOSE_SIGNAL_V1",
"title": "STRONG_CLOSE_SIGNAL_V1",
"type": "object",
"properties": {
"formula_id": { "const": "STRONG_CLOSE_SIGNAL_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["close_price", "high_price", "low_price"],
"x_formula_outputs": ["strong_close", "close_position_pct"]
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'TREND_FILTER_GATE_V1'
SCHEMA_ID = 'schema://formula/TREND_FILTER_GATE_V1'
SCHEMA_PATH = 'schemas/generated/trend_filter_gate_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/TREND_FILTER_GATE_V1",
"title": "TREND_FILTER_GATE_V1",
"type": "object",
"properties": {
"formula_id": { "const": "TREND_FILTER_GATE_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["close_price", "ma120", "ma120_prev"],
"x_formula_outputs": ["trend_filter_pass"]
}
@@ -0,0 +1,33 @@
"""Auto-generated schema model descriptor."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
SCHEMA_TITLE = 'VOLATILITY_EXPANSION_BREAKOUT_V1'
SCHEMA_ID = 'schema://formula/VOLATILITY_EXPANSION_BREAKOUT_V1'
SCHEMA_PATH = 'schemas/generated/volatility_expansion_breakout_v1.schema.json'
SCHEMA_PROPERTIES = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
SCHEMA_REQUIRED = ['formula_id', 'owner', 'status', 'inputs', 'outputs']
@dataclass(frozen=True)
class SchemaModel:
title: str
schema_id: str
path: str
properties: list[str]
required: list[str]
def load_schema() -> dict[str, Any]:
return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))
def describe() -> SchemaModel:
return SchemaModel(
title=SCHEMA_TITLE,
schema_id=SCHEMA_ID,
path=SCHEMA_PATH,
properties=list(SCHEMA_PROPERTIES),
required=list(SCHEMA_REQUIRED),
)
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema://formula/VOLATILITY_EXPANSION_BREAKOUT_V1",
"title": "VOLATILITY_EXPANSION_BREAKOUT_V1",
"type": "object",
"properties": {
"formula_id": { "const": "VOLATILITY_EXPANSION_BREAKOUT_V1" },
"owner": { "type": "string" },
"status": { "type": "string" },
"inputs": { "type": "array", "items": { "type": "string" } },
"outputs": { "type": "array", "items": { "type": "string" } }
},
"required": ["formula_id", "owner", "status", "inputs", "outputs"],
"x_formula_inputs": ["bb_width", "bb_width_20d_percentile", "ret_1d"],
"x_formula_outputs": ["volatility_expansion_breakout"]
}
@@ -0,0 +1,35 @@
"""Golden tests for BREAKOUT_FAILURE_STOP_V1 (governance/todo/technical_signals_p4_adoption_plan.yaml P4-6)."""
from __future__ import annotations
import importlib.util
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = ROOT / "tools" / "build_breakout_failure_stop_v1.py"
def _load_module():
spec = importlib.util.spec_from_file_location("build_breakout_failure_stop_v1", MODULE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_reentry_below_prior_high_within_window_triggers_sell_review() -> None:
mod = _load_module()
result = mod.evaluate(100, 95, 3)
assert result["breakout_failure"] is True
assert result["gate"] == "SELL_RISK_EXIT_REVIEW"
def test_reentry_after_window_does_not_trigger() -> None:
mod = _load_module()
result = mod.evaluate(100, 95, 10)
assert result["breakout_failure"] is False
def test_missing_prior_high_returns_null() -> None:
mod = _load_module()
result = mod.evaluate(None, 95, 3)
assert result["breakout_failure"] is None
@@ -0,0 +1,37 @@
"""Golden tests for CONSECUTIVE_STREAK_V1 (governance/todo/technical_signals_p4_adoption_plan.yaml P4-5)."""
from __future__ import annotations
import importlib.util
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = ROOT / "tools" / "build_consecutive_streak_v1.py"
def _load_module():
spec = importlib.util.spec_from_file_location("build_consecutive_streak_v1", MODULE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_trailing_three_up_days_gives_up_streak_3() -> None:
mod = _load_module()
result = mod.compute_streaks([1, 2, -1, 1, 2, 3])
assert result["up_streak"] == 3
assert result["down_streak"] == 0
def test_trailing_two_down_days_gives_down_streak_2() -> None:
mod = _load_module()
result = mod.compute_streaks([5, -1, -2])
assert result["down_streak"] == 2
assert result["up_streak"] == 0
def test_empty_changes_returns_null_not_zero() -> None:
mod = _load_module()
result = mod.compute_streaks([])
assert result["up_streak"] is None
assert result["down_streak"] is None
@@ -0,0 +1,31 @@
"""Golden tests for FIFTY_TWO_WEEK_HIGH_TRIGGER_V1 (governance/todo/technical_signals_p4_adoption_plan.yaml P4-4)."""
from __future__ import annotations
import importlib.util
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = ROOT / "tools" / "build_fifty_two_week_high_trigger_v1.py"
def _load_module():
spec = importlib.util.spec_from_file_location("build_fifty_two_week_high_trigger_v1", MODULE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_close_at_or_above_52w_high_triggers() -> None:
mod = _load_module()
assert mod.evaluate(105, 100) is True
def test_close_below_52w_high_does_not_trigger() -> None:
mod = _load_module()
assert mod.evaluate(95, 100) is False
def test_missing_high52w_returns_null() -> None:
mod = _load_module()
assert mod.evaluate(105, None) is None
@@ -0,0 +1,31 @@
"""Golden tests for GOLDEN_CROSS_SIGNAL_V1 (governance/todo/technical_signals_p4_adoption_plan.yaml P4-1)."""
from __future__ import annotations
import importlib.util
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = ROOT / "tools" / "build_golden_cross_signal_v1.py"
def _load_module():
spec = importlib.util.spec_from_file_location("build_golden_cross_signal_v1", MODULE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_ma20_crossing_above_ma60_detected() -> None:
mod = _load_module()
assert mod.golden_cross_today(105, 98, 100, 99) is True
def test_no_cross_when_ma20_already_above() -> None:
mod = _load_module()
assert mod.golden_cross_today(105, 101, 100, 99) is False
def test_missing_prev_values_returns_null_not_false() -> None:
mod = _load_module()
assert mod.golden_cross_today(105, None, 100, 99) is None
@@ -0,0 +1,36 @@
"""Golden tests for STRONG_CLOSE_SIGNAL_V1 (governance/todo/technical_signals_p4_adoption_plan.yaml P4-2)."""
from __future__ import annotations
import importlib.util
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = ROOT / "tools" / "build_strong_close_signal_v1.py"
def _load_module():
spec = importlib.util.spec_from_file_location("build_strong_close_signal_v1", MODULE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_close_near_high_is_strong_close() -> None:
mod = _load_module()
result = mod.evaluate_strong_close(99, 100, 90)
assert result["strong_close"] is True
assert result["close_position_pct"] == 90.0
def test_close_near_low_is_not_strong_close() -> None:
mod = _load_module()
result = mod.evaluate_strong_close(91, 100, 90)
assert result["strong_close"] is False
def test_degenerate_high_equals_low_returns_null() -> None:
mod = _load_module()
result = mod.evaluate_strong_close(100, 100, 100)
assert result["strong_close"] is None
assert result["close_position_pct"] is None
@@ -0,0 +1,36 @@
"""Golden tests for TREND_FILTER_GATE_V1 (governance/todo/technical_signals_p4_adoption_plan.yaml P4-7)."""
from __future__ import annotations
import importlib.util
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = ROOT / "tools" / "build_trend_filter_gate_v1.py"
def _load_module():
spec = importlib.util.spec_from_file_location("build_trend_filter_gate_v1", MODULE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_above_rising_ma120_passes() -> None:
mod = _load_module()
assert mod.evaluate(105, 100, 99) is True
def test_below_ma120_fails() -> None:
mod = _load_module()
assert mod.evaluate(95, 100, 99) is False
def test_above_but_falling_ma120_fails() -> None:
mod = _load_module()
assert mod.evaluate(105, 100, 101) is False
def test_missing_ma120_returns_null() -> None:
mod = _load_module()
assert mod.evaluate(105, None, 99) is None
@@ -0,0 +1,35 @@
"""Golden tests for VOLATILITY_EXPANSION_BREAKOUT_V1 (governance/todo/technical_signals_p4_adoption_plan.yaml P4-3)."""
from __future__ import annotations
import importlib.util
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = ROOT / "tools" / "build_volatility_expansion_breakout_v1.py"
def _load_module():
spec = importlib.util.spec_from_file_location("build_volatility_expansion_breakout_v1", MODULE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_squeeze_then_strong_move_triggers_signal() -> None:
mod = _load_module()
prev_squeeze = mod.squeeze_detected(10)
assert prev_squeeze is True
assert mod.evaluate(prev_squeeze, 4.5) is True
def test_no_squeeze_does_not_trigger_even_with_strong_move() -> None:
mod = _load_module()
prev_squeeze = mod.squeeze_detected(80)
assert prev_squeeze is False
assert mod.evaluate(prev_squeeze, 4.5) is False
def test_missing_percentile_returns_null() -> None:
mod = _load_module()
assert mod.squeeze_detected(None) is None
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""BREAKOUT_FAILURE_STOP_V1 — spec/formulas/domains/exit.yaml.
governance/todo/technical_signals_p4_adoption_plan.yaml P4-6.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUT = ROOT / "Temp" / "breakout_failure_stop_v1.json"
def evaluate(prior_high: float | None, close_price: float | None, days_since_breakout: int | None) -> dict:
if prior_high is None or close_price is None or days_since_breakout is None:
return {"breakout_failure": None}
breakout_failure = days_since_breakout <= 7 and close_price < prior_high
result = {"breakout_failure": breakout_failure}
if breakout_failure:
result["gate"] = "SELL_RISK_EXIT_REVIEW"
result["reason_code"] = "breakout_failure_stop"
return result
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--prior-high", type=float, default=None)
ap.add_argument("--close", type=float, default=None)
ap.add_argument("--days-since-breakout", type=int, default=None)
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
result = {
"formula_id": "BREAKOUT_FAILURE_STOP_V1",
**evaluate(args.prior_high, args.close, args.days_since_breakout),
}
out = Path(args.out)
out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""CONSECUTIVE_STREAK_V1 — spec/formulas/domains/entry.yaml.
Symmetric up_streak/down_streak from the most recent close-to-close changes.
governance/todo/technical_signals_p4_adoption_plan.yaml P4-5.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUT = ROOT / "Temp" / "consecutive_streak_v1.json"
def compute_streaks(daily_close_changes: list[float] | None) -> dict:
if not daily_close_changes:
return {"up_streak": None, "down_streak": None}
up_streak = 0
for change in reversed(daily_close_changes):
if change > 0:
up_streak += 1
else:
break
down_streak = 0
for change in reversed(daily_close_changes):
if change < 0:
down_streak += 1
else:
break
return {"up_streak": up_streak, "down_streak": down_streak}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--changes", default=None, help="comma-separated daily close changes")
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
daily_close_changes = [float(x) for x in args.changes.split(",")] if args.changes else None
result = {"formula_id": "CONSECUTIVE_STREAK_V1", **compute_streaks(daily_close_changes)}
out = Path(args.out)
out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""FIFTY_TWO_WEEK_HIGH_TRIGGER_V1 — spec/formulas/domains/entry.yaml.
Feeds BREAKOUT_QUALITY_GATE_V2 -- never an independent BUY trigger.
governance/todo/technical_signals_p4_adoption_plan.yaml P4-4.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUT = ROOT / "Temp" / "fifty_two_week_high_trigger_v1.json"
def evaluate(close_price: float | None, high52w: float | None) -> bool | None:
if close_price is None or high52w is None:
return None
return close_price >= high52w
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--close", type=float, default=None)
ap.add_argument("--high52w", type=float, default=None)
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
result = {
"formula_id": "FIFTY_TWO_WEEK_HIGH_TRIGGER_V1",
"fifty_two_week_high_breakout": evaluate(args.close, args.high52w),
"hard_constraint": "feeds_BREAKOUT_QUALITY_GATE_V2_only_not_buy_trigger",
}
out = Path(args.out)
out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""GOLDEN_CROSS_SIGNAL_V1 — spec/formulas/domains/entry.yaml.
Detects ma20 crossing above ma60 (golden cross). Auxiliary STRATEGY_SCORING signal
only -- never an independent BUY trigger. governance/todo/technical_signals_p4_adoption_plan.yaml P4-1.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUT = ROOT / "Temp" / "golden_cross_signal_v1.json"
def golden_cross_today(ma20: float | None, ma20_prev: float | None, ma60: float | None, ma60_prev: float | None) -> bool | None:
if None in (ma20, ma20_prev, ma60, ma60_prev):
return None
return ma20_prev <= ma60_prev and ma20 > ma60
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--ma20", type=float, default=None)
ap.add_argument("--ma20-prev", type=float, default=None)
ap.add_argument("--ma60", type=float, default=None)
ap.add_argument("--ma60-prev", type=float, default=None)
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
result = {
"formula_id": "GOLDEN_CROSS_SIGNAL_V1",
"golden_cross_today": golden_cross_today(args.ma20, args.ma20_prev, args.ma60, args.ma60_prev),
"hard_constraint": "auxiliary_signal_only_not_buy_trigger",
}
out = Path(args.out)
out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""STRONG_CLOSE_SIGNAL_V1 — spec/formulas/domains/entry.yaml.
governance/todo/technical_signals_p4_adoption_plan.yaml P4-2.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUT = ROOT / "Temp" / "strong_close_signal_v1.json"
def evaluate_strong_close(close_price: float, high_price: float, low_price: float) -> dict:
if high_price == low_price:
return {"close_position_pct": None, "strong_close": None}
close_position_pct = (close_price - low_price) / (high_price - low_price) * 100
return {"close_position_pct": close_position_pct, "strong_close": close_position_pct >= 80}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--close", type=float, required=True)
ap.add_argument("--high", type=float, required=True)
ap.add_argument("--low", type=float, required=True)
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
result = {"formula_id": "STRONG_CLOSE_SIGNAL_V1", **evaluate_strong_close(args.close, args.high, args.low)}
out = Path(args.out)
out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
"""TREND_FILTER_GATE_V1 — spec/formulas/domains/entry.yaml.
governance/todo/technical_signals_p4_adoption_plan.yaml P4-7.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUT = ROOT / "Temp" / "trend_filter_gate_v1.json"
def evaluate(close_price: float | None, ma120: float | None, ma120_prev: float | None) -> bool | None:
if close_price is None or ma120 is None or ma120_prev is None:
return None
return close_price > ma120 and ma120 > ma120_prev
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--close", type=float, default=None)
ap.add_argument("--ma120", type=float, default=None)
ap.add_argument("--ma120-prev", type=float, default=None)
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
result = {
"formula_id": "TREND_FILTER_GATE_V1",
"trend_filter_pass": evaluate(args.close, args.ma120, args.ma120_prev),
}
out = Path(args.out)
out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""VOLATILITY_EXPANSION_BREAKOUT_V1 — spec/formulas/domains/entry.yaml.
This signal does NOT authorize a buy by itself -- callers must separately confirm
BREAKOUT_QUALITY_GATE_V2 != BLOCKED_LATE_CHASE. governance/todo/technical_signals_p4_adoption_plan.yaml P4-3.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUT = ROOT / "Temp" / "volatility_expansion_breakout_v1.json"
SQUEEZE_PERCENTILE_THRESHOLD = 20.0
EXPANSION_RET_THRESHOLD_PCT = 3.0
def squeeze_detected(bb_width_20d_percentile: float | None) -> bool | None:
if bb_width_20d_percentile is None:
return None
return bb_width_20d_percentile <= SQUEEZE_PERCENTILE_THRESHOLD
def evaluate(squeeze_detected_previous_day: bool | None, ret_1d: float | None) -> bool | None:
if squeeze_detected_previous_day is None or ret_1d is None:
return None
return squeeze_detected_previous_day and ret_1d >= EXPANSION_RET_THRESHOLD_PCT
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--bb-width-20d-percentile-prev", type=float, default=None)
ap.add_argument("--ret-1d", type=float, default=None)
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
prev_squeeze = squeeze_detected(args.bb_width_20d_percentile_prev)
result = {
"formula_id": "VOLATILITY_EXPANSION_BREAKOUT_V1",
"squeeze_detected_previous_day": prev_squeeze,
"volatility_expansion_breakout": evaluate(prev_squeeze, args.ret_1d),
"hard_constraint": "requires_BREAKOUT_QUALITY_GATE_V2_pass_separately",
}
out = Path(args.out)
out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())