QQuery / vcp_debug.py
glorifiedjx's picture
upto date!
fd48404 verified
Raw
History Blame Contribute Delete
8.47 kB
# -*- coding: utf-8 -*-
"""
VCP (Volatility Contraction Pattern) - ๋ฏธ๋„ˆ๋น„๋‹ˆ ๊ธฐ์ค€
TICKER ํ•˜๋‚˜๋งŒ ๋ฐ”๊พธ๋ฉด ๋จ!
๋ŒํŒŒ ์กฐ๊ฑด (๋ชจ๋‘ ์ถฉ์กฑํ•ด์•ผ):
1. ์ข…๊ฐ€๊ฐ€ ์ˆ˜์ถ•๊ตฌ๊ฐ„ ๊ณ ๊ฐ€๋ฅผ ๋ŒํŒŒ
2. ๋‹น์ผ ๊ฐ€๊ฒฉ ์ƒ์Šน๋ฅ  3% ์ด์ƒ (์ „์ผ ์ข…๊ฐ€ ๋Œ€๋น„)
3. ๋‹น์ผ ์–‘๋ด‰ (์ข…๊ฐ€ > ์‹œ๊ฐ€)
4. ๊ฑฐ๋ž˜๋Ÿ‰ 20์ผMA ร— 2.0 ์ด์ƒ ํญ๋ฐœ
"""
import sys, ssl
import pandas as pd
import numpy as np
import FinanceDataReader as fdr
from datetime import datetime, timedelta, timezone
_KST = timezone(timedelta(hours=9))
def _now_kst(): return datetime.now(_KST)
ssl._create_default_https_context = ssl._create_unverified_context
if sys.platform == 'win32':
sys.stdout.reconfigure(encoding='utf-8')
# =============================================
# [์—ฌ๊ธฐ๋งŒ ๋ฐ”๊พธ๋ฉด ๋จ]
# =============================================
TICKER = '222080'
# =============================================
# ์„ค์ •
# =============================================
VOL_MA_PERIOD = 20
VOL_CONTRACT_THR = 0.8 # ์ˆ˜์ถ• ๊ธฐ์ค€ (20์ผMA ร— 0.8 ์ดํ•˜)
VOL_EXPLODE_THR = 2.0 # ํญ๋ฐœ ๊ธฐ์ค€ (20์ผMA ร— 2.0 ์ด์ƒ)
MIN_PRICE_GAIN = 0.03 # ๋ŒํŒŒ์ผ ์ตœ์†Œ ์ƒ์Šน๋ฅ  (3%)
MIN_CONTRACT_DAYS = 2 # ์ตœ์†Œ ์ˆ˜์ถ• ์ผ์ˆ˜
EXPLODE_WINDOW = 5 # ์ˆ˜์ถ• ํ›„ N์ผ ๋‚ด ๋ŒํŒŒ ํ™•์ธ
DATA_START = (_now_kst() - timedelta(days=400)).strftime('%Y-%m-%d')
ANALYZE_FROM = (_now_kst() - timedelta(days=365)).strftime('%Y-%m-%d')
ANALYZE_TO = _now_kst().strftime('%Y-%m-%d') # ์˜ค๋Š˜(KST) โ€” ๋งค ์‹คํ–‰์‹œ ์ตœ์‹ 
# =============================================
# ๋ฐ์ดํ„ฐ ๋กœ๋“œ
# =============================================
print("=" * 65)
print(f"VCP ๋ถ„์„: {TICKER} | ๊ธฐ๊ฐ„: {ANALYZE_FROM} ~ {ANALYZE_TO}")
print("=" * 65)
df = fdr.DataReader(TICKER, DATA_START, ANALYZE_TO)
if df is None or len(df) == 0:
print("๋ฐ์ดํ„ฐ ์—†์Œ"); sys.exit()
df['vol_ma20'] = df['Volume'].rolling(VOL_MA_PERIOD).mean()
df['vol_contract'] = df['Volume'] < df['vol_ma20'] * VOL_CONTRACT_THR
df['price_range'] = df['High'] - df['Low']
df['range_ma10'] = df['price_range'].rolling(10).mean()
df['prev_close'] = df['Close'].shift(1)
print(f"๋ฐ์ดํ„ฐ: {len(df)}์ผ ๋กœ๋“œ ์™„๋ฃŒ\n")
# =============================================
# VCP ๊ฐ์ง€
# =============================================
vcps = []
i = VOL_MA_PERIOD
while i < len(df) - 1:
if not df['vol_contract'].iloc[i]:
i += 1; continue
# ์ˆ˜์ถ• ๊ตฌ๊ฐ„ ์ฐพ๊ธฐ
start = i
while start > VOL_MA_PERIOD and df['vol_contract'].iloc[start - 1]:
start -= 1
end = i
while end < len(df) - 1 and df['vol_contract'].iloc[end + 1]:
end += 1
days = end - start + 1
if days < MIN_CONTRACT_DAYS:
i = end + 1; continue
zone = df.iloc[start:end+1]
zone_low = float(zone['Low'].min())
zone_high = float(zone['High'].max())
zone_vol = float(zone['Volume'].mean())
vol_ma = float(df['vol_ma20'].iloc[end])
vol_ratio = round(zone_vol / vol_ma, 2) if vol_ma > 0 else 0
# ๊ฐ€๊ฒฉ ์ˆ˜์ถ• ํ™•์ธ
pre_range_ma = float(df['range_ma10'].iloc[max(0, start-1)])
zone_range = float(zone['price_range'].mean())
is_contracting = (zone_range < pre_range_ma) if pre_range_ma > 0 else False
# ๋ŒํŒŒ ํ™•์ธ (4๊ฐ€์ง€ ์กฐ๊ฑด ๋ชจ๋‘)
breakout = False
breakout_date = None
breakout_price = None
breakout_vol_r = None
breakout_gain = None
fail_reason = None
for j in range(end + 1, min(end + EXPLODE_WINDOW + 1, len(df))):
close_j = float(df['Close'].iloc[j])
open_j = float(df['Open'].iloc[j])
vol_j = float(df['Volume'].iloc[j])
vol_ma_j = float(df['vol_ma20'].iloc[j])
prev_close = float(df['prev_close'].iloc[j])
if prev_close == 0 or pd.isna(prev_close):
continue
price_gain = (close_j - prev_close) / prev_close # ์ „์ผ ๋Œ€๋น„ ์ƒ์Šน๋ฅ 
is_bullish = close_j > open_j # ์–‘๋ด‰
price_break = close_j > zone_high # ๊ตฌ๊ฐ„ ๊ณ ๊ฐ€ ๋ŒํŒŒ
vol_break = vol_j > vol_ma_j * VOL_EXPLODE_THR # ๊ฑฐ๋ž˜๋Ÿ‰ ํญ๋ฐœ
gain_ok = price_gain >= MIN_PRICE_GAIN # 3% ์ด์ƒ ์ƒ์Šน
if price_break and vol_break and gain_ok and is_bullish:
breakout = True
breakout_date = str(df.index[j])[:10]
breakout_price = close_j
breakout_vol_r = round(vol_j / vol_ma_j, 2)
breakout_gain = round(price_gain * 100, 1)
break
elif price_break:
# ๊ฐ€๊ฒฉ์€ ๋ŒํŒŒํ–ˆ์ง€๋งŒ ๋‹ค๋ฅธ ์กฐ๊ฑด ๋ฏธ์ถฉ์กฑ โ†’ ์ด์œ  ๊ธฐ๋ก
reasons = []
if not vol_break:
reasons.append(f"๊ฑฐ๋ž˜๋Ÿ‰ ๋ถ€์กฑ({round(vol_j/vol_ma_j,1)}x, ํ•„์š” {VOL_EXPLODE_THR}x)")
if not gain_ok:
reasons.append(f"์ƒ์Šน๋ฅ  ๋ถ€์กฑ({price_gain*100:+.1f}%, ํ•„์š” {MIN_PRICE_GAIN*100:.0f}%+)")
if not is_bullish:
reasons.append("์Œ๋ด‰")
fail_reason = str(df.index[j])[:10] + " ๊ฐ€๊ฒฉ๋ŒํŒŒํ–ˆ์ง€๋งŒ: " + ", ".join(reasons)
vcp = {
'start_date': str(df.index[start])[:10],
'end_date': str(df.index[end])[:10],
'days': days,
'zone_low': round(zone_low, 0),
'zone_high': round(zone_high, 0),
'vol_ratio': vol_ratio,
'is_contracting': is_contracting,
'zone_range': round(zone_range, 0),
'pre_range_ma': round(pre_range_ma, 0),
'confirmed': breakout,
'breakout_date': breakout_date,
'breakout_price': breakout_price,
'breakout_vol_r': breakout_vol_r,
'breakout_gain': breakout_gain,
'fail_reason': fail_reason,
}
vcps.append(vcp)
i = end + 1
# ๋ถ„์„ ๊ธฐ๊ฐ„ ํ•„ํ„ฐ
vcps = [v for v in vcps if v['end_date'] >= ANALYZE_FROM]
# =============================================
# ๊ฒฐ๊ณผ ์ถœ๋ ฅ
# =============================================
print(f"๊ฐ์ง€๋œ VCP: {len(vcps)}๊ฐœ\n")
if len(vcps) == 0:
print("VCP ํŒจํ„ด ์—†์Œ"); sys.exit()
for idx, v in enumerate(vcps, 1):
status = "[ํ™•์ •] " if v['confirmed'] else "[๋ฏธํ™•์ •]"
print(f"{'='*65}")
print(f"VCP #{idx} {status} {v['start_date']} ~ {v['end_date']} ({v['days']}์ผ)")
print(f"{'='*65}")
print(f" ์ˆ˜์ถ•๊ตฌ๊ฐ„: {v['zone_low']:,.0f} ~ {v['zone_high']:,.0f}์›")
print(f" ๊ฑฐ๋ž˜๋Ÿ‰: 20์ผMA ๋Œ€๋น„ {v['vol_ratio']*100:.0f}% (๊ธฐ์ค€ {VOL_CONTRACT_THR*100:.0f}% ์ดํ•˜)")
if v['pre_range_ma'] > 0:
shrink = (1 - v['zone_range'] / v['pre_range_ma']) * 100
mark = 'O' if v['is_contracting'] else 'X'
print(f" ๊ฐ€๊ฒฉ์ˆ˜์ถ•: {mark} (๊ตฌ๊ฐ„ {v['zone_range']:,.0f} vs ์ง์ „MA {v['pre_range_ma']:,.0f}, {shrink:+.0f}%)")
if v['confirmed']:
print(f" ๋ŒํŒŒ: {v['breakout_date']} | "
f"์ข…๊ฐ€ {v['breakout_price']:,.0f}์› | "
f"์ƒ์Šน๋ฅ  +{v['breakout_gain']}% | "
f"๊ฑฐ๋ž˜๋Ÿ‰ MAร—{v['breakout_vol_r']} | ์–‘๋ด‰")
elif v['fail_reason']:
print(f" ๋ŒํŒŒ์‹œ๋„: {v['fail_reason']}")
else:
print(f" ๋ŒํŒŒ: ์—†์Œ")
print(f"\n [ํŒ๋‹จ ์ด์œ ]")
print(f" - ๊ฑฐ๋ž˜๋Ÿ‰ {v['days']}์ผ ์—ฐ์† 20์ผMAร—{VOL_CONTRACT_THR} ์ดํ•˜ ์ˆ˜์ถ•")
print(f" - ๊ฐ€๊ฒฉ์ˆ˜์ถ•: {'ํ™•์ธ' if v['is_contracting'] else '๋ฏธํ™•์ธ'}")
if v['confirmed']:
print(f" - ๊ตฌ๊ฐ„๊ณ ๊ฐ€({v['zone_high']:,.0f}) ๋ŒํŒŒ + ์ƒ์Šน๋ฅ  {v['breakout_gain']}% + ๊ฑฐ๋ž˜๋Ÿ‰ MAร—{v['breakout_vol_r']} + ์–‘๋ด‰ โ†’ ํ™•์ •")
else:
print(f" - 4๊ฐ€์ง€ ๋ŒํŒŒ์กฐ๊ฑด ๋ฏธ์ถฉ์กฑ (๊ฐ€๊ฒฉ๋ŒํŒŒ+3%์ƒ์Šน+๊ฑฐ๋ž˜๋Ÿ‰2x+์–‘๋ด‰)")
print()
# ์š”์•ฝ
confirmed = [v for v in vcps if v['confirmed']]
pending = [v for v in vcps if not v['confirmed']]
print("=" * 65)
print("์ตœ์ข… ์š”์•ฝ")
print("=" * 65)
print(f"์ „์ฒด: {len(vcps)}๊ฐœ | ํ™•์ •: {len(confirmed)}๊ฐœ | ๋ฏธํ™•์ •: {len(pending)}๊ฐœ")
if confirmed:
print("\n[ํ™•์ • VCP]")
for v in confirmed:
print(f" {v['start_date']}~{v['end_date']} | "
f"๊ตฌ๊ฐ„ {v['zone_low']:,.0f}~{v['zone_high']:,.0f} | "
f"๋ŒํŒŒ {v['breakout_date']} +{v['breakout_gain']}% MAร—{v['breakout_vol_r']}")
if pending:
print("\n[๋ฏธํ™•์ • VCP]")
for v in pending:
print(f" {v['start_date']}~{v['end_date']} | "
f"๊ตฌ๊ฐ„ {v['zone_low']:,.0f}~{v['zone_high']:,.0f}")