feat(vol_breakout): 15m 현물 롱 라이브·모니터·cron 운영 추가
vol_breakout 멀티종목 tick, vol_live HTML 모니터, 마감 봉만 저장하는 캔들 다운로드, 텔레그램 체결 알림, cron/watch 감시 스크립트 및 테스트를 포함한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
@@ -18,6 +20,20 @@ from bithumb.config import load_settings
|
||||
from bithumb.operations.runner import OperationsRunner
|
||||
|
||||
|
||||
def _write_loop_pid(path: Path) -> None:
|
||||
"""loop PID 파일 기록 (watch 재시작용)."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(f"{os.getpid()}\n", encoding="utf-8")
|
||||
|
||||
|
||||
def _remove_loop_pid(path: Path) -> None:
|
||||
"""loop 종료 시 PID 파일 삭제."""
|
||||
try:
|
||||
path.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _configure_logging(verbose: bool) -> None:
|
||||
level = logging.DEBUG if verbose else logging.INFO
|
||||
logging.basicConfig(
|
||||
@@ -66,7 +82,12 @@ def main() -> int:
|
||||
runner = OperationsRunner(settings)
|
||||
sync = not args.no_sync
|
||||
|
||||
if args.loop > 0 and settings.ops_loop_pid_file is not None:
|
||||
_write_loop_pid(settings.ops_loop_pid_file)
|
||||
atexit.register(_remove_loop_pid, settings.ops_loop_pid_file)
|
||||
|
||||
while True:
|
||||
loop_started = time.monotonic()
|
||||
try:
|
||||
report = runner.tick(sync_candles=sync)
|
||||
except Exception as exc:
|
||||
@@ -81,7 +102,9 @@ def main() -> int:
|
||||
)
|
||||
if args.loop <= 0:
|
||||
break
|
||||
time.sleep(args.loop)
|
||||
elapsed = time.monotonic() - loop_started
|
||||
sleep_sec = max(0.0, float(args.loop) - elapsed)
|
||||
time.sleep(sleep_sec)
|
||||
continue
|
||||
|
||||
port = report.get("portfolio") or {}
|
||||
@@ -100,10 +123,18 @@ def main() -> int:
|
||||
f"코인 {port.get('coin_qty', 0):.8f} {settings.symbol}"
|
||||
)
|
||||
print(f"리포트: {settings.ops_report_json}")
|
||||
if report.get("ledger_pending_count") is not None:
|
||||
print(
|
||||
f"ledger pending: {report.get('ledger_pending_count')} · "
|
||||
f"backlog dropped: {report.get('backlog_dropped_count', 0)} · "
|
||||
f"tick: {report.get('last_tick_duration_sec')}s"
|
||||
)
|
||||
|
||||
if args.loop <= 0:
|
||||
break
|
||||
time.sleep(args.loop)
|
||||
elapsed = time.monotonic() - loop_started
|
||||
sleep_sec = max(0.0, float(args.loop) - elapsed)
|
||||
time.sleep(sleep_sec)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user