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:
103
scripts/3_watch_ops.py
Normal file
103
scripts/3_watch_ops.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""read-only 감시 + 불일치 시 조치 tick / loop 재시작."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = ROOT / "src"
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
|
||||
from bithumb.config import load_settings
|
||||
from bithumb.operations.ops_lock import ops_tick_lock
|
||||
from bithumb.operations.watch_ops import (
|
||||
inspect_ops_watch,
|
||||
inspect_vol_watch,
|
||||
is_vol_breakout_ops,
|
||||
remediate_ops_watch,
|
||||
remediate_vol_watch,
|
||||
)
|
||||
|
||||
|
||||
def _configure_logging(verbose: bool) -> None:
|
||||
level = logging.DEBUG if verbose else logging.INFO
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""CLI 진입점."""
|
||||
parser = argparse.ArgumentParser(description="운영 read-only 감시 + 조치")
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="점검·알림만 (tick/재시작 없음)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inspect-only",
|
||||
action="store_true",
|
||||
help="stdout 출력만 (텔레그램·조치 없음)",
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
args = parser.parse_args()
|
||||
_configure_logging(args.verbose)
|
||||
|
||||
settings = load_settings()
|
||||
|
||||
watch_lock = settings.ops_tick_lock_path
|
||||
if watch_lock is not None:
|
||||
watch_lock = watch_lock.parent / "ops.watch.lock"
|
||||
if watch_lock is not None:
|
||||
with ops_tick_lock(watch_lock, blocking=False) as acquired:
|
||||
if not acquired:
|
||||
print("watch already running — skip")
|
||||
return 0
|
||||
return _run_watch(settings, args)
|
||||
return _run_watch(settings, args)
|
||||
|
||||
|
||||
def _run_watch(settings, args) -> int:
|
||||
vol_mode = is_vol_breakout_ops(settings)
|
||||
report = inspect_vol_watch(settings) if vol_mode else inspect_ops_watch(settings)
|
||||
|
||||
print("=== ops watch ===")
|
||||
if vol_mode:
|
||||
print("mode: vol_breakout")
|
||||
print(f"checked_at: {report.checked_at}")
|
||||
print(f"ledger_pending: {report.ledger_pending}")
|
||||
print(f"executable_pending: {report.executable_pending}")
|
||||
print(f"tick_age_sec: {report.tick_age_sec}")
|
||||
print(f"loop_running: {report.loop_running}")
|
||||
for issue in report.issues:
|
||||
print(f" [{issue.severity}] {issue.kind}: {issue.message}")
|
||||
|
||||
if args.inspect_only:
|
||||
return 1 if report.issues else 0
|
||||
|
||||
if not report.issues:
|
||||
print("OK — 조치 없음")
|
||||
return 0
|
||||
|
||||
result = (
|
||||
remediate_vol_watch(settings, report, dry_run=args.dry_run)
|
||||
if vol_mode
|
||||
else remediate_ops_watch(settings, report, dry_run=args.dry_run)
|
||||
)
|
||||
print("\n=== remediation ===")
|
||||
for action in result.actions:
|
||||
print(f" action: {action}")
|
||||
for msg in result.messages:
|
||||
print(f" {msg}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user