fix: .env 미로드(ncue) fallback 및 B-1 운영 설정 정합
python-dotenv 없을 때 env_loader가 .env를 직접 파싱하도록 해 LIVE_TRADING_ENABLED=1이 dry-run으로 보이던 문제를 해결한다. 06 기동 시 로더·LIVE 상태를 출력하고, B-1 한도 검증·잔고 점검 스크립트를 추가한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
13
.env.example
13
.env.example
@@ -60,16 +60,13 @@ MONITOR_ALERT_KRW_AMOUNT=40000
|
|||||||
MONITOR_LOOP_SLEEP_SEC=180
|
MONITOR_LOOP_SLEEP_SEC=180
|
||||||
MATCH_LIVE_CACHE_SEC=180
|
MATCH_LIVE_CACHE_SEC=180
|
||||||
|
|
||||||
# 3 실거래 — Phase별 권장값: docs/05_ops/env.recommended.md
|
# 3 실거래 — 기본: Phase B-1 (운영). Phase C dry-run은 docs/05_ops/env.recommended.md
|
||||||
# Phase C (dry-run): LIVE=0, LIVE_* 무제한(시뮬 정합), COOLDOWN=3(1봉)
|
LIVE_TRADING_ENABLED=1
|
||||||
# Phase B-1: LIVE=1, LIVE_DAILY_KRW_MAX=400000, MAX_TRADES=15, COOLDOWN=3
|
|
||||||
# Phase B-2: LIVE_DAILY_KRW_MAX=5000000, MAX_TRADES=30, COOLDOWN=120
|
|
||||||
LIVE_TRADING_ENABLED=0
|
|
||||||
LIVE_ORDER_KRW=40000
|
LIVE_ORDER_KRW=40000
|
||||||
LIVE_BUY_PCT_LARGE=1.0
|
LIVE_BUY_PCT_LARGE=1.0
|
||||||
LIVE_BUY_PCT_SMALL=0.05
|
LIVE_BUY_PCT_SMALL=0.05
|
||||||
LIVE_DAILY_KRW_MAX=4000000
|
LIVE_DAILY_KRW_MAX=400000
|
||||||
LIVE_COOLDOWN_MIN=3
|
LIVE_COOLDOWN_MIN=3
|
||||||
LIVE_MAX_TRADES_PER_DAY=999
|
LIVE_MAX_TRADES_PER_DAY=15
|
||||||
LIVE_DAILY_LOSS_LIMIT_KRW=20000
|
LIVE_DAILY_LOSS_LIMIT_KRW=40000
|
||||||
LIVE_SLIPPAGE_PCT=0.05
|
LIVE_SLIPPAGE_PCT=0.05
|
||||||
|
|||||||
@@ -391,9 +391,9 @@ LIVE_TRADING_ENABLED = _getenv("LIVE_TRADING_ENABLED", "0").strip() in (
|
|||||||
LIVE_ORDER_KRW = _getenv_int("LIVE_ORDER_KRW", "40000")
|
LIVE_ORDER_KRW = _getenv_int("LIVE_ORDER_KRW", "40000")
|
||||||
LIVE_BUY_PCT_LARGE = _getenv_float("LIVE_BUY_PCT_LARGE", "1.0")
|
LIVE_BUY_PCT_LARGE = _getenv_float("LIVE_BUY_PCT_LARGE", "1.0")
|
||||||
LIVE_BUY_PCT_SMALL = _getenv_float("LIVE_BUY_PCT_SMALL", "0.05")
|
LIVE_BUY_PCT_SMALL = _getenv_float("LIVE_BUY_PCT_SMALL", "0.05")
|
||||||
# Phase C dry-run: 초기자금×10 (hybrid full tier 여유). B-1은 .env에서 400000 권장.
|
# Phase B-1 기본: 일한도=초기자금 1배. Phase C dry-run은 .env에서 4000000 등으로 상향.
|
||||||
LIVE_DAILY_KRW_MAX = _getenv_int("LIVE_DAILY_KRW_MAX", "4000000")
|
LIVE_DAILY_KRW_MAX = _getenv_int("LIVE_DAILY_KRW_MAX", "400000")
|
||||||
LIVE_COOLDOWN_MIN = _getenv_int("LIVE_COOLDOWN_MIN", "3")
|
LIVE_COOLDOWN_MIN = _getenv_int("LIVE_COOLDOWN_MIN", "3")
|
||||||
LIVE_MAX_TRADES_PER_DAY = _getenv_int("LIVE_MAX_TRADES_PER_DAY", "10")
|
LIVE_MAX_TRADES_PER_DAY = _getenv_int("LIVE_MAX_TRADES_PER_DAY", "15")
|
||||||
LIVE_DAILY_LOSS_LIMIT_KRW = _getenv_int("LIVE_DAILY_LOSS_LIMIT_KRW", "20000")
|
LIVE_DAILY_LOSS_LIMIT_KRW = _getenv_int("LIVE_DAILY_LOSS_LIMIT_KRW", "40000")
|
||||||
LIVE_SLIPPAGE_PCT = _getenv_float("LIVE_SLIPPAGE_PCT", "0.05")
|
LIVE_SLIPPAGE_PCT = _getenv_float("LIVE_SLIPPAGE_PCT", "0.05")
|
||||||
|
|||||||
@@ -6,41 +6,73 @@ config·HTS·스크립트 진입 전에 한 번 호출하면 cwd와 무관하게
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from deepcoin.paths import PROJECT_ROOT
|
from deepcoin.paths import PROJECT_ROOT
|
||||||
|
|
||||||
_ENV_LOADED = False
|
_ENV_LOADED = False
|
||||||
|
_ENV_LOADER = "none"
|
||||||
ENV_FILE = PROJECT_ROOT / ".env"
|
ENV_FILE = PROJECT_ROOT / ".env"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_env_fallback(*, override: bool) -> None:
|
||||||
|
"""
|
||||||
|
python-dotenv 미설치 시 .env 를 직접 파싱해 os.environ 에 반영합니다.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
override: True면 기존 키를 .env 값으로 덮어씀.
|
||||||
|
"""
|
||||||
|
if not ENV_FILE.is_file():
|
||||||
|
return
|
||||||
|
for line in ENV_FILE.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if line.startswith("export "):
|
||||||
|
line = line[7:].strip()
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
key, _, value = line.partition("=")
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip().strip('"').strip("'")
|
||||||
|
if not key:
|
||||||
|
continue
|
||||||
|
if not override and key in os.environ:
|
||||||
|
continue
|
||||||
|
os.environ[key] = value
|
||||||
|
|
||||||
|
|
||||||
def load_project_env(*, override: bool = False) -> bool:
|
def load_project_env(*, override: bool = False) -> bool:
|
||||||
"""
|
"""
|
||||||
PROJECT_ROOT/.env 를 python-dotenv로 로드합니다.
|
PROJECT_ROOT/.env 를 로드합니다 (python-dotenv 우선, 없으면 fallback).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
override: True면 기존 OS 환경 변수를 .env 값으로 덮어씀.
|
override: True면 기존 OS 환경 변수를 .env 값으로 덮어씀.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
.env 파일이 존재해 로드 시도했으면 True, 없으면 False.
|
.env 파일이 존재해 로드했으면 True, 없으면 False.
|
||||||
"""
|
"""
|
||||||
global _ENV_LOADED
|
global _ENV_LOADED, _ENV_LOADER
|
||||||
if _ENV_LOADED and not override:
|
if _ENV_LOADED and not override:
|
||||||
return ENV_FILE.is_file()
|
return ENV_FILE.is_file()
|
||||||
|
|
||||||
try:
|
if not ENV_FILE.is_file():
|
||||||
from dotenv import load_dotenv
|
|
||||||
except ImportError:
|
|
||||||
_ENV_LOADED = True
|
_ENV_LOADED = True
|
||||||
|
_ENV_LOADER = "missing"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if ENV_FILE.is_file():
|
try:
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(ENV_FILE, override=override)
|
load_dotenv(ENV_FILE, override=override)
|
||||||
_ENV_LOADED = True
|
_ENV_LOADER = "dotenv"
|
||||||
return True
|
except ImportError:
|
||||||
|
_load_env_fallback(override=override)
|
||||||
|
_ENV_LOADER = "fallback"
|
||||||
|
|
||||||
_ENV_LOADED = True
|
_ENV_LOADED = True
|
||||||
return False
|
return True
|
||||||
|
|
||||||
|
|
||||||
def env_status() -> dict[str, str | bool]:
|
def env_status() -> dict[str, str | bool]:
|
||||||
@@ -50,4 +82,6 @@ def env_status() -> dict[str, str | bool]:
|
|||||||
"env_file": str(ENV_FILE),
|
"env_file": str(ENV_FILE),
|
||||||
"env_exists": ENV_FILE.is_file(),
|
"env_exists": ENV_FILE.is_file(),
|
||||||
"loaded": _ENV_LOADED,
|
"loaded": _ENV_LOADED,
|
||||||
|
"loader": _ENV_LOADER,
|
||||||
|
"live_trading_enabled": os.getenv("LIVE_TRADING_ENABLED", ""),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Live Phase A — dry-run 검증
|
# Live Phase A — dry-run 검증
|
||||||
|
|
||||||
- 일시: 2026-06-03 08:50:59
|
- 일시: 2026-06-03 19:55:24
|
||||||
- 결과: **WARN**
|
- 결과: **PASS**
|
||||||
|
|
||||||
## Plan (목적)
|
## Plan (목적)
|
||||||
|
|
||||||
@@ -19,14 +19,10 @@ python scripts/06_execute_live.py --once
|
|||||||
## Check (점검 결과)
|
## Check (점검 결과)
|
||||||
|
|
||||||
- GT_SIGNAL_CAUSAL=True
|
- GT_SIGNAL_CAUSAL=True
|
||||||
- LIVE_TRADING_ENABLED=False
|
- LIVE_TRADING_ENABLED=True
|
||||||
- monitor_rules: buy_compound_tight, sell_mtf_cross_all_tf
|
- monitor_rules: buy_compound_tight, sell_mtf_cross_all_tf
|
||||||
- hybrid DD: {'dd_large_pct': 5.0, 'dd_medium_pct': 2.0}
|
- hybrid DD: {'dd_large_pct': 5.0, 'dd_medium_pct': 2.0}
|
||||||
|
|
||||||
### 이슈
|
|
||||||
|
|
||||||
- paper_portfolio.json 과 sim replay 불일치 — signal_history 갱신 후 06 --once 1회 권장
|
|
||||||
|
|
||||||
## Act (다음 단계)
|
## Act (다음 단계)
|
||||||
|
|
||||||
1. `05_run_monitor.py` 1~2일 병행 (알림만)
|
1. `05_run_monitor.py` 1~2일 병행 (알림만)
|
||||||
|
|||||||
@@ -6,16 +6,44 @@ from pathlib import Path
|
|||||||
|
|
||||||
runpy.run_path(str(Path(__file__).resolve().parent / "_bootstrap.py"))
|
runpy.run_path(str(Path(__file__).resolve().parent / "_bootstrap.py"))
|
||||||
|
|
||||||
from config import LIVE_TRADING_ENABLED, MONITOR_LOOP_SLEEP_SEC
|
from deepcoin.env_loader import ENV_FILE, env_status # noqa: E402
|
||||||
|
|
||||||
|
from config import (
|
||||||
|
GT_INITIAL_CASH_KRW,
|
||||||
|
LIVE_DAILY_KRW_MAX,
|
||||||
|
LIVE_DAILY_LOSS_LIMIT_KRW,
|
||||||
|
LIVE_MAX_TRADES_PER_DAY,
|
||||||
|
LIVE_TRADING_ENABLED,
|
||||||
|
MONITOR_LOOP_SLEEP_SEC,
|
||||||
|
)
|
||||||
from deepcoin.ops.live_trader import LiveTrader
|
from deepcoin.ops.live_trader import LiveTrader
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="WLD 실거래 (06)")
|
parser = argparse.ArgumentParser(description="WLD 실거래 (06)")
|
||||||
parser.add_argument("--once", action="store_true", help="1회만 실행")
|
parser.add_argument("--once", action="store_true", help="1회만 실행")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
trader = LiveTrader()
|
st = env_status()
|
||||||
|
mode = "LIVE=ON (실주문)" if LIVE_TRADING_ENABLED else "LIVE=OFF (dry-run)"
|
||||||
|
print(
|
||||||
|
f"[06] 운영 설정 · {mode} · "
|
||||||
|
f"초기₩{GT_INITIAL_CASH_KRW:,} · 일한도₩{LIVE_DAILY_KRW_MAX:,} · "
|
||||||
|
f"일손실₩{LIVE_DAILY_LOSS_LIMIT_KRW:,} · max_trades={LIVE_MAX_TRADES_PER_DAY}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"[06] .env 로더={st.get('loader')} · 파일={ENV_FILE.name} · "
|
||||||
|
f"LIVE_TRADING_ENABLED(raw)={st.get('live_trading_enabled')!r}"
|
||||||
|
)
|
||||||
|
if st.get("loader") == "fallback":
|
||||||
|
print(
|
||||||
|
"주의: python-dotenv 미설치 — fallback 파서 사용. "
|
||||||
|
"권장: pip install python-dotenv (requirements.txt)"
|
||||||
|
)
|
||||||
if not LIVE_TRADING_ENABLED:
|
if not LIVE_TRADING_ENABLED:
|
||||||
print("주의: LIVE_TRADING_ENABLED=0 — 주문 없이 dry_run 로그만")
|
print(
|
||||||
|
"주의: LIVE_TRADING_ENABLED=0 — .env에 1인지 확인 후 재기동. "
|
||||||
|
"(ncue 등 dotenv 없는 환경이면 pip install python-dotenv)"
|
||||||
|
)
|
||||||
|
trader = LiveTrader()
|
||||||
if args.once:
|
if args.once:
|
||||||
trader.run_once()
|
trader.run_once()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ def check_config() -> list[str]:
|
|||||||
print(f" monitor_rules={[r['rule_id'] for r in rules]}")
|
print(f" monitor_rules={[r['rule_id'] for r in rules]}")
|
||||||
if not GT_SIGNAL_CAUSAL:
|
if not GT_SIGNAL_CAUSAL:
|
||||||
issues.append("GT_SIGNAL_CAUSAL=0 — hybrid live sizing 비활성")
|
issues.append("GT_SIGNAL_CAUSAL=0 — hybrid live sizing 비활성")
|
||||||
if LIVE_TRADING_ENABLED:
|
if not LIVE_TRADING_ENABLED:
|
||||||
issues.append("LIVE_TRADING_ENABLED=1 — dry-run 점검 시 0 권장")
|
issues.append("LIVE_TRADING_ENABLED=0 — 실전 운영 시 1 필요")
|
||||||
if len(rules) != 2:
|
if len(rules) != 2:
|
||||||
issues.append(f"monitor_rules {len(rules)}개 (기대 2)")
|
issues.append(f"monitor_rules {len(rules)}개 (기대 2)")
|
||||||
expected = {"buy_compound_tight", "sell_mtf_cross_all_tf"}
|
expected = {"buy_compound_tight", "sell_mtf_cross_all_tf"}
|
||||||
@@ -118,27 +118,37 @@ def check_config() -> list[str]:
|
|||||||
|
|
||||||
def check_capital_alignment() -> list[str]:
|
def check_capital_alignment() -> list[str]:
|
||||||
"""
|
"""
|
||||||
초기 자금 40만 원 기준 원화 한도·알림 비율 점검 (100만 시대 ×0.4).
|
초기 자금 40만 원 기준 원화 한도·알림 비율 점검.
|
||||||
|
|
||||||
|
LIVE_TRADING_ENABLED=1 → Phase B-1(운영), 0 → Phase C(dry-run).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
불일치 시 이슈 문자열 목록.
|
불일치 시 이슈 문자열 목록.
|
||||||
"""
|
"""
|
||||||
issues: list[str] = []
|
issues: list[str] = []
|
||||||
_print_header("1b. 초기 자금·비율 (40만 원)")
|
phase = "B-1 실전" if LIVE_TRADING_ENABLED else "C dry-run"
|
||||||
|
_print_header(f"1b. 초기 자금·비율 (40만 원, {phase})")
|
||||||
ic = int(GT_INITIAL_CASH_KRW)
|
ic = int(GT_INITIAL_CASH_KRW)
|
||||||
expected = {
|
expected: dict[str, int] = {
|
||||||
"GT_INITIAL_CASH_KRW": 400_000,
|
"GT_INITIAL_CASH_KRW": 400_000,
|
||||||
"MONITOR_ALERT_KRW_AMOUNT": int(ic * 0.10),
|
"MONITOR_ALERT_KRW_AMOUNT": int(ic * 0.10),
|
||||||
"LIVE_ORDER_KRW": int(ic * 0.10),
|
"LIVE_ORDER_KRW": int(ic * 0.10),
|
||||||
"LIVE_DAILY_LOSS_LIMIT_KRW": int(ic * 0.05),
|
|
||||||
"LIVE_DAILY_KRW_MAX": int(ic * 10),
|
|
||||||
}
|
}
|
||||||
|
if LIVE_TRADING_ENABLED:
|
||||||
|
expected["LIVE_DAILY_LOSS_LIMIT_KRW"] = int(ic * 0.10)
|
||||||
|
expected["LIVE_DAILY_KRW_MAX"] = ic
|
||||||
|
expected["LIVE_MAX_TRADES_PER_DAY"] = 15
|
||||||
|
else:
|
||||||
|
expected["LIVE_DAILY_LOSS_LIMIT_KRW"] = int(ic * 0.05)
|
||||||
|
expected["LIVE_DAILY_KRW_MAX"] = int(ic * 10)
|
||||||
|
expected["LIVE_MAX_TRADES_PER_DAY"] = 999
|
||||||
actual = {
|
actual = {
|
||||||
"GT_INITIAL_CASH_KRW": ic,
|
"GT_INITIAL_CASH_KRW": ic,
|
||||||
"MONITOR_ALERT_KRW_AMOUNT": int(MONITOR_ALERT_KRW_AMOUNT),
|
"MONITOR_ALERT_KRW_AMOUNT": int(MONITOR_ALERT_KRW_AMOUNT),
|
||||||
"LIVE_ORDER_KRW": int(LIVE_ORDER_KRW),
|
"LIVE_ORDER_KRW": int(LIVE_ORDER_KRW),
|
||||||
"LIVE_DAILY_LOSS_LIMIT_KRW": int(LIVE_DAILY_LOSS_LIMIT_KRW),
|
"LIVE_DAILY_LOSS_LIMIT_KRW": int(LIVE_DAILY_LOSS_LIMIT_KRW),
|
||||||
"LIVE_DAILY_KRW_MAX": int(LIVE_DAILY_KRW_MAX),
|
"LIVE_DAILY_KRW_MAX": int(LIVE_DAILY_KRW_MAX),
|
||||||
|
"LIVE_MAX_TRADES_PER_DAY": int(LIVE_MAX_TRADES_PER_DAY),
|
||||||
}
|
}
|
||||||
for key, exp in expected.items():
|
for key, exp in expected.items():
|
||||||
got = actual[key]
|
got = actual[key]
|
||||||
@@ -147,13 +157,16 @@ def check_capital_alignment() -> list[str]:
|
|||||||
print(f" [{mark}] {key}={got:,} (기대 {exp:,})")
|
print(f" [{mark}] {key}={got:,} (기대 {exp:,})")
|
||||||
if not ok:
|
if not ok:
|
||||||
issues.append(f"{key}={got:,} ≠ 기대 {exp:,}")
|
issues.append(f"{key}={got:,} ≠ 기대 {exp:,}")
|
||||||
paper = PaperPortfolio.load()
|
if not LIVE_TRADING_ENABLED:
|
||||||
if int(paper.cash_krw) != ic and paper.qty < 1e-12:
|
paper = PaperPortfolio.load()
|
||||||
issues.append(
|
if int(paper.cash_krw) != ic and paper.qty < 1e-12:
|
||||||
f"paper 현금 ₩{paper.cash_krw:,.0f} ≠ 초기 ₩{ic:,} (보유 없을 때)"
|
issues.append(
|
||||||
)
|
f"paper 현금 ₩{paper.cash_krw:,.0f} ≠ 초기 ₩{ic:,} (보유 없을 때)"
|
||||||
elif int(getattr(paper, "initial_cash_krw", 0) or paper.cash_krw) != ic:
|
)
|
||||||
print(f" [INFO] paper 운용 중 (cash=₩{paper.cash_krw:,.0f})")
|
elif int(getattr(paper, "initial_cash_krw", 0) or paper.cash_krw) != ic:
|
||||||
|
print(f" [INFO] paper 운용 중 (cash=₩{paper.cash_krw:,.0f})")
|
||||||
|
else:
|
||||||
|
print(" [INFO] LIVE=1 — paper_portfolio 검사 생략 (실계좌·live_signal_history 사용)")
|
||||||
return issues
|
return issues
|
||||||
|
|
||||||
|
|
||||||
@@ -354,7 +367,7 @@ def main() -> int:
|
|||||||
print(f" WARN: {i}")
|
print(f" WARN: {i}")
|
||||||
print(" → 이슈 확인 후 Phase B(소액 파일럿) 진행")
|
print(" → 이슈 확인 후 Phase B(소액 파일럿) 진행")
|
||||||
return 1
|
return 1
|
||||||
print(" Phase A PASS — Phase B(소액 LIVE_TRADING_ENABLED=1) 준비 완료")
|
print(" 운영 설정 PASS — 06_execute_live 실전 기동 가능")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
48
scripts/check_balance.py
Normal file
48
scripts/check_balance.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""빗썸 실계좌 잔고 조회 (운영 점검용)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import runpy
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
runpy.run_path(str(Path(__file__).resolve().parent / "_bootstrap.py"))
|
||||||
|
|
||||||
|
from config import GT_INITIAL_CASH_KRW, LIVE_TRADING_ENABLED, SYMBOL # noqa: E402
|
||||||
|
from deepcoin.ops.monitor import Monitor # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
"""
|
||||||
|
KRW·거래 심볼 잔고를 출력합니다.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
0 성공, 1 API 오류.
|
||||||
|
"""
|
||||||
|
print(f"LIVE_TRADING_ENABLED={int(LIVE_TRADING_ENABLED)}")
|
||||||
|
print(f"GT_INITIAL_CASH_KRW=₩{GT_INITIAL_CASH_KRW:,}")
|
||||||
|
m = Monitor(cooldown_file=None)
|
||||||
|
raw = m.getBalances()
|
||||||
|
if isinstance(raw, dict) and raw.get("error"):
|
||||||
|
print("API_ERROR", raw.get("error"))
|
||||||
|
return 1
|
||||||
|
if not isinstance(raw, list):
|
||||||
|
print("UNEXPECTED_RESPONSE", type(raw))
|
||||||
|
return 1
|
||||||
|
krw = next((x for x in raw if x.get("currency") == "KRW"), None)
|
||||||
|
coin = next((x for x in raw if x.get("currency") == SYMBOL), None)
|
||||||
|
if krw:
|
||||||
|
bal = float(krw["balance"])
|
||||||
|
print(f"KRW available=₩{bal:,.0f}")
|
||||||
|
if bal < float(GT_INITIAL_CASH_KRW):
|
||||||
|
print(f" [WARN] 가용 KRW < 초기자금 ₩{GT_INITIAL_CASH_KRW:,}")
|
||||||
|
if coin:
|
||||||
|
print(f"{SYMBOL} qty={float(coin['balance']):.6f}")
|
||||||
|
else:
|
||||||
|
print(f"{SYMBOL} qty=0")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user