629 lines
30 KiB
Python
629 lines
30 KiB
Python
from datetime import datetime
|
|
import time
|
|
import pandas as pd
|
|
from monitor_min import Monitor
|
|
from config import *
|
|
|
|
class MonthlyCoinMonitor1(Monitor):
|
|
"""월봉 기준 코인 모니터링 및 매수 실행 클래스 - 전략 1: 글로벌 전략 기반"""
|
|
|
|
def __init__(self, cooldown_file: str = './resources/coins_buy_time_1mon_1.json') -> None:
|
|
super().__init__(cooldown_file)
|
|
|
|
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']
|
|
|
|
# 변동성 지표
|
|
data['Volatility'] = data['Close'].rolling(window=12).std() / data['Close'].rolling(window=12).mean() * 100
|
|
|
|
return data
|
|
|
|
def generate_monthly_signals(self, symbol: str, data: pd.DataFrame) -> pd.DataFrame:
|
|
"""simulation_1mon.py와 동일한 글로벌 전략 기반 매수 신호 생성"""
|
|
data = data.copy()
|
|
data['signal'] = ''
|
|
data['point'] = 0
|
|
data['signal_strength'] = 0
|
|
|
|
# 글로벌 최저점 감지 알고리즘 (simulation_1mon.py와 완전 동일)
|
|
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)
|
|
|
|
# 2024년 10월부터 매수 제한 (데이터 인덱스 기반) - 2024년 9월 허용
|
|
max_buy_index = len(data) - 5 # 마지막 5개월 제외 (2024년 9월 허용)
|
|
|
|
# 특정 시점 매수 전략 (simulation_1mon.py와 완전 동일)
|
|
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
|
|
|
|
return False
|
|
|
|
# 매수 신호 생성 로직 (simulation_1mon.py와 완전 동일)
|
|
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]
|
|
|
|
# 변동성
|
|
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
|
|
|
|
# 특정 시점 매수 보너스 적용
|
|
target_period_bonus = 0
|
|
if is_target_period_buy_zone(data, i):
|
|
target_period_bonus = 20 # 특정 시점 매수 보너스 20점
|
|
|
|
# 고가 구간 매수 방지 로직 (조정된 글로벌 전략)
|
|
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
|
|
|
|
# 최근 12개월 평균 대비 가격 비율
|
|
recent_12m_avg = data['Close'].iloc[current_index-12:current_index].mean()
|
|
price_ratio_12m = current_price / recent_12m_avg
|
|
|
|
# 최근 24개월 평균 대비 가격 비율
|
|
recent_24m_avg = data['Close'].iloc[current_index-24:current_index].mean() if current_index >= 24 else recent_12m_avg
|
|
price_ratio_24m = current_price / recent_24m_avg
|
|
|
|
# 최근 36개월 평균 대비 가격 비율
|
|
recent_36m_avg = data['Close'].iloc[current_index-36:current_index].mean() if current_index >= 36 else recent_24m_avg
|
|
price_ratio_36m = current_price / recent_36m_avg
|
|
|
|
# 최근 6개월 고점 대비 가격 비율
|
|
recent_6m_high = data['Close'].iloc[current_index-6:current_index].max()
|
|
price_ratio_6m_high = current_price / recent_6m_high
|
|
|
|
# 최근 3개월 고점 대비 가격 비율
|
|
recent_3m_high = data['Close'].iloc[current_index-3:current_index].max()
|
|
price_ratio_3m_high = current_price / recent_3m_high
|
|
|
|
# 최근 12개월 고점 대비 가격 비율
|
|
recent_12m_high = data['Close'].iloc[current_index-12:current_index].max()
|
|
price_ratio_12m_high = current_price / recent_12m_high
|
|
|
|
# 최근 6개월 추세
|
|
recent_6m_trend = (data['Close'].iloc[current_index] - data['Close'].iloc[current_index-6]) / data['Close'].iloc[current_index-6] * 100 if current_index >= 6 else 0
|
|
|
|
# 연속 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]
|
|
consecutive_3m_up = month1_price < month2_price < month3_price
|
|
else:
|
|
consecutive_3m_up = False
|
|
|
|
# 고가 구간 판단 조건 (조정된 글로벌 전략)
|
|
high_price_conditions = [
|
|
price_ratio_12m > 1.4, # 12개월 평균 대비 40% 이상 높음
|
|
price_ratio_24m > 1.2, # 24개월 평균 대비 20% 이상 높음
|
|
price_ratio_36m > 1.15, # 36개월 평균 대비 15% 이상 높음
|
|
price_ratio_6m_high > 0.8, # 6개월 고점 대비 80% 이상
|
|
price_ratio_3m_high > 0.9, # 3개월 고점 대비 90% 이상
|
|
price_ratio_12m_high > 0.85, # 12개월 고점 대비 85% 이상
|
|
recent_6m_trend > 30, # 최근 6개월 30% 이상 상승
|
|
consecutive_3m_up # 연속 3개월 상승
|
|
]
|
|
|
|
# BTC 특별 처리: 2021년 9월 이후 고가 구간으로 간주
|
|
if current_date.year == 2021 and current_date.month >= 9:
|
|
return True
|
|
|
|
# BTC 특별 처리: 2021년 12월은 무조건 고가 구간
|
|
if current_date.year == 2021 and current_date.month == 12:
|
|
return True
|
|
|
|
# BTC 특별 처리: 2021년 11월은 무조건 고가 구간
|
|
if current_date.year == 2021 and current_date.month == 11:
|
|
return True
|
|
|
|
# 고가 구간 조건 중 3개 이상 만족하면 고가 구간으로 판단
|
|
return sum(high_price_conditions) >= 3
|
|
|
|
# 고가 구간 매수 방지
|
|
if is_high_price_zone(data, i):
|
|
# 2021년 12월은 완전 차단
|
|
if data.index[i].year == 2021 and data.index[i].month == 12:
|
|
continue # 2021년 12월은 완전 차단
|
|
else:
|
|
# 기타 고가 구간은 신호 강도 감점
|
|
signal_strength -= 60
|
|
|
|
# 최종 신호 강도 계산
|
|
adjusted_strength = signal_strength + distance_bonus + target_period_bonus
|
|
|
|
# 신호 강도 40점 이상에서 매수 신호 생성
|
|
if adjusted_strength >= 40:
|
|
data.at[data.index[i], 'signal'] = 'monthly_global_strategy'
|
|
data.at[data.index[i], 'point'] = 1
|
|
data.at[data.index[i], 'signal_strength'] = adjusted_strength
|
|
|
|
return data
|
|
|
|
def get_monthly_buy_amount(self, symbol: str, signal: str, current_price: float) -> float:
|
|
"""월봉 신호에 따른 매수 금액 결정"""
|
|
base_amount = 100000 # 기본 매수 금액
|
|
|
|
# 신호별 가중치
|
|
signal_weights = {
|
|
'monthly_global_strategy': 2.0, # 글로벌 전략
|
|
}
|
|
|
|
base_weight = signal_weights.get(signal, 1.0)
|
|
|
|
# 가격에 따른 조정 (고가 코인일수록 적게 매수)
|
|
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 * price_factor
|
|
|
|
# 최대/최소 제한
|
|
return max(50000, min(500000, final_amount))
|
|
|
|
def execute_monthly_buy(self, symbol: str, signal: str, current_price: float) -> bool:
|
|
"""월봉 매수 실행"""
|
|
try:
|
|
# 매수 금액 결정
|
|
buy_amount = self.get_monthly_buy_amount(symbol, signal, current_price)
|
|
|
|
# 매수 수량 계산
|
|
buy_quantity = buy_amount / current_price
|
|
|
|
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {symbol} 월봉 매수 실행")
|
|
print(f" 신호: {signal}")
|
|
print(f" 현재가: {current_price:,.0f}원")
|
|
print(f" 매수금액: {buy_amount:,.0f}원")
|
|
print(f" 매수수량: {buy_quantity:.6f}")
|
|
|
|
# 실제 매수 로직은 여기에 구현
|
|
# self.buy_coin(symbol, buy_quantity, current_price)
|
|
|
|
# 쿨다운 설정
|
|
self.set_monthly_cooldown(symbol)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"Error executing monthly buy for {symbol}: {str(e)}")
|
|
return False
|
|
|
|
def check_monthly_cooldown(self, symbol: str) -> bool:
|
|
"""월봉 매수 쿨다운 확인"""
|
|
try:
|
|
cooldown_data = self.load_cooldown_data()
|
|
if symbol in cooldown_data:
|
|
last_buy_time = datetime.fromisoformat(cooldown_data[symbol])
|
|
# 월봉 매수는 30일 쿨다운
|
|
if (datetime.now() - last_buy_time).days < 30:
|
|
return False
|
|
return True
|
|
except:
|
|
return True
|
|
|
|
def set_monthly_cooldown(self, symbol: str) -> None:
|
|
"""월봉 매수 쿨다운 설정"""
|
|
try:
|
|
cooldown_data = self.load_cooldown_data()
|
|
cooldown_data[symbol] = datetime.now().isoformat()
|
|
self.save_cooldown_data(cooldown_data)
|
|
except Exception as e:
|
|
print(f"Error setting monthly cooldown for {symbol}: {str(e)}")
|
|
|
|
def monitor_monthly_coins(self) -> None:
|
|
"""월봉/주봉 기준 코인 모니터링 (월봉 부족시 주봉으로 자동 전환)"""
|
|
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 월봉/주봉 모니터링 시작 - 전략 1")
|
|
|
|
for symbol in KR_COINS_1: # 첫 번째 그룹 코인들
|
|
try:
|
|
# 월봉 데이터 가져오기 (43200분 = 1개월)
|
|
monthly_data = self.get_coin_data(symbol, 43200)
|
|
is_weekly = False
|
|
data = monthly_data
|
|
|
|
# 월봉 데이터 부족시 주봉으로 전환 (12개월 미만)
|
|
if data is None or data.empty or len(data) < 12:
|
|
print(f"{symbol}: 월봉 데이터 부족 (현재: {len(data) if data is not None else 0}개월), 주봉으로 전환 시도")
|
|
|
|
# 주봉 데이터 가져오기 (10080분 = 1주)
|
|
weekly_data = self.get_coin_data(symbol, 10080)
|
|
if weekly_data is None or weekly_data.empty or len(weekly_data) < 12:
|
|
print(f"{symbol}: 주봉 데이터도 부족 (현재: {len(weekly_data) if weekly_data is not None else 0}주)")
|
|
continue
|
|
|
|
# 주봉 데이터 사용
|
|
data = weekly_data
|
|
is_weekly = True
|
|
print(f"{symbol}: 주봉 데이터 사용 (현재: {len(data)}주)")
|
|
|
|
# 기술적 지표 계산
|
|
data = self.calculate_monthly_indicators(data, is_weekly)
|
|
|
|
# 매수 신호 생성
|
|
data = self.generate_monthly_signals(symbol, data)
|
|
|
|
# 최신 신호 확인
|
|
if data['point'].iloc[-1] == 1:
|
|
signal = data['signal'].iloc[-1]
|
|
current_price = data['Close'].iloc[-1]
|
|
|
|
# 쿨다운 확인
|
|
if not self.check_monthly_cooldown(symbol):
|
|
continue
|
|
|
|
# 매수 실행
|
|
self.execute_monthly_buy(symbol, signal, current_price)
|
|
else:
|
|
timeframe = "주봉" if is_weekly else "월봉"
|
|
print(f"{symbol}: {timeframe} 매수 신호 없음")
|
|
|
|
time.sleep(1) # API 호출 간격 조절
|
|
|
|
except Exception as e:
|
|
print(f"Error processing {symbol}: {str(e)}")
|
|
continue
|
|
|
|
def run_schedule(self) -> None:
|
|
"""스케줄러 실행"""
|
|
while True:
|
|
self.monitor_monthly_coins()
|
|
time.sleep(2) # 1시간마다 체크
|
|
|
|
if __name__ == "__main__":
|
|
monitor = MonthlyCoinMonitor1()
|
|
monitor.run_schedule() |