BACKTEST_CONTEXT.md
Cortex Investment Agent -- Backtest Context Reference Auto-generated scan, read-only. Last updated: 2026-02-25
1. 技術指標完整清單
系統有兩層指標計算:
- Pipeline 層 (
data_pipeline/indicators.py):寫入technical_indicators表,共 47 欄位 - Snapshot 層 (
scripts/daily_analysis_snapshot.py):讀取 Pipeline 結果 + OHLC 原始資料,計算衍生欄位後寫入daily_technical_snapshot表(180+ 欄位)
1.1 MA (Simple Moving Average)
| 項目 | 值 |
|---|---|
| 計算方式 | close.rolling(window=N).mean() (pandas) |
| 週期 | 5, 10, 20, 50, 60, 100, 120, 200, 240 |
| Pipeline 輸出欄位 | ma_5, ma_10, ma_20, ma_50, ma_60, ma_100, ma_120, ma_200, ma_240 |
| Snapshot 映射 | ma_5 → ma5, ma_20 → ma20, ma_60 → ma60, ma_120 → ma120, ma_240 → ma240 |
| Snapshot 衍生欄位 | ma_alignment ("bullish"/"bearish"/"mixed"), price_position ("above_all"/"below_all"/"mixed"), ma60_slope ("positive"/"negative"/"flat") |
MA period taxonomy contract
每個 ma_<N> 欄位都是真正的 N 日 SMA — 沒有 alias。TW 市場慣用的 60/120/240 (季線/半年線/年線) 與 US 慣用的 50/100/200 並存。
ma_50is true 50-day MA.ma_60is true 60-day MA (TW 季線).ma_100is true 100-day MA.ma_120is true 120-day MA (TW 半年線).ma_200is true 200-day MA.ma_240is true 240-day MA (TW 年線).
Pre-2026-04 bug history: snapshot 的
ma60欄位實際存的是 50 日 MA (因為 writer aliasediv["ma_50"])。已於 migration 041 修正並 backfill 全部歷史資料。ma_10仍只存在technical_indicators,未載入 snapshot。
1.2 EMA (Exponential Moving Average)
| 項目 | 值 |
|---|---|
| 計算方式 | close.ewm(span=N, adjust=False).mean() (pandas) |
| 週期 | 5, 10, 20, 50, 100, 200 |
| Pipeline 輸出欄位 | ema_5, ema_10, ema_20, ema_50, ema_100, ema_200 |
| Snapshot 映射 | 無(EMA 未載入 daily_technical_snapshot) |
1.3 RSI (Relative Strength Index)
| 項目 | 值 |
|---|---|
| 計算方式 | Pipeline: ta.rsi(close, length=14) (pandas_ta);Snapshot: 手動 gain/loss rolling(14) |
| 週期 | 14 |
| Pipeline 輸出欄位 | rsi |
| Snapshot 映射 | rsi → rsi_value |
| Snapshot 衍生欄位 | rsi_status ("overbought"/"oversold"/"neutral"), rsi_divergence ("bullish_warning"/"bearish_warning"/"none"), rsi_slope_3 (3-day diff) |
1.4 MACD (Moving Average Convergence Divergence)
| 項目 | 值 |
|---|---|
| 計算方式 | Pipeline: ta.macd(close, fast=12, slow=26, signal=9) (pandas_ta);Snapshot: 手動 EMA12-EMA26 |
| 參數 | fast=12, slow=26, signal=9 |
| Pipeline 輸出欄位 | macd, macd_signal, macd_hist |
| Snapshot 映射 | macd → macd_dif, macd_signal → macd_dea, macd_hist → macd_histogram |
| Snapshot 衍生欄位 | macd_cross_signal ("golden_cross"/"death_cross"/"none"), macd_divergence ("bullish"/"bearish"/"none"), macd_hist_slope (3-day diff), prev_macd_histogram |
1.5 KD (Stochastic Oscillator)
| 項目 | 值 |
|---|---|
| 計算方式 | Pipeline: ta.stoch(high, low, close, k=14, d=3, smooth_k=3);Snapshot: 手動 rolling(9) + ewm |
| 參數 | k_period=14, d_period=3, smooth=3(Pipeline); k_period=9, d_period=3(Snapshot) |
| Pipeline 輸出欄位 | k, d |
| Snapshot 映射 | k → kdj_k, d → kdj_d |
| Snapshot 衍生欄位 | kdj_j = 3k - 2d, kdj_signal ("high_passivization"/"low_passivization"/"neutral") |
1.6 BB (Bollinger Bands)
| 項目 | 值 |
|---|---|
| 計算方式 | Pipeline: ta.bbands(close, length=20, std=2);Snapshot: 手動 rolling(20).mean() ± 2*std |
| 參數 | period=20, std=2 |
| Pipeline 輸出欄位 | bb_upper, bb_middle, bb_lower |
| Snapshot 映射 | 直接 1:1 |
| Snapshot 衍生欄位 | bb_percent_b = (close-lower)/(upper-lower), bb_bandwidth = (upper-lower)/middle, bb_status ("expanding"/"contracting"/"squeezing"/"normal"), prev_bb_bandwidth |
1.7 ATR (Average True Range)
| 項目 | 值 |
|---|---|
| 計算方式 | Pipeline: ta.atr(high, low, close, length=14);Snapshot: 手動 TR rolling(14).mean() |
| 週期 | 14 |
| Pipeline 輸出欄位 | atr |
| Snapshot 映射 | atr → atr_value |
| Snapshot 衍生欄位 | atr_status ("high_volatility"/"normal"), atr_stop_distance (= atr * 2.0) |
1.8 ADX (Average Directional Index)
| 項目 | 值 |
|---|---|
| 計算方式 | Pipeline: ta.adx(high, low, close, length=14) 取 ADX_14;Snapshot: 手動 DI → DX → rolling(14) |
| 週期 | 14 |
| Pipeline 輸出欄位 | adx |
| Snapshot 映射 | adx → adx_value |
| Snapshot 衍生欄位 | adx_status ("trending"/"non_trending"), adx_slope (3-day diff), adx_yesterday, adx_rising_days (連續上升天數), plus_di, minus_di |
1.9 OBV (On-Balance Volume)
| 項目 | 值 |
|---|---|
| 計算方式 | Pipeline: ta.obv(close, volume);Snapshot: 手動 sign(diff)*volume cumsum |
| Pipeline 輸出欄位 | obv |
| Snapshot 映射 | OBV 原始值不寫入 snapshot |
| Snapshot 衍生欄位 | obv_trend ("new_high"/"new_low"/"flat"), obv_slope ("positive"/"negative"/"flat") |
1.10 Williams %R
| 項目 | 值 |
|---|---|
| 計算方式 | ta.willr(high, low, close, length=14) (pandas_ta) |
| 週期 | 14 |
| Pipeline 輸出欄位 | willr |
| Snapshot 映射 | 無(未載入 daily_technical_snapshot) |
1.11 Volume MA
| 項目 | 值 |
|---|---|
| 計算方式 | volume.rolling(window=N).mean() (pandas) |
| 週期 | 5, 20 |
| Pipeline 輸出欄位 | volume_ma_5, volume_ma_20 |
| Snapshot 映射 | volume_ma_20 → avg_volume_20(volume_ma_5 未載入) |
| Snapshot 衍生欄位 | volume_ratio = current_volume / avg_volume_20, volume_status ("aggressive_buying"/"aggressive_selling"/"normal"), volume_divergence ("PRICE_UP_VOL_LOW"/"PRICE_DOWN_VOL_HIGH"/None) |
1.12 VWAP (Volume Weighted Average Price)
| 項目 | 值 |
|---|---|
| 計算方式 | 手動:(typical_price * volume).cumsum() / volume.cumsum() |
| Pipeline 輸出欄位 | vwap |
| Snapshot 映射 | 直接 1:1 → vwap |
1.13 Ichimoku Cloud
| 項目 | 值 |
|---|---|
| 計算方式 | ta.ichimoku(high, low, close, tenkan=9, kijun=26, senkou=52) (pandas_ta) |
| 參數 | tenkan=9, kijun=26, senkou=52 |
| Pipeline 輸出欄位 | ichimoku_tenkan, ichimoku_kijun |
| Snapshot 映射 | 無(未載入 daily_technical_snapshot) |
1.14 CCI (Commodity Channel Index)
| 項目 | 值 |
|---|---|
| 計算方式 | Pipeline: ta.cci(high, low, close, length=20);Agent: 手動 (TP-SMA(TP))/(0.015*MAD) |
| 週期 | 20 |
| Pipeline 輸出欄位 | cci |
| Snapshot 映射 | 無 |
1.15 MFI (Money Flow Index)
| 項目 | 值 |
|---|---|
| 計算方式 | ta.mfi(high, low, close, volume, length=14) (pandas_ta) |
| 週期 | 14 |
| Pipeline 輸出欄位 | mfi |
| Snapshot 映射 | 無 |
1.16 CHOP (Choppiness Index) -- Pipeline Only
| 項目 | 值 |
|---|---|
| 計算方式 | ta.chop(high, low, close, length=14) (pandas_ta) |
| 週期 | 14 |
| Pipeline 輸出欄位 | chop |
| Snapshot 映射 | chop → chop_value |
| Snapshot 衍生欄位 | chop_status ("extreme_squeeze" if >70 / "choppy" if >61.8 / "trending" if <38.2 / else "neutral"), chop_slope (3-day diff) |
1.17 WaveTrend (WT1, WT2) -- Pipeline Only
| 項目 | 值 |
|---|---|
| 計算方式 | 手動:HLC3 → EMA(n1=10) → deviation → CI → wt1 = EMA(CI, n2=21), wt2 = SMA(wt1, signal=4) |
| 參數 | n1=10, n2=21, signal_period=4 |
| Pipeline 輸出欄位 | wt1, wt2 |
| Snapshot 映射 | wt1 → wt1_value, wt2 → wt2_value |
| Snapshot 衍生欄位 | wt_signal ("deep_bullish_cross"/"bullish_cross"/"deep_bearish_cross"/"bearish_cross"/"none"), wt_status ("extreme_overbought"/"extreme_oversold"/"overbought"/"oversold"/"neutral"), wt_divergence ("bearish_warning"/"bullish_warning") |
1.18 Aroon -- Pipeline Only
| 項目 | 值 |
|---|---|
| 計算方式 | ta.aroon(high, low, length=25) (pandas_ta) |
| 週期 | 25 |
| Pipeline 輸出欄位 | aroon_up, aroon_down, aroon_osc |
| Snapshot 映射 | → aroon_up_value, aroon_down_value, aroon_osc_value |
| Snapshot 衍生欄位 | aroon_signal ("strong_uptrend"/"strong_downtrend"/"neutral") |
1.19 KC (Keltner Channel) -- Pipeline Only
| 項目 | 值 |
|---|---|
| 計算方式 | ta.kc(high, low, close, length=20, scalar=1.5) (pandas_ta) |
| 參數 | period=20, multiplier=1.5 |
| Pipeline 輸出欄位 | kc_upper, kc_middle, kc_lower |
| Snapshot 映射 | 直接 1:1 |
| Snapshot 衍生欄位 (TTM Squeeze) | squeeze_on (bool: bb_upper < kc_upper AND bb_lower > kc_lower), squeeze_off (bool: 昨日 squeeze_on 今日 off), squeeze_duration (連續 squeeze 天數) |
1.20 Supertrend -- Pipeline Only
| 項目 | 值 |
|---|---|
| 計算方式 | ta.supertrend(high, low, close, length=10, multiplier=3.0) (pandas_ta) |
| 參數 | period=10, multiplier=3.0 |
| Pipeline 輸出欄位 | supertrend (數值), supertrend_direction ("bullish"/"bearish") |
| Snapshot 映射 | → supertrend_value, supertrend_direction |
| Snapshot 衍生欄位 | distance_to_supertrend_pct = (close-supertrend)/close*100, supertrend_flip_recent (bool: 5日內方向翻轉), supertrend_flip_type ("bullish"/"bearish") |
1.21 Donchian Channel -- Pipeline Only
| 項目 | 值 |
|---|---|
| 計算方式 | ta.donchian(high, low, lower_length=N, upper_length=N) (pandas_ta),跑兩次 (20, 55) |
| 參數 | short=20, long=55 |
| Pipeline 輸出欄位 | donchian_high_20, donchian_low_20, donchian_high_55, donchian_low_55 |
| Snapshot 映射 | 直接 1:1 |
| Snapshot 衍生欄位 | breakout_20d ("upward"/"downward"), breakout_55d ("upward"/"downward") |
1.22 Snapshot-Only 衍生指標(不在 Pipeline 中)
以下指標僅在 daily_analysis_snapshot.py 中從 OHLC 原始資料計算,不存在於 technical_indicators 表:
CTI (Correlation Trend Indicator)
- 計算:
np.corrcoef(close.tail(20), np.arange(20))[0,1],clipped [-1, 1] - 欄位:
cti_score,cti_interpretation("strong_trend"/"weak_trend"/"range"),cti_linearity("high"/"medium"/"low"),cti_delta(placeholder,目前未實作)
Vortex Indicator (VI)
- 計算:手動
vm_plus/vm_minus rolling(14) / TR rolling(14) - 欄位:
vi_plus,vi_minus,vi_signal("bullish_cross"/"bearish_cross"/"bullish"/"bearish"),vi_plus_prev,vi_minus_prev
DMI Derived
- 計算:從
plus_di,minus_di衍生 - 欄位:
di_spread= plus_di - minus_di,di_spread_slope("widening"/"narrowing"/"stable"),adx_rising_days
Relative Strength vs Market
- 計算:
stock_close / benchmark_close(benchmark: 0050.TW), 20d/60d percentage change - 欄位:
rs_eval_ready(bool),rs_vs_market_20d,rs_vs_market_60d,rs_slope("improving"/"deteriorating"/"stable")
Slope Features (3-day delta)
rsi_slope_3:RSI[-1] - RSI[-4]macd_hist_slope:histogram[-1] - histogram[-4]adx_slope:ADX[-1] - ADX[-4]chop_slope:CHOP[-1] - CHOP[-4]
2. JSON Schema 結構
build_agent_payload() 輸出 JSON 的完整欄位樹。此 payload 是 LLM 分析的輸入。
頂層結構
{
"meta": { ... },
"market_summary": { ... },
"indicator_settings": { ... },
"technical_indicators": { ... },
"signals": { ... },
"risk": { ... }
}
2.1 meta
{
"symbol": "string (e.g. 2330.TW)",
"trading_date": "ISO 8601 date string",
"interval": "string (e.g. 1d)",
"bar_state": "string",
"data_quality": "string (complete/partial)",
"missing_fields": ["string", ...],
"data_source": "string",
"notes": ["string", ...]
}
2.2 market_summary
{
"current_price": float,
"change_percent": float,
"market_phase": "string",
"price_bar": {
"open": float,
"high": float,
"low": float,
"close": float,
"prev_close": float,
"vwap": float,
"is_adjusted": bool,
"adjustment_type": "string"
}
}
2.3 indicator_settings
{
"moving_averages": { "type": "SMA/EMA", "periods": [5,10,20,50,100,200], "source": "close" },
"macd": { "fast": 12, "slow": 26, "signal": 9, "source": "close" },
"rsi": { "period": 14, "source": "close" },
"kdj": { "k_period": 14, "d_period": 3, "j_period": 3, "source": "close" },
"bollinger_bands": { "period": 20, "stddev": 2, "source": "close" },
"atr": { "period": 14 },
"adx": { "period": 14 },
"cti": { "range": [-1.0, 1.0], "comment": "rolling corr(close, index)" },
"choppiness": { "period": 14 },
"wavetrend": { "n1": 10, "n2": 21, "signal_period": 4 },
"aroon": { "period": 25 },
"keltner": { "period": 20, "multiplier": 1.5 },
"supertrend": { "period": 10, "multiplier": 3.0 },
"donchian": { "short": 20, "long": 55 },
"relative_strength": { "benchmark": "0050.TW" }
}
2.4 technical_indicators
{
"trend": {
"moving_averages": {
"ma5": float, "ma20": float, "ma60": float,
"alignment": "bullish|bearish|mixed",
"price_position": "above_all|below_all|mixed",
"ma60_slope": "positive|negative|flat"
},
"cti": {
"score": float (-1~1),
"interpretation": "strong_trend|weak_trend|range",
"linearity": "high|medium|low"
},
"adx": { "value": float, "status": "trending|non_trending" },
"vortex": {
"vi_plus": float, "vi_minus": float,
"signal": "bullish_cross|bearish_cross|bullish|bearish"
},
"choppiness": { "value": float, "status": "extreme_squeeze|choppy|trending|neutral" },
"aroon": {
"up": float, "down": float, "oscillator": float,
"signal": "strong_uptrend|strong_downtrend|neutral"
},
"supertrend": {
"value": float, "direction": "bullish|bearish",
"distance_pct": float, "flip_recent": bool, "flip_type": "bullish|bearish|null"
},
"relative_strength": {
"eval_ready": bool, "rs_20d": float, "rs_60d": float,
"slope": "improving|deteriorating|stable"
}
},
"momentum": {
"macd": {
"dif": float, "dea": float, "histogram": float,
"cross_signal": "golden_cross|death_cross|none",
"divergence": "bullish|bearish|none"
},
"rsi": {
"value": float, "status": "overbought|oversold|neutral",
"divergence": "bullish_warning|bearish_warning|none"
},
"kdj": {
"k": float, "d": float, "j": float,
"signal": "high_passivization|low_passivization|neutral"
},
"wavetrend": {
"wt1": float, "wt2": float,
"signal": "deep_bullish_cross|bullish_cross|deep_bearish_cross|bearish_cross|none",
"status": "extreme_overbought|extreme_oversold|overbought|oversold|neutral",
"divergence": "bearish_warning|bullish_warning|null"
},
"di_derived": {
"spread": float, "spread_slope": "widening|narrowing|stable",
"adx_rising_days": int
}
},
"volatility": {
"bollinger_bands": {
"upper": float, "middle": float, "lower": float,
"percent_b": float, "bandwidth": float,
"status": "expanding|contracting|squeezing|normal"
},
"atr": { "value": float, "status": "high_volatility|normal" },
"keltner": { "upper": float, "middle": float, "lower": float },
"squeeze": { "on": bool, "off": bool, "duration": int },
"donchian": {
"high_20": float, "low_20": float,
"high_55": float, "low_55": float,
"breakout_20d": "upward|downward|null",
"breakout_55d": "upward|downward|null"
}
},
"volume": {
"current_volume": int, "average_volume_20": int,
"volume_ratio": float, "status": "aggressive_buying|aggressive_selling|normal",
"obv": { "trend": "new_high|new_low|flat", "slope": "positive|negative|flat" }
},
"slopes": {
"rsi_slope_3": float, "macd_hist_slope": float,
"adx_slope": float, "cti_delta": float|null, "chop_slope": float
},
"patterns": {
"detected": [
{
"name": "string", "score": float, "reliability": "string",
"trigger_price": float, "invalidation_price": float,
"detected_at": "string", "notes": "string"
}
],
"support_resistance": {
"major_support": float, "major_resistance": float,
"low_20d_min": float, "low_60d_min": float,
"distance_to_support_20d_pct": float, "distance_to_support_60d_pct": float,
"distance_to_support_20d_price": float, "distance_to_support_60d_price": float,
"historical_highs": {
"all_time_high": {
"price": float, "date": "string", "prev_price": float,
"touched_intraday": bool, "is_new_intraday": bool, "is_new_close": bool
},
"high_52w": {
"price": float, "date": "string", "prev_price": float,
"touched_intraday": bool, "is_new_intraday": bool, "is_new_close": bool
},
"swing_high_20d": { "price": float, "date": "string" },
"swing_high_60d": { "price": float, "date": "string" }
},
"notes": []
},
"breakout_context": {
"is_new_all_time_high": bool, "is_new_52w_high": bool,
"close_above_prev_ath": bool, "close_above_prev_52w_high": bool,
"breakout_type": "string|null",
"ath_eval_ready": bool, "high_52w_eval_ready": bool,
"distance_to_all_time_high_pct": float,
"distance_to_52w_high_pct": float,
"price_discovery_mode": bool
}
}
}
2.5 signals
{
"bias": "bullish|bearish|neutral",
"confidence": float (0.0000 ~ 1.0000),
"reasons": ["翻譯後的 ReasonCode 字串", ...],
"warnings": ["翻譯後的 WarningCode 字串", ...],
"volume_divergence": "PRICE_UP_VOL_LOW|PRICE_DOWN_VOL_HIGH|null",
"market_structure_events": ["MarketEvent value string", ...]
}
2.6 risk
{
"atr_value": float,
"atr_stop_distance": float,
"structure_stop": float,
"invalidation_level": float,
"rr_targets": [
{ "name": "to_major_resistance", "target_price": float }
],
"position_sizing_hint": {
"comment": "position size = risk_per_trade / stop_distance"
}
}
atr_stop_distance= ATR * 2.0structure_stop= major_support(全歷史最近、左右各 5 根嚴格確認的 adjusted 前低;不設到期)invalidation_level= structure_stop;沒有已確認前低時為 null,不使用 ATR fallbackrr_targets最多一筆 (to_major_resistance),若 major_resistance 為 null 則空陣列
high_volume_breakdown 的前低另由完整歷史 low 序列計算,無固定日期上限。
局部低點須嚴格低於左右各 5 根,右側確認完成後才可使用;取日期最近的一個。
收盤嚴格低於該前低且有放量賣壓才成立,通道破底不能繞過前低要求。
共用支撐與停損採同一個已確認前低規則;raw/adjusted 各自確認,決策使用 adjusted。
3. 訊號產生邏輯
3.1 Bias 計算 — 純 Rule-Based
compute_bias_confidence_reasons_warnings() in daily_analysis_snapshot.py
起始分數:score = 0.5
14 個評分類別及其精確調整值
| 類別 | 條件 | 分數調整 | ReasonCode / WarningCode |
|---|---|---|---|
| A. Trend Structure | ma_alignment == "bullish" | +0.20 | MA_BULLISH_STACK |
| ma_alignment == "bearish" | -0.20 | MA_BEARISH_STACK | |
| close > ma60 | ±0 | PRICE_ABOVE_MA60 | |
| close <= ma60 | ±0 | PRICE_BELOW_MA60 | |
| MA entangled (|ma5-ma20|/close < 0.003) | ±0 | MA_ENTANGLED | |
| B. Trend Strength | adx >= 25 | +0.10 | ADX_STRONG_TREND |
| adx > adx_yesterday AND adx >= 20 | +0.04 | ADX_ACCELERATING | |
| plus_di > minus_di | ±0 | DI_BULLISH | |
| plus_di <= minus_di | ±0 | DI_BEARISH | |
| C. Momentum | vi_signal == "bullish_cross" | +0.08 | VORTEX_GOLDEN_CROSS |
| vi_signal == "bearish_cross" | -0.08 | VORTEX_DEAD_CROSS | |
| vi_signal == "bullish" | +0.04 | (無) | |
| vi_signal == "bearish" | -0.04 | (無) | |
| vortex diff widening (bullish) | +0.04 | VORTEX_DIFF_WIDENING | |
| macd_hist > 0 | +0.08 | MACD_HIST_POSITIVE | |
| macd_hist < 0 | -0.08 | MACD_HIST_NEGATIVE | |
| |hist| > |prev_hist| | +0.03 | MACD_HIST_EXPANDING | |
| D. Volume | vol_ratio >= 1.2 + aggressive_buying | +0.08 | VOLUME_EXPANSION_BUY |
| vol_ratio >= 1.2 + aggressive_selling | -0.08 | VOLUME_EXPANSION_SELL | |
| bullish MA + pullback + low volume | ±0 | LOW_VOLUME_PULLBACK | |
| E. Reserved | inactive compatibility slot; 20-day breakout is scored by M | ±0 | (無新 emission) |
| E2. Breakout Highs | is_new_ath_close | +0.10 | BREAKOUT_ALL_TIME_HIGH |
| is_new_52w_high_close (elif) | +0.08 | BREAKOUT_52W_HIGH | |
| F. Squeeze/CTI | cti_score >= 0.6 (且 < 0.8) | +0.10 | (無) |
| cti_score <= -0.6 (且 > -0.8) | -0.10 | (無) | |
| |cti_score| >= 0.8 | ±0 | ⚠️ CTI_EXTREME_LINEARITY | |
| Warnings | rsi >= 70 (且 score > 0.5) | -0.05 | ⚠️ RSI_OVERBOUGHT |
| rsi <= 30 | ±0 | ⚠️ RSI_OVERSOLD | |
| rsi_divergence == "bearish_warning" | -0.08 | ⚠️ DIVERGENCE_BEARISH | |
| rsi_divergence == "bullish_warning" | +0.06 | ⚠️ DIVERGENCE_BULLISH | |
| G. Choppiness | chop_status in (extreme_squeeze, choppy) | score += (0.5-score)*0.30 | ⚠️ CHOP_CHOPPY |
| chop_status == "trending" | +0.05 | CHOP_TRENDING | |
| H. WaveTrend | deep_bullish_cross | +0.10 | WT_DEEP_BULLISH_CROSS |
| deep_bearish_cross | -0.10 | WT_DEEP_BEARISH_CROSS | |
| bullish_cross | +0.06 | WT_BULLISH_CROSS | |
| bearish_cross | -0.06 | WT_BEARISH_CROSS | |
| extreme_overbought | -0.05 | ⚠️ WT_EXTREME | |
| overbought | -0.03 | ⚠️ WT_OVERBOUGHT | |
| wt_divergence == "bearish_warning" | -0.06 | ⚠️ WT_DIVERGENCE_BEARISH | |
| wt_divergence == "bullish_warning" | +0.04 | ⚠️ WT_DIVERGENCE_BULLISH | |
| I. Aroon | strong_uptrend AND score >= 0.5 | +0.04 | AROON_FRESH_UPTREND |
| strong_downtrend AND score <= 0.5 | -0.04 | AROON_FRESH_DOWNTREND | |
| aroon_up < 50 AND adx > 25 | ±0 | ⚠️ AROON_STALE_TREND | |
| J. TTM Squeeze | squeeze_off | +0.08 | SQUEEZE_RELEASE |
| squeeze_duration > 10 | ±0 | ⚠️ SQUEEZE_PROLONGED | |
| K. DMI Derived | di_spread_slope == "widening" AND adx > 20 | +0.04 | DI_SPREAD_WIDENING |
| adx_rising_days >= 3 | +0.03 | ADX_SUSTAINED_RISE | |
| L. Supertrend | direction == "bullish" | +0.03 | SUPERTREND_BULLISH |
| direction == "bearish" | ±0 | ⚠️ SUPERTREND_BEARISH | |
| flip == "bullish" | +0.06 | SUPERTREND_FLIP_BULLISH | |
| flip == "bearish" | -0.06 | SUPERTREND_FLIP_BEARISH | |
| |dist_supertrend| < 1.0 | ±0 | ⚠️ NEAR_SUPERTREND | |
| M. Donchian | breakout_20d == "upward" | +0.05 | DONCHIAN_BREAKOUT_20D |
| breakout_20d == "downward" | -0.05 | (無) | |
| breakout_55d == "upward" | +0.08 | DONCHIAN_BREAKOUT_55D | |
| breakout_55d == "downward" | -0.08 | (無) | |
| N. RS vs Market | rs_slope == "rising" AND rs_20d > 2.0 | +0.04 | RS_OUTPERFORM |
| rs_slope == "falling" AND rs_20d < -2.0 | -0.04 | RS_UNDERPERFORM + ⚠️ RS_LAGGING |
回測的 high_20 是前一個 OHLC 交易日 snapshot 的 adjusted donchian_high_20;前一交易日缺 snapshot 時為 null,不取更早一筆,也不引用 major_resistance。欄位為舊資料與回測設定保留,但 Category E 不再加分或產生 BREAKOUT_ABOVE_RECENT_HIGH;相同的 20 日突破只由 Category M 的 breakout_20d 評分,避免同一證據重複加分。舊設定仍可解析 Category E,但新的 solo、ablation、forward selection、fallback、weight tuning 與 Layer 3 組合會排除這個 inactive 槽位,不能把 no-op 報成最佳化收益。
loader 保留原始 raw OHLC;直接比較支撐、壓力、布林或一目均衡表價格的條件使用 indicator_close。有此欄位但值無效時不觸發;僅舊的直接呼叫完全沒有此欄位時沿用 close。Phase 1 直接保留 loader 的欄位,既有 parquet 須重新產生才能採用新版資料。
最終處理
score = max(0.0, min(1.0, score)) # clamp to [0, 1]
if score >= 0.6 → bias = "bullish"
if score <= 0.4 → bias = "bearish"
else → bias = "neutral"
confidence = round(score, 4) # confidence 就是 score 本身
3.2 Confidence 計算
Confidence 就是累積分數本身,沒有獨立的 confidence 公式或 sub-dimension breakdown。
confidence = round(score, 4) 其中 score 是從 0.5 開始經過 14 個類別累加/累減後 clamp 到 [0, 1] 的值。
3.3 Warnings 產生
Warnings 在評分過程中同步產生,由 WarningCode enum 標記。額外的 warnings 包括:
| 條件 | WarningCode |
|---|---|
| close / major_resistance >= 0.98 | NEAR_RESISTANCE |
| close / major_support <= 1.02 | NEAR_SUPPORT |
| distance_to_ath_pct 在 -2.0% ~ 0 之間 | NEAR_ALL_TIME_HIGH |
| avg_volume_20 < 500,000 OR avg_amount_20 < 10M OR flat_days_20 > 10 | LOW_LIQUIDITY |
Warning 去重規則:若 NEAR_ALL_TIME_HIGH 存在且 major_resistance 與 prev_ath_price 差距 < 0.5%,則移除 NEAR_RESISTANCE。
3.4 Signal Channel 分類
classify_channel() in api/routes/signals.py -- 嚴格 cascade(first match wins):
共用品質門檻 _passes_common_quality()
bias,confidence,data_quality,macd_histogram,cti_score,volume_ratio皆非 Nonedata_quality == "complete"
Channel 1: momentum_bullish
- bias == "bullish" AND confidence >= 0.70
- rsi_divergence != "bearish_warning"(RSI 背離否決)
- trend quality score >= 2/5:
- +1 if ma_alignment == "bullish" OR price_position == "above_all"
- +1 if cti_score > 0.6
- +1 if MACD histogram expanding (hist > 0 AND |hist| > |prev_hist|)
- +1 if volume_ratio >= 1.2
- +1 if has_high_pattern
Channel 2: momentum_bearish
- bias == "bearish" AND confidence >= 0.70
- rsi_divergence != "bullish_warning"
- trend quality score >= 2/5(方向反轉)
Channel 3: reversal_bullish
- confidence >= 0.40 AND price_position != "above_all"
- CTI 否決:cti_score NOT < -0.8(過度線性下跌不可反轉)
- reversal score >= 3/5:
- +1 if rsi <= 35 OR rsi_divergence == "bullish_warning"
- +1 if macd_histogram > prev_macd_histogram
- +1 if close > ma5 AND ma5 >= ma20
- +1 if volume_ratio >= 1.2 AND volume_status == "aggressive_buying"
- +1 if has_high_pattern AND has_bottom_pattern
Channel 4: reversal_bearish
- confidence >= 0.40 AND price_position != "below_all"
- CTI 否決:cti_score NOT > 0.8
- reversal score >= 3/5(方向反轉)
Channel 5: squeeze_bullish
- bb_status in ("contracting", "squeezing") AND adx < 20
- ma_alignment in ("bullish", "mixed") AND price_position != "below_all"
- 若 close > bb_upper 且 bandwidth_ratio >= 1.3 → "strong" breakout → 踢出 squeeze
Channel 6: squeeze_bearish
- 同 squeeze_bullish 但方向反轉
- 若 close < bb_lower 且 bandwidth_ratio >= 1.3 → 踢出 squeeze
Channel 7: neutral
- 以上皆不符合時的預設值
3.5 Entry/Exit 訊號
系統目前沒有明確的 entry/exit 訊號。只有方向性的 bias + confidence + signal channel。
沒有以下機制: - 明確的 entry_price / exit_price - Stop-loss / take-profit 具體建議(risk block 提供 invalidation_level 和 rr_targets 但非交易指令) - 持倉期間建議
4. 資料來源與覆蓋範圍
4.1 股票池
重要更正 (2026-02-25 DB 實查):系統實際爬取的是台灣全市場(TWSE + TPEx),遠超
markets.yaml定義的 20 支精選股。
4.1.1 設定層 vs 實際 DB
| 層級 | 來源 | 台股數量 | 美股數量 | 說明 |
|---|---|---|---|---|
markets.yaml 設定 |
手動清單 | 20 (.TW) | 7 | 原始精選清單 |
init.sql 種子 |
SQL 初始化 | 25 (.TW) | 7 | 含額外 2379, 2327, 2301, 2395, 2207 |
taiwan_stocks.py 同步 |
TWSE/TPEx API | 全市場 | — | 動態同步上市櫃全股票 |
stocks 表實際 |
DB | 2,019 (.TW) + 897 (.TWO) | 7 | 共 2,923 支 |
4.1.2 各表實際資料量(DB 查詢 2026-02-25)
| 表 | 台股 .TW | 櫃買 .TWO | 美股 | 合計 tickers | 合計 rows |
|---|---|---|---|---|---|
stocks (master) |
2,019 | 897 | 7 | 2,923 | 2,923 |
stock_ohlc |
1,229 | 897 | 7 | 2,133 | 2,397,970 |
daily_technical_snapshot |
1,228 | 897 | 0 | 2,125 | 382,385 |
technical_indicators |
6 | 0 | 7 | 13 | 3,900 |
daily_chip_snapshot |
1,228 | 897 | 0 | 2,125 | 769,250 |
daily_financial_snapshot |
1,228 | 897 | 0 | 2,125 | 216,649 |
4.1.3 關鍵發現
- 美股無 snapshot:7 支美股有 OHLC 資料但
daily_technical_snapshot為 0 筆。回測僅能對台股進行 technical_indicators表僅 13 支:此表僅覆蓋 6 支台股 (2317, 2330, 2412, 2454, 2881, 2882) + 7 支美股。絕大多數股票的指標由daily_technical_snapshot在 snapshot 流程中直接從 OHLC 計算,不經過此表- 台股包含 ETF:1,228 支 .TW 中約 136 支為 ETF(代號 00xxx),1,950 支為一般股票,20 支為其他(存託憑證等),19 支為 TWO 歸類問題
- TWO (櫃買) 已覆蓋:897 支櫃買市場股票已完整爬取
4.1.4 markets.yaml 精選 20 支台股
以下為系統原始設定的核心觀察清單(仍用於 RS benchmark):
| Ticker | 名稱 | Sector |
|---|---|---|
| 2330.TW | 台積電 | 半導體 |
| 2317.TW | 鴻海 | 電子代工 |
| 2454.TW | 聯發科 | IC 設計 |
| 2412.TW | 中華電 | 電信 |
| 2308.TW | 台達電 | 電子零組件 |
| 2881.TW | 富邦金 | 金融 |
| 2882.TW | 國泰金 | 金融 |
| 2891.TW | 中信金 | 金融 |
| 2303.TW | 聯電 | 半導體 |
| 3711.TW | 日月光投控 | 封測 |
| 1301.TW | 台塑 | 塑化 |
| 1303.TW | 南亞 | 塑化 |
| 2002.TW | 中鋼 | 鋼鐵 |
| 2886.TW | 兆豐金 | 金融 |
| 2884.TW | 玉山金 | 金融 |
| 2892.TW | 第一金 | 金融 |
| 5880.TW | 合庫金 | 金融 |
| 3008.TW | 大立光 | 光學 |
| 2357.TW | 華碩 | 品牌 PC |
| 2382.TW | 廣達 | 伺服器/AI |
4.1.5 美股 7 支 (Magnificent 7)
AAPL, MSFT, GOOGL, AMZN, NVDA, META, TSLA(僅有 OHLC,無 snapshot/chip/financial)
4.2 資料來源
| 項目 | 值 |
|---|---|
| OHLCV 來源 | taiwan_stocks.py 同步全市場(FinMind 為主要來源,yfinance 備援) |
| 台股清單同步 | taiwan_stocks.py 動態從 TWSE/TPEx API 拉取上市櫃完整名單 |
| 頻率 | 日線 (1d) |
| 更新排程 | cron 0 6 * * 1-5 (Asia/Taipei 工作日 06:00) |
| Rate Limit | 60 requests/minute |
| 重試 | max_attempts=3, exponential backoff factor=2 |
4.3 資料欄位與日期範圍
OHLCV 表 (stock_ohlc):ticker, timestamp, open, high, low, close, volume, adjusted_close
| 資料集 | 最早日期 | 最新日期 | 說明 |
|---|---|---|---|
stock_ohlc (TW/TWO) |
2021-02-04 | 2026-02-24 | ⚠️ 非 2020-01-01,markets.yaml 的 start_date: 2020-01-01 為設定值,實際 DB 從 2021-02 起 |
stock_ohlc (US) |
2021-02-04 | 2026-02-24 | ~1,269 交易日 |
daily_technical_snapshot |
2023-11-08 | 2026-02-24 | 549 個交易日 |
| snapshot 多數台股起始 | 2025-08-14 | 2026-02-24 | 1,738 支台股僅 ~126 天 snapshot |
| snapshot 早期批次 | 2024-02-02 | 2026-02-24 | ~316 支有 492 天(~2 年) |
daily_chip_snapshot |
2024-08-27 | 2026-02-24 | |
daily_financial_snapshot |
2025-09-18 | 2026-02-24 | 僅 ~5 個月,非常新 |
technical_indicators |
2024-11-11 | 2026-01-30 | 僅 13 支股票 |
⚠️ 回測可用資料深度警告
| 問題 | 影響 |
|---|---|
| OHLC 起始為 2021-02-04 而非 2020-01-01 | 無法回測 COVID crash (2020-01~03),BACKTEST_SPEC 的 covid_crash 子區間不可用 |
| 多數 snapshot 僅從 2025-08-14 起 | 若回測依賴 snapshot 欄位(bias, confidence, channel),多數股票僅有 ~6 個月資料 |
| 316 支有 ~2 年 snapshot | 這批是回測可用的核心股票池 |
| Financial snapshot 僅 ~5 個月 | Strategy E(三維度投票)的資料極淺 |
| 美股無 snapshot | Strategy A~F 全部無法對美股執行 |
4.4 前日資料處理
- 前日指標值:從
indicators_df.iloc[-2]取得(不是從 DB 載入前日 snapshot) - 前日收盤:通過
load_prev_close()SQL 查詢 - cti_delta:placeholder,目前未實作
- 無 prev_day_snapshot 概念:不存在載入前一日完整 snapshot 的邏輯
4.5 分析維度覆蓋狀態
| 維度 | 狀態 | DB 覆蓋 | 說明 |
|---|---|---|---|
| Technical | ready | 2,125 tickers / 382K rows | 完整實作,180+ 欄位 |
| Chip (籌碼) | ready | 2,125 tickers / 769K rows | Engine A (法人流向 0.65) + Engine B (融資壓力 0.35) |
| Financial (財務) | ready | 2,125 tickers / 217K rows | Engine A (營收) + B (獲利) + C (評價),但僅 5 個月資料 |
| Fundamental (基本面) | not_ready | 0 | placeholder,所有欄位標記 status="placeholder" |
5. 目前缺少什麼(Backtesting Gaps)
5.1 無可量化的 Entry/Exit 規則
- 系統只產生 bias (bullish/bearish/neutral) + confidence (0~1)
- 沒有明確定義 "何時進場" 和 "何時出場" 的門檻
- Signal channel 提供分類但非交易觸發條件
- 需要:定義 confidence 門檻(如 >= 0.7 做多)、持倉退出條件(如 bias 翻轉或 confidence 低於門檻)
5.2 Confidence 無法分解
- Confidence 是一個累積分數,沒有 sub-dimension breakdown
- 無法分析 "哪個指標類別貢獻了多少信心"
- 需要:記錄各類別的分數貢獻(如 trend_score, momentum_score, volume_score)
5.3 無 Benchmark / 基準收益
- RS vs Market 用 0050.TW 但僅用於相對強弱判斷
- 沒有 buy-and-hold 基準用於比較策略表現
- 需要:記錄 benchmark 每日收益供 alpha 計算
5.4 無持倉追蹤
- 系統為每日獨立 snapshot,沒有 "目前持有" 的概念
- 無法計算連續持倉的盈虧
- 需要:持倉狀態機 (idle → long → exit) 和對應的 PnL 追蹤
5.5 Signal Scoring Weights 與 Snapshot Bias 斷裂
signal_scoring.py定義了精細的權重系統 (momentum, reversal, squeeze weights)- 但
daily_analysis_snapshot.py的compute_bias_confidence_reasons_warnings()使用獨立的 hardcoded 分數調整 - 兩套邏輯各自獨立,signal channel 分類與 bias 計算用不同的權重
- 需要:統一或明確區分兩套系統的角色
5.6 Pattern 可靠性未整合
patterns.detected有score,reliability欄位- 但 patterns 未參與 bias/confidence 計算
- 需要:決定 pattern 是否應影響信號,以及如何影響
5.7 多維度整合缺失
- Technical, Chip, Financial 三個維度各自產生 bias/confidence
- 沒有跨維度的綜合信號(如:技術面看多 + 籌碼面看多 → 強化信號)
- 需要:定義多維度融合公式或投票機制
5.8 歷史回測基礎設施
已有部分基礎(signal_backtest.py):
- Horizons: 1, 3, 5, 20 trading days
- Deadzone: ±0.2%
- Cooldown: 5 days per ticker
- Squeeze volatility threshold: 5%
但缺少: - 完整的回測引擎(entry/exit 模擬) - 滑點和手續費模型 - 資金管理規則 - 績效指標計算 (Sharpe, max drawdown, win rate)
附錄:完整 Enum 值
SignalChannel (7)
momentum_bullish, momentum_bearish, reversal_bullish, reversal_bearish, squeeze_bullish, squeeze_bearish, neutral
Bias (3)
bullish, bearish, neutral
RiskFlag (11)
rsi_overbought, rsi_oversold, macd_divergence, rsi_divergence_bearish, rsi_divergence_bullish, bb_expanding, breakout_watch, volume_divergence, margin_buildup_risk, distribution_signs, foreign_distribution
QualityFlag (3)
complete, partial, low_liquidity
ReasonCode (48)
MA_BULLISH_STACK, MA_BEARISH_STACK, PRICE_ABOVE_MA60, PRICE_BELOW_MA60, MA_ENTANGLED, ADX_STRONG_TREND, ADX_ACCELERATING, DI_BULLISH, DI_BEARISH, VORTEX_GOLDEN_CROSS, VORTEX_DEAD_CROSS, VORTEX_DIFF_WIDENING, MACD_HIST_POSITIVE, MACD_HIST_NEGATIVE, MACD_HIST_EXPANDING, VOLUME_EXPANSION_BUY, VOLUME_EXPANSION_SELL, LOW_VOLUME_PULLBACK, PULLBACK_HOLD_MA5, BREAKOUT_ABOVE_RECENT_HIGH, SQUEEZE_ACTIVE, SQUEEZE_NARROW_BB, FOREIGN_NET_BUY, FOREIGN_NET_SELL, INST_CONSENSUS_BUY, INST_CONSENSUS_SELL, INSTITUTIONAL_STREAK_BUY, INSTITUTIONAL_STREAK_SELL, FLOW_WITH_VOLUME, SHORT_COVERING, SHORT_BUILDUP, MARGIN_CLEARING, BREAKOUT_52W_HIGH, BREAKOUT_ALL_TIME_HIGH, CHOP_TRENDING, WT_BULLISH_CROSS, WT_BEARISH_CROSS, WT_DEEP_BULLISH_CROSS, WT_DEEP_BEARISH_CROSS, AROON_FRESH_UPTREND, AROON_FRESH_DOWNTREND, SQUEEZE_RELEASE, DI_SPREAD_WIDENING, ADX_SUSTAINED_RISE, SUPERTREND_BULLISH, SUPERTREND_FLIP_BULLISH, SUPERTREND_FLIP_BEARISH, DONCHIAN_BREAKOUT_20D, DONCHIAN_BREAKOUT_55D, RS_OUTPERFORM, RS_UNDERPERFORM
WarningCode (27)
RSI_OVERBOUGHT, RSI_OVERSOLD, DIVERGENCE_BEARISH, DIVERGENCE_BULLISH, CTI_EXTREME_LINEARITY, CLIMAX_UNCONFIRMED, NEAR_RESISTANCE, NEAR_SUPPORT, VOLUME_DIVERGENCE, DISTRIBUTION_VOLUME, BREAKOUT_WATCH, BREAKOUT_CONFIRMED, DATA_PARTIAL, LOW_LIQUIDITY, NEAR_ALL_TIME_HIGH, CHOP_CHOPPY, WT_OVERBOUGHT, WT_OVERSOLD, WT_EXTREME, WT_DIVERGENCE_BEARISH, WT_DIVERGENCE_BULLISH, AROON_STALE_TREND, SQUEEZE_PROLONGED, SUPERTREND_BEARISH, NEAR_SUPERTREND, RS_LAGGING, MARGIN_INCREASING_FAST, FOREIGN_SELLING_STREAK, DATA_PARTIAL_CHIP, LOW_FLOAT_VOLUME, USING_STALE_MARGIN_DATA
MarketEvent (11)
TOUCHED_ALL_TIME_HIGH, NEW_ALL_TIME_HIGH, ATH_INTRADAY_ONLY, TOUCHED_52W_HIGH, NEW_52W_HIGH, HIGH_52W_INTRADAY_ONLY, LIMIT_UP_CLOSE, LIMIT_DOWN_CLOSE, GAP_UP, GAP_DOWN, NEAR_ALL_TIME_HIGH
ChipSignalType (9)
neutral, institutional_accumulation, short_squeeze, foreign_entry, cost_defense, institutional_distribution, retail_trap, margin_call_risk, fake_breakout