Spaces:
Running
Running
| # -*- 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}") |