Recover 7 existing scripts from remote repo and rebuild 5 missing ones: - twse-margin-trading.py (融資融券) - twse-day-trading.py (當沖交易) - twse-foreign-holdings.py (外資持股) - yahoo-dividend-browserless.py (股利資訊) - yahoo-broker-trading-browserless.py (主力券商進出) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
92 lines
2.5 KiB
Python
92 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import sys
|
||
import urllib.parse
|
||
import urllib.request
|
||
|
||
|
||
def fetch_closes(symbol: str, range_: str = "6mo", interval: str = "1d") -> list[float]:
|
||
base = f"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}"
|
||
params = {"range": range_, "interval": interval}
|
||
url = f"{base}?{urllib.parse.urlencode(params)}"
|
||
|
||
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||
data = json.loads(resp.read().decode("utf-8", errors="replace"))
|
||
|
||
result = data.get("chart", {}).get("result", [])
|
||
if not result:
|
||
return []
|
||
|
||
quote = result[0].get("indicators", {}).get("quote", [])
|
||
if not quote:
|
||
return []
|
||
|
||
closes = quote[0].get("close", [])
|
||
return [float(x) for x in closes if x is not None]
|
||
|
||
|
||
def ema(values: list[float], period: int) -> list[float]:
|
||
if not values:
|
||
return []
|
||
k = 2 / (period + 1)
|
||
out = [values[0]]
|
||
for v in values[1:]:
|
||
out.append(v * k + out[-1] * (1 - k))
|
||
return out
|
||
|
||
|
||
def macd(closes: list[float]) -> tuple[list[float], list[float], list[float]]:
|
||
ema12 = ema(closes, 12)
|
||
ema26 = ema(closes, 26)
|
||
dif = [a - b for a, b in zip(ema12, ema26)]
|
||
dea = ema(dif, 9)
|
||
hist = [d - s for d, s in zip(dif, dea)]
|
||
return dif, dea, hist
|
||
|
||
|
||
def main() -> None:
|
||
symbol = sys.argv[1] if len(sys.argv) > 1 else "00922.TW"
|
||
closes = fetch_closes(symbol)
|
||
|
||
if len(closes) < 35:
|
||
print(f"{symbol} 資料不足,無法判斷 MACD(至少 35 筆,當前 {len(closes)})")
|
||
return
|
||
|
||
dif, dea, hist = macd(closes)
|
||
|
||
d0, d1 = dif[-1], dif[-2]
|
||
s0, s1 = dea[-1], dea[-2]
|
||
h0, h1 = hist[-1], hist[-2]
|
||
|
||
golden_cross = d1 <= s1 and d0 > s0
|
||
death_cross = d1 >= s1 and d0 < s0
|
||
low_zone = d0 < 0 and s0 < 0
|
||
|
||
if golden_cross and low_zone:
|
||
signal = "✅ 低位金叉:偏多訊號"
|
||
elif golden_cross:
|
||
signal = "✅ 金叉:短線轉強"
|
||
elif death_cross:
|
||
signal = "⚠️ 死叉:短線轉弱"
|
||
elif h0 > h1 and h0 > 0:
|
||
signal = "📈 多頭延續(柱體擴大)"
|
||
elif h0 < h1 and h0 < 0:
|
||
signal = "📉 空頭延續(柱體擴大)"
|
||
else:
|
||
signal = "👀 MACD 震盪中,先觀察"
|
||
|
||
print(f"{symbol} MACD 摘要")
|
||
print(f"- DIF:{d0:.4f}")
|
||
print(f"- DEA:{s0:.4f}")
|
||
print(f"- HIST:{h0:.4f}")
|
||
print(f"- 訊號:{signal}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|