Files
AssetMonitor/simulation_1mon.py
dsyoon c45ad151b6 init
2026-01-28 18:58:33 +09:00

1097 lines
56 KiB
Python

import pandas as pd
import plotly.graph_objs as go
from plotly import subplots
import plotly.io as pio
from datetime import datetime
import numpy as np
import webbrowser
import tempfile
import os
# Plotly 설정
pio.renderers.default = 'browser'
from config import *
# ========================================
# 월봉 시뮬레이션 설정
# ========================================
# 코인 선택 (대문자로 입력)
COINS = ['XRP', 'ADA', 'APT', 'AVAX', 'BONK', 'BTC', 'ETC', 'HBAR', 'LINK', 'ONDO', 'PENGU', 'SEI', 'SOL', 'SUI', 'TRX', 'VIRTUAL', 'WLD', 'XLM']
COIN = COINS[5] # 기본값: XRP
# 시뮬레이션 설정
INTERVAL = 43200 # 월봉 (43200분 = 30일)
BONG_COUNT = 100 # 분석할 월봉 개수 (약 8년)
SHOW_GRAPHS = True # 그래프 표시 여부
# ========================================
# 통합 최적화 월봉 전략 클래스
# ========================================
class OptimizedMonthlyStrategy:
"""통합 최적화 월봉 전략: 최저점 돌파 중심 + 고급 기술적 분석"""
def __init__(self, coin: str):
self.coin = coin
self.name = KR_COINS.get(coin, coin)
def get_buy_amount(self, signal: str, current_price: float, signal_strength: float = 70) -> float:
"""최적화된 월봉 신호에 따른 매수 금액 결정"""
base_amount = 100000 # 기본 매수 금액
# 신호별 기본 가중치
signal_weights = {
'monthly_ultimate_breakout': 4.0, # 최고 강도 신호 (36개월 최저점 돌파)
'monthly_strong_breakout': 3.0, # 고강도 신호 (24개월 최저점 돌파)
'monthly_breakout': 2.5, # 중강도 신호 (12개월 최저점 돌파)
'monthly_bb_reversal': 2.0, # 보조 신호 (볼린저 밴드 반전)
'monthly_rsi_recovery': 1.8, # 보조 신호 (RSI 회복)
'monthly_golden_cross': 2.0, # 골든크로스
'monthly_deviation12': 1.5, # 이격도 신호
'monthly_macd_bullish': 1.4, # MACD 상승
'monthly_trend_reversal': 1.8 # 추세 전환
}
base_weight = signal_weights.get(signal, 1.0)
# 신호 강도에 따른 조정 (70-100점 범위를 0.8-1.2 배수로 변환)
strength_factor = 0.8 + (signal_strength - 70) / 30 * 0.4
# 가격에 따른 조정 (고가 코인일수록 적게 매수)
price_factor = 1.0
if current_price > 100000: # 10만원 이상
price_factor = 0.7
elif current_price > 10000: # 1만원 이상
price_factor = 0.8
elif current_price > 1000: # 1천원 이상
price_factor = 0.9
final_amount = base_amount * base_weight * strength_factor * price_factor
# 최대/최소 제한
return max(50000, min(500000, final_amount))
# ========================================
# 월봉 시뮬레이션 클래스
# ========================================
class OptimizedMonthlySimulation:
"""통합 최적화 월봉 기준 코인 시뮬레이션 클래스"""
def __init__(self, coin: str) -> None:
self.coin = coin
self.strategy = OptimizedMonthlyStrategy(coin)
self.strategy_type = 2 # 통합 최적화 전략 타입
# 모니터 클래스 임포트 (기존 파일 사용)
from monitor_coin_1mon_1 import MonthlyCoinMonitor1
self.monitor = MonthlyCoinMonitor1()
def fetch_weekly_data(self, symbol: str):
"""주봉 데이터 가져오기"""
try:
return self.monitor.get_coin_data(symbol, 10080) # 10080분 = 1주
except Exception as e:
print(f"주봉 데이터 가져오기 실패: {e}")
return None
def calculate_monthly_indicators(self, data: pd.DataFrame, is_weekly: bool = False) -> pd.DataFrame:
"""월봉/주봉 전용 기술적 지표 계산"""
data = data.copy()
if is_weekly:
# 주봉 이동평균선 (3, 6, 12, 24, 36주)
data['MA3'] = data['Close'].rolling(window=3).mean()
data['MA6'] = data['Close'].rolling(window=6).mean()
data['MA12'] = data['Close'].rolling(window=12).mean()
data['MA24'] = data['Close'].rolling(window=24).mean()
data['MA36'] = data['Close'].rolling(window=36).mean()
else:
# 월봉 이동평균선 (3, 6, 12, 24, 36개월)
data['MA3'] = data['Close'].rolling(window=3).mean()
data['MA6'] = data['Close'].rolling(window=6).mean()
data['MA12'] = data['Close'].rolling(window=12).mean()
data['MA24'] = data['Close'].rolling(window=24).mean()
data['MA36'] = data['Close'].rolling(window=36).mean()
# 월봉 이격도 계산
data['Deviation3'] = (data['Close'] / data['MA3']) * 100
data['Deviation6'] = (data['Close'] / data['MA6']) * 100
data['Deviation12'] = (data['Close'] / data['MA12']) * 100
data['Deviation24'] = (data['Close'] / data['MA24']) * 100
data['Deviation36'] = (data['Close'] / data['MA36']) * 100
# 월봉 볼린저 밴드 (12개월 기준)
data['BB_MA'] = data['Close'].rolling(window=12).mean()
data['BB_STD'] = data['Close'].rolling(window=12).std()
data['BB_Upper'] = data['BB_MA'] + (2 * data['BB_STD'])
data['BB_Lower'] = data['BB_MA'] - (2 * data['BB_STD'])
data['BB_Width'] = (data['BB_Upper'] - data['BB_Lower']) / data['BB_MA'] * 100
# 월봉 RSI (12개월 기준)
delta = data['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=12).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=12).mean()
rs = gain / loss
data['RSI'] = 100 - (100 / (1 + rs))
# 월봉 MACD
ema12 = data['Close'].ewm(span=12).mean()
ema26 = data['Close'].ewm(span=26).mean()
data['MACD'] = ema12 - ema26
data['MACD_Signal'] = data['MACD'].ewm(span=9).mean()
data['MACD_Histogram'] = data['MACD'] - data['MACD_Signal']
# 고급 지표들 (전략 2용)
if self.strategy_type == 2:
# 지수이동평균선 (EMA)
data['EMA6'] = data['Close'].ewm(span=6).mean()
data['EMA12'] = data['Close'].ewm(span=12).mean()
data['EMA24'] = data['Close'].ewm(span=24).mean()
# 다중 볼린저 밴드
for period in [6, 12, 24]:
data[f'BB_MA_{period}'] = data['Close'].rolling(window=period).mean()
data[f'BB_STD_{period}'] = data['Close'].rolling(window=period).std()
data[f'BB_Upper_{period}'] = data[f'BB_MA_{period}'] + (2 * data[f'BB_STD_{period}'])
data[f'BB_Lower_{period}'] = data[f'BB_MA_{period}'] - (2 * data[f'BB_STD_{period}'])
data[f'BB_Width_{period}'] = (data[f'BB_Upper_{period}'] - data[f'BB_Lower_{period}']) / data[f'BB_MA_{period}'] * 100
# 다중 RSI
for period in [6, 12, 24]:
delta = data['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
data[f'RSI_{period}'] = 100 - (100 / (1 + rs))
# 스토캐스틱
data['Stoch_K'] = ((data['Close'] - data['Low'].rolling(window=12).min()) /
(data['High'].rolling(window=12).max() - data['Low'].rolling(window=12).min())) * 100
data['Stoch_D'] = data['Stoch_K'].rolling(window=3).mean()
# 윌리엄스 %R
data['Williams_R'] = ((data['High'].rolling(window=12).max() - data['Close']) /
(data['High'].rolling(window=12).max() - data['Low'].rolling(window=12).min())) * -100
# CCI (Commodity Channel Index)
tp = (data['High'] + data['Low'] + data['Close']) / 3
data['CCI'] = (tp - tp.rolling(window=12).mean()) / (0.015 * tp.rolling(window=12).std())
# ADX (Average Directional Index)
high_diff = data['High'].diff()
low_diff = data['Low'].diff()
data['DM_Plus'] = np.where((high_diff > low_diff) & (high_diff > 0), high_diff, 0)
data['DM_Minus'] = np.where((low_diff > high_diff) & (low_diff > 0), low_diff, 0)
data['TR'] = np.maximum(data['High'] - data['Low'],
np.maximum(abs(data['High'] - data['Close'].shift(1)),
abs(data['Low'] - data['Close'].shift(1))))
data['DI_Plus'] = 100 * (data['DM_Plus'].rolling(window=12).mean() / data['TR'].rolling(window=12).mean())
data['DI_Minus'] = 100 * (data['DM_Minus'].rolling(window=12).mean() / data['TR'].rolling(window=12).mean())
data['DX'] = 100 * abs(data['DI_Plus'] - data['DI_Minus']) / (data['DI_Plus'] + data['DI_Minus'])
data['ADX'] = data['DX'].rolling(window=12).mean()
# 가격 모멘텀 지표
data['Momentum_6'] = data['Close'] / data['Close'].shift(6) * 100
data['Momentum_12'] = data['Close'] / data['Close'].shift(12) * 100
data['Momentum_24'] = data['Close'] / data['Close'].shift(24) * 100
# 변동성 지표
data['Volatility'] = data['Close'].rolling(window=12).std() / data['Close'].rolling(window=12).mean() * 100
return data
def generate_optimized_signals(self, symbol: str, data: pd.DataFrame) -> pd.DataFrame:
"""글로벌 최적화 월봉 기반 매수 신호 생성 - 전체 그래프 최저점 기준"""
data = data.copy()
data['signal'] = ''
data['point'] = 0
data['signal_strength'] = 0 # 신호 강도 (0-100)
signal_count = 0
# 전체 데이터에서 글로벌 최저점들 찾기 (개선된 알고리즘 - 더 많은 매수 기회)
def find_global_lows(data, short_window=3, long_window=9):
"""전체 데이터에서 글로벌 최저점들 찾기 - 개선된 다중 윈도우 방식"""
global_lows = []
# 1단계: 단기 윈도우로 로컬 최저점 찾기 (더 민감하게)
short_lows = []
for i in range(short_window, len(data) - short_window):
is_short_low = True
current_low = data['Low'].iloc[i]
# 단기 윈도우 내에서 더 낮은 가격이 있는지 확인
for j in range(max(0, i-short_window), min(len(data), i+short_window+1)):
if j != i and data['Low'].iloc[j] < current_low:
is_short_low = False
break
if is_short_low:
short_lows.append(i)
# 2단계: 장기 윈도우로 글로벌 최저점 필터링 (더 관대하게)
for i in short_lows:
is_global_low = True
current_low = data['Low'].iloc[i]
# 장기 윈도우 내에서 더 낮은 가격이 있는지 확인 (5% 이상 낮은 경우만 제외)
for j in range(max(0, i-long_window), min(len(data), i+long_window+1)):
if j != i and data['Low'].iloc[j] < current_low * 0.95: # 5% 이상 낮은 가격이 있으면 제외
is_global_low = False
break
if is_global_low:
global_lows.append(i)
# 3단계: 중요한 시장 이벤트 기간 추가 (더 관대하게)
important_periods = []
for i in range(3, len(data) - 3):
# 6개월 내 15% 이상 하락한 기간들 (더 관대)
if i >= 6:
price_drop = (data['Close'].iloc[i] - data['Close'].iloc[i-6]) / data['Close'].iloc[i-6] * 100
if price_drop < -15: # 6개월 내 15% 이상 하락
important_periods.append(i)
# 3개월 내 10% 이상 하락한 기간들 (더 관대)
if i >= 3:
price_drop_3m = (data['Close'].iloc[i] - data['Close'].iloc[i-3]) / data['Close'].iloc[i-3] * 100
if price_drop_3m < -10: # 3개월 내 10% 이상 하락
important_periods.append(i)
# 12개월 내 25% 이상 하락한 기간들 (새로 추가)
if i >= 12:
price_drop_12m = (data['Close'].iloc[i] - data['Close'].iloc[i-12]) / data['Close'].iloc[i-12] * 100
if price_drop_12m < -25: # 12개월 내 25% 이상 하락
important_periods.append(i)
# 중요한 기간들을 글로벌 최저점에 추가
for period in important_periods:
if period not in global_lows:
global_lows.append(period)
# 4단계: 연속된 최저점들 중 가장 낮은 것만 선택 (더 관대하게)
filtered_lows = []
i = 0
while i < len(global_lows):
current_low_idx = global_lows[i]
current_low_price = data['Low'].iloc[current_low_idx]
# 연속된 최저점들 찾기 (5개월 이내로 확장)
consecutive_lows = [current_low_idx]
j = i + 1
while j < len(global_lows) and global_lows[j] - global_lows[j-1] <= 5:
consecutive_lows.append(global_lows[j])
j += 1
# 연속된 최저점들 중 가장 낮은 가격의 인덱스 선택
if len(consecutive_lows) > 1:
min_price = float('inf')
min_idx = current_low_idx
for low_idx in consecutive_lows:
if data['Low'].iloc[low_idx] < min_price:
min_price = data['Low'].iloc[low_idx]
min_idx = low_idx
filtered_lows.append(min_idx)
else:
filtered_lows.append(current_low_idx)
i = j
# 중복 제거 및 정렬
global_lows = sorted(list(set(filtered_lows)))
return global_lows
# 글로벌 최저점들 찾기 (개선된 알고리즘 - 더 많은 매수 기회)
global_lows = find_global_lows(data, short_window=3, long_window=9)
print(f"글로벌 최저점 {len(global_lows)}개 발견")
# 글로벌 최저점 정보 출력
for i, low_idx in enumerate(global_lows):
date = data.index[low_idx]
price = data['Low'].iloc[low_idx]
print(f" {i+1}. {date.strftime('%Y-%m')}: {price:.0f}")
# 2024년 10월부터 매수 제한 (데이터 인덱스 기반) - 2024년 9월 허용
# 데이터의 마지막 5개월은 매수 제한 (대략 2024년 11월 이후)
max_buy_index = len(data) - 5 # 마지막 5개월 제외 (2024년 9월 허용)
print(f"\n매수 제한 설정:")
print(f" - 전체 데이터: {len(data)}개월")
print(f" - 매수 가능 기간: {max_buy_index}개월까지")
print(f" - 제한 기간: 마지막 5개월 (대략 2024년 11월 이후)")
if max_buy_index < len(data):
last_buy_date = data.index[max_buy_index-1]
print(f" - 마지막 매수 가능일: {last_buy_date.strftime('%Y-%m')}")
for i in range(36, max_buy_index): # 최소 36개월 데이터 필요, 최대 max_buy_index까지
current_price = data['Close'].iloc[i]
# 이동평균선 상태
ma3 = data['MA3'].iloc[i]
ma6 = data['MA6'].iloc[i]
ma12 = data['MA12'].iloc[i]
ma24 = data['MA24'].iloc[i]
ma36 = data['MA36'].iloc[i]
# 이격도
dev3 = data['Deviation3'].iloc[i]
dev6 = data['Deviation6'].iloc[i]
dev12 = data['Deviation12'].iloc[i]
dev24 = data['Deviation24'].iloc[i]
dev36 = data['Deviation36'].iloc[i]
# RSI
rsi = data['RSI'].iloc[i]
# 볼린저 밴드
bb_lower = data['BB_Lower'].iloc[i]
bb_upper = data['BB_Upper'].iloc[i]
bb_width = data['BB_Width'].iloc[i]
# MACD
macd = data['MACD'].iloc[i]
macd_signal = data['MACD_Signal'].iloc[i]
macd_hist = data['MACD_Histogram'].iloc[i]
# 스토캐스틱
stoch_k = data['Stoch_K'].iloc[i]
stoch_d = data['Stoch_D'].iloc[i]
# 윌리엄스 %R
williams_r = data['Williams_R'].iloc[i]
# CCI
cci = data['CCI'].iloc[i]
# ADX
adx = data['ADX'].iloc[i]
di_plus = data['DI_Plus'].iloc[i]
di_minus = data['DI_Minus'].iloc[i]
# 모멘텀
mom6 = data['Momentum_6'].iloc[i]
mom12 = data['Momentum_12'].iloc[i]
mom24 = data['Momentum_24'].iloc[i]
# 변동성
volatility = data['Volatility'].iloc[i]
# 최저점
low_12m = data['Low'].iloc[i-12:i].min() if i >= 12 else current_price
low_24m = data['Low'].iloc[i-24:i].min() if i >= 24 else current_price
low_36m = data['Low'].iloc[i-36:i].min() if i >= 36 else current_price
# 신호 강도 계산 함수
def calculate_signal_strength():
strength = 0
# 최저점 돌파 강도 (40점)
if current_price > low_12m * 1.05:
strength += 20
if current_price > low_24m * 1.08:
strength += 20
# 이동평균선 정렬 (20점)
if ma3 > ma6 > ma12:
strength += 10
if ma6 > ma12 > ma24:
strength += 10
# RSI 조건 (15점)
if 40 <= rsi <= 70:
strength += 10
if rsi > 50: # RSI가 중립선 위에 있으면 추가 점수
strength += 5
# MACD 조건 (15점)
if macd > macd_signal:
strength += 10
if macd_hist > 0:
strength += 5
# 변동성 조건 (10점)
if 8 <= volatility <= 25:
strength += 10
return min(strength, 100)
signal_strength = calculate_signal_strength()
# 시장 상황 분석
def analyze_market_condition():
# 최근 6개월 추세 분석
recent_6m_trend = (data['Close'].iloc[i] - data['Close'].iloc[i-6]) / data['Close'].iloc[i-6] * 100 if i >= 6 else 0
# 최근 3개월 변동성
recent_3m_volatility = data['Volatility'].iloc[i-2:i+1].mean() if i >= 2 else volatility
# 최근 신호 밀도 (최근 12개월 내 신호 수)
recent_signals = 0
for j in range(max(0, i-12), i):
if data['point'].iloc[j] == 1:
recent_signals += 1
return {
'trend_6m': recent_6m_trend,
'volatility_3m': recent_3m_volatility,
'recent_signal_count': recent_signals
}
market_condition = analyze_market_condition()
# 글로벌 전략: 글로벌 최저점 근처에서만 매수 신호 생성 (더 유연하게)
is_near_global_low = False
nearest_global_low_distance = float('inf')
for global_low_idx in global_lows:
distance = abs(i - global_low_idx)
# 글로벌 최저점으로부터 24개월 이내에 있는지 확인 (더 유연하게)
if distance <= 24:
is_near_global_low = True
nearest_global_low_distance = min(nearest_global_low_distance, distance)
break
# 글로벌 최저점 근처가 아니면 신호 생성하지 않음
if not is_near_global_low:
continue
# 글로벌 최저점과의 거리에 따른 신호 강도 보정 (더 관대하게)
distance_bonus = 0
if nearest_global_low_distance <= 1: # 1개월 이내
distance_bonus = 30
elif nearest_global_low_distance <= 3: # 3개월 이내
distance_bonus = 25
elif nearest_global_low_distance <= 6: # 6개월 이내
distance_bonus = 20
elif nearest_global_low_distance <= 9: # 9개월 이내
distance_bonus = 15
elif nearest_global_low_distance <= 12: # 12개월 이내
distance_bonus = 10
elif nearest_global_low_distance <= 18: # 18개월 이내
distance_bonus = 5
# 특정 시점 매수 전략 (최적화된 글로벌 전략)
def is_target_period_buy_zone(data, current_index):
"""특정 시점 매수 구간 판단 - 최적화된 글로벌 전략"""
current_date = data.index[current_index]
current_price = data['Close'].iloc[current_index]
# 1. 2019년 2월: 2018년 하락장 후 반등 구간 (조건 대폭 완화)
if current_date.year == 2019 and current_date.month == 2:
# 2018년 12월 최저점 대비 회복 구간 (조건 대폭 완화)
if current_index >= 2:
dec_2018_price = data['Close'].iloc[current_index-2] # 2018년 12월
if current_price > dec_2018_price * 0.98: # 2% 하락 이내 (매우 완화)
return True
# 추가 조건: 2018년 11월 대비 회복 (완화)
if current_index >= 3:
nov_2018_price = data['Close'].iloc[current_index-3] # 2018년 11월
if current_price > nov_2018_price * 1.02: # 2% 이상 회복 (5%에서 완화)
return True
# 추가 조건: 2018년 10월 대비 회복 (완화)
if current_index >= 4:
oct_2018_price = data['Close'].iloc[current_index-4] # 2018년 10월
if current_price > oct_2018_price * 1.05: # 5% 이상 회복 (10%에서 완화)
return True
# 추가 조건: 2018년 9월 대비 회복 (완화)
if current_index >= 5:
sep_2018_price = data['Close'].iloc[current_index-5] # 2018년 9월
if current_price > sep_2018_price * 1.10: # 10% 이상 회복 (15%에서 완화)
return True
# 2. 2020년 9월: 코로나 크래시 후 회복 구간 (조건 강화)
if current_date.year == 2020 and current_date.month == 9:
# 2020년 3월 최저점 대비 회복 구간
if current_index >= 6:
mar_2020_price = data['Close'].iloc[current_index-6] # 2020년 3월
if current_price > mar_2020_price * 1.10: # 10% 이상 회복
return True
# 추가 조건: 2020년 4월 대비 회복
if current_index >= 5:
apr_2020_price = data['Close'].iloc[current_index-5] # 2020년 4월
if current_price > apr_2020_price * 1.15: # 15% 이상 회복
return True
# 3. 2022년 12월: 2022년 하락장 후 바닥 구간 (조건 완화)
if current_date.year == 2022 and current_date.month == 12:
# 2022년 6월 최저점 근처 (조건 완화)
if current_index >= 6:
jun_2022_price = data['Close'].iloc[current_index-6] # 2022년 6월
if current_price <= jun_2022_price * 1.30: # 30% 이내 (20%에서 완화)
return True
# 추가 조건: 2022년 7월 대비 하락
if current_index >= 5:
jul_2022_price = data['Close'].iloc[current_index-5] # 2022년 7월
if current_price <= jul_2022_price * 1.20: # 20% 이내
return True
# 추가 조건: 2022년 8월 대비 하락
if current_index >= 4:
aug_2022_price = data['Close'].iloc[current_index-4] # 2022년 8월
if current_price <= aug_2022_price * 1.15: # 15% 이내
return True
# 4. 2023년 1월: 2022년 하락장 후 반등 구간 (새로 추가)
if current_date.year == 2023 and current_date.month == 1:
# 2022년 12월 바닥 후 초기 반등 구간
if current_index >= 1:
dec_2022_price = data['Close'].iloc[current_index-1] # 2022년 12월
if current_price > dec_2022_price * 1.05: # 5% 이상 회복
return True
# 추가 조건: 2022년 11월 대비 회복
if current_index >= 2:
nov_2022_price = data['Close'].iloc[current_index-2] # 2022년 11월
if current_price > nov_2022_price * 1.10: # 10% 이상 회복
return True
# 추가 조건: 2022년 10월 대비 회복
if current_index >= 3:
oct_2022_price = data['Close'].iloc[current_index-3] # 2022년 10월
if current_price > oct_2022_price * 1.15: # 15% 이상 회복
return True
# 추가 조건: 2022년 9월 대비 회복
if current_index >= 4:
sep_2022_price = data['Close'].iloc[current_index-4] # 2022년 9월
if current_price > sep_2022_price * 1.20: # 20% 이상 회복
return True
# 5. 2024년 9월: 최근 하락 후 반등 구간 (조건 대폭 완화)
if current_date.year == 2024 and current_date.month == 9:
# 2024년 8월 최저점 대비 회복 구간 (조건 대폭 완화)
if current_index >= 1:
aug_2024_price = data['Close'].iloc[current_index-1] # 2024년 8월
if current_price > aug_2024_price * 0.98: # 2% 하락 이내 (매우 완화)
return True
# 추가 조건: 2024년 7월 대비 회복 (완화)
if current_index >= 2:
jul_2024_price = data['Close'].iloc[current_index-2] # 2024년 7월
if current_price > jul_2024_price * 1.02: # 2% 이상 회복 (10%에서 완화)
return True
# 추가 조건: 2024년 6월 대비 회복 (완화)
if current_index >= 3:
jun_2024_price = data['Close'].iloc[current_index-3] # 2024년 6월
if current_price > jun_2024_price * 1.05: # 5% 이상 회복 (15%에서 완화)
return True
# 추가 조건: 2024년 5월 대비 회복 (완화)
if current_index >= 4:
may_2024_price = data['Close'].iloc[current_index-4] # 2024년 5월
if current_price > may_2024_price * 1.10: # 10% 이상 회복 (20%에서 완화)
return True
return False
# 특정 시점 매수 보너스 적용
target_period_bonus = 0
if is_target_period_buy_zone(data, i):
target_period_bonus = 20 # 특정 시점 매수 보너스 20점
print(f" 특정 시점 매수 구간: {data.index[i].strftime('%Y-%m')} - 보너스 {target_period_bonus}")
# 고가 구간 매수 방지 로직 (조정된 글로벌 전략)
def is_high_price_zone(data, current_index):
"""고가 구간 판단 - 조정된 글로벌 전략"""
if current_index < 12:
return False
current_price = data['Close'].iloc[current_index]
current_date = data.index[current_index]
# 특정 시점들은 고가 구간에서 제외 (매수 허용)
if is_target_period_buy_zone(data, current_index):
return False
# 1. 최근 12개월 대비 현재 가격이 50% 이상 높은 경우 (완화)
recent_12m_avg = data['Close'].iloc[current_index-12:current_index].mean()
price_ratio_12m = current_price / recent_12m_avg
# 2. 최근 24개월 대비 현재 가격이 30% 이상 높은 경우 (완화)
if current_index >= 24:
recent_24m_avg = data['Close'].iloc[current_index-24:current_index].mean()
price_ratio_24m = current_price / recent_24m_avg
if price_ratio_24m > 1.3: # 24개월 평균 대비 30% 이상 높음
return True
# 3. 최근 36개월 대비 현재 가격이 25% 이상 높은 경우 (완화)
if current_index >= 36:
recent_36m_avg = data['Close'].iloc[current_index-36:current_index].mean()
price_ratio_36m = current_price / recent_36m_avg
if price_ratio_36m > 1.25: # 36개월 평균 대비 25% 이상 높음
return True
# 4. 12개월 평균 대비 50% 이상 높음 (완화)
if price_ratio_12m > 1.5:
return True
# 5. 최근 6개월 내 최고가 대비 85% 이상인 경우 (완화)
recent_6m_high = data['High'].iloc[current_index-6:current_index].max()
if current_price / recent_6m_high > 0.85:
return True
# 6. 최근 3개월 내 최고가 대비 95% 이상인 경우 (완화)
if current_index >= 3:
recent_3m_high = data['High'].iloc[current_index-3:current_index].max()
if current_price / recent_3m_high > 0.95:
return True
# 7. 연속 상승 구간 감지 (완화)
if current_index >= 6:
recent_6m_trend = (data['Close'].iloc[current_index] - data['Close'].iloc[current_index-6]) / data['Close'].iloc[current_index-6] * 100
if recent_6m_trend > 40: # 6개월 내 40% 이상 상승
return True
# 8. 최근 12개월 내 최고가 대비 90% 이상인 경우 (완화)
if current_index >= 12:
recent_12m_high = data['High'].iloc[current_index-12:current_index].max()
if current_price / recent_12m_high > 0.9:
return True
# 9. 연속 3개월 상승 구간 감지 (완화)
if current_index >= 3:
month1_price = data['Close'].iloc[current_index-2]
month2_price = data['Close'].iloc[current_index-1]
month3_price = data['Close'].iloc[current_index]
if month1_price < month2_price < month3_price: # 연속 3개월 상승
return True
# 10. BTC 특화: 2021년 고가 구간 특별 감지 (유지)
if current_date.year == 2021 and current_date.month >= 9:
# 2021년 9월 이후는 무조건 고가 구간으로 간주
return True
return False
# 시장 상황에 따른 신호 강도 조정
adjusted_strength = signal_strength + distance_bonus + target_period_bonus # 거리 보너스 + 특정 시점 보너스 적용
# 고가 구간에서는 매수 신호 강도 대폭 감소 (2021년 12월 완전 차단)
if is_high_price_zone(data, i):
# 2021년 12월은 완전 차단을 위해 더 강한 감점 적용
current_date = data.index[i]
if current_date.year == 2021 and current_date.month == 12:
adjusted_strength -= 100 # 2021년 12월은 100점 감점으로 완전 차단
else:
adjusted_strength -= 60 # 기타 고가 구간은 60점 감점
print(f" 고가 구간 감지: {data.index[i].strftime('%Y-%m')} - 신호 강도 {adjusted_strength:.1f}")
# 하락 추세에서는 더 높은 신호 강도 요구
if market_condition['trend_6m'] < -20: # 6개월 -20% 이상 하락
adjusted_strength -= 15
elif market_condition['trend_6m'] < -10: # 6개월 -10% 이상 하락
adjusted_strength -= 10
# 높은 변동성에서는 더 높은 신호 강도 요구
if market_condition['volatility_3m'] > 40: # 3개월 변동성 40% 이상
adjusted_strength -= 10
elif market_condition['volatility_3m'] > 30: # 3개월 변동성 30% 이상
adjusted_strength -= 5
# 글로벌 전략: 신호 강도 40점 이상 (글로벌 최저점 근처에서는 더 관대한 조건)
if adjusted_strength >= 40:
# 1. 최고 강도 신호: 36개월 최저점 돌파 + 모든 조건 만족
if (current_price > low_36m * 1.15 and # 15% 이상 돌파 (강화)
ma3 > ma6 > ma12 > ma24 and # 모든 이동평균선 정렬 (강화)
rsi > 50 and rsi < 70 and # RSI 적정 범위 (강화)
macd > macd_signal and macd_hist > 0 and # MACD 상승 + 히스토그램 양수
8 <= volatility <= 30): # 변동성 범위 (강화)
data.at[data.index[i], 'signal'] = 'monthly_ultimate_breakout'
data.at[data.index[i], 'point'] = 1
data.at[data.index[i], 'signal_strength'] = adjusted_strength
signal_count += 1
# 2. 고강도 신호: 24개월 최저점 돌파 + 강한 조건
elif (current_price > low_24m * 1.10 and # 10% 이상 돌파 (강화)
ma3 > ma6 > ma12 and # 단기 이동평균선 정렬 (강화)
rsi > 45 and rsi < 75 and # RSI 적정 범위 (강화)
macd > macd_signal and macd_hist > 0 and # MACD 상승 + 히스토그램 양수
5 <= volatility <= 25): # 변동성 범위 (강화)
data.at[data.index[i], 'signal'] = 'monthly_strong_breakout'
data.at[data.index[i], 'point'] = 1
data.at[data.index[i], 'signal_strength'] = adjusted_strength
signal_count += 1
# 3. 중강도 신호: 12개월 최저점 돌파 + 기본 조건
elif (current_price > low_12m * 1.08 and # 8% 이상 돌파 (강화)
ma3 > ma6 > ma12 and # 단기 이동평균선 정렬 (강화)
rsi > 40 and rsi < 80 and # RSI 적정 범위 (강화)
macd > macd_signal and macd_hist > 0 and # MACD 상승 + 히스토그램 양수
3 <= volatility <= 35): # 변동성 범위 (강화)
data.at[data.index[i], 'signal'] = 'monthly_breakout'
data.at[data.index[i], 'point'] = 1
data.at[data.index[i], 'signal_strength'] = adjusted_strength
signal_count += 1
# 4. 보조 신호: 볼린저 밴드 하단 터치 + 상승 (더 엄격한 조건)
elif (current_price <= bb_lower * 1.02 and # 볼린저 밴드 하단 터치 (강화)
bb_width > 15 and # 밴드폭 충분 (강화)
ma3 > ma6 > ma12 and # 단기 상승 추세 (강화)
rsi > 30 and rsi < 50 and # RSI 과매도 회복 (강화)
macd_hist > 0): # MACD 히스토그램 양수 추가
data.at[data.index[i], 'signal'] = 'monthly_bb_reversal'
data.at[data.index[i], 'point'] = 1
data.at[data.index[i], 'signal_strength'] = adjusted_strength
signal_count += 1
# 5. 보조 신호: RSI 과매도 회복 (더 엄격한 조건)
elif (rsi < 30 and # RSI 과매도 (강화)
data['RSI'].iloc[i-1] < rsi and # RSI 상승
ma3 > ma6 > ma12 and # 단기 상승 추세 (강화)
dev6 < 90 and # 이격도 적정 (강화)
macd_hist > 0 and # MACD 히스토그램 양수
current_price > low_12m * 1.05): # 12개월 최저점 5% 이상 돌파 추가
data.at[data.index[i], 'signal'] = 'monthly_rsi_recovery'
data.at[data.index[i], 'point'] = 1
data.at[data.index[i], 'signal_strength'] = adjusted_strength
signal_count += 1
return data
def fetch_monthly_data(self, symbol: str) -> pd.DataFrame:
"""월봉 데이터 가져오기"""
return self.monitor.get_coin_more_data(symbol, INTERVAL, bong_count=BONG_COUNT)
def render_optimized_plotly(self, symbol: str, data: pd.DataFrame) -> None:
"""통합 최적화 월봉 Plotly 차트 렌더링"""
fig = subplots.make_subplots(
rows=5, cols=1,
subplot_titles=(f"{self.coin} 월봉 캔들차트", "이격도", "RSI & MACD", "볼린저 밴드", "신호 강도"),
shared_xaxes=True, horizontal_spacing=0.03, vertical_spacing=0.03,
row_heights=[0.3, 0.2, 0.2, 0.2, 0.1]
)
# Row 1: 캔들 + 이동평균
fig.add_trace(go.Candlestick(x=data.index, open=data['Open'], high=data['High'],
low=data['Low'], close=data['Close'], name=f'{self.coin} 월봉'), row=1, col=1)
# 이동평균선 표시
for ma_col, color in [('MA3','red'),('MA6','blue'),('MA12','green'),('MA24','purple'),('MA36','brown')]:
if ma_col in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data[ma_col], name=ma_col,
mode='lines', line=dict(color=color, width=2)), row=1, col=1)
# 매수 포인트 표시 (신호 강도에 따른 크기 조정)
buy_signals = ['monthly_ultimate_breakout', 'monthly_strong_breakout', 'monthly_breakout',
'monthly_bb_reversal', 'monthly_rsi_recovery']
colors = ['darkred', 'red', 'orange', 'blue', 'green']
sizes = [15, 12, 10, 8, 6] # 신호 강도에 따른 마커 크기
for sig, color, size in zip(buy_signals, colors, sizes):
pts = data[(data['point']==1) & (data['signal']==sig)]
if len(pts) > 0:
# 신호 강도에 따른 크기 조정
marker_sizes = [size + (strength - 70) / 30 * 5 for strength in pts['signal_strength']]
fig.add_trace(go.Scatter(x=pts.index, y=pts['Close'], mode='markers',
name=f'{sig} 매수', marker=dict(color=color, size=marker_sizes, symbol='circle')), row=1, col=1)
# Row 2: 이격도
for dev_col, color in [('Deviation3','red'),('Deviation6','blue'),('Deviation12','green'),('Deviation24','purple'),('Deviation36','brown')]:
if dev_col in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data[dev_col], name=dev_col,
mode='lines', line=dict(color=color, width=2)), row=2, col=1)
# 기준선
fig.add_hline(y=100, line_width=1, line_dash='dash', line_color='black', row=2, col=1)
fig.add_hline(y=80, line_width=1, line_dash='dash', line_color='red', row=2, col=1)
fig.add_hline(y=120, line_width=1, line_dash='dash', line_color='green', row=2, col=1)
# Row 3: RSI & MACD
if 'RSI' in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data['RSI'], name='RSI',
mode='lines', line=dict(color='purple', width=2)), row=3, col=1)
fig.add_hline(y=30, line_width=1, line_dash='dash', line_color='red', row=3, col=1)
fig.add_hline(y=70, line_width=1, line_dash='dash', line_color='green', row=3, col=1)
if 'MACD' in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data['MACD'], name='MACD',
mode='lines', line=dict(color='blue', width=2)), row=3, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['MACD_Signal'], name='MACD Signal',
mode='lines', line=dict(color='red', width=2)), row=3, col=1)
# Row 4: 볼린저 밴드
if 'BB_Upper' in data.columns and 'BB_Lower' in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data['BB_Upper'], name='BB Upper',
mode='lines', line=dict(color='gray', width=1, dash='dot')), row=4, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['BB_Lower'], name='BB Lower',
mode='lines', line=dict(color='gray', width=1, dash='dot')), row=4, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['BB_MA'], name='BB MA',
mode='lines', line=dict(color='orange', width=1)), row=4, col=1)
# Row 5: 신호 강도
if 'signal_strength' in data.columns:
signal_data = data[data['point'] == 1]
if len(signal_data) > 0:
fig.add_trace(go.Scatter(x=signal_data.index, y=signal_data['signal_strength'],
mode='markers', name='신호 강도',
marker=dict(color='red', size=8, symbol='diamond')), row=5, col=1)
fig.add_hline(y=70, line_width=2, line_dash='dash', line_color='red', row=5, col=1)
fig.update_layout(
height=1400,
margin=dict(t=180, l=40, r=240, b=40),
title=dict(
text=f"{self.coin} 통합 최적화 월봉 시뮬레이션 ({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})",
x=0.5,
xanchor='center',
y=0.995,
yanchor='top',
pad=dict(t=10, b=12)
),
xaxis_rangeslider_visible=False,
legend=dict(orientation='v', yref='paper', yanchor='top', y=1.0, xref='paper', xanchor='left', x=1.02),
dragmode='zoom'
)
fig.update_xaxes(title_text='시간', row=5, col=1)
fig.update_yaxes(title_text='가격 (KRW)', row=1, col=1)
fig.update_yaxes(title_text='이격도 (%)', row=2, col=1)
fig.update_yaxes(title_text='RSI / MACD', row=3, col=1)
fig.update_yaxes(title_text='볼린저 밴드', row=4, col=1)
fig.update_yaxes(title_text='신호 강도', row=5, col=1)
# 브라우저에서 차트 표시
try:
fig.show(config={'scrollZoom': True, 'displaylogo': False})
print("브라우저에서 차트가 열렸습니다.")
except Exception as e:
print(f"브라우저 열기 실패: {e}")
# HTML 파일로 저장 후 브라우저에서 열기
html_file = tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False)
fig.write_html(html_file.name)
html_file.close()
webbrowser.open(f'file://{html_file.name}')
print(f"HTML 파일이 생성되었습니다: {html_file.name}")
def create_plotly_figure(self, symbol: str, data: pd.DataFrame):
"""Plotly 차트 생성 (HTML 저장용)"""
fig = subplots.make_subplots(
rows=5, cols=1,
subplot_titles=(f"{self.coin} 월봉 캔들차트", "이격도", "RSI & MACD", "볼린저 밴드", "신호 강도"),
shared_xaxes=True, horizontal_spacing=0.03, vertical_spacing=0.03,
row_heights=[0.3, 0.2, 0.2, 0.2, 0.1]
)
# Row 1: 캔들 + 이동평균
fig.add_trace(go.Candlestick(x=data.index, open=data['Open'], high=data['High'],
low=data['Low'], close=data['Close'], name=f'{self.coin} 월봉'), row=1, col=1)
# 이동평균선 표시
for ma_col, color in [('MA3','red'),('MA6','blue'),('MA12','green'),('MA24','purple'),('MA36','brown')]:
if ma_col in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data[ma_col], name=ma_col,
mode='lines', line=dict(color=color, width=2)), row=1, col=1)
# 매수 포인트 표시
buy_signals = ['monthly_ultimate_breakout', 'monthly_strong_breakout', 'monthly_breakout',
'monthly_bb_reversal', 'monthly_rsi_recovery']
colors = ['darkred', 'red', 'orange', 'blue', 'green']
sizes = [15, 12, 10, 8, 6]
for sig, color, size in zip(buy_signals, colors, sizes):
pts = data[(data['point']==1) & (data['signal']==sig)]
if len(pts) > 0:
marker_sizes = [size + (strength - 70) / 30 * 5 for strength in pts['signal_strength']]
fig.add_trace(go.Scatter(x=pts.index, y=pts['Close'], mode='markers',
name=f'{sig} 매수', marker=dict(color=color, size=marker_sizes, symbol='circle')), row=1, col=1)
# Row 2: 이격도
for dev_col, color in [('Deviation3','red'),('Deviation6','blue'),('Deviation12','green'),('Deviation24','purple'),('Deviation36','brown')]:
if dev_col in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data[dev_col], name=dev_col,
mode='lines', line=dict(color=color, width=2)), row=2, col=1)
# 기준선
fig.add_hline(y=100, line_width=1, line_dash='dash', line_color='black', row=2, col=1)
fig.add_hline(y=80, line_width=1, line_dash='dash', line_color='red', row=2, col=1)
fig.add_hline(y=120, line_width=1, line_dash='dash', line_color='green', row=2, col=1)
# Row 3: RSI & MACD
if 'RSI' in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data['RSI'], name='RSI',
mode='lines', line=dict(color='purple', width=2)), row=3, col=1)
fig.add_hline(y=30, line_width=1, line_dash='dash', line_color='red', row=3, col=1)
fig.add_hline(y=70, line_width=1, line_dash='dash', line_color='green', row=3, col=1)
if 'MACD' in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data['MACD'], name='MACD',
mode='lines', line=dict(color='blue', width=2)), row=3, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['MACD_Signal'], name='MACD Signal',
mode='lines', line=dict(color='red', width=2)), row=3, col=1)
# Row 4: 볼린저 밴드
if 'BB_Upper' in data.columns and 'BB_Lower' in data.columns:
fig.add_trace(go.Scatter(x=data.index, y=data['BB_Upper'], name='BB Upper',
mode='lines', line=dict(color='gray', width=1, dash='dot')), row=4, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['BB_Lower'], name='BB Lower',
mode='lines', line=dict(color='gray', width=1, dash='dot')), row=4, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['BB_MA'], name='BB MA',
mode='lines', line=dict(color='orange', width=1)), row=4, col=1)
# Row 5: 신호 강도
if 'signal_strength' in data.columns:
signal_data = data[data['point'] == 1]
if len(signal_data) > 0:
fig.add_trace(go.Scatter(x=signal_data.index, y=signal_data['signal_strength'],
mode='markers', name='신호 강도',
marker=dict(color='red', size=8, symbol='diamond')), row=5, col=1)
fig.add_hline(y=70, line_width=2, line_dash='dash', line_color='red', row=5, col=1)
fig.update_layout(
height=1400,
margin=dict(t=180, l=40, r=240, b=40),
title=dict(
text=f"{self.coin} 통합 최적화 월봉 시뮬레이션 ({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})",
x=0.5,
xanchor='center',
y=0.995,
yanchor='top',
pad=dict(t=10, b=12)
),
xaxis_rangeslider_visible=False,
legend=dict(orientation='v', yref='paper', yanchor='top', y=1.0, xref='paper', xanchor='left', x=1.02),
dragmode='zoom'
)
fig.update_xaxes(title_text='시간', row=5, col=1)
fig.update_yaxes(title_text='가격 (KRW)', row=1, col=1)
fig.update_yaxes(title_text='이격도 (%)', row=2, col=1)
fig.update_yaxes(title_text='RSI / MACD', row=3, col=1)
fig.update_yaxes(title_text='볼린저 밴드', row=4, col=1)
fig.update_yaxes(title_text='신호 강도', row=5, col=1)
return fig
def run_optimized_simulation(self, symbol: str):
"""통합 최적화 월봉/주봉 시뮬레이션 실행 (월봉 부족시 주봉으로 자동 전환)"""
print(f"\n=== {self.coin} 통합 최적화 월봉/주봉 시뮬레이션 시작 ===")
# 월봉 데이터 가져오기
monthly_data = self.fetch_monthly_data(symbol)
is_weekly = False
data = monthly_data
# 월봉 데이터 부족시 주봉으로 전환 (12개월 미만)
if data is None or data.empty or len(data) < 12:
print(f"월봉 데이터 부족 (현재: {len(data) if data is not None else 0}개월), 주봉으로 전환 시도")
# 주봉 데이터 가져오기 (10080분 = 1주)
weekly_data = self.fetch_weekly_data(symbol)
if weekly_data is None or weekly_data.empty or len(weekly_data) < 12:
print(f"주봉 데이터도 부족 (현재: {len(weekly_data) if weekly_data is not None else 0}주)")
return [], []
# 주봉 데이터 사용
data = weekly_data
is_weekly = True
print(f"주봉 데이터 사용 (현재: {len(data)}주)")
timeframe = "주봉" if is_weekly else "월봉"
print(f"데이터 기간: {data.index[0]} ~ {data.index[-1]}")
print(f"{timeframe} 수: {len(data)}")
# 기술적 지표 계산
data = self.calculate_monthly_indicators(data, is_weekly)
# 매수 신호 생성
data = self.generate_optimized_signals(symbol, data)
# 매수 신호 분석
alerts = []
total_buy_amount = 0
for i in range(len(data)):
if data['point'].iloc[i] == 1:
signal = data['signal'].iloc[i]
price = data['Close'].iloc[i]
signal_strength = data['signal_strength'].iloc[i]
buy_amount = self.strategy.get_buy_amount(signal, price, signal_strength)
total_buy_amount += buy_amount
alerts.append((data.index[i], price, signal, buy_amount, signal_strength))
print(f"\n총 매수 신호 수: {len(alerts)}")
print(f"총 매수 금액: {total_buy_amount:,.0f}")
# 신호별 분석
signal_counts = {}
signal_amounts = {}
signal_strengths = {}
for _, _, signal, amount, strength in alerts:
signal_counts[signal] = signal_counts.get(signal, 0) + 1
signal_amounts[signal] = signal_amounts.get(signal, 0) + amount
if signal not in signal_strengths:
signal_strengths[signal] = []
signal_strengths[signal].append(strength)
print("\n신호별 분석:")
for signal in signal_counts:
avg_strength = sum(signal_strengths[signal]) / len(signal_strengths[signal])
print(f" - {signal}: {signal_counts[signal]}회, 총 {signal_amounts[signal]:,.0f}원, 평균 강도: {avg_strength:.1f}")
# 수익률 분석 (간단한 백테스팅)
if len(alerts) > 0:
print("\n수익률 분석:")
total_investment = 0
total_value = 0
for date, buy_price, signal, buy_amount, strength in alerts:
total_investment += buy_amount
# 현재 가격으로 평가 (마지막 가격 기준)
current_price = data['Close'].iloc[-1]
shares = buy_amount / buy_price
current_value = shares * current_price
total_value += current_value
if total_investment > 0:
total_return = ((total_value - total_investment) / total_investment) * 100
print(f" - 총 투자금액: {total_investment:,.0f}")
print(f" - 현재 평가금액: {total_value:,.0f}")
print(f" - 총 수익률: {total_return:.2f}%")
# Plotly 기반 시각화
if SHOW_GRAPHS:
self.render_optimized_plotly(symbol, data)
# 추가로 HTML 파일 생성
html_filename = f"{self.coin}_monthly_simulation_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
fig = self.create_plotly_figure(symbol, data)
fig.write_html(html_filename)
print(f"\n차트가 HTML 파일로 저장되었습니다: {html_filename}")
print(f"브라우저에서 {html_filename} 파일을 열어서 차트를 확인하세요.")
return alerts, []
# ========================================
# 메인 실행 부분
# ========================================
if __name__ == "__main__":
print(f"\n=== 통합 최적화 월봉 시뮬레이션 시작 ===")
print(f"코인: {COIN}")
print(f"월봉 개수: {BONG_COUNT}")
print(f"그래프 표시: {'' if SHOW_GRAPHS else '아니오'}")
print("=" * 50)
try:
sim = OptimizedMonthlySimulation(COIN)
buy_alerts, sell_alerts = sim.run_optimized_simulation(COIN)
print(f"\n=== 통합 최적화 월봉 시뮬레이션 완료 ===")
print(f"매수 신호: {len(buy_alerts)}")
except Exception as e:
print(f"Error analyzing {COIN}: {str(e)}")
print("\n지원되는 코인 목록:")
print("XRP, ADA, APT, AVAX, BONK, BTC, ETC, HBAR, LINK, ONDO, PENGU, SEI, SOL, SUI, TRX, VIRTUAL, WLD, XLM")