최초 프로젝트 업로드 (Script Auto Commit)

This commit is contained in:
2025-12-03 22:37:46 +09:00
commit ed7084dd8f
30 changed files with 3254 additions and 0 deletions

138
src/threading_utils.py Normal file
View File

@@ -0,0 +1,138 @@
import time
import threading
from typing import List
from .config import RuntimeConfig
from .common import logger
from .signals import process_symbol
from .notifications import send_telegram
def run_sequential(symbols: List[str], timeframe: str | None = None, indicator_timeframe: str | None = None, candle_count: int | None = None, telegram_token: str | None = None, telegram_chat_id: str | None = None, dry_run: bool | None = None, symbol_delay: float | None = None, parse_mode: str | None = None, aggregate_enabled: bool = False, cfg: RuntimeConfig | None = None):
if cfg is not None:
timeframe = timeframe or cfg.timeframe
indicator_timeframe = indicator_timeframe or cfg.indicator_timeframe
candle_count = cfg.candle_count if (candle_count is None) else candle_count
telegram_token = telegram_token or cfg.telegram_bot_token
telegram_chat_id = telegram_chat_id or cfg.telegram_chat_id
dry_run = cfg.dry_run if (dry_run is None) else dry_run
symbol_delay = cfg.symbol_delay if (symbol_delay is None) else symbol_delay
parse_mode = parse_mode or cfg.telegram_parse_mode
logger.info("순차 처리 시작 (심볼 수=%d)", len(symbols))
alerts = []
buy_signal_count = 0
for i, sym in enumerate(symbols):
try:
res = process_symbol(sym, timeframe, candle_count, telegram_token, telegram_chat_id, dry_run, indicators=None, indicator_timeframe=indicator_timeframe, cfg=cfg)
for line in res.get("summary", []):
logger.info(line)
if res.get("telegram"):
buy_signal_count += 1
if dry_run:
logger.info("[dry-run] 알림 내용:\n%s", res["telegram"])
else:
# dry_run이 아닐 때는 콘솔에 메시지 출력하지 않음
pass
if telegram_token and telegram_chat_id:
send_telegram(telegram_token, telegram_chat_id, res["telegram"], add_thread_prefix=False, parse_mode=parse_mode)
else:
logger.warning("텔레그램 토큰/채팅 ID가 설정되지 않아 메시지 전송 불가")
alerts.append({"symbol": sym, "text": res["telegram"]})
except Exception as e:
logger.exception("심볼 처리 오류: %s -> %s", sym, e)
if i < len(symbols) - 1 and symbol_delay is not None:
logger.debug("다음 심볼까지 %.2f초 대기", symbol_delay)
time.sleep(symbol_delay)
if aggregate_enabled and len(alerts) > 1:
summary_lines = [f"알림 발생 심볼 수: {len(alerts)}", "\n"]
summary_lines += [f"- {a['symbol']}" for a in alerts]
summary_text = "\n".join(summary_lines)
if dry_run:
logger.info("[dry-run] 알림 요약:\n%s", summary_text)
else:
if telegram_token and telegram_chat_id:
send_telegram(telegram_token, telegram_chat_id, summary_text, add_thread_prefix=False, parse_mode=parse_mode)
else:
logger.warning("텔레그램 토큰/채팅 ID가 설정되지 않아 요약 메시지 전송 불가")
# 매수 조건이 하나도 충족되지 않은 경우 알림 전송
if telegram_token and telegram_chat_id and not any(a.get("text") for a in alerts):
send_telegram(telegram_token, telegram_chat_id, "[알림] 충족된 매수 조건 없음 (프로그램 정상 작동 중)", add_thread_prefix=False, parse_mode=parse_mode)
return buy_signal_count
def run_with_threads(symbols: List[str], timeframe: str | None = None, indicator_timeframe: str | None = None, candle_count: int | None = None, telegram_token: str | None = None, telegram_chat_id: str | None = None, dry_run: bool | None = None, symbol_delay: float | None = None, max_threads: int | None = None, parse_mode: str | None = None, aggregate_enabled: bool = False, cfg: RuntimeConfig | None = None):
if cfg is not None:
timeframe = timeframe or cfg.timeframe
indicator_timeframe = indicator_timeframe or cfg.indicator_timeframe
candle_count = cfg.candle_count if (candle_count is None) else candle_count
telegram_token = telegram_token or cfg.telegram_bot_token
telegram_chat_id = telegram_chat_id or cfg.telegram_chat_id
dry_run = cfg.dry_run if (dry_run is None) else dry_run
symbol_delay = cfg.symbol_delay if (symbol_delay is None) else symbol_delay
max_threads = cfg.max_threads if (max_threads is None) else max_threads
parse_mode = parse_mode or cfg.telegram_parse_mode
logger.info("병렬 처리 시작 (심볼 수=%d, 스레드 수=%d, 심볼 간 지연=%.2f초)", len(symbols), max_threads or 0, symbol_delay or 0.0)
semaphore = threading.Semaphore(max_threads)
threads = []
last_request_time = [0]
throttle_lock = threading.Lock()
results = {}
results_lock = threading.Lock()
def worker(symbol: str):
try:
with semaphore:
with throttle_lock:
elapsed = time.time() - last_request_time[0]
if symbol_delay is not None and elapsed < symbol_delay:
sleep_time = symbol_delay - elapsed
logger.debug("[%s] 스로틀 대기: %.2f", symbol, sleep_time)
time.sleep(sleep_time)
last_request_time[0] = time.time()
res = process_symbol(symbol, timeframe, candle_count, telegram_token, telegram_chat_id, dry_run, indicators=None, indicator_timeframe=indicator_timeframe, cfg=cfg)
with results_lock:
results[symbol] = res
except Exception as e:
logger.exception("[%s] 워커 스레드 오류: %s", symbol, e)
for sym in symbols:
t = threading.Thread(target=worker, args=(sym,), name=f"Worker-{sym}")
threads.append(t)
t.start()
for t in threads:
t.join()
alerts = []
buy_signal_count = 0
for sym in symbols:
with results_lock:
res = results.get(sym)
if not res:
logger.warning("심볼 결과 없음: %s", sym)
continue
for line in res.get("summary", []):
logger.info(line)
if res.get("telegram"):
buy_signal_count += 1
if dry_run:
logger.info("[dry-run] 알림 내용:\n%s", res["telegram"])
if telegram_token and telegram_chat_id:
send_telegram(telegram_token, telegram_chat_id, res["telegram"], add_thread_prefix=False, parse_mode=parse_mode)
else:
logger.warning("텔레그램 토큰/채팅 ID가 설정되지 않아 메시지 전송 불가")
alerts.append({"symbol": sym, "text": res["telegram"]})
if aggregate_enabled and len(alerts) > 1:
summary_lines = [f"알림 발생 심볼 수: {len(alerts)}", "\n"]
summary_lines += [f"- {a['symbol']}" for a in alerts]
summary_text = "\n".join(summary_lines)
if dry_run:
logger.info("[dry-run] 알림 요약:\n%s", summary_text)
else:
if telegram_token and telegram_chat_id:
send_telegram(telegram_token, telegram_chat_id, summary_text, add_thread_prefix=False, parse_mode=parse_mode)
else:
logger.warning("텔레그램 토큰/채팅 ID가 설정되지 않아 요약 메시지 전송 불가")
# 매수 조건이 하나도 충족되지 않은 경우 알림 전송
if telegram_token and telegram_chat_id and not any(a.get("text") for a in alerts):
send_telegram(telegram_token, telegram_chat_id, "[알림] 충족된 매수 조건 없음 (프로그램 정상 작동 중)", add_thread_prefix=False, parse_mode=parse_mode)
logger.info("병렬 처리 완료")
return buy_signal_count