diff --git a/.agent/skills/sakata/Dockerfile b/.agent/skills/sakata/Dockerfile new file mode 100644 index 0000000..eded3d9 --- /dev/null +++ b/.agent/skills/sakata/Dockerfile @@ -0,0 +1,37 @@ +FROM python:3.11-slim + +# 安裝系統依賴 (TA-Lib 與中文字體) +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + wget \ + fonts-noto-cjk \ + && rm -rf /var/lib/apt/lists/* + +# 安裝 TA-Lib C library +RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \ + tar -xzf ta-lib-0.4.0-src.tar.gz && \ + cd ta-lib && \ + ./configure --prefix=/usr && \ + make && \ + make install && \ + cd .. && \ + rm -rf ta-lib ta-lib-0.4.0-src.tar.gz + +# 設置工作目錄 +WORKDIR /app + +# 複製依賴文件 +COPY requirements.txt . + +# 安裝 Python 依賴 +RUN pip install --no-cache-dir -r requirements.txt + +# 複製腳本 +COPY scripts/ ./scripts/ + +# 設置環境變數 +ENV PYTHONUNBUFFERED=1 +ENV MPLCONFIGDIR=/tmp/matplotlib + +# 預設入口點 +ENTRYPOINT ["python", "scripts/sakata_analyzer.py"] diff --git a/.agent/skills/sakata/SKILL.md b/.agent/skills/sakata/SKILL.md new file mode 100644 index 0000000..a4a710e --- /dev/null +++ b/.agent/skills/sakata/SKILL.md @@ -0,0 +1,174 @@ +--- +description: 酒田戰法 K 線型態分析,識別 80 種反轉與延續信號 +--- +# 酒田戰法 (Sakata Strategy) + +專業 K 線型態分析 Skill,結合自動化型態偵測與 **AI 智能分析**。 + +## 使用方式 + +用戶請求:`/sakata [TICKER]` 或 `分析 [TICKER] 的 K 線型態` + +--- + +## ⚙️ Step 1: 執行型態偵測腳本 + +```bash +cd .agent/skills/sakata/scripts +docker run -v $(pwd)/output:/app/output sakata-skill python sakata_analyzer.py --ticker [TICKER] --days 120 +``` + +> 首次使用需建置 Docker:`docker build -t sakata-skill .` + +腳本輸出: +- `output/[TICKER]_sakata.png` - K 線圖表 +- `output/[TICKER]_sakata.md` - 原始偵測報告 + +--- + +## 🧠 Step 2: AI 智能分析 (核心) + +讀取腳本輸出後,Agent 必須進行以下 **深度分析**: + +### 2.1 型態品質評估 + +針對偵測到的每個型態,評估其可靠性: + +```markdown +#### 型態品質評估 +| 型態 | 日期 | 原始強度 | AI 評估 | 調整理由 | +|------|------|---------|---------|---------| +| 晨星 | 01/25 | ⭐⭐⭐ | ⭐⭐⭐⭐ | 成交量放大 2.5x 確認 | +| 吊人 | 01/28 | ⭐⭐ | ⭐ | 趨勢不明確,信號減弱 | +``` + +評估因素: +- **成交量確認**: 型態伴隨成交量放大更可靠 +- **趨勢背景**: 反轉型態需要明確先前趨勢 +- **位置**: 頂部/底部出現的反轉型態更有效 +- **型態清晰度**: 標準型態 vs 變體 + +### 2.2 多型態衝突分析 + +當多個信號衝突時,Agent 需判斷主導方向: + +```markdown +#### 信號衝突分析 +**衝突**: 01/26 空頭吞噬 vs 01/28 錘子 + +**AI 判斷**: 偏向看多 +**理由**: +1. 錘子為更近期信號 +2. 錘子出現在支撐位附近 +3. RSI 已進入超賣區,增加反彈機率 +``` + +### 2.3 趨勢整合分析 + +結合均線、趨勢、支撐壓力進行綜合判斷: + +```markdown +#### 趨勢整合分析 +**當前位置**: +- 股價 $145.20 +- MA20: $142.50 (股價在上方 ✅) +- MA50: $138.80 (股價在上方 ✅) + +**支撐/壓力**: +- 近期支撐: $140.00 (前低) +- 近期壓力: $150.00 (前高) + +**趨勢判斷**: 短期上升趨勢,中期盤整 +``` + +### 2.4 交易情境模擬 + +提供不同情境下的操作建議: + +```markdown +#### 情境分析 +**情境 A: 積極多頭** (信心高) +- 現價進場,停損 $138 +- 目標: $155 → $165 + +**情境 B: 保守觀望** (信心中) +- 等待回測 $142 (MA20) 再進場 +- 或等待突破 $150 確認 + +**情境 C: 空頭防禦** (風險意識) +- 若跌破 $140,可能轉空 +- 避免追高 +``` + +--- + +## 📝 Step 3: 輸出綜合報告 + +Agent 整合所有分析,產出最終報告: + +```markdown +# [TICKER] 酒田戰法智能分析報告 + +**分析時間**: YYYY-MM-DD HH:MM +**分析師**: AI Agent + +--- + +## 📊 核心結論 + +> **主要研判: [看多/看空/中性]** +> +> 基於 [X] 個多頭信號、[Y] 個空頭信號的綜合分析, +> 目前偏向 [方向],信心度 [高/中/低]。 + +## 🎯 交易建議 + +| 項目 | 建議 | 說明 | +|------|------|------| +| 操作方向 | 買入/賣出/觀望 | ... | +| 建議進場 | $XX.XX | 理由 | +| 停損價位 | $XX.XX | 基於 ATR/支撐 | +| 第一目標 | $XX.XX | 壓力/型態目標 | +| 第二目標 | $XX.XX | 延伸目標 | + +## 📈 型態分析摘要 +[列出重要型態及其解讀] + +## ⚠️ 風險提示 +[AI 識別的主要風險因素] + +## 📉 K 線圖表 +![K線圖](./output/[TICKER]_sakata.png) +``` + +--- + +## 📚 型態參考文檔 + +當需要解釋特定型態時,參考: +- [patterns/single_candle.md](patterns/single_candle.md) - 單根型態 +- [patterns/double_candle.md](patterns/double_candle.md) - 雙根型態 +- [patterns/triple_candle.md](patterns/triple_candle.md) - 三根型態 +- [patterns/complex_patterns.md](patterns/complex_patterns.md) - 複雜型態 + +--- + +## 🔧 技術組件 + +``` +.agent/skills/sakata/ +├── SKILL.md # 本指令檔 +├── patterns/ # 型態定義文檔 +├── scripts/ +│ ├── Dockerfile # Docker 環境 (TA-Lib) +│ ├── sakata_analyzer.py # 型態偵測主程式 +│ └── ... +└── output/ # 輸出目錄 +``` + +## ⚠️ 重要提醒 + +1. 腳本輸出只是**原始數據**,Agent 必須進行 Step 2 的智能分析 +2. 不要只列出型態,要**解讀其意義** +3. 多個信號時要**判斷優先級**,不是簡單加總 +4. 最終建議需考慮**風險報酬比** diff --git a/.agent/skills/sakata/output/AAPL_sakata.md b/.agent/skills/sakata/output/AAPL_sakata.md new file mode 100644 index 0000000..e959d60 --- /dev/null +++ b/.agent/skills/sakata/output/AAPL_sakata.md @@ -0,0 +1,79 @@ +# AAPL 酒田戰法分析報告 v2.0 + +**生成時間**: 2026-01-30 03:33 +**數據範圍**: 2025-11-03 ~ 2026-01-29 (60 日) +**當前價格**: $258.28 + +--- + +## 📊 核心結論 + +| 指標 | 數值 | +|------|------| +| 總信號數 | 4 | +| 多頭信號 | 2 🟢 | +| 空頭信號 | 2 🔴 | +| 整體偏向 | **NEUTRAL** | +| 信心度 | ⭐⭐⭐⭐ (4.0/5) | + + +## 📊 趨勢背景 + +| 指標 | 數值 | 說明 | +|------|------|------| +| **當前位置** | 中間 | 近60日區間位置 (33%) | +| **趨勢方向** | 下降趨勢 📉 | 基於 MA20 斜率 | +| **MA20** | $258.27 | 股價在上方 ✅ | +| **MA60** | $268.87 | 股價在下方 ❌ | +| **近期高點** | $288.62 | | +| **近期低點** | $243.42 | | + +--- + +## 🎯 最新交易建議 + +| 項目 | 數值 | +|------|------| +| **建議** | 🟢 買入 | +| **信號狀態** | ⚡ 特殊情況 | +| **觸發型態** | 向上窗口 | +| **信號日期** | 2026-01-27 (2 天前) | +| **建議進場** | $259.56 | +| **停損價位** | $250.22 | +| **目標價位** | $278.24 | +| **信號強度** | ⭐⭐⭐ | + +> **狀態說明**: ⚠️ 二空上漲中 (強勢軋空,勿追空) + +> **酒田特殊警示**: ⚠️ 二空上漲中 (強勢軋空,勿追空) + +--- + +## 📈 偵測到的型態 + +| 日期 | 型態 | 英文名稱 | 方向 | 強度 | +|------|------|---------|------|------| +| 01/27 | 向上窗口 | Rising Window | 🟢 多頭 | ⭐⭐⭐⭐⭐ | +| 01/26 | 向上窗口 | Rising Window | 🟢 多頭 | ⭐⭐⭐⭐⭐ | +| 01/20 | 向下窗口 | Falling Window | 🔴 空頭 | ⭐⭐⭐⭐⭐ | +| 01/08 | 向下窗口 | Falling Window | 🔴 空頭 | ⭐⭐⭐⭐⭐ | + +--- + +## 📉 K 線圖表 + +![AAPL 酒田戰法分析](AAPL_sakata.png) + +--- + +## ⚠️ 風險提示 + +1. 本分析僅供參考,不構成投資建議 +2. K 線型態需配合成交量、趨勢等其他指標確認 +3. **三山/三川型態需跌破/突破頸線才算確立** +4. 請嚴格執行停損紀律 +5. 過去表現不代表未來結果 + +--- + +*由酒田戰法 Agent Skill v2.0 自動生成* diff --git a/.agent/skills/sakata/output/AAPL_sakata.png b/.agent/skills/sakata/output/AAPL_sakata.png new file mode 100644 index 0000000..edb5e10 Binary files /dev/null and b/.agent/skills/sakata/output/AAPL_sakata.png differ diff --git a/.agent/skills/sakata/patterns/complex_patterns.md b/.agent/skills/sakata/patterns/complex_patterns.md new file mode 100644 index 0000000..c7dd7f3 --- /dev/null +++ b/.agent/skills/sakata/patterns/complex_patterns.md @@ -0,0 +1,135 @@ +# 複雜型態 (Complex Patterns) + +酒田五法及其他複雜 K 線型態。 + +--- + +## 酒田五法 (Sakata Goho) + +### 1. 三山 (Sanzan / Three Mountains) +**別名**: 三重頂、頭肩頂 +**信號**: 強空頭反轉 ⭐⭐⭐⭐⭐ +**描述**: +- 價格三次觸及相似高點後回落 +- 形成 M 型或頭肩型結構 +- 頸線跌破確認 + +**交易策略**: +- 進場: 跌破頸線時放空 +- 停損: 最後一個高點上方 +- 目標: 頸線向下投射等距離 + +--- + +### 2. 三川 (Sansen / Three Rivers) +**別名**: 三重底、頭肩底 +**信號**: 強多頭反轉 ⭐⭐⭐⭐⭐ +**描述**: +- 價格三次觸及相似低點後反彈 +- 形成 W 型或倒頭肩型結構 +- 頸線突破確認 + +**交易策略**: +- 進場: 突破頸線時做多 +- 停損: 最後一個低點下方 +- 目標: 頸線向上投射等距離 + +--- + +### 3. 三空 (Sanku / Three Gaps) + +#### 三空上漲 (Three Gaps Up) +**信號**: 空頭反轉 ⭐⭐⭐⭐ +**描述**: 連續三個向上跳空 +**意義**: 多頭力竭,即將回調 + +#### 三空下跌 (Three Gaps Down) +**信號**: 多頭反轉 ⭐⭐⭐⭐ +**描述**: 連續三個向下跳空 +**意義**: 空頭力竭,即將反彈 + +**格言**: "三空不補,後市必補" + +--- + +### 4. 三兵 (Sanpei / Three Soldiers) + +#### 三白兵 (Red Three Soldiers) +見三根型態文檔 + +#### 三黑鴉 (Three Black Crows) +見三根型態文檔 + +--- + +### 5. 三法 (Sanpoh / Three Methods) + +#### 上升三法 (Rising Three Methods) +**信號**: 多頭延續 ⭐⭐⭐⭐ +**描述**: +1. 大陽線 +2. 2-5 根小陰線 (在第一根範圍內) +3. 大陽線突破新高 + +**意義**: 健康的休整後繼續上漲 + +#### 下降三法 (Falling Three Methods) +**信號**: 空頭延續 ⭐⭐⭐⭐ +**描述**: +1. 大陰線 +2. 2-5 根小陽線 (在第一根範圍內) +3. 大陰線跌破新低 + +**意義**: 健康的反彈後繼續下跌 + +--- + +## 島型型態 (Island Patterns) + +### 島型頂 (Island Top) +**信號**: 強空頭反轉 ⭐⭐⭐⭐⭐ +**描述**: +- 向上跳空 +- 數日在高位整理 +- 向下跳空,形成完全隔離的「島」 + +### 島型底 (Island Bottom) +**信號**: 強多頭反轉 ⭐⭐⭐⭐⭐ +**描述**: 島型頂的相反版本 + +--- + +## 圓弧型態 + +### 圓頂 (Rounding Top) +**信號**: 空頭反轉 ⭐⭐⭐ +**描述**: 價格緩慢形成圓弧頂部 +**週期**: 通常需要較長時間形成 + +### 圓底 (Rounding Bottom) +**信號**: 多頭反轉 ⭐⭐⭐ +**描述**: 價格緩慢形成圓弧底部 +**別名**: 碟形底 + +--- + +## 缺口型態 + +### 竭盡缺口 (上) (Exhaustion Gap Up) +**信號**: 空頭反轉 ⭐⭐⭐⭐ +**描述**: 趨勢末端的最後一個跳空 +**特徵**: 通常伴隨極大成交量 + +### 竭盡缺口 (下) (Exhaustion Gap Down) +**信號**: 多頭反轉 ⭐⭐⭐⭐ +**描述**: 趨勢末端的最後一個跳空 + +--- + +## 其他複雜型態 + +- 梯底 (Ladder Bottom) +- 藏嬰吞噬 (Concealing Baby Swallow) +- 棍子三明治 (Stick Sandwich) +- 脫離 (Breakaway) +- 鋪墊 (Mat Hold) diff --git a/.agent/skills/sakata/patterns/double_candle.md b/.agent/skills/sakata/patterns/double_candle.md new file mode 100644 index 0000000..2e1df10 --- /dev/null +++ b/.agent/skills/sakata/patterns/double_candle.md @@ -0,0 +1,98 @@ +# 雙根 K 線型態 (Double Candle Patterns) + +25 種雙根 K 線型態定義。 + +--- + +## 反轉型態 + +### 1. 多頭吞噬 (Bullish Engulfing) +**信號**: 強多頭反轉 +**描述**: 大陽線完全包覆前一根陰線 +**條件**: 出現在下跌趨勢末端 + +### 2. 空頭吞噬 (Bearish Engulfing) +**信號**: 強空頭反轉 +**描述**: 大陰線完全包覆前一根陽線 +**條件**: 出現在上升趨勢末端 + +### 3. 烏雲蓋頂 (Dark Cloud Cover) +**信號**: 空頭反轉 +**描述**: 陰線開盤高於前日高點,收盤低於前日實體中點 +**強度**: 中等 + +### 4. 刺透 (Piercing) +**信號**: 多頭反轉 +**描述**: 陽線開盤低於前日低點,收盤高於前日實體中點 +**強度**: 中等 + +### 5. 孕線 (Harami) +**信號**: 反轉 (需確認) +**描述**: 小實體完全在前一根大實體內 +**強度**: 較弱,需等確認K線 + +### 6. 十字孕線 (Harami Cross) +**信號**: 反轉 +**描述**: 孕線但第二根為十字星 +**強度**: 比普通孕線更強 + +### 7. 鑷子頂 (Tweezers Top) +**信號**: 空頭反轉 +**描述**: 連續兩根K線具有相同高點 +**條件**: 出現在上升趨勢 + +### 8. 鑷子底 (Tweezers Bottom) +**信號**: 多頭反轉 +**描述**: 連續兩根K線具有相同低點 +**條件**: 出現在下跌趨勢 + +### 9. 反沖 (Kicking) +**信號**: 強反轉 +**描述**: 光頭陽線後跳空光頭陰線 (或相反) +**強度**: 極強 + +### 10. 反擊線 (Counterattack) +**信號**: 反轉 +**描述**: 兩根相反顏色K線,收盤價相同 +**強度**: 中等 + +--- + +## 延續型態 + +### 11. 頸內線 (In Neck) +**信號**: 空頭延續 +**描述**: 下跌後陽線收盤僅達前日收盤 +**意義**: 反彈失敗 + +### 12. 頸上線 (On Neck) +**信號**: 空頭延續 +**描述**: 下跌後陽線收盤達前日低點 +**意義**: 反彈力道不足 + +### 13. 切入線 (Thrusting) +**信號**: 空頭延續 +**描述**: 陽線收盤達前日實體中點以下 +**意義**: 反彈但未能突破 + +### 14. 分離線 (Separating Lines) +**信號**: 趨勢延續 +**描述**: 兩根同向K線,開盤價相同但跳空 +**強度**: 中等 + +### 15. 向上窗口 (Rising Window) +**信號**: 多頭延續 +**描述**: 跳空向上,留下缺口 +**意義**: 強勁買盤 + +### 16. 向下窗口 (Falling Window) +**信號**: 空頭延續 +**描述**: 跳空向下,留下缺口 +**意義**: 強勁賣盤 + +--- + +## 其他雙根型態 + +### 17-25. 其他變體 +相同低價、家鴿、陷阱、改良陷阱、二隻烏鴉等。 diff --git a/.agent/skills/sakata/patterns/single_candle.md b/.agent/skills/sakata/patterns/single_candle.md new file mode 100644 index 0000000..b246f96 --- /dev/null +++ b/.agent/skills/sakata/patterns/single_candle.md @@ -0,0 +1,108 @@ +# 單根 K 線型態 (Single Candle Patterns) + +15 種單根 K 線型態定義。 + +--- + +## 1. 十字星 (Doji) +**信號**: 中性/反轉 +**描述**: 開盤價 ≈ 收盤價,上下影線相當 +**意義**: 市場猶豫,趨勢可能反轉 + +--- + +## 2. 蜻蜓十字 (Dragonfly Doji) +**信號**: 多頭反轉 +**描述**: 長下影線,無上影線,開=收 +**意義**: 下跌後買盤進場,看漲信號 + +--- + +## 3. 墓碑十字 (Gravestone Doji) +**信號**: 空頭反轉 +**描述**: 長上影線,無下影線,開=收 +**意義**: 上漲後賣壓湧現,看跌信號 + +--- + +## 4. 長腳十字 (Long Legged Doji) +**信號**: 中性 +**描述**: 極長的上下影線,開=收 +**意義**: 極度猶豫,大幅波動 + +--- + +## 5. 錘子 (Hammer) +**信號**: 多頭反轉 +**描述**: 小實體在上,長下影線 (≥2倍實體) +**意義**: 跌勢後的買盤反擊 + +--- + +## 6. 吊人 (Hanging Man) +**信號**: 空頭反轉 +**描述**: 與錘子形態相同,但出現在上升趨勢頂部 +**意義**: 賣壓開始顯現 + +--- + +## 7. 流星 (Shooting Star) +**信號**: 空頭反轉 +**描述**: 小實體在下,長上影線 (≥2倍實體) +**意義**: 多頭力竭,看跌 + +--- + +## 8. 倒錘子 (Inverted Hammer) +**信號**: 多頭反轉 +**描述**: 與流星相同,但出現在下跌趨勢底部 +**意義**: 潛在反轉信號 + +--- + +## 9. 光頭光腳 (Marubozu) +**信號**: 趨勢延續 +**描述**: 無上下影線的大陽/大陰線 +**意義**: 強勁的單方向動能 + +--- + +## 10. 陀螺 (Spinning Top) +**信號**: 中性 +**描述**: 小實體,短上下影線 +**意義**: 市場平衡,猶豫 + +--- + +## 11. 高浪線 (High Wave) +**信號**: 中性/反轉 +**描述**: 極小實體,極長上下影線 +**意義**: 極度不確定性 + +--- + +## 12. 捉腰帶線 (Belt Hold) +**信號**: 反轉 +**描述**: 大陽線開盤為最低價 / 大陰線開盤為最高價 +**意義**: 強勁的趨勢起始 + +--- + +## 13. 探底 (Takuri) +**信號**: 多頭反轉 +**描述**: 類似錘子但下影線更長 (≥3倍實體) +**意義**: 極強的買盤反擊 + +--- + +## 14. 強勢陽線 (Strong Bullish) +**信號**: 多頭延續 +**描述**: 大陽線,實體佔總長度 >70% +**意義**: 強勁買盤 + +--- + +## 15. 強勢陰線 (Strong Bearish) +**信號**: 空頭延續 +**描述**: 大陰線,實體佔總長度 >70% +**意義**: 強勁賣盤 diff --git a/.agent/skills/sakata/patterns/triple_candle.md b/.agent/skills/sakata/patterns/triple_candle.md new file mode 100644 index 0000000..47bf473 --- /dev/null +++ b/.agent/skills/sakata/patterns/triple_candle.md @@ -0,0 +1,111 @@ +# 三根 K 線型態 (Triple Candle Patterns) + +20 種三根 K 線型態定義。 + +--- + +## 強反轉型態 + +### 1. 晨星 (Morning Star) +**信號**: 強多頭反轉 ⭐⭐⭐⭐⭐ +**描述**: +1. 大陰線 +2. 跳空小實體 (任何顏色) +3. 大陽線收盤超過第一根實體中點 + +**條件**: 出現在下跌趨勢末端 + +### 2. 十字晨星 (Morning Doji Star) +**信號**: 極強多頭反轉 ⭐⭐⭐⭐⭐ +**描述**: 晨星變體,中間為十字星 +**強度**: 比普通晨星更強 + +### 3. 夜星 (Evening Star) +**信號**: 強空頭反轉 ⭐⭐⭐⭐⭐ +**描述**: +1. 大陽線 +2. 跳空小實體 +3. 大陰線收盤低於第一根實體中點 + +**條件**: 出現在上升趨勢末端 + +### 4. 十字夜星 (Evening Doji Star) +**信號**: 極強空頭反轉 ⭐⭐⭐⭐⭐ +**描述**: 夜星變體,中間為十字星 +**強度**: 比普通夜星更強 + +### 5. 棄嬰 (Abandoned Baby) +**信號**: 極強反轉 ⭐⭐⭐⭐⭐ +**描述**: 類似晨星/夜星,但中間十字星與兩側完全跳空 +**稀有度**: 非常罕見 + +--- + +## 三兵型態 (Three Soldiers) + +### 6. 三白兵 (Three White Soldiers) +**信號**: 強多頭 ⭐⭐⭐⭐ +**描述**: 連續三根陽線,每根開盤在前日實體內,收盤創新高 +**條件**: 底部出現效果最佳 + +### 7. 三黑鴉 (Three Black Crows) +**信號**: 強空頭 ⭐⭐⭐⭐ +**描述**: 連續三根陰線,每根開盤在前日實體內,收盤創新低 +**條件**: 頂部出現效果最佳 + +### 8. 前進受阻 (Advance Block) +**信號**: 弱化多頭 ⭐⭐⭐ +**描述**: 三白兵變體,後面陽線實體漸小,上影線漸長 +**意義**: 買盤力道減弱 + +### 9. 相同三鴉 (Identical Three Crows) +**信號**: 極強空頭 ⭐⭐⭐⭐⭐ +**描述**: 三黑鴉變體,每根開盤等於前日收盤 +**強度**: 比普通三黑鴉更強 + +--- + +## 內部/外部型態 + +### 10. 三內部上升 (Three Inside Up) +**信號**: 多頭反轉 ⭐⭐⭐⭐ +**描述**: 孕線後第三根陽線突破第一根高點 +**強度**: 中高 + +### 11. 三內部下降 (Three Inside Down) +**信號**: 空頭反轉 ⭐⭐⭐⭐ +**描述**: 孕線後第三根陰線跌破第一根低點 +**強度**: 中高 + +### 12. 三外部上升 (Three Outside Up) +**信號**: 多頭反轉 ⭐⭐⭐⭐ +**描述**: 多頭吞噬後第三根陽線確認 +**強度**: 中高 + +### 13. 三外部下降 (Three Outside Down) +**信號**: 空頭反轉 ⭐⭐⭐⭐ +**描述**: 空頭吞噬後第三根陰線確認 +**強度**: 中高 + +--- + +## 其他三根型態 + +### 14. 南方三星 (Three Stars In South) +**信號**: 多頭反轉 +**描述**: 三根依次縮短的陰線,代表賣壓衰竭 + +### 15. 三星 (Tristar) +**信號**: 反轉 +**描述**: 連續三根十字星 + +### 16. 向上跳空二鴉 (Upside Gap Two Crows) +**信號**: 空頭反轉 +**描述**: 跳空陰線後再一根更大陰線 + +### 17. 奇特三川底 (Unique 3 River) +**信號**: 多頭反轉 +**描述**: 特殊底部反轉型態 + +### 18-20. 跳空相關型態 +跳空並列三法、跳空缺口等。 diff --git a/.agent/skills/sakata/requirements.txt b/.agent/skills/sakata/requirements.txt new file mode 100644 index 0000000..1f6ac1d --- /dev/null +++ b/.agent/skills/sakata/requirements.txt @@ -0,0 +1,6 @@ +pandas>=2.0.0 +numpy>=1.24.0 +yfinance>=0.2.30 +matplotlib>=3.7.0 +ta-lib>=0.4.28 +mplfinance>=0.12.10b0 diff --git a/.agent/skills/sakata/scripts/chart_plotter.py b/.agent/skills/sakata/scripts/chart_plotter.py new file mode 100644 index 0000000..94d13b6 --- /dev/null +++ b/.agent/skills/sakata/scripts/chart_plotter.py @@ -0,0 +1,326 @@ +""" +Sakata Chart Plotter v2.0 +K 線圖表繪製 + 型態標註 + +改良: 使用英文標籤避免中文字體問題 +""" + +import pandas as pd +import numpy as np +import mplfinance as mpf +import matplotlib.pyplot as plt +from matplotlib.patches import FancyBboxPatch +import matplotlib.font_manager as fm +import os +from datetime import datetime + + +def calculate_support_resistance(df: pd.DataFrame, window: int = 20) -> dict: + """Calculate support and resistance levels.""" + highs = df['High'].values + lows = df['Low'].values + closes = df['Close'].values + + resistance_levels = [] + support_levels = [] + + for i in range(window, len(df) - window): + if highs[i] == max(highs[i-window:i+window+1]): + resistance_levels.append(highs[i]) + if lows[i] == min(lows[i-window:i+window+1]): + support_levels.append(lows[i]) + + def cluster_levels(levels, threshold=0.02): + if not levels: + return [] + levels = sorted(levels) + clusters = [[levels[0]]] + for level in levels[1:]: + if (level - clusters[-1][-1]) / clusters[-1][-1] < threshold: + clusters[-1].append(level) + else: + clusters.append([level]) + return [np.mean(c) for c in clusters] + + resistance = cluster_levels(resistance_levels)[-3:] if resistance_levels else [] + support = cluster_levels(support_levels)[:3] if support_levels else [] + + return { + 'resistance': resistance, + 'support': support, + 'current_price': closes[-1] + } + + +def calculate_rsi(df: pd.DataFrame, period: int = 14) -> pd.Series: + """Calculate RSI indicator.""" + delta = df['Close'].diff() + gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() + loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() + rs = gain / loss + rsi = 100 - (100 / (1 + rs)) + return rsi + + +def create_sakata_chart( + df: pd.DataFrame, + detected_patterns: list, + signals: list, + ticker: str, + output_dir: str = './output' +) -> str: + """ + Create a candlestick chart with Sakata pattern annotations. + Uses English labels for better font compatibility. + """ + # Ensure output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Calculate technical indicators + df = df.copy() + df['MA20'] = df['Close'].rolling(window=20).mean() + df['MA50'] = df['Close'].rolling(window=50).mean() + df['RSI'] = calculate_rsi(df) + + # Calculate support/resistance + sr_levels = calculate_support_resistance(df) + + # Prepare annotation markers + buy_signals = [] + sell_signals = [] + + # 去重: 每天每型態只標記一次 + seen_dates_buy = set() + seen_dates_sell = set() + + for pattern in detected_patterns[-30:]: + date = pattern['date'] + if date in df.index: + date_key = str(date)[:10] + if pattern['direction'] == 'bullish': + if date_key not in seen_dates_buy: + buy_signals.append({ + 'date': date, + 'price': df.loc[date, 'Low'] * 0.98, + 'name': pattern['english'] # 使用英文名 + }) + seen_dates_buy.add(date_key) + else: + if date_key not in seen_dates_sell: + sell_signals.append({ + 'date': date, + 'price': df.loc[date, 'High'] * 1.02, + 'name': pattern['english'] # 使用英文名 + }) + seen_dates_sell.add(date_key) + + # Create marker arrays + buy_markers = np.nan * np.ones(len(df)) + sell_markers = np.nan * np.ones(len(df)) + + for sig in buy_signals: + if sig['date'] in df.index: + idx = df.index.get_loc(sig['date']) + buy_markers[idx] = df.iloc[idx]['Low'] * 0.97 + + for sig in sell_signals: + if sig['date'] in df.index: + idx = df.index.get_loc(sig['date']) + sell_markers[idx] = df.iloc[idx]['High'] * 1.03 + + # Define custom style + mc = mpf.make_marketcolors( + up='#26a69a', + down='#ef5350', + edge='inherit', + wick='inherit', + volume='in', + ohlc='i' + ) + + s = mpf.make_mpf_style( + marketcolors=mc, + gridstyle='-', + gridcolor='#e0e0e0', + y_on_right=True, + rc={ + 'font.size': 10, + 'axes.labelsize': 12, + 'axes.titlesize': 14 + } + ) + + # Create additional plots + apds = [] + + # Buy/Sell markers + if not np.all(np.isnan(buy_markers)): + apds.append(mpf.make_addplot( + buy_markers, type='scatter', markersize=100, + marker='^', color='#26a69a', panel=0 + )) + + if not np.all(np.isnan(sell_markers)): + apds.append(mpf.make_addplot( + sell_markers, type='scatter', markersize=100, + marker='v', color='#ef5350', panel=0 + )) + + # Moving averages + apds.append(mpf.make_addplot(df['MA20'], color='#2196F3', width=1.5, panel=0)) + apds.append(mpf.make_addplot(df['MA50'], color='#FF9800', width=1.5, panel=0)) + + # RSI in separate panel + apds.append(mpf.make_addplot(df['RSI'], color='#9C27B0', width=1, panel=2, ylabel='RSI')) + + # RSI overbought/oversold lines + rsi_70 = np.full(len(df), 70) + rsi_30 = np.full(len(df), 30) + apds.append(mpf.make_addplot(rsi_70, color='#ef5350', width=0.5, linestyle='--', panel=2)) + apds.append(mpf.make_addplot(rsi_30, color='#26a69a', width=0.5, linestyle='--', panel=2)) + + # Generate main chart (使用英文標題避免字體問題) + fig, axes = mpf.plot( + df, + type='candle', + style=s, + title=f'\n{ticker} - Sakata Pattern Analysis', + ylabel='Price', + ylabel_lower='Volume', + volume=True, + addplot=apds if apds else None, + figsize=(18, 12), + returnfig=True, + panel_ratios=(5, 1.5, 1.5) + ) + + ax = axes[0] + + # Draw support lines + for level in sr_levels['support']: + ax.axhline(y=level, color='#26a69a', linestyle='--', linewidth=1.5, alpha=0.7) + ax.text(df.index[2], level, f' Support ${level:.2f}', + fontsize=9, color='#26a69a', va='bottom') + + # Draw resistance lines + for level in sr_levels['resistance']: + ax.axhline(y=level, color='#ef5350', linestyle='--', linewidth=1.5, alpha=0.7) + ax.text(df.index[2], level, f' Resistance ${level:.2f}', + fontsize=9, color='#ef5350', va='top') + + # Add signal summary box (使用英文) + bullish_count = len(set(p['date'] for p in detected_patterns if p['direction'] == 'bullish')) + bearish_count = len(set(p['date'] for p in detected_patterns if p['direction'] == 'bearish')) + + summary_text = ( + f"Signal Summary\n" + f"{'='*14}\n" + f"Bullish: {bullish_count}\n" + f"Bearish: {bearish_count}\n" + f"{'='*14}\n" + f"Close: ${sr_levels['current_price']:.2f}" + ) + ax.text( + 0.02, 0.98, summary_text, + transform=ax.transAxes, fontsize=10, + verticalalignment='top', + bbox=dict(boxstyle='round,pad=0.5', facecolor='white', alpha=0.9, edgecolor='#ccc'), + family='monospace' + ) + + # Add recent patterns annotation (使用英文) + if detected_patterns: + # 去重 + seen = set() + unique_recent = [] + for p in detected_patterns[:10]: + key = (str(p['date'])[:10], p['english']) + if key not in seen: + seen.add(key) + unique_recent.append(p) + + pattern_text = "Recent Patterns\n" + "="*16 + "\n" + for p in unique_recent[:6]: + date_str = p['date'].strftime('%m/%d') if hasattr(p['date'], 'strftime') else str(p['date'])[:10] + signal_emoji = '+' if p['direction'] == 'bullish' else '-' + # 截斷過長的型態名稱 + name = p['english'][:18] if len(p['english']) > 18 else p['english'] + pattern_text += f"{date_str}: {name} [{signal_emoji}]\n" + + ax.text( + 0.98, 0.98, pattern_text, + transform=ax.transAxes, fontsize=9, + verticalalignment='top', horizontalalignment='right', + bbox=dict(boxstyle='round,pad=0.5', facecolor='white', alpha=0.9, edgecolor='#ccc'), + family='monospace' + ) + + # Add legend + from matplotlib.lines import Line2D + legend_elements = [ + Line2D([0], [0], color='#2196F3', linewidth=2, label='MA20'), + Line2D([0], [0], color='#FF9800', linewidth=2, label='MA50'), + Line2D([0], [0], color='#26a69a', linestyle='--', linewidth=1.5, label='Support'), + Line2D([0], [0], color='#ef5350', linestyle='--', linewidth=1.5, label='Resistance'), + Line2D([0], [0], marker='^', color='#26a69a', linestyle='None', markersize=10, label='Bullish Signal'), + Line2D([0], [0], marker='v', color='#ef5350', linestyle='None', markersize=10, label='Bearish Signal'), + ] + ax.legend(handles=legend_elements, loc='upper center', ncol=6, fontsize=8, + bbox_to_anchor=(0.5, 1.02), framealpha=0.9) + + # Save chart + output_path = os.path.join(output_dir, f'{ticker}_sakata.png') + fig.savefig(output_path, dpi=150, bbox_inches='tight', facecolor='white') + plt.close(fig) + + return output_path + + +def create_pattern_detail_chart( + df: pd.DataFrame, + pattern: dict, + ticker: str, + output_dir: str = './output' +) -> str: + """ + Create a detailed chart for a specific pattern occurrence. + """ + # Focus on 20 days around the pattern + pattern_date = pattern['date'] + + if pattern_date in df.index: + idx = df.index.get_loc(pattern_date) + start_idx = max(0, idx - 10) + end_idx = min(len(df), idx + 10) + focused_df = df.iloc[start_idx:end_idx].copy() + else: + focused_df = df.tail(20).copy() + + # Create marker for the pattern + marker = np.nan * np.ones(len(focused_df)) + if pattern_date in focused_df.index: + local_idx = focused_df.index.get_loc(pattern_date) + marker[local_idx] = focused_df.iloc[local_idx]['Low'] * 0.97 + + mc = mpf.make_marketcolors(up='#26a69a', down='#ef5350', edge='inherit', wick='inherit') + s = mpf.make_mpf_style(marketcolors=mc, gridstyle='-', gridcolor='#e0e0e0') + + apds = [] + if not np.all(np.isnan(marker)): + apds.append(mpf.make_addplot(marker, type='scatter', markersize=200, marker='*', color='gold')) + + fig, axes = mpf.plot( + focused_df, + type='candle', + style=s, + title=f"\n{ticker} - {pattern['english']}", + figsize=(10, 6), + returnfig=True, + addplot=apds if apds else None + ) + + output_path = os.path.join(output_dir, f"{ticker}_{pattern['pattern']}_detail.png") + fig.savefig(output_path, dpi=150, bbox_inches='tight', facecolor='white') + plt.close(fig) + + return output_path diff --git a/.agent/skills/sakata/scripts/output/AAPL_sakata.md b/.agent/skills/sakata/scripts/output/AAPL_sakata.md new file mode 100644 index 0000000..2f0abe2 --- /dev/null +++ b/.agent/skills/sakata/scripts/output/AAPL_sakata.md @@ -0,0 +1,82 @@ +# AAPL 酒田戰法分析報告 + +**生成時間**: 2026-01-30 02:43 +**數據範圍**: 2025-09-02 ~ 2026-01-29 (104 日) +**當前價格**: $258.28 + +--- + +## 📊 信號摘要 + +| 指標 | 數值 | +|------|------| +| 總信號數 | 57 | +| 多頭信號 | 17 🟢 | +| 空頭信號 | 40 🔴 | +| 整體偏向 | **BEARISH** | +| 平均強度 | ⭐⭐⭐⭐ (4.9/5) | + +--- + +## 🎯 最新交易建議 + +| 項目 | 數值 | +|------|------| +| **建議** | 🔴 賣出 | +| **觸發型態** | 三山 | +| **信號日期** | 2026-01-29 | +| **建議進場** | $256.99 | +| **停損價位** | $266.44 | +| **目標價位** | $238.08 | +| **信號強度** | ⭐⭐⭐⭐⭐ | + +--- + +## 📈 偵測到的型態 + +| 日期 | 型態 | 英文名稱 | 方向 | 強度 | +|------|------|---------|------|------| +| 01/29 | 三山 | Three Mountains (Triple Top) | 🔴 空頭 | ⭐⭐⭐⭐⭐ | +| 01/28 | 三山 | Three Mountains (Triple Top) | 🔴 空頭 | ⭐⭐⭐⭐⭐ | +| 01/27 | 三山 | Three Mountains (Triple Top) | 🔴 空頭 | ⭐⭐⭐⭐⭐ | +| 01/27 | 三川 | Three Rivers (Triple Bottom) | 🟢 多頭 | ⭐⭐⭐⭐⭐ | +| 01/27 | 向上窗口 | Rising Window | 🟢 多頭 | ⭐⭐⭐⭐⭐ | +| 01/26 | 三山 | Three Mountains (Triple Top) | 🔴 空頭 | ⭐⭐⭐⭐⭐ | +| 01/26 | 三川 | Three Rivers (Triple Bottom) | 🟢 多頭 | ⭐⭐⭐⭐⭐ | +| 01/26 | 向上窗口 | Rising Window | 🟢 多頭 | ⭐⭐⭐⭐⭐ | +| 01/23 | 三山 | Three Mountains (Triple Top) | 🔴 空頭 | ⭐⭐⭐⭐⭐ | +| 01/23 | 三川 | Three Rivers (Triple Bottom) | 🟢 多頭 | ⭐⭐⭐⭐⭐ | +| 01/22 | 三山 | Three Mountains (Triple Top) | 🔴 空頭 | ⭐⭐⭐⭐⭐ | +| 01/22 | 三川 | Three Rivers (Triple Bottom) | 🟢 多頭 | ⭐⭐⭐⭐⭐ | +| 01/21 | 三山 | Three Mountains (Triple Top) | 🔴 空頭 | ⭐⭐⭐⭐⭐ | +| 01/21 | 三川 | Three Rivers (Triple Bottom) | 🟢 多頭 | ⭐⭐⭐⭐⭐ | +| 01/20 | 三山 | Three Mountains (Triple Top) | 🔴 空頭 | ⭐⭐⭐⭐⭐ | + +--- + +## 📉 K 線圖表 + +![AAPL 酒田戰法分析](AAPL_sakata.png) + +--- + +## 📚 型態類型說明 + +| 類型 | 說明 | +|------|------| +| 反轉型態 | 趨勢可能即將改變,需配合其他指標確認 | +| 延續型態 | 當前趨勢可能持續,可順勢操作 | +| 中性型態 | 市場猶豫,建議觀望 | + +--- + +## ⚠️ 風險提示 + +1. 本分析僅供參考,不構成投資建議 +2. K 線型態需配合成交量、趨勢等其他指標確認 +3. 請嚴格執行停損紀律 +4. 過去表現不代表未來結果 + +--- + +*由酒田戰法 Agent Skill 自動生成* diff --git a/.agent/skills/sakata/scripts/output/AAPL_sakata.png b/.agent/skills/sakata/scripts/output/AAPL_sakata.png new file mode 100644 index 0000000..b51415a Binary files /dev/null and b/.agent/skills/sakata/scripts/output/AAPL_sakata.png differ diff --git a/.agent/skills/sakata/scripts/pattern_detector.py b/.agent/skills/sakata/scripts/pattern_detector.py new file mode 100644 index 0000000..ff0f34c --- /dev/null +++ b/.agent/skills/sakata/scripts/pattern_detector.py @@ -0,0 +1,758 @@ +""" +Sakata 80 Candlestick Pattern Detector v2.0 +酒田戰法 80 種 K 線型態偵測器 (改良版) + +改良重點: +1. 趨勢濾網: 高檔偵測三山, 低檔偵測三川 +2. 時間跨度: 三山/三川至少間隔 10 根 K 線 +3. 結構分析: 計算頸線、量價背離 +""" + +import numpy as np +import pandas as pd +import talib + +# Pattern categories and their TA-Lib function names +TALIB_PATTERNS = { + # Single Candle Patterns (單根型態) + 'CDL_DOJI': {'name': '十字星', 'en': 'Doji', 'type': 'reversal', 'signal': 'neutral'}, + 'CDL_DOJISTAR': {'name': '十字星', 'en': 'Doji Star', 'type': 'reversal', 'signal': 'neutral'}, + 'CDL_DRAGONFLYDOJI': {'name': '蜻蜓十字', 'en': 'Dragonfly Doji', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_GRAVESTONEDOJI': {'name': '墓碑十字', 'en': 'Gravestone Doji', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_LONGLEGGEDDOJI': {'name': '長腳十字', 'en': 'Long Legged Doji', 'type': 'reversal', 'signal': 'neutral'}, + 'CDL_HAMMER': {'name': '錘子', 'en': 'Hammer', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_HANGINGMAN': {'name': '吊人', 'en': 'Hanging Man', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_SHOOTINGSTAR': {'name': '流星', 'en': 'Shooting Star', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_INVERTEDHAMMER': {'name': '倒錘子', 'en': 'Inverted Hammer', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_MARUBOZU': {'name': '光頭光腳', 'en': 'Marubozu', 'type': 'continuation', 'signal': 'trend'}, + 'CDL_SPINNINGTOP': {'name': '陀螺', 'en': 'Spinning Top', 'type': 'reversal', 'signal': 'neutral'}, + 'CDL_HIGHWAVE': {'name': '高浪線', 'en': 'High Wave', 'type': 'reversal', 'signal': 'neutral'}, + 'CDL_BELTHOLD': {'name': '捉腰帶線', 'en': 'Belt Hold', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_TAKURI': {'name': '探底', 'en': 'Takuri', 'type': 'reversal', 'signal': 'bullish'}, + + # Double Candle Patterns (雙根型態) + 'CDL_ENGULFING': {'name': '吞噬', 'en': 'Engulfing', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_DARKCLOUDCOVER': {'name': '烏雲蓋頂', 'en': 'Dark Cloud Cover', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_PIERCING': {'name': '刺透', 'en': 'Piercing', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_HARAMI': {'name': '孕線', 'en': 'Harami', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_HARAMICROSS': {'name': '十字孕線', 'en': 'Harami Cross', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_MATCHINGLOW': {'name': '相同低價', 'en': 'Matching Low', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_KICKING': {'name': '反沖', 'en': 'Kicking', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_KICKINGBYLENGTH': {'name': '反沖(長度)', 'en': 'Kicking by Length', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_COUNTERATTACK': {'name': '反擊線', 'en': 'Counterattack', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_CLOSINGMARUBOZU': {'name': '收盤光頭', 'en': 'Closing Marubozu', 'type': 'continuation', 'signal': 'trend'}, + 'CDL_HIKKAKE': {'name': '陷阱', 'en': 'Hikkake', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_HIKKAKEMOD': {'name': '改良陷阱', 'en': 'Modified Hikkake', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_HOMINGPIGEON': {'name': '家鴿', 'en': 'Homing Pigeon', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_INNECK': {'name': '頸內線', 'en': 'In Neck', 'type': 'continuation', 'signal': 'bearish'}, + 'CDL_ONNECK': {'name': '頸上線', 'en': 'On Neck', 'type': 'continuation', 'signal': 'bearish'}, + 'CDL_THRUSTING': {'name': '切入線', 'en': 'Thrusting', 'type': 'continuation', 'signal': 'bearish'}, + 'CDL_SEPARATINGLINES': {'name': '分離線', 'en': 'Separating Lines', 'type': 'continuation', 'signal': 'trend'}, + 'CDL_TWOCROWS': {'name': '二隻烏鴉', 'en': 'Two Crows', 'type': 'reversal', 'signal': 'bearish'}, + + # Triple Candle Patterns (三根型態) + 'CDL_MORNINGSTAR': {'name': '晨星', 'en': 'Morning Star', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_MORNINGDOJISTAR': {'name': '十字晨星', 'en': 'Morning Doji Star', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_EVENINGSTAR': {'name': '夜星', 'en': 'Evening Star', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_EVENINGDOJISTAR': {'name': '十字夜星', 'en': 'Evening Doji Star', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_ABANDONEDBABY': {'name': '棄嬰', 'en': 'Abandoned Baby', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_3WHITESOLDIERS': {'name': '三白兵', 'en': 'Three White Soldiers', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_3BLACKCROWS': {'name': '三黑鴉', 'en': 'Three Black Crows', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_3INSIDE': {'name': '三內部', 'en': 'Three Inside', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_3OUTSIDE': {'name': '三外部', 'en': 'Three Outside', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_3STARSINSOUTH': {'name': '南方三星', 'en': 'Three Stars In South', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_TRISTAR': {'name': '三星', 'en': 'Tristar', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_UPSIDEGAP2CROWS': {'name': '向上跳空二鴉', 'en': 'Upside Gap Two Crows', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_UNIQUE3RIVER': {'name': '奇特三川底', 'en': 'Unique 3 River', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_XSIDEGAP3METHODS': {'name': '跳空並列三法', 'en': 'Side Gap Three Methods', 'type': 'continuation', 'signal': 'trend'}, + 'CDL_TASUKIGAP': {'name': '跳空缺口', 'en': 'Tasuki Gap', 'type': 'continuation', 'signal': 'trend'}, + 'CDL_GAPSIDESIDEWHITE': {'name': '向上跳空並列陽線', 'en': 'Gap Side Side White', 'type': 'continuation', 'signal': 'bullish'}, + + # Complex Patterns (複雜型態) + 'CDL_ADVANCEBLOCK': {'name': '前進受阻', 'en': 'Advance Block', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_STALLEDPATTERN': {'name': '停滯型態', 'en': 'Stalled Pattern', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_CONCEALBABYSWALL': {'name': '藏嬰吞噬', 'en': 'Concealing Baby Swallow', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_LADDERBOTTOM': {'name': '梯底', 'en': 'Ladder Bottom', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_RICKSHAWMAN': {'name': '黃包車夫', 'en': 'Rickshaw Man', 'type': 'reversal', 'signal': 'neutral'}, + 'CDL_RISEFALL3METHODS': {'name': '上升/下降三法', 'en': 'Rise/Fall Three Methods', 'type': 'continuation', 'signal': 'trend'}, + 'CDL_STICKSANDWICH': {'name': '棍子三明治', 'en': 'Stick Sandwich', 'type': 'reversal', 'signal': 'bullish'}, + 'CDL_BREAKAWAY': {'name': '脫離', 'en': 'Breakaway', 'type': 'reversal', 'signal': 'trend'}, + 'CDL_MATHOLD': {'name': '鋪墊', 'en': 'Mat Hold', 'type': 'continuation', 'signal': 'bullish'}, + 'CDL_IDENTICAL3CROWS': {'name': '相同三鴉', 'en': 'Identical Three Crows', 'type': 'reversal', 'signal': 'bearish'}, + 'CDL_SHORTLINE': {'name': '短線', 'en': 'Short Line', 'type': 'neutral', 'signal': 'neutral'}, + 'CDL_LONGLINE': {'name': '長線', 'en': 'Long Line', 'type': 'continuation', 'signal': 'trend'}, +} + +# Custom patterns not in TA-Lib (酒田特有型態) +CUSTOM_PATTERNS = { + 'SANZAN': {'name': '三山', 'en': 'Three Mountains (Triple Top)', 'type': 'reversal', 'signal': 'bearish'}, + 'SANSEN': {'name': '三川', 'en': 'Three Rivers (Triple Bottom)', 'type': 'reversal', 'signal': 'bullish'}, + 'SANKU_UP': {'name': '三空(上漲)', 'en': 'Three Gaps Up', 'type': 'reversal', 'signal': 'bearish'}, + 'SANKU_DOWN': {'name': '三空(下跌)', 'en': 'Three Gaps Down', 'type': 'reversal', 'signal': 'bullish'}, + 'SANPOH_UP': {'name': '上升三法', 'en': 'Rising Three Methods', 'type': 'continuation', 'signal': 'bullish'}, + 'SANPOH_DOWN': {'name': '下降三法', 'en': 'Falling Three Methods', 'type': 'continuation', 'signal': 'bearish'}, + 'ISLAND_TOP': {'name': '島型頂', 'en': 'Island Top', 'type': 'reversal', 'signal': 'bearish'}, + 'ISLAND_BOTTOM': {'name': '島型底', 'en': 'Island Bottom', 'type': 'reversal', 'signal': 'bullish'}, + 'ROUND_TOP': {'name': '圓頂', 'en': 'Rounding Top', 'type': 'reversal', 'signal': 'bearish'}, + 'ROUND_BOTTOM': {'name': '圓底', 'en': 'Rounding Bottom', 'type': 'reversal', 'signal': 'bullish'}, + 'TWEEZERS_TOP': {'name': '鑷子頂', 'en': 'Tweezers Top', 'type': 'reversal', 'signal': 'bearish'}, + 'TWEEZERS_BOTTOM': {'name': '鑷子底', 'en': 'Tweezers Bottom', 'type': 'reversal', 'signal': 'bullish'}, + 'WINDOW_UP': {'name': '向上窗口', 'en': 'Rising Window', 'type': 'continuation', 'signal': 'bullish'}, + 'WINDOW_DOWN': {'name': '向下窗口', 'en': 'Falling Window', 'type': 'continuation', 'signal': 'bearish'}, + 'EXHAUSTION_UP': {'name': '竭盡缺口(上)', 'en': 'Exhaustion Gap Up', 'type': 'reversal', 'signal': 'bearish'}, + 'EXHAUSTION_DOWN': {'name': '竭盡缺口(下)', 'en': 'Exhaustion Gap Down', 'type': 'reversal', 'signal': 'bullish'}, + 'STRONG_BULL': {'name': '強勢陽線', 'en': 'Strong Bullish Candle', 'type': 'continuation', 'signal': 'bullish'}, + 'STRONG_BEAR': {'name': '強勢陰線', 'en': 'Strong Bearish Candle', 'type': 'continuation', 'signal': 'bearish'}, +} + + +# ============================================================ +# 趨勢濾網 (Trend Filter) +# ============================================================ + +def calculate_trend_context(df: pd.DataFrame) -> dict: + """ + 計算趨勢背景資訊,用於過濾不合理的信號。 + + Returns: + dict: { + 'ma20': MA20 值, + 'ma60': MA60 值, + 'position': 'high' | 'mid' | 'low', + 'trend': 'uptrend' | 'downtrend' | 'sideways' + } + """ + close = df['Close'].values + + # 計算均線 + ma20 = talib.SMA(close, timeperiod=20) + ma60 = talib.SMA(close, timeperiod=60) + + # 計算 ATR 用於判斷波動性 + atr = talib.ATR(df['High'].values, df['Low'].values, close, timeperiod=14) + + # 計算過去 60 天的高低點 + lookback = min(60, len(df)) + recent_high = df['High'].iloc[-lookback:].max() + recent_low = df['Low'].iloc[-lookback:].min() + price_range = recent_high - recent_low + + # 當前價格在區間中的位置 + current_price = close[-1] + position_pct = (current_price - recent_low) / price_range if price_range > 0 else 0.5 + + if position_pct > 0.7: + position = 'high' + elif position_pct < 0.3: + position = 'low' + else: + position = 'mid' + + # 趨勢判斷 (基於 MA20 斜率) + if len(ma20) >= 10: + ma20_slope = (ma20[-1] - ma20[-10]) / ma20[-10] if ma20[-10] > 0 else 0 + if ma20_slope > 0.02: + trend = 'uptrend' + elif ma20_slope < -0.02: + trend = 'downtrend' + else: + trend = 'sideways' + else: + trend = 'sideways' + + return { + 'ma20': ma20, + 'ma60': ma60, + 'atr': atr, + 'position': position, + 'position_pct': position_pct, + 'trend': trend, + 'recent_high': recent_high, + 'recent_low': recent_low + } + + +# ============================================================ +# 結構分析器 (Structure Analyzer) +# ============================================================ + +def find_swing_points(prices: np.ndarray, min_distance: int = 5) -> tuple: + """ + 找出擺盪高低點,確保相鄰點之間有最小間距。 + + Args: + prices: 價格陣列 + min_distance: 相鄰擺盪點的最小間距 + + Returns: + (peaks, troughs): 高點和低點的索引列表 + """ + peaks = [] + troughs = [] + + for i in range(min_distance, len(prices) - min_distance): + # 找高點 + is_peak = True + for j in range(1, min_distance + 1): + if prices[i] <= prices[i - j] or prices[i] <= prices[i + j]: + is_peak = False + break + if is_peak: + # 確保和前一個高點有足夠間距 + if not peaks or (i - peaks[-1]) >= min_distance: + peaks.append(i) + + # 找低點 + is_trough = True + for j in range(1, min_distance + 1): + if prices[i] >= prices[i - j] or prices[i] >= prices[i + j]: + is_trough = False + break + if is_trough: + if not troughs or (i - troughs[-1]) >= min_distance: + troughs.append(i) + + return peaks, troughs + + +def calculate_neckline(df: pd.DataFrame, peaks: list, troughs: list, pattern_type: str) -> dict: + """ + 計算頸線位置。 + + Args: + df: DataFrame + peaks: 高點索引列表 + troughs: 低點索引列表 + pattern_type: 'sanzan' (三山) 或 'sansen' (三川) + + Returns: + dict: { + 'neckline': 頸線價格, + 'confirmed': 是否已跌破/突破頸線, + 'distance_pct': 當前價格距離頸線的百分比 + } + """ + if pattern_type == 'sanzan' and len(troughs) >= 2: + # 三山的頸線 = 兩個谷底的連線 (取較高者) + trough_prices = [df['Low'].iloc[t] for t in troughs[-2:]] + neckline = max(trough_prices) + current_price = df['Close'].iloc[-1] + confirmed = current_price < neckline + distance_pct = (current_price - neckline) / neckline * 100 + + elif pattern_type == 'sansen' and len(peaks) >= 2: + # 三川的頸線 = 兩個高點的連線 (取較低者) + peak_prices = [df['High'].iloc[p] for p in peaks[-2:]] + neckline = min(peak_prices) + current_price = df['Close'].iloc[-1] + confirmed = current_price > neckline + distance_pct = (current_price - neckline) / neckline * 100 + + else: + return {'neckline': None, 'confirmed': False, 'distance_pct': 0} + + return { + 'neckline': neckline, + 'confirmed': confirmed, + 'distance_pct': distance_pct + } + + +def check_volume_divergence(df: pd.DataFrame, peaks: list) -> dict: + """ + 檢查量價背離。 + + 三山時,第三座山的成交量應該萎縮 (看空確認) + 三川時,第三個底的成交量應該萎縮然後放大 (看多確認) + + Returns: + dict: { + 'divergence': bool, + 'type': 'bearish' | 'bullish' | None, + 'description': 說明 + } + """ + if 'Volume' not in df.columns or len(peaks) < 3: + return {'divergence': False, 'type': None, 'description': '無成交量資料'} + + volumes = df['Volume'].values + + # 取最後三個高點/低點的成交量 + peak_volumes = [volumes[p] for p in peaks[-3:]] + + # 量價背離: 價格創新高,但成交量遞減 + vol_trend = (peak_volumes[-1] < peak_volumes[-2] < peak_volumes[-3]) + + if vol_trend: + return { + 'divergence': True, + 'type': 'bearish', + 'description': f'量價背離: 成交量由 {peak_volumes[-3]:,.0f} → {peak_volumes[-2]:,.0f} → {peak_volumes[-1]:,.0f} 遞減' + } + + return {'divergence': False, 'type': None, 'description': '成交量正常'} + + +# ============================================================ +# 改良版三山/三川偵測 +# ============================================================ + +def detect_sanzan_v2(df: pd.DataFrame, trend_ctx: dict, + lookback: int = 60, min_peak_distance: int = 10) -> dict: + """ + 改良版三山 (Triple Top) 偵測。 + + 改良點: + 1. 只在高檔 (position = 'high') 才偵測 + 2. 三個高點至少間隔 min_peak_distance 根 K 線 + 3. 計算頸線和量價背離 + + Returns: + dict: 完整的型態分析結果 + """ + signals = np.zeros(len(df)) + analysis = { + 'detected': False, + 'stage': 0, # 1-5 階段 + 'peaks': [], + 'neckline': None, + 'volume_divergence': None, + 'description': '' + } + + # 濾網1: 只在高檔偵測三山 + if trend_ctx['position'] not in ['high', 'mid']: + analysis['description'] = '股價位於低檔,不適合偵測三山' + return {'signals': signals, 'analysis': analysis} + + highs = df['High'].values + + # 找擺盪高點 (至少間隔 min_peak_distance) + peaks, troughs = find_swing_points(highs, min_distance=min_peak_distance) + + if len(peaks) < 3: + analysis['description'] = f'高點數量不足 ({len(peaks)}/3)' + return {'signals': signals, 'analysis': analysis} + + # 取最後三個高點 + last_3_peaks = peaks[-3:] + peak_values = [highs[p] for p in last_3_peaks] + avg_peak = np.mean(peak_values) + + # 檢查三個高點是否在相近水平 (容許 5% 誤差) + tolerance = 0.05 + peaks_aligned = all(abs(p - avg_peak) / avg_peak < tolerance for p in peak_values) + + if not peaks_aligned: + analysis['description'] = '三個高點水平差異過大' + return {'signals': signals, 'analysis': analysis} + + # 檢查時間跨度 (三個高點的總跨度應該夠大) + total_span = last_3_peaks[-1] - last_3_peaks[0] + if total_span < 20: # 至少跨越 20 根 K 線 + analysis['description'] = f'時間跨度不足 ({total_span}/20 根K線)' + return {'signals': signals, 'analysis': analysis} + + # 計算頸線 + neckline_info = calculate_neckline(df, last_3_peaks, troughs, 'sanzan') + + # 檢查量價背離 + volume_info = check_volume_divergence(df, last_3_peaks) + + # 判斷型態階段 + current_price = df['Close'].iloc[-1] + if neckline_info['neckline']: + if neckline_info['confirmed']: + stage = 5 # 已跌破頸線,型態確立 + elif current_price < avg_peak * 0.97: + stage = 4 # 正在形成右肩 + else: + stage = 3 # 第三山頂形成中 + else: + stage = 2 # 僅形成兩個山頂 + + # 只在型態較成熟時才發出信號 (stage >= 3) + if stage >= 3: + signals[-1] = -100 if stage >= 4 else -50 + analysis['detected'] = True + + analysis.update({ + 'stage': stage, + 'peaks': [{'idx': p, 'price': highs[p], 'date': df.index[p]} for p in last_3_peaks], + 'neckline': neckline_info, + 'volume_divergence': volume_info, + 'avg_peak': avg_peak, + 'description': f'三山型態 ({stage}/5 階段)' + + (' ⚠️ 量價背離' if volume_info['divergence'] else '') + }) + + return {'signals': signals, 'analysis': analysis} + + +def detect_sansen_v2(df: pd.DataFrame, trend_ctx: dict, + lookback: int = 60, min_trough_distance: int = 10) -> dict: + """ + 改良版三川 (Triple Bottom) 偵測。 + + 改良點: + 1. 只在低檔 (position = 'low') 才偵測 + 2. 三個低點至少間隔 min_trough_distance 根 K 線 + 3. 計算頸線和量能確認 + """ + signals = np.zeros(len(df)) + analysis = { + 'detected': False, + 'stage': 0, + 'troughs': [], + 'neckline': None, + 'volume_divergence': None, + 'description': '' + } + + # 濾網1: 只在低檔偵測三川 + if trend_ctx['position'] not in ['low', 'mid']: + analysis['description'] = '股價位於高檔,不適合偵測三川' + return {'signals': signals, 'analysis': analysis} + + lows = df['Low'].values + + # 找擺盪低點 + peaks, troughs = find_swing_points(lows, min_distance=min_trough_distance) + + if len(troughs) < 3: + analysis['description'] = f'低點數量不足 ({len(troughs)}/3)' + return {'signals': signals, 'analysis': analysis} + + # 取最後三個低點 + last_3_troughs = troughs[-3:] + trough_values = [lows[t] for t in last_3_troughs] + avg_trough = np.mean(trough_values) + + # 檢查三個低點是否在相近水平 + tolerance = 0.05 + troughs_aligned = all(abs(t - avg_trough) / avg_trough < tolerance for t in trough_values) + + if not troughs_aligned: + analysis['description'] = '三個低點水平差異過大' + return {'signals': signals, 'analysis': analysis} + + # 時間跨度檢查 + total_span = last_3_troughs[-1] - last_3_troughs[0] + if total_span < 20: + analysis['description'] = f'時間跨度不足 ({total_span}/20 根K線)' + return {'signals': signals, 'analysis': analysis} + + # 計算頸線 + neckline_info = calculate_neckline(df, peaks, last_3_troughs, 'sansen') + + # 判斷型態階段 + current_price = df['Close'].iloc[-1] + if neckline_info['neckline']: + if neckline_info['confirmed']: + stage = 5 + elif current_price > avg_trough * 1.03: + stage = 4 + else: + stage = 3 + else: + stage = 2 + + if stage >= 3: + signals[-1] = 100 if stage >= 4 else 50 + analysis['detected'] = True + + analysis.update({ + 'stage': stage, + 'troughs': [{'idx': t, 'price': lows[t], 'date': df.index[t]} for t in last_3_troughs], + 'neckline': neckline_info, + 'avg_trough': avg_trough, + 'description': f'三川型態 ({stage}/5 階段)' + }) + + return {'signals': signals, 'analysis': analysis} + + +# ============================================================ +# TA-Lib 型態偵測 (保持原有功能) +# ============================================================ + +def detect_talib_patterns(df: pd.DataFrame) -> dict: + """Detect all TA-Lib candlestick patterns.""" + results = {} + + open_prices = df['Open'].values + high_prices = df['High'].values + low_prices = df['Low'].values + close_prices = df['Close'].values + + for pattern_key, pattern_info in TALIB_PATTERNS.items(): + func_name = pattern_key + + try: + func = getattr(talib, func_name) + pattern_result = func(open_prices, high_prices, low_prices, close_prices) + results[pattern_key] = { + 'values': pattern_result, + 'info': pattern_info + } + except Exception as e: + print(f"Warning: Could not compute {pattern_key}: {e}") + + return results + + +# ============================================================ +# 其他自訂型態 (簡化版) +# ============================================================ + +def detect_sanku_v2(df: pd.DataFrame, trend_ctx: dict) -> dict: + """ + 改良版三空偵測 (v2.1) - 使用狀態機記錄連續缺口。 + + 三空上漲: 連續 3 個向上缺口 -> 空頭信號 (漲勢竭盡) + 三空下跌: 連續 3 個向下缺口 -> 多頭信號 (跌勢竭盡) + + Returns: + dict: { + 'up_signals': 三空上漲信號, + 'down_signals': 三空下跌信號, + 'sequences': 偵測到的序列詳情 + } + """ + up_signals = np.zeros(len(df)) + down_signals = np.zeros(len(df)) + sequences = [] + + highs = df['High'].values + lows = df['Low'].values + + # 狀態機: 追蹤連續缺口 + gap_up_streak = 0 + gap_up_start = -1 + gap_down_streak = 0 + gap_down_start = -1 + + for i in range(1, len(df)): + # 檢測向上缺口 + is_gap_up = lows[i] > highs[i-1] + # 檢測向下缺口 + is_gap_down = highs[i] < lows[i-1] + + # 向上缺口序列追蹤 + if is_gap_up: + if gap_up_streak == 0: + gap_up_start = i + gap_up_streak += 1 + + # 三空上漲確認 + if gap_up_streak >= 3: + up_signals[i] = -100 # 空頭信號 + sequences.append({ + 'type': 'SANKU_UP', + 'start_idx': gap_up_start, + 'end_idx': i, + 'gaps': gap_up_streak, + 'signal': 'bearish', + 'description': f'連續 {gap_up_streak} 個向上缺口 (漲勢竭盡)' + }) + else: + gap_up_streak = 0 + gap_up_start = -1 + + # 向下缺口序列追蹤 + if is_gap_down: + if gap_down_streak == 0: + gap_down_start = i + gap_down_streak += 1 + + # 三空下跌確認 + if gap_down_streak >= 3: + down_signals[i] = 100 # 多頭信號 + sequences.append({ + 'type': 'SANKU_DOWN', + 'start_idx': gap_down_start, + 'end_idx': i, + 'gaps': gap_down_streak, + 'signal': 'bullish', + 'description': f'連續 {gap_down_streak} 個向下缺口 (跌勢竭盡)' + }) + else: + gap_down_streak = 0 + gap_down_start = -1 + + return { + 'up_signals': up_signals, + 'down_signals': down_signals, + 'sequences': sequences + } + + +def detect_tweezers_v2(df: pd.DataFrame, trend_ctx: dict, lookback: int = 20) -> tuple: + """ + 改良版鑷子偵測 (v2.1)。 + + 關鍵修正: 只有在創下 20 日新高/新低時出現的雙針,才算有效鑷子。 + 半山腰或盤整區的雙針視為雜訊,直接丟棄。 + + Args: + df: OHLC DataFrame + trend_ctx: 趨勢背景 + lookback: 回看天數 (預設 20 日) + """ + top_signals = np.zeros(len(df)) + bottom_signals = np.zeros(len(df)) + + highs = df['High'].values + lows = df['Low'].values + + tolerance = 0.002 # 0.2% tolerance + + for i in range(lookback, len(df)): + # 計算 lookback 期間的最高/最低 + recent_high = max(highs[i-lookback:i]) + recent_low = min(lows[i-lookback:i]) + + # ======================================== + # 鑷子頂: 需同時滿足以下條件 + # 1. 今日高點 ≈ 昨日高點 (容差 0.2%) + # 2. 今日高點 >= 近 20 日最高 + # 3. 趨勢位置在高檔或中間 + # ======================================== + if trend_ctx['position'] in ['high', 'mid']: + two_highs_equal = abs(highs[i] - highs[i-1]) / highs[i] < tolerance + at_20d_high = highs[i] >= recent_high * 0.998 # 允許微小誤差 + + if two_highs_equal and at_20d_high: + top_signals[i] = -100 + + # ======================================== + # 鑷子底: 需同時滿足以下條件 + # 1. 今日低點 ≈ 昨日低點 (容差 0.2%) + # 2. 今日低點 <= 近 20 日最低 + # 3. 趨勢位置在低檔或中間 + # ======================================== + if trend_ctx['position'] in ['low', 'mid']: + two_lows_equal = abs(lows[i] - lows[i-1]) / lows[i] < tolerance + at_20d_low = lows[i] <= recent_low * 1.002 # 允許微小誤差 + + if two_lows_equal and at_20d_low: + bottom_signals[i] = 100 + + return top_signals, bottom_signals + + +def detect_windows(df: pd.DataFrame) -> tuple: + """Detect Rising/Falling Window (gap) patterns.""" + up_signals = np.zeros(len(df)) + down_signals = np.zeros(len(df)) + + highs = df['High'].values + lows = df['Low'].values + + for i in range(1, len(df)): + # Rising window (gap up) + if lows[i] > highs[i-1]: + up_signals[i] = 100 + + # Falling window (gap down) + if highs[i] < lows[i-1]: + down_signals[i] = -100 + + return up_signals, down_signals + + +# ============================================================ +# 主要入口函數 +# ============================================================ + +def detect_all_patterns(df: pd.DataFrame) -> dict: + """ + 偵測所有 80 種酒田型態 (改良版)。 + + Returns: + dict: { + 'patterns': DataFrame (每日每型態的信號), + 'trend_context': 趨勢背景資訊, + 'sanzan_analysis': 三山詳細分析, + 'sansen_analysis': 三川詳細分析 + } + """ + # 計算趨勢背景 + trend_ctx = calculate_trend_context(df) + + # 建立結果 DataFrame + results = pd.DataFrame(index=df.index) + + # TA-Lib 型態 + talib_results = detect_talib_patterns(df) + for pattern_key, pattern_data in talib_results.items(): + results[pattern_key] = pattern_data['values'] + + # 改良版三山/三川 + sanzan_result = detect_sanzan_v2(df, trend_ctx) + sansen_result = detect_sansen_v2(df, trend_ctx) + results['SANZAN'] = sanzan_result['signals'] + results['SANSEN'] = sansen_result['signals'] + + # 其他自訂型態 (使用 v2.1 改良版) + sanku_result = detect_sanku_v2(df, trend_ctx) + results['SANKU_UP'] = sanku_result['up_signals'] + results['SANKU_DOWN'] = sanku_result['down_signals'] + + tweezers_top, tweezers_bottom = detect_tweezers_v2(df, trend_ctx) + results['TWEEZERS_TOP'] = tweezers_top + results['TWEEZERS_BOTTOM'] = tweezers_bottom + + window_up, window_down = detect_windows(df) + results['WINDOW_UP'] = window_up + results['WINDOW_DOWN'] = window_down + + return { + 'patterns': results, + 'trend_context': trend_ctx, + 'sanzan_analysis': sanzan_result['analysis'], + 'sansen_analysis': sansen_result['analysis'] + } + + +def get_pattern_info(pattern_key: str) -> dict: + """Get pattern information by key.""" + if pattern_key in TALIB_PATTERNS: + return TALIB_PATTERNS[pattern_key] + elif pattern_key in CUSTOM_PATTERNS: + return CUSTOM_PATTERNS[pattern_key] + return None + + +def summarize_detected_patterns(df: pd.DataFrame, pattern_results: pd.DataFrame) -> list: + """ + Summarize all detected patterns with dates and signals. + + Returns: + List of dicts: [{date, pattern, signal, strength, info}, ...] + """ + detected = [] + + for col in pattern_results.columns: + non_zero = pattern_results[col] != 0 + if non_zero.any(): + for idx in pattern_results[non_zero].index: + signal_value = pattern_results.loc[idx, col] + info = get_pattern_info(col) + + if info: + detected.append({ + 'date': idx, + 'pattern': col, + 'name': info['name'], + 'english': info['en'], + 'type': info['type'], + 'direction': 'bullish' if signal_value > 0 else 'bearish', + 'strength': abs(signal_value), + 'info': info + }) + + # Sort by date + detected.sort(key=lambda x: x['date'], reverse=True) + + return detected diff --git a/.agent/skills/sakata/scripts/requirements.txt b/.agent/skills/sakata/scripts/requirements.txt new file mode 100644 index 0000000..18c8ae1 --- /dev/null +++ b/.agent/skills/sakata/scripts/requirements.txt @@ -0,0 +1,14 @@ +# Core dependencies +yfinance>=0.2.36 +pandas>=2.0.0 +numpy>=1.24.0 + +# Technical Analysis +TA-Lib>=0.4.28 + +# Charting +mplfinance>=0.12.10b0 +matplotlib>=3.7.0 + +# Utilities +argparse>=1.4.0 diff --git a/.agent/skills/sakata/scripts/sakata_analyzer.py b/.agent/skills/sakata/scripts/sakata_analyzer.py new file mode 100644 index 0000000..d7f722b --- /dev/null +++ b/.agent/skills/sakata/scripts/sakata_analyzer.py @@ -0,0 +1,467 @@ +#!/usr/bin/env python3 +""" +Sakata Analyzer v2.0 - 酒田戰法分析器 (改良版) +Main entry point for candlestick pattern analysis. + +改良重點: +1. 整合結構分析 (頸線、量價背離) +2. 改進報告格式 (顯示型態階段、關鍵價位) +3. 趨勢背景信息 + +Usage: + python sakata_analyzer.py --ticker AAPL + python sakata_analyzer.py --ticker TSLA --days 180 +""" + +import argparse +import os +import time +from datetime import datetime, timedelta +import pandas as pd +import yfinance as yf + +from pattern_detector import detect_all_patterns, summarize_detected_patterns +from chart_plotter import create_sakata_chart +from signal_generator import SignalGenerator + + +# Rate limiting settings for Yahoo Finance +REQUEST_DELAY = 1.0 # seconds between requests +MAX_RETRIES = 3 +RETRY_DELAY = 5.0 # seconds + + +def fetch_stock_data(ticker: str, days: int = 120, retries: int = 0) -> pd.DataFrame: + """ + Fetch OHLCV data from Yahoo Finance with rate limiting. + """ + print(f"📊 正在取得 {ticker} 的 {days} 日數據...") + + try: + time.sleep(REQUEST_DELAY) + + end_date = datetime.now() + start_date = end_date - timedelta(days=days + 30) + + stock = yf.Ticker(ticker) + df = stock.history(start=start_date, end=end_date) + + if df.empty: + raise ValueError(f"No data found for {ticker}") + + df = df.tail(days) + + print(f"✅ 成功取得 {len(df)} 筆數據 ({df.index[0].strftime('%Y-%m-%d')} ~ {df.index[-1].strftime('%Y-%m-%d')})") + + return df + + except Exception as e: + if retries < MAX_RETRIES: + print(f"⚠️ 請求失敗,{RETRY_DELAY} 秒後重試... ({retries + 1}/{MAX_RETRIES})") + time.sleep(RETRY_DELAY * (retries + 1)) + return fetch_stock_data(ticker, days, retries + 1) + else: + raise Exception(f"Failed to fetch data for {ticker} after {MAX_RETRIES} retries: {e}") + + +def generate_structure_section(analysis: dict, pattern_type: str) -> str: + """ + 生成結構分析區塊 (頸線、量價背離)。 + """ + if not analysis.get('detected'): + return "" + + name = '三山' if pattern_type == 'sanzan' else '三川' + stage = analysis.get('stage', 0) + + section = f""" +## 🏔️ {name}型態結構分析 + +| 項目 | 數值 | +|------|------| +| **型態階段** | {stage}/5 {'⚠️ 預警中' if stage < 4 else '✅ 確立'} | +""" + + # 頸線信息 + neckline = analysis.get('neckline', {}) + if neckline and neckline.get('neckline'): + status = '✅ 已跌破' if neckline.get('confirmed') else '⏳ 未跌破' + if pattern_type == 'sansen': + status = '✅ 已突破' if neckline.get('confirmed') else '⏳ 未突破' + + section += f"""| **頸線價位** | ${neckline['neckline']:.2f} | +| **頸線狀態** | {status} | +| **距離頸線** | {neckline.get('distance_pct', 0):.1f}% | +""" + + # 量價背離 + vol_div = analysis.get('volume_divergence', {}) + if vol_div and vol_div.get('divergence'): + section += f"""| **量價背離** | ⚠️ {vol_div['description']} | +""" + + # 關鍵價位 + if pattern_type == 'sanzan' and analysis.get('peaks'): + avg_peak = analysis.get('avg_peak', 0) + section += f"""| **平均山頂** | ${avg_peak:.2f} | +""" + elif pattern_type == 'sansen' and analysis.get('troughs'): + avg_trough = analysis.get('avg_trough', 0) + section += f"""| **平均谷底** | ${avg_trough:.2f} | +""" + + # 型態階段說明 + stage_desc = { + 1: '僅形成一個頂/底', + 2: '形成兩個頂/底', + 3: '第三頂/底形成中', + 4: '型態成形,等待確認', + 5: '型態確立,已突破/跌破頸線' + } + section += f""" +### 階段說明 +{stage_desc.get(stage, '未知')} + +""" + + return section + + +def generate_trend_section(trend_ctx: dict, current_price: float) -> str: + """ + 生成趨勢背景區塊。 + """ + position_map = {'high': '高檔', 'mid': '中間', 'low': '低檔'} + trend_map = {'uptrend': '上升趨勢 📈', 'downtrend': '下降趨勢 📉', 'sideways': '盤整 ↔️'} + + ma20 = trend_ctx.get('ma20', [0])[-1] if len(trend_ctx.get('ma20', [])) > 0 else 0 + ma60 = trend_ctx.get('ma60', [0])[-1] if len(trend_ctx.get('ma60', [])) > 0 else 0 + + section = f""" +## 📊 趨勢背景 + +| 指標 | 數值 | 說明 | +|------|------|------| +| **當前位置** | {position_map.get(trend_ctx.get('position', 'mid'), '中間')} | 近60日區間位置 ({trend_ctx.get('position_pct', 0)*100:.0f}%) | +| **趨勢方向** | {trend_map.get(trend_ctx.get('trend', 'sideways'), '盤整')} | 基於 MA20 斜率 | +| **MA20** | ${ma20:.2f} | {'股價在上方 ✅' if current_price > ma20 else '股價在下方 ❌'} | +| **MA60** | ${ma60:.2f} | {'股價在上方 ✅' if current_price > ma60 else '股價在下方 ❌'} | +| **近期高點** | ${trend_ctx.get('recent_high', 0):.2f} | | +| **近期低點** | ${trend_ctx.get('recent_low', 0):.2f} | | + +""" + return section + + +def generate_markdown_report( + ticker: str, + df: pd.DataFrame, + detected_patterns: list, + signals: list, + chart_path: str, + analysis_result: dict, + output_dir: str = './output' +) -> str: + """ + Generate an improved Markdown analysis report with structure analysis. + """ + signal_gen = SignalGenerator() + summary = signal_gen.summarize_signals(signals) + recommendation = signal_gen.get_latest_recommendation( + signals, + current_price=df.iloc[-1]['Close'], + current_date=df.index[-1] + ) + + current_price = df.iloc[-1]['Close'] + trend_ctx = analysis_result.get('trend_context', {}) + sanzan_analysis = analysis_result.get('sanzan_analysis', {}) + sansen_analysis = analysis_result.get('sansen_analysis', {}) + + # 判斷是否有結構型態 + has_structure = sanzan_analysis.get('detected') or sansen_analysis.get('detected') + + # Build report + report = f"""# {ticker} 酒田戰法分析報告 v2.0 + +**生成時間**: {datetime.now().strftime('%Y-%m-%d %H:%M')} +**數據範圍**: {df.index[0].strftime('%Y-%m-%d')} ~ {df.index[-1].strftime('%Y-%m-%d')} ({len(df)} 日) +**當前價格**: ${current_price:.2f} + +--- + +## 📊 核心結論 + +| 指標 | 數值 | +|------|------| +| 總信號數 | {summary['total']} | +| 多頭信號 | {summary['bullish']} 🟢 | +| 空頭信號 | {summary['bearish']} 🔴 | +| 整體偏向 | **{summary['bias']}** | +| 信心度 | {'⭐' * int(summary['avg_strength'])} ({summary['avg_strength']}/5) | + +""" + + # 添加趨勢背景 + report += generate_trend_section(trend_ctx, current_price) + + # 添加結構分析 (如果有) + if sanzan_analysis.get('detected'): + report += generate_structure_section(sanzan_analysis, 'sanzan') + + if sansen_analysis.get('detected'): + report += generate_structure_section(sansen_analysis, 'sansen') + + # 交易建議 + report += """--- + +## 🎯 最新交易建議 + +""" + + if recommendation: + # v2.2: 根據狀態決定顯示方式 + status = recommendation.get('status', 'ACTIVE') + status_reason = recommendation.get('status_reason', '') + days_diff = recommendation.get('days_since_signal', 0) + + # 決定 emoji 和建議文字 + if recommendation['recommendation'] == 'HOLD': + rec_emoji = '⏸️ 觀望' + elif recommendation['recommendation'] == 'BUY': + rec_emoji = '🟢 買入' + else: + rec_emoji = '🔴 賣出' + + # 狀態標籤 + status_badge = '' + if status == 'EXPIRED': + status_badge = '⚠️ 已過期' + elif status == 'STOPPED_OUT': + status_badge = '🛑 已停損' + elif status == 'SPECIAL': + status_badge = '⚡ 特殊情況' + elif status == 'ACTIVE': + status_badge = '✅ 有效' + + report += f"""| 項目 | 數值 | +|------|------| +| **建議** | {rec_emoji} | +| **信號狀態** | {status_badge} | +| **觸發型態** | {recommendation['pattern']} | +| **信號日期** | {recommendation['date'].strftime('%Y-%m-%d') if hasattr(recommendation['date'], 'strftime') else recommendation['date']} ({days_diff} 天前) | +| **建議進場** | ${recommendation['entry']:.2f} | +| **停損價位** | ${recommendation['stop_loss']:.2f} | +| **目標價位** | ${recommendation['target']:.2f} | +| **信號強度** | {recommendation['strength']} | + +""" + + # 狀態原因說明 + if status_reason: + report += f"""> **狀態說明**: {status_reason} + +""" + + # 連續窗口警示 + window_status = recommendation.get('window_status', {}) + if window_status.get('warning'): + report += f"""> **酒田特殊警示**: {window_status['warning']} + +""" + + # 只有 ACTIVE 狀態才顯示盈虧比 + if status == 'ACTIVE': + risk = abs(recommendation['entry'] - recommendation['stop_loss']) + reward = abs(recommendation['target'] - recommendation['entry']) + rr_ratio = reward / risk if risk > 0 else 0 + report += f"""**盈虧比 (R/R)**: 1 : {rr_ratio:.1f} + +""" + else: + report += "> ⚠️ 目前無明確交易信號,建議觀望。\n\n" + + # 偵測到的型態 (去重後顯示) + report += """--- + +## 📈 偵測到的型態 + +| 日期 | 型態 | 英文名稱 | 方向 | 強度 | +|------|------|---------|------|------| +""" + + # 去重: 同一天同一型態只顯示一次 + seen = set() + unique_patterns = [] + for pattern in detected_patterns[:20]: + key = (pattern['date'].strftime('%Y-%m-%d') if hasattr(pattern['date'], 'strftime') else str(pattern['date'])[:10], pattern['name']) + if key not in seen: + seen.add(key) + unique_patterns.append(pattern) + + for pattern in unique_patterns[:12]: + date_str = pattern['date'].strftime('%m/%d') if hasattr(pattern['date'], 'strftime') else str(pattern['date'])[:10] + direction = '🟢 多頭' if pattern['direction'] == 'bullish' else '🔴 空頭' + strength = '⭐' * min(5, max(1, pattern['strength'] // 20)) + + report += f"| {date_str} | {pattern['name']} | {pattern['english']} | {direction} | {strength} |\n" + + report += f""" +--- + +## 📉 K 線圖表 + +![{ticker} 酒田戰法分析]({os.path.basename(chart_path)}) + +--- + +## ⚠️ 風險提示 + +1. 本分析僅供參考,不構成投資建議 +2. K 線型態需配合成交量、趨勢等其他指標確認 +3. **三山/三川型態需跌破/突破頸線才算確立** +4. 請嚴格執行停損紀律 +5. 過去表現不代表未來結果 + +--- + +*由酒田戰法 Agent Skill v2.0 自動生成* +""" + + # Save report + os.makedirs(output_dir, exist_ok=True) + report_path = os.path.join(output_dir, f'{ticker}_sakata.md') + + with open(report_path, 'w', encoding='utf-8') as f: + f.write(report) + + return report_path + + +def analyze(ticker: str, days: int = 120, output_dir: str = './output'): + """ + Run full Sakata analysis on a stock. + """ + print(f"\n{'='*60}") + print(f"🏯 酒田戰法分析器 v2.0 - {ticker}") + print(f"{'='*60}\n") + + # 1. Fetch data + df = fetch_stock_data(ticker, days) + + # 2. Detect patterns (v2.0 - returns rich analysis) + print("\n🔍 正在偵測 K 線型態 (含結構分析)...") + analysis_result = detect_all_patterns(df) + pattern_results = analysis_result['patterns'] + trend_ctx = analysis_result['trend_context'] + sanzan_analysis = analysis_result['sanzan_analysis'] + sansen_analysis = analysis_result['sansen_analysis'] + + detected_patterns = summarize_detected_patterns(df, pattern_results) + print(f"✅ 偵測到 {len(detected_patterns)} 個型態") + + # 顯示結構分析結果 + if sanzan_analysis.get('detected'): + print(f" 📉 三山型態: 階段 {sanzan_analysis.get('stage')}/5") + if sansen_analysis.get('detected'): + print(f" 📈 三川型態: 階段 {sansen_analysis.get('stage')}/5") + + # 3. Generate signals + print("\n📈 正在產生交易信號...") + signal_gen = SignalGenerator() + signals = signal_gen.generate_signals(df, detected_patterns, trend_ctx) + print(f"✅ 產生 {len(signals)} 個交易信號") + + # 4. Create chart + print("\n🎨 正在繪製圖表...") + chart_path = create_sakata_chart(df, detected_patterns, signals, ticker, output_dir) + print(f"✅ 圖表已儲存: {chart_path}") + + # 5. Generate report + print("\n📝 正在產生報告...") + report_path = generate_markdown_report( + ticker, df, detected_patterns, signals, chart_path, analysis_result, output_dir + ) + print(f"✅ 報告已儲存: {report_path}") + + # 6. Summary + summary = signal_gen.summarize_signals(signals) + recommendation = signal_gen.get_latest_recommendation( + signals, + current_price=df.iloc[-1]['Close'], + current_date=df.index[-1] + ) + + print(f"\n{'='*60}") + print("📊 分析完成!") + print(f"{'='*60}") + print(f" 趨勢位置: {trend_ctx.get('position', 'mid')} | 趨勢方向: {trend_ctx.get('trend', 'sideways')}") + print(f" 多頭信號: {summary['bullish']} | 空頭信號: {summary['bearish']}") + print(f" 整體偏向: {summary['bias']}") + + if recommendation: + print(f"\n 🎯 最新建議: {recommendation['recommendation']}") + print(f" 型態: {recommendation['pattern']}") + print(f" 進場: ${recommendation['entry']:.2f}") + print(f" 停損: ${recommendation['stop_loss']:.2f}") + print(f" 目標: ${recommendation['target']:.2f}") + + print(f"\n 📁 輸出檔案:") + print(f" {chart_path}") + print(f" {report_path}") + print(f"{'='*60}\n") + + return { + 'chart': chart_path, + 'report': report_path, + 'patterns': detected_patterns, + 'signals': signals, + 'summary': summary, + 'analysis': analysis_result + } + + +def main(): + parser = argparse.ArgumentParser( + description='酒田戰法分析器 v2.0 - Sakata Candlestick Pattern Analyzer', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +範例: + python sakata_analyzer.py --ticker AAPL + python sakata_analyzer.py --ticker TSLA --days 180 + python sakata_analyzer.py --ticker NVDA --output ./charts + """ + ) + + parser.add_argument( + '--ticker', '-t', + type=str, + required=True, + help='股票代碼 (例: AAPL, TSLA, 2330.TW)' + ) + + parser.add_argument( + '--days', '-d', + type=int, + default=120, + help='分析天數 (預設: 120)' + ) + + parser.add_argument( + '--output', '-o', + type=str, + default='./output', + help='輸出目錄 (預設: ./output)' + ) + + args = parser.parse_args() + + analyze( + ticker=args.ticker.upper(), + days=args.days, + output_dir=args.output + ) + + +if __name__ == '__main__': + main() diff --git a/.agent/skills/sakata/scripts/signal_generator.py b/.agent/skills/sakata/scripts/signal_generator.py new file mode 100644 index 0000000..8bf554f --- /dev/null +++ b/.agent/skills/sakata/scripts/signal_generator.py @@ -0,0 +1,415 @@ +""" +Sakata Signal Generator +買賣信號產生器 + +Generates buy/sell signals with entry prices, stop-loss levels, and risk-reward ratios. +""" + +import pandas as pd +import numpy as np +from datetime import datetime +from typing import Dict, List, Optional + + +class SignalGenerator: + """Generate trading signals based on detected Sakata patterns.""" + + def __init__(self, atr_period: int = 14, risk_reward_ratio: float = 2.0): + """ + Initialize the signal generator. + + Args: + atr_period: Period for ATR calculation (for stop-loss) + risk_reward_ratio: Target risk-reward ratio + """ + self.atr_period = atr_period + self.risk_reward_ratio = risk_reward_ratio + + def calculate_atr(self, df: pd.DataFrame) -> pd.Series: + """Calculate Average True Range.""" + high = df['High'] + low = df['Low'] + close = df['Close'] + + tr1 = high - low + tr2 = abs(high - close.shift(1)) + tr3 = abs(low - close.shift(1)) + + tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1) + atr = tr.rolling(window=self.atr_period).mean() + + return atr + + def generate_signals( + self, + df: pd.DataFrame, + detected_patterns: List[Dict], + trend_ctx: Dict = None + ) -> List[Dict]: + """ + Generate trading signals from detected patterns. + + Args: + df: OHLC DataFrame + detected_patterns: List of detected pattern dicts + trend_ctx: 趨勢背景 (v2.1 新增) + + Returns: + List of signal dicts with entry, stop-loss, and target prices + """ + signals = [] + atr = self.calculate_atr(df) + + for pattern in detected_patterns: + date = pattern['date'] + + if date not in df.index: + continue + + idx = df.index.get_loc(date) + current_price = df.iloc[idx]['Close'] + current_atr = atr.iloc[idx] if not pd.isna(atr.iloc[idx]) else current_price * 0.02 + + # Determine signal direction + is_bullish = pattern['direction'] == 'bullish' + + # Calculate entry, stop-loss, and target + if is_bullish: + # Bullish signal - buy + entry_price = current_price * 1.005 # Slightly above close + stop_loss = current_price - (current_atr * 1.5) # 1.5x ATR below + risk = entry_price - stop_loss + target_price = entry_price + (risk * self.risk_reward_ratio) + else: + # Bearish signal - sell/short + entry_price = current_price * 0.995 # Slightly below close + stop_loss = current_price + (current_atr * 1.5) # 1.5x ATR above + risk = stop_loss - entry_price + target_price = entry_price - (risk * self.risk_reward_ratio) + + # Calculate signal strength (v2.1: 傳入 trend_ctx) + strength = self._calculate_signal_strength(pattern, df, idx, trend_ctx) + + signal = { + 'date': date, + 'pattern': pattern['name'], + 'pattern_en': pattern['english'], + 'direction': 'BUY' if is_bullish else 'SELL', + 'current_price': round(current_price, 2), + 'entry_price': round(entry_price, 2), + 'stop_loss': round(stop_loss, 2), + 'target_price': round(target_price, 2), + 'risk': round(abs(risk), 2), + 'reward': round(abs(risk) * self.risk_reward_ratio, 2), + 'risk_reward': f"1:{self.risk_reward_ratio}", + 'strength': strength, + 'strength_stars': '⭐' * strength, + 'pattern_type': pattern['type'], + 'atr': round(current_atr, 2) + } + + signals.append(signal) + + # Sort by date (newest first) and strength + signals.sort(key=lambda x: (x['date'], -x['strength']), reverse=True) + + return signals + + def _calculate_signal_strength( + self, + pattern: Dict, + df: pd.DataFrame, + idx: int, + trend_ctx: Dict = None + ) -> int: + """ + Calculate signal strength from 1-5. + + v2.1 改良: 考慮順勢/逆勢權重 + - 順勢 (uptrend + BUY 或 downtrend + SELL): +2 + - 逆勢 (downtrend + BUY 或 uptrend + SELL): -1 + + Factors: + - Pattern reliability (reversal vs continuation) + - Volume confirmation + - Trend alignment (v2.1 改良) + - Pattern clarity (signal value) + """ + strength = 3 # Base strength + + # Pattern type bonus + pattern_type = pattern.get('type', '') + if pattern_type == 'reversal': + strength += 1 # Reversal patterns are stronger signals + + # Signal clarity + raw_strength = pattern.get('strength', 50) + if raw_strength >= 100: + strength += 1 + elif raw_strength <= 30: + strength -= 1 + + # Volume confirmation (if available) + if 'Volume' in df.columns and idx > 0: + current_vol = df.iloc[idx]['Volume'] + avg_vol = df['Volume'].iloc[max(0, idx-20):idx].mean() + if current_vol > avg_vol * 1.5: + strength += 1 # High volume confirmation + + # v2.1: 趨勢順逆權重調整 + is_bullish = pattern['direction'] == 'bullish' + + # 使用傳入的 trend_ctx 或計算 MA20 斜率 + if trend_ctx and 'trend' in trend_ctx: + trend = trend_ctx['trend'] + elif idx >= 20: + ma20_current = df['Close'].iloc[idx-20:idx].mean() + ma20_prev = df['Close'].iloc[idx-25:idx-5].mean() if idx >= 25 else ma20_current + if ma20_current > ma20_prev * 1.01: + trend = 'uptrend' + elif ma20_current < ma20_prev * 0.99: + trend = 'downtrend' + else: + trend = 'sideways' + else: + trend = 'sideways' + + # 順勢/逆勢權重 + if trend == 'uptrend': + if is_bullish: + strength += 2 # 順勢多頭: +2 + else: + strength -= 1 # 逆勢空頭: -1 + elif trend == 'downtrend': + if not is_bullish: + strength += 2 # 順勢空頭: +2 + else: + strength -= 1 # 逆勢多頭: -1 (這是最危險的!) + # sideways: 不調整 + + return max(1, min(5, strength)) # Clamp to 1-5 + + def check_consecutive_windows( + self, + signals: List[Dict], + lookback_days: int = 5 + ) -> Dict: + """ + v2.2: 檢測連續同向窗口 (二空/三空)。 + + 二空: 連續 2 個同向缺口 -> 強勢警示 + 三空: 連續 3 個同向缺口 -> 力竭反轉信號 + """ + from datetime import timedelta + + # 篩選窗口型態 + window_signals = [ + s for s in signals + if 'Window' in s.get('pattern_en', '') or '窗口' in s.get('pattern', '') + ] + + if len(window_signals) < 2: + return {'count': 0, 'direction': None, 'warning': None} + + # 依日期排序 (新到舊) + window_signals.sort(key=lambda x: x['date'], reverse=True) + + # 檢測連續同向缺口 (從最新開始) + consecutive_up = 0 + consecutive_down = 0 + current_direction = None + max_consecutive_up = 0 + max_consecutive_down = 0 + + for i, sig in enumerate(window_signals[:5]): + sig_direction = sig['direction'] + + if i == 0: + # 第一個信號確定方向 + current_direction = sig_direction + if sig_direction == 'BUY': + consecutive_up = 1 + else: + consecutive_down = 1 + else: + # 檢查是否同向 + if sig_direction == current_direction: + if sig_direction == 'BUY': + consecutive_up += 1 + else: + consecutive_down += 1 + else: + # 方向改變,停止計數 + break + + max_consecutive_up = max(max_consecutive_up, consecutive_up) + max_consecutive_down = max(max_consecutive_down, consecutive_down) + + max_consecutive = max(max_consecutive_up, max_consecutive_down) + direction = 'UP' if max_consecutive_up > max_consecutive_down else 'DOWN' + + warning = None + if max_consecutive == 2: + if direction == 'UP': + warning = '⚠️ 二空上漲中 (強勢軋空,勿追空)' + else: + warning = '⚠️ 二空下跌中 (強勢殺多,勿追多)' + elif max_consecutive >= 3: + if direction == 'UP': + warning = '🔴 三空力竭 (準備賣出)' + else: + warning = '🟢 三空力竭 (準備買入)' + + return { + 'count': max_consecutive, + 'direction': direction, + 'warning': warning + } + + def get_latest_recommendation( + self, + signals: List[Dict], + current_price: float = None, + current_date = None, + lookback_days: int = 3 + ) -> Optional[Dict]: + """ + v2.2 實戰邏輯: 取得最新交易建議。 + + 改良重點: + 1. 時效性: 超過 3 天的信號標記為「已過期」 + 2. 停損檢查: 已觸發停損的標記為「觀望」 + 3. 二空/三空: 連續窗口序列特殊處理 + + Args: + signals: List of all signals + current_price: 當前股價 + current_date: 當前日期 + lookback_days: 信號有效天數 (預設 3 天) + + Returns: + Recommendation dict with status + """ + from datetime import datetime, timedelta + + if not signals: + return None + + # 設定當前日期 + if current_date is None: + current_date = datetime.now() + elif hasattr(current_date, 'to_pydatetime'): + current_date = current_date.to_pydatetime() + + # 依日期排序 (新到舊) + sorted_signals = sorted(signals, key=lambda x: x['date'], reverse=True) + + if not sorted_signals: + return None + + # 取最新信號 + latest_signal = sorted_signals[0] + signal_date = latest_signal['date'] + + if hasattr(signal_date, 'to_pydatetime'): + signal_date = signal_date.to_pydatetime() + elif isinstance(signal_date, str): + signal_date = datetime.strptime(signal_date[:10], '%Y-%m-%d') + + # 計算天數差 + days_diff = (current_date - signal_date).days if hasattr(current_date, 'days') == False else 0 + try: + days_diff = abs((current_date - signal_date).days) + except: + days_diff = 0 + + # ======================================== + # 1. 時效性檢查 + # ======================================== + is_expired = days_diff > lookback_days + + # ======================================== + # 2. 停損觸發檢查 + # ======================================== + stop_loss_triggered = False + if current_price is not None: + if latest_signal['direction'] == 'SELL': + # 空單: 股價 > 停損價 = 已停損 + stop_loss_triggered = current_price > latest_signal['stop_loss'] + else: + # 多單: 股價 < 停損價 = 已停損 + stop_loss_triggered = current_price < latest_signal['stop_loss'] + + # ======================================== + # 3. 連續窗口檢查 (二空/三空) + # ======================================== + window_status = self.check_consecutive_windows(signals) + + # ======================================== + # 4. 決定狀態和建議 + # ======================================== + status = 'ACTIVE' + status_reason = None + recommendation = latest_signal['direction'] + + if stop_loss_triggered: + status = 'STOPPED_OUT' + status_reason = f"股價 ${current_price:.2f} 已觸發停損 ${latest_signal['stop_loss']:.2f}" + recommendation = 'HOLD' + elif is_expired: + status = 'EXPIRED' + status_reason = f"信號已過期 ({days_diff} 天前)" + recommendation = 'HOLD' + elif window_status['warning']: + status = 'SPECIAL' + status_reason = window_status['warning'] + # 三空力竭時反向操作 + if window_status['count'] >= 3: + recommendation = 'SELL' if window_status['direction'] == 'UP' else 'BUY' + + return { + 'recommendation': recommendation, + 'pattern': latest_signal['pattern'], + 'entry': latest_signal['entry_price'], + 'stop_loss': latest_signal['stop_loss'], + 'target': latest_signal['target_price'], + 'strength': latest_signal['strength_stars'], + 'date': latest_signal['date'], + 'status': status, + 'status_reason': status_reason, + 'days_since_signal': days_diff, + 'window_status': window_status + } + + def summarize_signals(self, signals: List[Dict]) -> Dict: + """ + Create a summary of all signals. + """ + if not signals: + return { + 'total': 0, + 'bullish': 0, + 'bearish': 0, + 'bias': 'NEUTRAL', + 'avg_strength': 0 + } + + bullish = [s for s in signals if s['direction'] == 'BUY'] + bearish = [s for s in signals if s['direction'] == 'SELL'] + + avg_strength = sum(s['strength'] for s in signals) / len(signals) + + if len(bullish) > len(bearish) * 1.5: + bias = 'BULLISH' + elif len(bearish) > len(bullish) * 1.5: + bias = 'BEARISH' + else: + bias = 'NEUTRAL' + + return { + 'total': len(signals), + 'bullish': len(bullish), + 'bearish': len(bearish), + 'bias': bias, + 'avg_strength': round(avg_strength, 1) + } diff --git a/sessions/2026-01-29-ONDS-catalysts.md b/sessions/2026-01-29-ONDS-catalysts.md deleted file mode 100644 index b47dce8..0000000 --- a/sessions/2026-01-29-ONDS-catalysts.md +++ /dev/null @@ -1,17 +0,0 @@ -# ONDS Catalyst Timeline (Jan 29, 2026) - -## 🔥 Imminent & High Impact -| Date | Event | Impact | Status | -|------|-------|--------|--------| -| **Jan 28** | **DoD "Blue List" Approval** | 🚀 **Extreme** | **Confirmed**. Optimus Drone approved for US Gov use. Unlocks huge TAM. | -| **Jan 31** | **Border Contract PO** | 🚀 **High** | **Pending**. Initial PO for border protection system expected within Jan. | -| **Mar 11** | **Q4 2025 Earnings** | 🟡 Medium | Estimated. Revenue guidance raised on Jan 16. | - -## 📅 Summary -- **Blue List Confirmed**: The biggest regulatory hurdle is cleared. This validates the tech for defense usage. -- **Contract Watch**: All eyes are now on the **Border Protection Contract**. With Blue List approval, the PO is likely imminent (days away). -- **Revenue Outlook**: FY2026 guidance raised to $180M (from $170M). - -## 🚦 Trade Implication -- **Bullish Confirmation**: The Blue List news is the "smoking gun" for the bull case. -- **Next Leg Up**: The stock should price in the specific monetary value of the upcoming border contract once announced. diff --git a/sessions/2026-01-29-ONDS-entry-exit.md b/sessions/2026-01-29-ONDS-entry-exit.md deleted file mode 100644 index 2e6302d..0000000 --- a/sessions/2026-01-29-ONDS-entry-exit.md +++ /dev/null @@ -1,26 +0,0 @@ -# Entry/Exit Analysis: ONDS (Jan 29, 2026) - -## 🎯 Strategy: Catalyst Breakout / Aggressive Trend -**Thesis**: The "Blue List" approval is a fundamental shift. We are no longer buying the *rumor* of safety; we are buying the *fact*. The next leg up is driven by the pending border contract PO. We want to be fully positioned BEFORE that PO drops. - -## 🟢 Entry Zones -| Zone | Price Range | Strategy | Confidence | -|------|-------------|----------|------------| -| **Primary (Aggressive)** | **$12.15 - $12.45** | **Market Buy**. Current levels are consolidating nicely above $12. Don't get cute waiting for deep dips. | 🟢 High | -| **Secondary (Dip)** | **$11.85 - $12.00** | **Limit Buy**. If market chops, add heavily here. This is key support. | 🟡 Medium | -| **Breakout Add** | **Above $13.10** | **Momentum Add**. Adding to winners if it clears local resistance with volume. | 🟢 High | - -## 🔴 Stop Loss -- **Hard Stop**: **$11.60** - - *Rationale*: A break below $11.60 invalidates the immediate bullish consolidation and suggests the "Blue List" bump is failing or being sold off. -- **Trailing Stop**: Activate at $13.50, trail by $0.50. - -## 🏁 Profit Targets -1. **Target 1: $13.50** (Near-term resistance). Take 25% profit here to lock in gains. -2. **Target 2: $15.00** (Contract PO Gap). This is the expected move when the Border Contract is officially announced. -3. **Target 3: $18.00+** (Blue Sky). Longer term hold for full valuation of gov contracts. - -## ⚡ Execution Plan -1. **Immediate**: Enter full starter position (50-70% size) at current market (~$12.30). -2. **Watch**: Monitor for the Border Contract PR. -3. **React**: If $11.80 breaks, cut. If $13.50 hits, trim. diff --git a/sessions/2026-01-29-ONDS-quick-decision.md b/sessions/2026-01-29-ONDS-quick-decision.md deleted file mode 100644 index 004a342..0000000 --- a/sessions/2026-01-29-ONDS-quick-decision.md +++ /dev/null @@ -1,53 +0,0 @@ -# Quick Decision: ONDS (Jan 29, 2026) - -## 📊 決策總結 -**執行時間**: 2026-01-29 22:20 -**標的**: Ondas Holdings (ONDS) -**最終決策**: ✅ **STRONG BUY** - ---- - -## 1️⃣ 核心論述 (Why?) -**"Blue List" 認證消除最大風險,邊境合約發布在即。** -- **催化劑 (Catalyst)**: DoD Blue List 批准 (已發生) + 邊境合約 PO (預計本月)。這是從「預期」轉向「落實」的關鍵時刻。 -- **基本面 (Fundamental)**: 營收指引上調至 $180M,政府背書確保長期營收來源。 -- **技術面 (Technical)**: 股價站穩 $12 支撐,呈現多頭排列,回檔即買點。 -- **風險分 (Risk Score)**: **90/100** (Critical Priority) - ---- - -## 2️⃣ 交易計劃 (Action Plan) - -### 💰 買多少 (Position Sizing) -- **建議倉位**: **8-10%** (高信心/高風險配置) -- **資金分配**: - - **第一批 (60%)**: 現價 **$12.30** 直接進場 (Aggressive)。 - - **第二批 (40%)**: 掛單 **$11.90** 防守/加碼。 - -### ⏰ 什麼時候買 (Timing) -- **立即執行**: 消息面 (Blue List) 強度足以支撐現價買入。不要等待完美低點,以免錯失合約發布後的跳空缺口。 - -### 🚪 出場設定 (Exit) -- **停損 (Stop)**: **$11.60** (跌破支撐與消息面失效)。 -- **目標 (Target)**: - - TP1: **$13.50** (前高/短線) - - TP2: **$15.00** (合約發布後預期目標) - ---- - -## 3️⃣ 檢查清單 (Final Check) -| 檢查項目 | 結果 | 備註 | -|----------|------|------| -| ✅ 催化劑明確? | YES | Blue List 已過,等待 PO | -| ✅ 風險可控? | YES | 下檔 $11.60,風險回報比 > 3:1 | -| ✅ 資金充裕? | CHECK | 確保總倉位不超標 | - ---- - -## 📝 執行指令 -```bash -# 範例掛單指令 (僅供參考) -BUY ONDS LIMIT 12.30 (60% Size) -BUY ONDS LIMIT 11.90 (40% Size) -STOP LOSS 11.60 -``` diff --git a/sessions/2026-01-29-ONDS-risk-score.md b/sessions/2026-01-29-ONDS-risk-score.md deleted file mode 100644 index d4e3409..0000000 --- a/sessions/2026-01-29-ONDS-risk-score.md +++ /dev/null @@ -1,28 +0,0 @@ -# Risk Score: ONDS (Jan 29, 2026) - -## 🛡️ Total Score: 90/100 (🔥 Critical Priority) - -### 1. Catalyst Clarity (35/35) - **MAX SCORE** -- **Blue List (Confirmed)**: The "Blue List" approval is a binary risk event that has resolved positively. There is no longer speculation—it is a fact. -- **Contract PO (Imminent)**: With the regulatory gate opened, the purchase order is a logistical formality expected within 48-72 hours. - -### 2. Technical Quality (22/25) -- **Support Hold**: Price successfully tested and held $12.00, now consolidating at $12.30. -- **Trend**: Bullish consolidation flag pattern forming after the initial pop. -- **Risk**: Failure to hold $11.80 would be the only technical negative, but sell-pressure is absorbed by news. - -### 3. Fundamentals (15/20) -- **Guidance Raised**: FY2026 revenue outlook up to $180M. -- **DoD Backing**: Government validation validates the tech stack's commercial viability. - -### 4. Sentiment & Liquidity (18/20) -- **Volume**: Elevated. Market is watching closely. -- **Institutional Interest**: DoD approval acts as a "seal of quality" for institutional entry. - -## ⚠️ Key Risks -1. **"Sell the News"?**: There's a small chance of profit-taking on the Blue List news, but the *actual* contract ($$$) hasn't dropped yet. The big run likely happens on the PO. -2. **Delay**: If the PO doesn't drop by Jan 31, impatience could cause a minor drift lower. - -## 💡 Conclusion -**Risk is drastically reduced** compared to yesterday. The regulatory gamble is gone. Now it's an execution play. -**Verdict**: **Aggressive Long**. diff --git a/sessions/2026-01-29-ONDS-stock-data.md b/sessions/2026-01-29-ONDS-stock-data.md deleted file mode 100644 index 304632b..0000000 --- a/sessions/2026-01-29-ONDS-stock-data.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -date: 2026-01-29 -ticker: ONDS -type: stock-data -analyst: AI Assistant ---- - -# 📊 ONDS 股票數據報告 (Jan 29 Update) - -**生成時間**: 2026-01-29 21:40 -**數據截至**: 2026-01-29 盤中 - ---- - -## 📌 快速摘要 ("Blue List" Catalyst) - -| 項目 | 數值 | 狀態 | 變化 | -|------|------|------|------| -| 當前股價 | **$12.30** | ✅ | 持穩於 $12 支撐之上 (+0.3%) | -| 催化劑更新 | **Blue List** | 🔥 | Optimus 無人機獲美國防部核准 | -| 預期催化劑 | 邊境合約 | ⏳ | 倒數 48 小時 (仍預期 1 月底) | -| 期權 OI | Call主導 | ✅ | $13 Call OI 增加至 1.8萬口 | -| 成交量 | 100M+ | 🔥 | 連續兩日爆量,換手積極 | - ---- - -## 1️⃣ 基礎市場數據 (Consolidation) - -- **股價表現**: $12.30 (Day Range: $12.06 - $13.31) -- **技術型態**: 昨日大漲後今日高檔震盪,成功守住 **$12.00** 關卡。 -- **成交量**: 100.93M (量縮整理,但仍維持高水位)。 -- **市值**: $5.19B。 - ---- - -## 2️⃣ 重大消息 (Confirmed Catalyst) - -### 🎖️ Optimus Drone 獲 "Blue List" 認證 -- **內容**: 子公司 American Robotics 的 Optimus 無人機被列入美國國防部 (DoD) 的「藍名單」(Blue UAS Cleared List)。 -- **意義**: - 1. **合規性**: 符合 NDAA 標準,可供美國政府與軍方採購。 - 2. **加速採購**: 政府機構可跳過繁瑣審查直接下單。 - 3. **前兆**: 這極有可能是「邊境保護合約」的前置作業完成訊號。 - ---- - -## 3️⃣ 期權籌碼 (Jan 30 明日到期) - -### 關鍵戰場: $13.00 -- **Call OI ($13.00)**: **18,780 口** (最大壓力/目標) -- **Put OI ($12.00)**: 11,707 口 (強力支撐) -- **IV**: 127% (極高,隱含波動劇烈) -- **解讀**: 多頭試圖在明日結算前攻克 $13,若成功將引發 Gamma Squeeze。 - ---- - -## 4️⃣ 綜合評估 - -### 投資論述 Updated -- **利多堆疊**: 營收指引上調 -> 股價反彈 -> **Blue List 認證 (今日)** -> 邊境合約 (預期明日/後天)。 -- **ז機**: "Blue List" 是極強的基本面背書,大幅降低了合約落空的風險。 -- **風險**: 1/31 若無合約公告,短線可能因預期落空回測 $11。 - -### 交易策略 -- **持有者**: 續抱,Blue List 消息證實公司在政府端的進展順利。 -- **空手者**: **$12.00 - $12.30** 仍是買點。 -- **目標**: 短線 $13.50,合約發布後看 $15+。 - ---- - -## 🔗 後續步驟 -1. `/catalyst-check ONDS` - 雖然已確認 Blue List,但仍需緊盯邊境合約公告。 diff --git a/sessions/2026-01-29.md b/sessions/2026-01-29.md deleted file mode 100644 index 0366560..0000000 --- a/sessions/2026-01-29.md +++ /dev/null @@ -1,64 +0,0 @@ -# 分析 Session: 2026-01-29 - -**建立時間**: 2026-01-29 20:41 -**狀態**: 🟡 進行中 - ---- - -## 📋 分析標的 - -| Ticker | 優先級 | 數據 | 催化劑 | 風險 | 進出場 | -|--------|--------|------|--------|------|--------| -| ONDS | **90** (🔥) | ✅ | ✅ | ✅ | ✅ | - -圖例: ⬜ 未完成 | 🟡 進行中 | ✅ 已完成 - ---- - -## 📊 篩選結果 - -> 執行 `/stock-scan` 後自動填入 - ---- - -## 📈 個股分析 - -### ONDS (Jan 29 Update) - -#### 基礎數據 -- **股價**: $12.30 (持穩) -- **新消息**: **Optimus Drone 獲 DoD "Blue List" 認證** 🔥 -- **解讀**: 政府採購綠燈,邊境合約發布機率大增。 -- [完整報告](2026-01-29-ONDS-stock-data.md) - -#### 催化劑 -- **✅ Blue List**: DoD 認證已獲批 (Jan 28)。 -- **🚀 邊境合約**: 預期 Jan 31 前發布 PO。 -- **Earnings**: 預計 Mar 11。 -- [完整報告](2026-01-29-ONDS-catalysts.md) - -#### 風險評分 -- **分數**: **90/100** (🔥 Critical) -- **變動**: 從 87 升至 90 (因 Blue List 確認)。 -- **觀點**: 管制風險消除,專注執行面。 -- [完整報告](2026-01-29-ONDS-risk-score.md) - -#### 進出場 -- **Buy Zone**: **$12.15 - $12.45** (Aggressive/Market) -- **Stop**: $11.60 (Strict) -- **Targets**: $13.50 (Short) / $15.00 (Contract) -- [完整報告](2026-01-29-ONDS-entry-exit.md) - ---- - -## 📝 手動筆記 - -[在此添加任何觀察或備註] - ---- - -## 🔄 執行紀錄 - -| 時間 | Workflow | 標的 | 備註 | -|------|----------|------|------| -| 22:25 | `/entry-exit` | ONDS | Buy @ $12.15-$12.45. Stop $11.60. [報告](2026-01-29-ONDS-entry-exit.md) | diff --git a/sessions/2026-01-30-ONDS-stock-data.md b/sessions/2026-01-30-ONDS-stock-data.md new file mode 100644 index 0000000..127546e --- /dev/null +++ b/sessions/2026-01-30-ONDS-stock-data.md @@ -0,0 +1,208 @@ +--- +date: 2026-01-30 +ticker: ONDS +type: stock-data +--- + +# 📊 ONDS 股票數據報告 + +## 📌 快速摘要 (5 指標) +| 項目 | 數值 | 評級 | +|------|------|------| +| 股價/市值 | $0.75-0.85 / $55-65M | ⚠️ | +| IV / OI | 待查 / 待查 | ❌ | +| 機構態度 | 持股 15-18% | ⚠️ | +| 財務狀況 | 待查 (小型股高風險) | ⚠️ | +| 新聞情緒 | 待查 | ❌ | + +> ⚠️ 部分數據因網路搜尋限制,標記為推測或待查 + +--- + +## 1️⃣ 基礎市場數據 + +| 指標 | 數值 | 狀態 | +|------|------|------| +| **當前股價** | $0.75 - $0.85 | ⚠️ 推測 (May 2024 數據) | +| **市值** | $55M - $65M | ⚠️ 推測 | +| **52週區間** | $0.60 - $1.88 | ⚠️ 推測 | +| **日均成交量** | 待查 | ❌ | +| **流通股數** | ~75M 股 (估算) | ⚠️ 推測 | +| **機構持股比例** | 15% - 18% | ⚠️ 推測 | + +**來源**: Yahoo Finance (歷史數據), SEC 13F + +--- + +## 2️⃣ 公司業務概覽 + +### Ondas Holdings Inc. (NASDAQ: ONDS) +**總部**: Waltham, Massachusetts + +### 雙主軸業務: + +#### 🛜 Ondas Networks (無線網路技術) +- **核心產品**: FullMAX 軟體定義無線電 (SDR) 平台 +- **技術標準**: IEEE 802.16s +- **特點**: 高可靠性、長距離、低延遲 +- **目標市場**: + - Class I 鐵路 (自動列車控制) + - 電力公用事業 + - 石油天然氣 + +#### 🚁 Ondas Autonomous Systems (OAS) +- **核心產品**: Drone-in-a-Box 自動化無人機系統 +- **關鍵併購**: American Robotics + Airobotics +- **產品線**: + - **Optimus System** (Airobotics): 工業/城市全自動無人機 + - **Scout System** (American Robotics): 首個獲 FAA BVLOS 批准的無人機 +- **應用場景**: + - 智慧城市 (緊急響應、公共安全) + - 工業監控 (礦場、煉油廠、港口) + - 國防 (戰術情報) + +**戰略整合**: FullMAX 無線技術 + 自動無人機 = 端到端生態系統 + +--- + +## 3️⃣ 機構持股 + +| 指標 | 數值 | +|------|------| +| **總機構持股** | ~11-13M 股 | +| **佔流通股** | 15-18% | +| **活躍機構數** | 40-50 家 | + +### Top 5 機構 (推測) +| 機構 | 持股狀態 | 評分 | +|------|----------|------| +| Vanguard Group | 最大持股方 (>2M 股) | 🟢 | +| BlackRock Inc. | 主要持股 | 🟢 | +| Geode Capital | 穩定持有 | 🟡 | +| State Street | Top 5 | 🟡 | +| Renaissance Technologies | 活躍交易 | 🟡 | + +**態度**: ⚠️ 中性 (低機構持股比例,高波動風險) + +**來源**: SEC 13F (Q4 2024 數據) + +--- + +## 4️⃣ 財務數據 + +| 指標 | 數值 | 狀態 | +|------|------|------| +| **年營收** | ~$15-20M | ⚠️ 推測 | +| **毛利率** | 待查 | ❌ | +| **現金** | 待查 | ❌ | +| **總債務** | 待查 | ❌ | +| **EPS** | 預計虧損 | ⚠️ 推測 | + +**財務狀況**: ⚠️ 高風險小型股,需查閱最新 10-Q/10-K + +--- + +## 5️⃣ 期權市場 + +| 指標 | 數值 | 狀態 | +|------|------|------| +| **IV** | 待查 | ❌ | +| **IV Rank** | 待查 | ❌ | +| **Call/Put OI** | 待查 | ❌ | +| **Call Wall** | 待查 | ❌ | +| **Put Wall** | 待查 | ❌ | + +> ❌ 小型股期權數據可能流動性不足 + +--- + +## 6️⃣ 分析師評級 + +| 指標 | 數值 | 狀態 | +|------|------|------| +| **目標價範圍** | $1.50 - $4.00 | ⚠️ 推測 | +| **評級共識** | 待查 | ❌ | + +--- + +## 7️⃣ 基本面評估 + +### 成長驅動 +1. **無人機智慧城市合約** - 中東擴張 (杜拜、阿布達比) + - 時間: 持續進行中 | 影響: 🟢 高 +2. **鐵路無線網路** - 北美 Class I 鐵路部署 + - 時間: 持續進行中 | 影響: 🟢 高 +3. **BVLOS 監管優勢** - Scout 系統獨特 FAA 批准 + - 時間: 已獲批 | 影響: 🟡 中 + +### 主要風險 +| 風險 | 嚴重性 | 描述 | +|------|--------|------| +| 財務風險 | 🔴 高 | 小型股現金消耗,可能需融資/稀釋 | +| 執行風險 | 🟠 中 | 商業化進度不確定 | +| 競爭風險 | 🟠 中 | 無人機/無線市場競爭激烈 | +| 流動性風險 | 🔴 高 | 低機構持股,高波動 | + +### 投資論述 + +**為什麼買?** +- 雙主軸業務 (無線網路 + 無人機) 具備協同效應 +- FAA BVLOS 批准形成監管護城河 +- 智慧城市/工業自動化趨勢受益者 + +**為什麼現在?** +- 需確認具體催化劑時間表 + +**上行空間** +- 分析師目標價 $1.50-$4.00 (當前 ~$0.80) +- 若成功商業化,市值可達 $200M+ + +**下行風險** +- 現金消耗 -> 稀釋風險 +- 商業化進度延遲 +- 整體市場對小型成長股不友善 + +### 決策建議 + +**適合類型**: +- ⚠️ 高風險投機 (小型股,低機構持股) +- ⚠️ 事件驅動 (需確認催化劑) + +**決策**: ⚠️ **觀察等待** - 需補充以下資訊: +1. 最新季報財務數據 (現金、燒錢率) +2. 具體合約/催化劑時間表 +3. 期權市場結構 + +--- + +## 📊 數據完整度 + +| 項目 | 狀態 | +|------|------| +| 基礎市場 | ⚠️ 推測 | +| 財務數據 | ❌ 遺漏 | +| 機構持股 | ⚠️ 推測 | +| 期權市場 | ❌ 遺漏 | +| 公司業務 | ✅ 已確認 | +| 風險評估 | ⚠️ 推測 | + +**整體完整度**: ~50% + +--- + +## 🔗 後續步驟 + +1. `/catalyst-check ONDS` - 催化劑詳析 +2. `/risk-score ONDS` - 風險評分 +3. `/entry-exit ONDS` - 進出場點 + +**手動補充建議**: +- 查閱 [SEC EDGAR](https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=ONDS) 最新 10-Q/10-K +- 查閱 [Yahoo Finance](https://finance.yahoo.com/quote/ONDS) 即時報價 +- 查閱 [Fintel](https://fintel.io/so/us/onds) 機構持股詳情 + +--- + +**更新時間**: 2026-01-30 16:56 +**分析師**: AI Agent +**數據標註**: ✅已確認 | ⚠️推測 | ❌遺漏 diff --git a/sessions/2026-01-30.md b/sessions/2026-01-30.md new file mode 100644 index 0000000..bfa3e33 --- /dev/null +++ b/sessions/2026-01-30.md @@ -0,0 +1,60 @@ +# 分析 Session: 2026-01-30 + +**建立時間**: 2026-01-30 16:55 +**狀態**: 🟡 進行中 + +--- + +## 📋 分析標的 + +| Ticker | 優先級 | 數據 | 催化劑 | 風險 | 進出場 | +|--------|--------|------|--------|------|--------| +| ONDS | ⚠️ 中 | 🟡 | ⬜ | ⬜ | ⬜ | + +圖例: ⬜ 未完成 | 🟡 進行中 | ✅ 已完成 + +--- + +## 📊 篩選結果 + +> 手動指定: ONDS (Ondas Holdings) + +--- + +## 📈 個股分析 + +### ONDS (Ondas Holdings) + +#### 基礎數據 +- **股價**: $0.75-0.85 ⚠️ +- **市值**: $55-65M ⚠️ +- **機構持股**: 15-18% ⚠️ +- **業務**: 無線網路 (FullMAX) + 自動無人機 (Drone-in-a-Box) + +> 詳見: [2026-01-30-ONDS-stock-data.md](./2026-01-30-ONDS-stock-data.md) + +#### 催化劑 +> 執行 `/catalyst-check ONDS` 後填入 + +#### 風險評分 +> 執行 `/risk-score ONDS` 後填入 + +#### 進出場 +> 執行 `/entry-exit ONDS` 後填入 + +--- + +## 📝 手動筆記 + +- ONDS 是高風險小型股,機構持股偏低 +- 雙主軸業務具備協同效應潛力 +- 需補充財務數據和催化劑時間表 + +--- + +## 🔄 執行紀錄 + +| 時間 | Workflow | 標的 | 備註 | +|------|----------|------|------| +| 16:55 | /new-session | - | Session 建立 | +| 16:56 | /stock-data | ONDS | 完成 ~50% (部分數據遺漏) |