WLD DeepCoin 단계별 구조 재편 및 설정·문서 통합
로고스/루트 레거시를 제거하고 deepcoin 패키지·scripts 01~05 CLI·docs/reference로 데이터·GT·분석·매칭·운영 단계를 정리했다. config와 .env 기반 설정, trade_anaysis.html 동기화 포함. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
205
deepcoin/analysis/general_analysis_wave.py
Normal file
205
deepcoin/analysis/general_analysis_wave.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
general_analysis 파동·시장 구조 (다우, 엘리어트 라이트, 피보나치, Wyckoff 태그).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from deepcoin.analysis.general_analysis_core import find_pivots, ga_col
|
||||
|
||||
|
||||
def _fib_levels(low: float, high: float) -> dict[str, float]:
|
||||
diff = high - low
|
||||
return {
|
||||
"fib_0": low,
|
||||
"fib_382": low + diff * 0.382,
|
||||
"fib_500": low + diff * 0.5,
|
||||
"fib_618": low + diff * 0.618,
|
||||
"fib_100": high,
|
||||
"fib_1618": low + diff * 1.618,
|
||||
}
|
||||
|
||||
|
||||
def general_analysis_wave_snapshot(win: pd.DataFrame) -> dict[str, object]:
|
||||
"""
|
||||
lookback 윈도우 마지막 시점 파동·구조 스냅샷.
|
||||
|
||||
Args:
|
||||
win: OHLCV.
|
||||
|
||||
Returns:
|
||||
ga_wave_* / ga_struct_* / ga_fib_* dict.
|
||||
"""
|
||||
res: dict[str, object] = {
|
||||
"struct_trend": "range",
|
||||
"struct_hh": 0,
|
||||
"struct_hl": 0,
|
||||
"struct_lh": 0,
|
||||
"struct_ll": 0,
|
||||
"struct_bos_bull": 0,
|
||||
"struct_bos_bear": 0,
|
||||
"struct_choch": 0,
|
||||
"elliott_wave_count": 0,
|
||||
"elliott_phase": "unknown",
|
||||
"wyckoff_phase": "unknown",
|
||||
"fib_near_level": "none",
|
||||
"ichi_trend": "neutral",
|
||||
"pitchfork_bias": "neutral",
|
||||
"pitchfork_dist_pct": 0.0,
|
||||
"wyckoff_spring": 0,
|
||||
"wyckoff_utad": 0,
|
||||
}
|
||||
if win is None or len(win) < 15:
|
||||
return {ga_col(k): v for k, v in res.items()}
|
||||
|
||||
h = win["High"].astype(float).values
|
||||
l = win["Low"].astype(float).values
|
||||
c = win["Close"].astype(float).values
|
||||
peaks, troughs = find_pivots(h, l, order=2)
|
||||
|
||||
# Dow HH/HL/LH/LL
|
||||
if len(peaks) >= 2 and len(troughs) >= 2:
|
||||
hh = int(h[peaks[-1]] > h[peaks[-2]])
|
||||
hl = int(l[troughs[-1]] > l[troughs[-2]])
|
||||
lh = int(h[peaks[-1]] < h[peaks[-2]])
|
||||
ll = int(l[troughs[-1]] < l[troughs[-2]])
|
||||
res["struct_hh"] = hh
|
||||
res["struct_hl"] = hl
|
||||
res["struct_lh"] = lh
|
||||
res["struct_ll"] = ll
|
||||
if hh and hl:
|
||||
res["struct_trend"] = "up"
|
||||
elif lh and ll:
|
||||
res["struct_trend"] = "down"
|
||||
if hh and c[-1] > h[peaks[-2]]:
|
||||
res["struct_bos_bull"] = 1
|
||||
if ll and c[-1] < l[troughs[-2]]:
|
||||
res["struct_bos_bear"] = 1
|
||||
if (hh and ll) or (lh and hl):
|
||||
res["struct_choch"] = 1
|
||||
|
||||
# Elliott lite: pivot count in window
|
||||
swings = len(peaks) + len(troughs)
|
||||
res["elliott_wave_count"] = swings
|
||||
if swings >= 5:
|
||||
res["elliott_phase"] = "impulse_late"
|
||||
elif swings >= 3:
|
||||
res["elliott_phase"] = "corrective"
|
||||
|
||||
# Wyckoff lite
|
||||
vol = win["Volume"].astype(float).values if "Volume" in win.columns else np.ones(len(c))
|
||||
vol_ma = vol[-20:].mean() if len(vol) >= 20 else vol.mean()
|
||||
price_range = (h[-20:].max() - l[-20:].min()) / max(c[-1], 1e-9) * 100
|
||||
if price_range < 6 and vol[-1] < vol_ma * 1.2:
|
||||
res["wyckoff_phase"] = "accumulation"
|
||||
elif price_range < 6 and vol[-1] > vol_ma * 1.5 and c[-1] > c[-5]:
|
||||
res["wyckoff_phase"] = "distribution"
|
||||
|
||||
if price_range < 8 and l[-1] < l[-5] and c[-1] > c[-2] and vol[-1] > vol_ma * 1.3:
|
||||
res["wyckoff_spring"] = 1
|
||||
if price_range < 8 and h[-1] > h[-5] and c[-1] < c[-2] and vol[-1] > vol_ma * 1.3:
|
||||
res["wyckoff_utad"] = 1
|
||||
|
||||
# Andrews Pitchfork (3피벗 중앙선 대비 종가 위치)
|
||||
pivots = sorted([(i, h[i]) for i in peaks] + [(i, l[i]) for i in troughs])
|
||||
if len(pivots) >= 3:
|
||||
p0, p1, p2 = pivots[-3], pivots[-2], pivots[-1]
|
||||
y0 = p0[1]
|
||||
y_mid = (p1[1] + p2[1]) / 2
|
||||
x0, x2 = p0[0], p2[0]
|
||||
if x2 != x0:
|
||||
slope = (y_mid - y0) / (x2 - x0)
|
||||
y_line = y0 + slope * (len(c) - 1 - x0)
|
||||
dist_pct = (c[-1] - y_line) / max(c[-1], 1e-9) * 100
|
||||
res["pitchfork_dist_pct"] = round(float(dist_pct), 3)
|
||||
if dist_pct > 0.5:
|
||||
res["pitchfork_bias"] = "above"
|
||||
elif dist_pct < -0.5:
|
||||
res["pitchfork_bias"] = "below"
|
||||
|
||||
# Fibonacci
|
||||
hi, lo = float(h.max()), float(l.min())
|
||||
levels = _fib_levels(lo, hi)
|
||||
price = float(c[-1])
|
||||
for name, lvl in levels.items():
|
||||
if abs(price - lvl) / max(price, 1e-9) * 100 < 1.5:
|
||||
res["fib_near_level"] = name.replace("fib_", "")
|
||||
break
|
||||
|
||||
if "ichi_cloud_top" in win.columns:
|
||||
row = win.iloc[-1]
|
||||
ct = float(row.get("ichi_cloud_top", np.nan))
|
||||
cb = float(row.get("ichi_cloud_bottom", np.nan))
|
||||
if not np.isnan(ct) and price > ct:
|
||||
res["ichi_trend"] = "above_cloud"
|
||||
elif not np.isnan(cb) and price < cb:
|
||||
res["ichi_trend"] = "below_cloud"
|
||||
else:
|
||||
res["ichi_trend"] = "in_cloud"
|
||||
|
||||
return {ga_col(k): v for k, v in res.items()}
|
||||
|
||||
|
||||
def general_analysis_wave_columns() -> list[str]:
|
||||
return [
|
||||
"struct_trend",
|
||||
"struct_hh",
|
||||
"struct_hl",
|
||||
"struct_lh",
|
||||
"struct_ll",
|
||||
"struct_bos_bull",
|
||||
"struct_bos_bear",
|
||||
"struct_choch",
|
||||
"elliott_wave_count",
|
||||
"elliott_phase",
|
||||
"wyckoff_phase",
|
||||
"fib_near_level",
|
||||
"ichi_trend",
|
||||
"pitchfork_bias",
|
||||
"pitchfork_dist_pct",
|
||||
"wyckoff_spring",
|
||||
"wyckoff_utad",
|
||||
]
|
||||
|
||||
|
||||
def general_analysis_apply_wave_to_bars(
|
||||
df: pd.DataFrame,
|
||||
interval: int,
|
||||
tail_rows: int | None = None,
|
||||
) -> pd.DataFrame:
|
||||
"""파동·구조 스냅샷을 최근 봉에 롤링 적용."""
|
||||
from deepcoin.analysis.general_analysis_config import CONTEXT_TAIL_ROWS, LOOKBACK_BARS
|
||||
|
||||
out = df.copy()
|
||||
lb = LOOKBACK_BARS.get(interval, 80)
|
||||
for k in general_analysis_wave_columns():
|
||||
col = ga_col(k)
|
||||
if k == "struct_trend":
|
||||
out[col] = "range"
|
||||
elif k in ("elliott_phase", "wyckoff_phase"):
|
||||
out[col] = "unknown"
|
||||
elif k == "fib_near_level":
|
||||
out[col] = "none"
|
||||
elif k in ("ichi_trend", "pitchfork_bias"):
|
||||
out[col] = "neutral"
|
||||
elif k == "pitchfork_dist_pct":
|
||||
out[col] = 0.0
|
||||
else:
|
||||
out[col] = 0
|
||||
|
||||
n = len(out)
|
||||
if n < lb + 1:
|
||||
return out
|
||||
if tail_rows is None:
|
||||
tail_rows = CONTEXT_TAIL_ROWS.get(interval, 5000)
|
||||
start = max(lb, n - tail_rows)
|
||||
|
||||
for i in range(start, n):
|
||||
snap = general_analysis_wave_snapshot(out.iloc[i - lb : i])
|
||||
idx = out.index[i]
|
||||
for k, v in snap.items():
|
||||
out.at[idx, k] = v
|
||||
|
||||
return out
|
||||
Reference in New Issue
Block a user