Compare commits
10 Commits
db36e2d69b
...
1c12a6c94a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c12a6c94a | ||
|
|
ff4981cce9 | ||
|
|
5fddb31e75 | ||
|
|
d85debf673 | ||
|
|
9514d34045 | ||
|
|
3a8b47068f | ||
|
|
49aee49a82 | ||
|
|
1fa1de3b0a | ||
|
|
75b6e8d227 | ||
|
|
576c24a737 |
66
coins_buy_time_1h_1.json
Normal file
66
coins_buy_time_1h_1.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"ARB": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-08-18T11:22:46.083340",
|
||||||
|
"signal": "fall_6p"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ENA": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-08-19T23:41:47.822635",
|
||||||
|
"signal": "deviation240"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"BONK": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-07T19:23:01.960389",
|
||||||
|
"signal": "movingaverage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PEPE": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-06T12:33:35.064847",
|
||||||
|
"signal": "movingaverage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"HBAR": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-04T00:35:27.567509",
|
||||||
|
"signal": "Deviation720"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ONDO": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-02T12:32:52.105429",
|
||||||
|
"signal": "Deviation720"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"APT": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-05T23:02:01.568291",
|
||||||
|
"signal": "movingaverage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"APE": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-06T16:59:22.979500",
|
||||||
|
"signal": "movingaverage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"KAIA": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-05T16:51:55.172129",
|
||||||
|
"signal": "movingaverage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PENGU": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-06T17:01:45.419112",
|
||||||
|
"signal": "Deviation720"
|
||||||
|
},
|
||||||
|
"sell": {
|
||||||
|
"datetime": "2025-09-03T06:15:29.849873",
|
||||||
|
"signal": "fall_6p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
coins_buy_time_1h_2.json
Normal file
74
coins_buy_time_1h_2.json
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"SUI": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-03T00:49:31.559907",
|
||||||
|
"signal": "Deviation720"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TRX": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-07T19:23:23.239284",
|
||||||
|
"signal": "deviation240"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"VIRTUAL": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-07T19:23:39.470757",
|
||||||
|
"signal": "movingaverage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WLD": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-06T17:00:58.538967",
|
||||||
|
"signal": "movingaverage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"XRP": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-01T14:49:49.052879",
|
||||||
|
"signal": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SEI": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-02T05:39:37.438356",
|
||||||
|
"signal": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"POL": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-02T05:40:39.898129",
|
||||||
|
"signal": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SHIB": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-06T16:35:02.824079",
|
||||||
|
"signal": "movingaverage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"STORJ": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-02T07:07:30.148618",
|
||||||
|
"signal": "Deviation720"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TON": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-02T06:27:28.447305",
|
||||||
|
"signal": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UXLINK": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-04T03:07:42.030095",
|
||||||
|
"signal": "movingaverage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"XLM": {
|
||||||
|
"buy": {
|
||||||
|
"datetime": "2025-09-02T05:49:19.368261",
|
||||||
|
"signal": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,9 @@ COIN_TELEGRAM_CHAT_ID = '574661323'
|
|||||||
STOCK_TELEGRAM_BOT_TOKEN = "6874078562:AAEHxGDavfc0ssAXPQIaW8JGYmTR7LNUJOw"
|
STOCK_TELEGRAM_BOT_TOKEN = "6874078562:AAEHxGDavfc0ssAXPQIaW8JGYmTR7LNUJOw"
|
||||||
STOCK_TELEGRAM_CHAT_ID = '574661323'
|
STOCK_TELEGRAM_CHAT_ID = '574661323'
|
||||||
|
|
||||||
|
# 몇초 만에 다시 매수를 할 것인지 체크
|
||||||
|
BUY_MINUTE_LIMIT = 900
|
||||||
|
|
||||||
# 볼린저 밴드 설정
|
# 볼린저 밴드 설정
|
||||||
BOLLINGER_PERIOD = 20 # 볼린저 밴드 기간
|
BOLLINGER_PERIOD = 20 # 볼린저 밴드 기간
|
||||||
BOLLINGER_STD = 2 # 표준편차 승수
|
BOLLINGER_STD = 2 # 표준편차 승수
|
||||||
@@ -30,6 +33,7 @@ KR_COINS = {
|
|||||||
"ARB": "아비트럼",
|
"ARB": "아비트럼",
|
||||||
"BONK": "봉크",
|
"BONK": "봉크",
|
||||||
"ENA": "에테나",
|
"ENA": "에테나",
|
||||||
|
"FANC": "팬시",
|
||||||
"HBAR": "헤데라",
|
"HBAR": "헤데라",
|
||||||
"KAIA": "카이아",
|
"KAIA": "카이아",
|
||||||
"LINK": "체인링크",
|
"LINK": "체인링크",
|
||||||
@@ -37,6 +41,7 @@ KR_COINS = {
|
|||||||
"PENGU": "펏지 펭귄",
|
"PENGU": "펏지 펭귄",
|
||||||
"PEPE": "페페",
|
"PEPE": "페페",
|
||||||
"POL": "폴리곤 에코시스템 토큰",
|
"POL": "폴리곤 에코시스템 토큰",
|
||||||
|
"PYTH":"피스 네트워크",
|
||||||
"SEI": "세이",
|
"SEI": "세이",
|
||||||
"SHIB": "시바이누",
|
"SHIB": "시바이누",
|
||||||
"STORJ": "스토리지",
|
"STORJ": "스토리지",
|
||||||
@@ -57,6 +62,7 @@ KR_COINS_1 = {
|
|||||||
"ARB": "아비트럼",
|
"ARB": "아비트럼",
|
||||||
"BONK": "봉크",
|
"BONK": "봉크",
|
||||||
"ENA": "에테나",
|
"ENA": "에테나",
|
||||||
|
"FANC": "팬시",
|
||||||
"HBAR": "헤데라",
|
"HBAR": "헤데라",
|
||||||
"KAIA": "카이아",
|
"KAIA": "카이아",
|
||||||
"LINK": "체인링크",
|
"LINK": "체인링크",
|
||||||
@@ -67,6 +73,7 @@ KR_COINS_1 = {
|
|||||||
|
|
||||||
KR_COINS_2 = {
|
KR_COINS_2 = {
|
||||||
"POL": "폴리곤 에코시스템 토큰",
|
"POL": "폴리곤 에코시스템 토큰",
|
||||||
|
"PYTH":"피스 네트워크",
|
||||||
"SEI": "세이",
|
"SEI": "세이",
|
||||||
"SHIB": "시바이누",
|
"SHIB": "시바이누",
|
||||||
"STORJ": "스토리지",
|
"STORJ": "스토리지",
|
||||||
|
|||||||
147
monitor.py
147
monitor.py
@@ -232,13 +232,21 @@ class Monitor(HTS):
|
|||||||
if data['point'].iloc[-1] != 1:
|
if data['point'].iloc[-1] != 1:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 인버스 데이터: 매수 신호를 매도로 처리 (fall_6p, deviation40 만 허용)
|
|
||||||
if is_inverse:
|
if is_inverse:
|
||||||
|
# BUY_MINUTE_LIMIT 이내라면 매수하지 않음
|
||||||
|
current_time = datetime.now()
|
||||||
|
last_buy_dt = self.buy_cooldown.get(symbol, {}).get('sell', {}).get('datetime')
|
||||||
|
if last_buy_dt:
|
||||||
|
time_diff = current_time - last_buy_dt
|
||||||
|
if time_diff.total_seconds() < BUY_MINUTE_LIMIT:
|
||||||
|
print(f"{symbol}: 매수 금지 중 (남은 시간: {BUY_MINUTE_LIMIT - time_diff.total_seconds():.0f}초)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 인버스 데이터: 매수 신호를 매도로 처리 (fall_6p, deviation40 만 허용)
|
||||||
# 허용된 인버스 매도 신호만 처리
|
# 허용된 인버스 매도 신호만 처리
|
||||||
last_signal = str(data['signal'].iloc[-1]) if 'signal' in data.columns else ''
|
last_signal = str(data['signal'].iloc[-1]) if 'signal' in data.columns else ''
|
||||||
if last_signal not in ['fall_6p', 'deviation40']:
|
if last_signal not in ['fall_6p', 'deviation40']:
|
||||||
return False
|
return False
|
||||||
current_time = datetime.now()
|
|
||||||
available_balance = 0
|
available_balance = 0
|
||||||
try:
|
try:
|
||||||
if balances and symbol in balances:
|
if balances and symbol in balances:
|
||||||
@@ -256,95 +264,84 @@ class Monitor(HTS):
|
|||||||
self.last_signal[symbol] = ''
|
self.last_signal[symbol] = ''
|
||||||
self.buy_cooldown.setdefault(symbol, {})['sell'] = {'datetime': current_time, 'signal': str(data['signal'].iloc[-1])}
|
self.buy_cooldown.setdefault(symbol, {})['sell'] = {'datetime': current_time, 'signal': str(data['signal'].iloc[-1])}
|
||||||
self._save_buy_cooldown()
|
self._save_buy_cooldown()
|
||||||
|
|
||||||
print(f"{KR_COINS[symbol]} ({symbol}) [{data['signal'].iloc[-1]} 매도], 현재가: {data['Close'].iloc[-1]:.4f}")
|
print(f"{KR_COINS[symbol]} ({symbol}) [{data['signal'].iloc[-1]} 매도], 현재가: {data['Close'].iloc[-1]:.4f}")
|
||||||
self.sendMsg("[KRW-COIN]\n" + f"• 매도 [COIN] {KR_COINS[symbol]} ({symbol}): {data['signal'].iloc[-1]} ({'₩'}{data['Close'].iloc[-1]:.4f})")
|
self.sendMsg("[KRW-COIN]\n" + f"• 매도 [COIN] {KR_COINS[symbol]} ({symbol}): {data['signal'].iloc[-1]} ({'₩'}{data['Close'].iloc[-1]:.4f})")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
check_5_week_lowest = False
|
|
||||||
|
|
||||||
# 5주봉이 20주봉이나 40주봉보다 아래에 있는지 체크
|
|
||||||
try:
|
|
||||||
# Convert hourly data to week-based rolling periods (5, 20, 40 weeks)
|
|
||||||
hours_in_week = 24 * 7 # 168 hours
|
|
||||||
period_5w = 5 * hours_in_week # 840 hours
|
|
||||||
period_20w = 20 * hours_in_week # 3,360 hours
|
|
||||||
period_40w = 40 * hours_in_week # 6,720 hours
|
|
||||||
|
|
||||||
if len(data) >= period_40w:
|
|
||||||
wma5 = data['Close'].rolling(window=period_5w).mean().iloc[-1]
|
|
||||||
wma20 = data['Close'].rolling(window=period_20w).mean().iloc[-1]
|
|
||||||
wma40 = data['Close'].rolling(window=period_40w).mean().iloc[-1]
|
|
||||||
|
|
||||||
# 5-week MA is the lowest among 5, 20, 40 week MAs
|
|
||||||
if (wma5 < wma20) and (wma5 < wma40):
|
|
||||||
check_5_week_lowest = True
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
# Ignore errors in MA calculation so as not to block trading logic
|
|
||||||
pass
|
|
||||||
|
|
||||||
buy_amount = 5100
|
|
||||||
current_time = datetime.now()
|
|
||||||
if data['signal'].iloc[-1] == 'fall_6p':
|
|
||||||
if data['Close'].iloc[-1] > 100:
|
|
||||||
buy_amount = 600000
|
|
||||||
else:
|
|
||||||
buy_amount = 300000
|
|
||||||
|
|
||||||
last_buy_dt = self.buy_cooldown.get(symbol, {}).get('buy', {}).get('datetime')
|
|
||||||
if last_buy_dt and self.last_signal.get(symbol) == 'fall_6p':
|
|
||||||
time_diff = current_time - last_buy_dt
|
|
||||||
if time_diff.total_seconds() < 4000:
|
|
||||||
print(f"{symbol}: 매수 금지 중 (남은 시간: {600 - time_diff.total_seconds():.0f}초)")
|
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
|
check_5_week_lowest = False
|
||||||
|
|
||||||
|
# BUY_MINUTE_LIMIT 이내라면 매수하지 않음
|
||||||
|
current_time = datetime.now()
|
||||||
last_buy_dt = self.buy_cooldown.get(symbol, {}).get('buy', {}).get('datetime')
|
last_buy_dt = self.buy_cooldown.get(symbol, {}).get('buy', {}).get('datetime')
|
||||||
if last_buy_dt:
|
if last_buy_dt:
|
||||||
time_diff = current_time - last_buy_dt
|
time_diff = current_time - last_buy_dt
|
||||||
if time_diff.total_seconds() < 1800:
|
if time_diff.total_seconds() < BUY_MINUTE_LIMIT:
|
||||||
print(f"{symbol}: 매수 금지 중 (남은 시간: {1800 - time_diff.total_seconds():.0f}초)")
|
print(f"{symbol}: 매수 금지 중 (남은 시간: {BUY_MINUTE_LIMIT - time_diff.total_seconds():.0f}초)")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if data['signal'].iloc[-1] == 'movingaverage':
|
try:
|
||||||
|
# 5주봉이 20주봉이나 40주봉보다 아래에 있는지 체크
|
||||||
|
# Convert hourly data to week-based rolling periods (5, 20, 40 weeks)
|
||||||
|
hours_in_week = 24 * 7 # 168 hours
|
||||||
|
period_5w = 5 * hours_in_week # 840 hours
|
||||||
|
period_20w = 20 * hours_in_week # 3,360 hours
|
||||||
|
period_40w = 40 * hours_in_week # 6,720 hours
|
||||||
|
|
||||||
|
if len(data) >= period_40w:
|
||||||
|
wma5 = data['Close'].rolling(window=period_5w).mean().iloc[-1]
|
||||||
|
wma20 = data['Close'].rolling(window=period_20w).mean().iloc[-1]
|
||||||
|
wma40 = data['Close'].rolling(window=period_40w).mean().iloc[-1]
|
||||||
|
|
||||||
|
# 5-week MA is the lowest among 5, 20, 40 week MAs
|
||||||
|
if (wma5 < wma20) and (wma5 < wma40):
|
||||||
|
check_5_week_lowest = True
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# Ignore errors in MA calculation so as not to block trading logic
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 체크: fall_6p
|
||||||
|
buy_amount = 5100
|
||||||
|
current_time = datetime.now()
|
||||||
|
if data['signal'].iloc[-1] == 'fall_6p':
|
||||||
|
if data['Close'].iloc[-1] > 100:
|
||||||
|
buy_amount = 300000
|
||||||
|
else:
|
||||||
|
buy_amount = 150000
|
||||||
|
elif data['signal'].iloc[-1] == 'movingaverage':
|
||||||
buy_amount = 10000
|
buy_amount = 10000
|
||||||
elif data['signal'].iloc[-1] == 'deviation40':
|
elif data['signal'].iloc[-1] == 'deviation40':
|
||||||
buy_amount = 50000
|
buy_amount = 30000
|
||||||
elif data['signal'].iloc[-1] == 'deviation240':
|
elif data['signal'].iloc[-1] == 'deviation240':
|
||||||
buy_amount = 60000
|
buy_amount = 7000
|
||||||
elif data['signal'].iloc[-1] == 'deviation1440':
|
elif data['signal'].iloc[-1] == 'deviation1440':
|
||||||
if symbol in ['BONK', 'PEPE', 'TON']:
|
if symbol in ['BONK', 'PEPE', 'TON']:
|
||||||
buy_amount = 20000
|
buy_amount = 50000
|
||||||
else:
|
else:
|
||||||
buy_amount = 30000
|
buy_amount = 70000
|
||||||
|
|
||||||
if data['signal'].iloc[-1] in ['movingaverage', 'deviation40', 'deviation240', 'deviation1440']:
|
if data['signal'].iloc[-1] in ['movingaverage', 'deviation40', 'deviation240', 'deviation1440']:
|
||||||
if check_5_week_lowest:
|
if check_5_week_lowest:
|
||||||
buy_amount *= 2
|
buy_amount *= 2
|
||||||
|
|
||||||
"""
|
# 매수를 진행함
|
||||||
# 분봉 시스널이 없을 때는 이전 봉보다 높으면 60분 마다 매수
|
buy_amount = self.hts.buyCoinMarket(symbol, buy_amount)
|
||||||
if data['point'].iloc[-1] != 1:
|
|
||||||
last_buy_dt = self.buy_cooldown.get(symbol, {}).get('buy', {}).get('datetime')
|
|
||||||
if last_buy_dt:
|
|
||||||
time_diff = current_time - last_buy_dt
|
|
||||||
if time_diff.total_seconds() < 3600:
|
|
||||||
print(f"{symbol}: 매수 금지 중 (남은 시간: {3600 - time_diff.total_seconds():.0f}초)")
|
|
||||||
return False
|
|
||||||
"""
|
|
||||||
buy_amount = self.hts.buyCoinMarket(symbol, buy_amount)
|
|
||||||
|
|
||||||
if self.cooldown_file is not None:
|
|
||||||
# 최근 매수 신호를 함께 기록하여 [신규] 포맷으로 저장
|
# 최근 매수 신호를 함께 기록하여 [신규] 포맷으로 저장
|
||||||
try:
|
if self.cooldown_file is not None:
|
||||||
self.last_signal[symbol] = str(data['signal'].iloc[-1])
|
try:
|
||||||
except Exception:
|
self.last_signal[symbol] = str(data['signal'].iloc[-1])
|
||||||
self.last_signal[symbol] = ''
|
except Exception:
|
||||||
self.buy_cooldown.setdefault(symbol, {})['buy'] = {'datetime': current_time, 'signal': str(data['signal'].iloc[-1])}
|
self.last_signal[symbol] = ''
|
||||||
# 매수를 저장함
|
self.buy_cooldown.setdefault(symbol, {})['buy'] = {'datetime': current_time, 'signal': str(data['signal'].iloc[-1])}
|
||||||
self._save_buy_cooldown()
|
|
||||||
|
|
||||||
print(f"{KR_COINS[symbol]} ({symbol}) [{data['signal'].iloc[-1]}], 현재가: {data['Close'].iloc[-1]:.4f}, 20분간 매수 금지 시작")
|
# 매수를 저장함
|
||||||
self.sendMsg("{}".format(self.format_message(symbol, KR_COINS[symbol], data['Close'].iloc[-1], data['signal'].iloc[-1], buy_amount)))
|
self._save_buy_cooldown()
|
||||||
|
|
||||||
|
print(f"{KR_COINS[symbol]} ({symbol}) [{data['signal'].iloc[-1]}], 현재가: {data['Close'].iloc[-1]:.4f}, {int(BUY_MINUTE_LIMIT/60)}분간 매수 금지 시작")
|
||||||
|
self.sendMsg("{}".format(self.format_message(symbol, KR_COINS[symbol], data['Close'].iloc[-1], data['signal'].iloc[-1], buy_amount)))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error buying {symbol}: {str(e)}")
|
print(f"Error buying {symbol}: {str(e)}")
|
||||||
return False
|
return False
|
||||||
@@ -457,8 +454,20 @@ class Monitor(HTS):
|
|||||||
# ------------- Formatting -------------
|
# ------------- Formatting -------------
|
||||||
def format_message(self, symbol: str, symbol_name: str, close: float, signal: str, buy_amount: float) -> str:
|
def format_message(self, symbol: str, symbol_name: str, close: float, signal: str, buy_amount: float) -> str:
|
||||||
message = f"[매수] {symbol_name} ({symbol}): "
|
message = f"[매수] {symbol_name} ({symbol}): "
|
||||||
message += f"₩{close:.4f}"
|
|
||||||
message += f" (₩{buy_amount:.4f})"
|
if int(close) >= 100:
|
||||||
|
message += f"₩{close}"
|
||||||
|
message += f" (₩{buy_amount})"
|
||||||
|
elif int(close) >= 10:
|
||||||
|
message += f"₩{close:.2f}"
|
||||||
|
message += f" (₩{buy_amount:.2f})"
|
||||||
|
elif int(close) >= 1:
|
||||||
|
message += f"₩{close:.3f}"
|
||||||
|
message += f" (₩{buy_amount:.3f})"
|
||||||
|
else:
|
||||||
|
message += f"₩{close:.4f}"
|
||||||
|
message += f" (₩{buy_amount:.4f})"
|
||||||
|
|
||||||
if signal != '':
|
if signal != '':
|
||||||
message += f"[{signal}]"
|
message += f"[{signal}]"
|
||||||
return message
|
return message
|
||||||
|
|||||||
Reference in New Issue
Block a user