Phase C dry-run·문서화·DB 증분 저장 및 운영 env 동기화
- 1분봉 다운로드 제외, MONITOR_PERSIST로 05/06 수집 시 coins.db INSERT - Phase C paper_fires 로그·07 모의 리포트, hybrid 시뮬 산출물·reference 문서 갱신 - .env Phase C(LIVE=0), bootstrap dotenv override=True Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
7
.env
7
.env
@@ -25,8 +25,8 @@ DAILY_INTERVAL_MIN=1440
|
|||||||
ENTRY_INTERVAL=3
|
ENTRY_INTERVAL=3
|
||||||
TREND_INTERVAL_1H=60
|
TREND_INTERVAL_1H=60
|
||||||
TREND_INTERVAL_1D=1440
|
TREND_INTERVAL_1D=1440
|
||||||
ALL_INTERVALS=1,3,5,10,15,30,60,240,1440
|
ALL_INTERVALS=3,5,10,15,30,60,240,1440
|
||||||
DOWNLOAD_INTERVALS=1,3,5,10,15,30,60,240,1440
|
DOWNLOAD_INTERVALS=3,5,10,15,30,60,240,1440
|
||||||
GENERAL_ANALYSIS_INTERVALS=3,5,10,15,30,60,240,1440
|
GENERAL_ANALYSIS_INTERVALS=3,5,10,15,30,60,240,1440
|
||||||
TIMING_INTERVALS=3,5,10,15
|
TIMING_INTERVALS=3,5,10,15
|
||||||
TREND_INTERVALS=60,240,1440
|
TREND_INTERVALS=60,240,1440
|
||||||
@@ -93,7 +93,7 @@ TRADING_FEE_RATE=0.0005
|
|||||||
GT_UNLIMITED_CHRONOLOGICAL_DAYS=300
|
GT_UNLIMITED_CHRONOLOGICAL_DAYS=300
|
||||||
|
|
||||||
# --- 모니터 ---
|
# --- 모니터 ---
|
||||||
MONITOR_LOOP_SLEEP_SEC=10
|
MONITOR_LOOP_SLEEP_SEC=180
|
||||||
MONITOR_POOL_WORKERS=12
|
MONITOR_POOL_WORKERS=12
|
||||||
MONITOR_DEFAULT_INTERVAL=60
|
MONITOR_DEFAULT_INTERVAL=60
|
||||||
MONITOR_API_RETRIES=3
|
MONITOR_API_RETRIES=3
|
||||||
@@ -175,6 +175,7 @@ SIM_GO_WF_POSITIVE_RATIO=0.5
|
|||||||
SIM_FEE_STRESS_MULT=2.0
|
SIM_FEE_STRESS_MULT=2.0
|
||||||
MONITOR_ALERT_COOLDOWN_MIN=180
|
MONITOR_ALERT_COOLDOWN_MIN=180
|
||||||
MONITOR_ALERT_KRW_AMOUNT=100000
|
MONITOR_ALERT_KRW_AMOUNT=100000
|
||||||
|
# Phase C: dry-run (~금요일 저녁) → 이후 B-1에서 LIVE_TRADING_ENABLED=1
|
||||||
LIVE_TRADING_ENABLED=0
|
LIVE_TRADING_ENABLED=0
|
||||||
LIVE_ORDER_KRW=100000
|
LIVE_ORDER_KRW=100000
|
||||||
LIVE_BUY_PCT_LARGE=1.0
|
LIVE_BUY_PCT_LARGE=1.0
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ COIN_TELEGRAM_CHAT_ID=
|
|||||||
SYMBOL=WLD
|
SYMBOL=WLD
|
||||||
CHART_LOOKBACK_DAYS=365
|
CHART_LOOKBACK_DAYS=365
|
||||||
|
|
||||||
|
# 01 다운로드 (1분봉 제외 — 시뮬·MTF는 3분 이상만 사용)
|
||||||
|
DOWNLOAD_INTERVALS=3,5,10,15,30,60,240,1440
|
||||||
|
DOWNLOAD_MONTHS=12
|
||||||
|
# 05/06 루프마다 API 봉을 coins.db에 증분 저장 (01과 동일 append_data)
|
||||||
|
MONITOR_PERSIST_CANDLES=1
|
||||||
|
|
||||||
# 02 Ground Truth
|
# 02 Ground Truth
|
||||||
GT_MIN_ORDER_KRW=5000
|
GT_MIN_ORDER_KRW=5000
|
||||||
GT_BUY_PCT_LARGE_LEG=1.0
|
GT_BUY_PCT_LARGE_LEG=1.0
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -1,17 +1,20 @@
|
|||||||
# DeepCoin — WLD MTF 분석·정답·운영
|
# DeepCoin — WLD MTF 분석·정답·운영
|
||||||
|
|
||||||
빗썸 KRW-WLD. **1, 3, 5, 10, 15, 30, 60, 240, 1440분** 봉을 적재하고,
|
빗썸 KRW-WLD. **3, 5, 10, 15, 30, 60, 240, 1440분** 봉을 적재하고 (1분봉은 다운로드 제외, 모니터는 API 최신 1봉만),
|
||||||
Ground Truth·기술적 분석·규칙 매칭·알림·**실거래(선택)**까지 단계별로 관리합니다.
|
Ground Truth·기술적 분석·규칙 매칭·알림·**실거래(선택)**까지 단계별로 관리합니다.
|
||||||
|
|
||||||
## 남은 작업 순서
|
## 남은 작업 순서
|
||||||
|
|
||||||
| 순서 | 단계 | 실행 |
|
| 순서 | 단계 | 상태 | 실행 |
|
||||||
|------|------|------|
|
|------|------|------|------|
|
||||||
| 1 | 시뮬레이션 | `python scripts/04_simulation_report.py` |
|
| 1 | 시뮬레이션 | 완료 (GO) | `python scripts/04_simulation_report.py` |
|
||||||
| 2 | 문서화 | `docs/reference/SIMULATION.md` 등 |
|
| 2 | 문서화 | 완료 | [docs/reference/SIMULATION.md](docs/reference/SIMULATION.md) 등 |
|
||||||
| 3 | 오픈(실거래) | `python scripts/06_execute_live.py` |
|
| 3 | 오픈(실거래 B-1) | **기동** | `python scripts/06_execute_live.py` (`LIVE=1`) |
|
||||||
| 4 | 1~2주 검증 | 실계좌 기록 |
|
| (C) | dry-run·알림 | 선택 병행 | `05_run_monitor.py` |
|
||||||
| 5 | 지속 거래 | 06 상시 |
|
| 4 | 1~2주 검증 | 대기 | `docs/05_ops/live_verification_*.md` |
|
||||||
|
| 5 | 지속 거래 | 대기 | 06 상시 + 월간 재시뮬 |
|
||||||
|
|
||||||
|
운영 배분: **hybrid primary** (= 시뮬 `sim_causal_hybrid`). [LIVE_TRADING.md](docs/reference/LIVE_TRADING.md)
|
||||||
|
|
||||||
## 파이프라인 CLI
|
## 파이프라인 CLI
|
||||||
|
|
||||||
|
|||||||
13
config.py
13
config.py
@@ -8,7 +8,7 @@ import os
|
|||||||
|
|
||||||
from deepcoin.env_loader import load_project_env
|
from deepcoin.env_loader import load_project_env
|
||||||
|
|
||||||
load_project_env()
|
load_project_env(override=True)
|
||||||
|
|
||||||
|
|
||||||
def _getenv(key: str, default: str = "") -> str:
|
def _getenv(key: str, default: str = "") -> str:
|
||||||
@@ -85,11 +85,12 @@ TREND_INTERVAL_1H = _getenv_int("TREND_INTERVAL_1H", "60")
|
|||||||
TREND_INTERVAL_1D = _getenv_int("TREND_INTERVAL_1D", "1440")
|
TREND_INTERVAL_1D = _getenv_int("TREND_INTERVAL_1D", "1440")
|
||||||
|
|
||||||
ALL_INTERVALS: tuple[int, ...] = _parse_int_tuple(
|
ALL_INTERVALS: tuple[int, ...] = _parse_int_tuple(
|
||||||
"ALL_INTERVALS", "1,3,5,10,15,30,60,240,1440"
|
"ALL_INTERVALS", "3,5,10,15,30,60,240,1440"
|
||||||
)
|
)
|
||||||
|
# 1분봉은 시뮬·MTF 분석 미사용. 실시간 모니터는 API로 최신 1봉만 조회.
|
||||||
DOWNLOAD_INTERVALS: tuple[int, ...] = _parse_int_tuple(
|
DOWNLOAD_INTERVALS: tuple[int, ...] = _parse_int_tuple(
|
||||||
"DOWNLOAD_INTERVALS",
|
"DOWNLOAD_INTERVALS",
|
||||||
",".join(str(x) for x in ALL_INTERVALS),
|
"3,5,10,15,30,60,240,1440",
|
||||||
)
|
)
|
||||||
GENERAL_ANALYSIS_INTERVALS: tuple[int, ...] = _parse_int_tuple(
|
GENERAL_ANALYSIS_INTERVALS: tuple[int, ...] = _parse_int_tuple(
|
||||||
"GENERAL_ANALYSIS_INTERVALS", "3,5,10,15,30,60,240,1440"
|
"GENERAL_ANALYSIS_INTERVALS", "3,5,10,15,30,60,240,1440"
|
||||||
@@ -257,6 +258,12 @@ MONITOR_NORM_WINDOW = _getenv_int("MONITOR_NORM_WINDOW", "20")
|
|||||||
MONITOR_TELEGRAM_BATCH_SIZE = _getenv_int("MONITOR_TELEGRAM_BATCH_SIZE", "20")
|
MONITOR_TELEGRAM_BATCH_SIZE = _getenv_int("MONITOR_TELEGRAM_BATCH_SIZE", "20")
|
||||||
# 규칙 알림 참고 금액(매수 시 수량=금액/가격). 매도 시에는 보유 수량 우선.
|
# 규칙 알림 참고 금액(매수 시 수량=금액/가격). 매도 시에는 보유 수량 우선.
|
||||||
MONITOR_ALERT_KRW_AMOUNT = _getenv_int("MONITOR_ALERT_KRW_AMOUNT", "100000")
|
MONITOR_ALERT_KRW_AMOUNT = _getenv_int("MONITOR_ALERT_KRW_AMOUNT", "100000")
|
||||||
|
# 05/06·live_eval API 수집 시 coins.db 증분 INSERT (01_download와 동일 append_data)
|
||||||
|
MONITOR_PERSIST_CANDLES = _getenv("MONITOR_PERSIST_CANDLES", "1").strip().lower() in (
|
||||||
|
"1",
|
||||||
|
"true",
|
||||||
|
"yes",
|
||||||
|
)
|
||||||
|
|
||||||
# --- general_analysis ---
|
# --- general_analysis ---
|
||||||
GA_COL_PREFIX = _getenv("GA_COL_PREFIX", "ga_")
|
GA_COL_PREFIX = _getenv("GA_COL_PREFIX", "ga_")
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ from deepcoin.matching.position_sizing import (
|
|||||||
from deepcoin.paths import resolve_ground_truth_file
|
from deepcoin.paths import resolve_ground_truth_file
|
||||||
from deepcoin.ops.alert_message import build_rule_alert_message
|
from deepcoin.ops.alert_message import build_rule_alert_message
|
||||||
from deepcoin.ops.monitor import Monitor
|
from deepcoin.ops.monitor import Monitor
|
||||||
from deepcoin.paths import LIVE_TRADES_LOG
|
from deepcoin.paths import LIVE_TRADES_LOG, PAPER_FIRES_LOG
|
||||||
|
|
||||||
|
|
||||||
class LiveTrader(Monitor):
|
class LiveTrader(Monitor):
|
||||||
@@ -87,6 +87,35 @@ class LiveTrader(Monitor):
|
|||||||
with LIVE_TRADES_LOG.open("a", encoding="utf-8") as f:
|
with LIVE_TRADES_LOG.open("a", encoding="utf-8") as f:
|
||||||
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
||||||
|
|
||||||
|
def _append_paper_fire(
|
||||||
|
self,
|
||||||
|
hit: dict[str, Any],
|
||||||
|
planned_krw: float,
|
||||||
|
would_trade: bool,
|
||||||
|
skip_reason: str = "",
|
||||||
|
order_log: dict[str, Any] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Phase C dry-run: 모든 발화·스킵 사유·모의 금액을 paper_fires.jsonl에 기록.
|
||||||
|
|
||||||
|
금요일 `07_phase_c_paper_report.py`로 forward 수익률(참고) 집계.
|
||||||
|
"""
|
||||||
|
PAPER_FIRES_LOG.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
row = {
|
||||||
|
"ts": datetime.now().isoformat(timespec="seconds"),
|
||||||
|
"signal_dt": hit.get("dt"),
|
||||||
|
"rule_id": hit.get("rule_id"),
|
||||||
|
"side": hit.get("side"),
|
||||||
|
"close": float(hit.get("close") or 0),
|
||||||
|
"planned_krw": round(float(planned_krw), 0),
|
||||||
|
"would_trade": bool(would_trade),
|
||||||
|
"skip_reason": skip_reason or "",
|
||||||
|
"live_enabled": bool(LIVE_TRADING_ENABLED),
|
||||||
|
"order_message": (order_log or {}).get("message", ""),
|
||||||
|
}
|
||||||
|
with PAPER_FIRES_LOG.open("a", encoding="utf-8") as f:
|
||||||
|
f.write(json.dumps(row, ensure_ascii=False) + "\n")
|
||||||
|
|
||||||
def _can_trade(self, rule_id: str, planned_krw: float | None = None) -> tuple[bool, str]:
|
def _can_trade(self, rule_id: str, planned_krw: float | None = None) -> tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
일·쿨다운·손실 한도 검사.
|
일·쿨다운·손실 한도 검사.
|
||||||
@@ -283,6 +312,9 @@ class LiveTrader(Monitor):
|
|||||||
if hit["side"] == "buy" and hit["rule_id"] not in self._approved_rules:
|
if hit["side"] == "buy" and hit["rule_id"] not in self._approved_rules:
|
||||||
print(f" [{hit['side']}] {rid} @ {hit['dt']}")
|
print(f" [{hit['side']}] {rid} @ {hit['dt']}")
|
||||||
print(" skip: EV/WF 미통과 규칙")
|
print(" skip: EV/WF 미통과 규칙")
|
||||||
|
self._append_paper_fire(
|
||||||
|
hit, 0.0, False, "EV/WF 미통과 규칙"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
planned = (
|
planned = (
|
||||||
self._resolve_buy_amount_krw(hit)
|
self._resolve_buy_amount_krw(hit)
|
||||||
@@ -293,11 +325,14 @@ class LiveTrader(Monitor):
|
|||||||
print(f" [{hit['side']}] {rid} @ {hit['dt']}")
|
print(f" [{hit['side']}] {rid} @ {hit['dt']}")
|
||||||
if not ok:
|
if not ok:
|
||||||
print(f" skip: {reason}")
|
print(f" skip: {reason}")
|
||||||
|
self._append_paper_fire(hit, planned, False, reason)
|
||||||
continue
|
continue
|
||||||
if hit["side"] == "buy" and planned <= 0:
|
if hit["side"] == "buy" and planned <= 0:
|
||||||
print(" skip: 매수금액 0")
|
print(" skip: 매수금액 0")
|
||||||
|
self._append_paper_fire(hit, 0.0, False, "매수금액 0")
|
||||||
continue
|
continue
|
||||||
log = self._execute_order(hit)
|
log = self._execute_order(hit)
|
||||||
|
self._append_paper_fire(hit, planned, True, "", log)
|
||||||
self._append_log(log)
|
self._append_log(log)
|
||||||
print(f" order: {log['message']} ok={log['ok']}")
|
print(f" order: {log['message']} ok={log['ok']}")
|
||||||
msg = build_rule_alert_message(hit, balances)
|
msg = build_rule_alert_message(hit, balances)
|
||||||
|
|||||||
@@ -415,18 +415,56 @@ class Monitor(HTS):
|
|||||||
bars_per_day = max((24 * 60) // max(interval, 1), 1)
|
bars_per_day = max((24 * 60) // max(interval, 1), 1)
|
||||||
return bars_per_day * lookback_days + DB_ROW_WARMUP_BARS
|
return bars_per_day * lookback_days + DB_ROW_WARMUP_BARS
|
||||||
|
|
||||||
def get_coin_saved_data(
|
def persist_api_candles_to_db(
|
||||||
self,
|
self,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
interval: int,
|
interval: int,
|
||||||
data: pd.DataFrame,
|
data: pd.DataFrame,
|
||||||
db_path: str = DB_PATH,
|
db_path: str = DB_PATH,
|
||||||
|
) -> tuple[int, int]:
|
||||||
|
"""
|
||||||
|
API로 받은 봉을 coins.db에 증분 INSERT합니다 (01_download.append_data와 동일).
|
||||||
|
|
||||||
|
dry-run·05·06·live_eval이 load_frames_from_db 할 때마다 최신 봉이 쌓입니다.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(추가 행 수, 스킵 행 수)
|
||||||
|
"""
|
||||||
|
if not MONITOR_PERSIST_CANDLES or data is None or data.empty:
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
|
from deepcoin.data.downloader import (
|
||||||
|
append_data,
|
||||||
|
get_last_timestamp,
|
||||||
|
months_for_interval,
|
||||||
|
prune_before_cutoff,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not isinstance(data.index, pd.DatetimeIndex):
|
||||||
|
data = data.copy()
|
||||||
|
data.index = pd.to_datetime(data.index)
|
||||||
|
data = data.sort_index()
|
||||||
|
|
||||||
|
last_ts = get_last_timestamp(symbol, interval, db_path=db_path)
|
||||||
|
inserted, skipped = append_data(
|
||||||
|
symbol, interval, data, last_ts=last_ts, db_path=db_path
|
||||||
|
)
|
||||||
|
if inserted > 0:
|
||||||
|
months = months_for_interval(interval, DOWNLOAD_MONTHS)
|
||||||
|
prune_before_cutoff(symbol, interval, months, db_path=db_path)
|
||||||
|
return inserted, skipped
|
||||||
|
|
||||||
|
def read_candles_from_db(
|
||||||
|
self,
|
||||||
|
symbol: str,
|
||||||
|
interval: int,
|
||||||
|
db_path: str = DB_PATH,
|
||||||
max_rows: int = DB_READ_LIMIT_DEFAULT,
|
max_rows: int = DB_READ_LIMIT_DEFAULT,
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
coins.db에서 저장된 봉을 읽고, API로 받은 최신 봉을 DB에 반영합니다.
|
coins.db에서 저장된 봉을 읽습니다.
|
||||||
|
|
||||||
scripts/01_download.py로 미리 적재해 두면 장기 MA 계산에 유리합니다.
|
scripts/01_download.py 또는 persist_api_candles_to_db로 적재된 데이터.
|
||||||
"""
|
"""
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(db_path)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -440,33 +478,6 @@ class Monitor(HTS):
|
|||||||
f"CREATE INDEX IF NOT EXISTS {table_name}_idx ON {table_name}(CODE, ymdhms)"
|
f"CREATE INDEX IF NOT EXISTS {table_name}_idx ON {table_name}(CODE, ymdhms)"
|
||||||
)
|
)
|
||||||
|
|
||||||
for i in range(1, len(data)):
|
|
||||||
ymdhms = data["datetime"].iloc[-i].strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
cursor.execute(
|
|
||||||
f"SELECT 1 FROM {table_name} WHERE CODE = ? AND ymdhms = ?",
|
|
||||||
(symbol, ymdhms),
|
|
||||||
)
|
|
||||||
if not cursor.fetchone():
|
|
||||||
cursor.execute(
|
|
||||||
f"INSERT INTO {table_name} "
|
|
||||||
"(CODE, NAME, ymdhms, ymd, hms, Close, Open, High, Low, Volume) "
|
|
||||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
(
|
|
||||||
symbol,
|
|
||||||
KR_COINS[symbol],
|
|
||||||
ymdhms,
|
|
||||||
data["datetime"].iloc[-i].strftime("%Y%m%d"),
|
|
||||||
data["datetime"].iloc[-i].strftime("%H%M%S"),
|
|
||||||
data["Close"].iloc[-i],
|
|
||||||
data["Open"].iloc[-i],
|
|
||||||
data["High"].iloc[-i],
|
|
||||||
data["Low"].iloc[-i],
|
|
||||||
data["Volume"].iloc[-i],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
f"SELECT Open, Close, High, Low, Volume, ymdhms AS datetime "
|
f"SELECT Open, Close, High, Low, Volume, ymdhms AS datetime "
|
||||||
f"FROM (SELECT Open, Close, High, Low, Volume, ymdhms "
|
f"FROM (SELECT Open, Close, High, Low, Volume, ymdhms "
|
||||||
@@ -491,26 +502,42 @@ class Monitor(HTS):
|
|||||||
df["datetime"] = df.index
|
df["datetime"] = df.index
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
def get_coin_saved_data(
|
||||||
|
self,
|
||||||
|
symbol: str,
|
||||||
|
interval: int,
|
||||||
|
data: pd.DataFrame,
|
||||||
|
db_path: str = DB_PATH,
|
||||||
|
max_rows: int = DB_READ_LIMIT_DEFAULT,
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""하위 호환: API 봉 저장 후 DB에서 읽기."""
|
||||||
|
self.persist_api_candles_to_db(symbol, interval, data, db_path=db_path)
|
||||||
|
return self.read_candles_from_db(
|
||||||
|
symbol, interval, db_path=db_path, max_rows=max_rows
|
||||||
|
)
|
||||||
|
|
||||||
def get_coin_some_data(
|
def get_coin_some_data(
|
||||||
self, symbol: str, interval: int, db_max_rows: int | None = None
|
self, symbol: str, interval: int, db_max_rows: int | None = None
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
WLD 시세: API 최신 봉 + coins.db 과거 봉 + 1분봉 최신 1개를 합칩니다.
|
WLD 시세: API 최신 봉 + coins.db 과거 봉 + 1분봉 최신 1개를 합칩니다.
|
||||||
|
|
||||||
DB가 비어 있으면 API·1분봉만 사용합니다. 과거 적재는 scripts/01_download.py 실행.
|
MONITOR_PERSIST_CANDLES=1 이면 API 청크를 즉시 coins.db에 INSERT합니다.
|
||||||
"""
|
"""
|
||||||
data = self.get_coin_data(symbol, interval)
|
data = self.get_coin_data(symbol, interval)
|
||||||
if data is None or data.empty:
|
if data is None or data.empty:
|
||||||
return pd.DataFrame()
|
return pd.DataFrame()
|
||||||
|
|
||||||
|
self.persist_api_candles_to_db(symbol, interval, data)
|
||||||
|
|
||||||
data_1 = self.get_coin_data(symbol, interval=1)
|
data_1 = self.get_coin_data(symbol, interval=1)
|
||||||
if data_1 is not None and not data_1.empty:
|
if data_1 is not None and not data_1.empty:
|
||||||
data_1 = data_1.copy()
|
data_1 = data_1.copy()
|
||||||
data_1.at[data_1.index[-1], "Volume"] = data_1["Volume"].iloc[-1] * 60
|
data_1.at[data_1.index[-1], "Volume"] = data_1["Volume"].iloc[-1] * 60
|
||||||
|
|
||||||
row_limit = DB_READ_LIMIT_DEFAULT if db_max_rows is None else int(db_max_rows)
|
row_limit = DB_READ_LIMIT_DEFAULT if db_max_rows is None else int(db_max_rows)
|
||||||
saved_data = self.get_coin_saved_data(
|
saved_data = self.read_candles_from_db(
|
||||||
symbol, interval, data, max_rows=row_limit
|
symbol, interval, max_rows=row_limit
|
||||||
)
|
)
|
||||||
parts = [data]
|
parts = [data]
|
||||||
if saved_data is not None and not saved_data.empty:
|
if saved_data is not None and not saved_data.empty:
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ MATCHING_GT_COMPARISON_JSON = DOCS_MATCHING / "gt_comparison_report.json"
|
|||||||
MATCHING_GT_COMPARISON_HTML = DOCS_MATCHING / "gt_comparison_report.html"
|
MATCHING_GT_COMPARISON_HTML = DOCS_MATCHING / "gt_comparison_report.html"
|
||||||
|
|
||||||
LIVE_TRADES_LOG = OPS_STATE_DIR / "live_trades.jsonl"
|
LIVE_TRADES_LOG = OPS_STATE_DIR / "live_trades.jsonl"
|
||||||
|
PAPER_FIRES_LOG = OPS_STATE_DIR / "paper_fires.jsonl"
|
||||||
|
PAPER_WEEKLY_REPORT_JSON = DOCS_OPS / "phase_c_paper_report.json"
|
||||||
|
|
||||||
CHART_BB_HTML = DOCS_CHARTS / "wld_bb_chart.html"
|
CHART_BB_HTML = DOCS_CHARTS / "wld_bb_chart.html"
|
||||||
CHART_TRUTH_HTML = DOCS_GROUND_TRUTH / "wld_ground_truth_chart.html"
|
CHART_TRUTH_HTML = DOCS_GROUND_TRUTH / "wld_ground_truth_chart.html"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -6,75 +6,75 @@
|
|||||||
"walk_forward": [
|
"walk_forward": [
|
||||||
{
|
{
|
||||||
"month": "2025-06",
|
"month": "2025-06",
|
||||||
"pnl_pct": 10.39,
|
"pnl_pct": 12.78,
|
||||||
"start_asset_krw": 1000000.0,
|
"start_asset_krw": 1000000.0,
|
||||||
"end_asset_krw": 1103874.0
|
"end_asset_krw": 1127825.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-07",
|
"month": "2025-07",
|
||||||
"pnl_pct": 60.89,
|
"pnl_pct": 60.89,
|
||||||
"start_asset_krw": 1103874.0,
|
"start_asset_krw": 1127825.0,
|
||||||
"end_asset_krw": 1776051.0
|
"end_asset_krw": 1814584.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-08",
|
"month": "2025-08",
|
||||||
"pnl_pct": 22.9,
|
"pnl_pct": 22.9,
|
||||||
"start_asset_krw": 1776051.0,
|
"start_asset_krw": 1814584.0,
|
||||||
"end_asset_krw": 2182728.0
|
"end_asset_krw": 2230083.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-09",
|
"month": "2025-09",
|
||||||
"pnl_pct": 57.63,
|
"pnl_pct": 57.63,
|
||||||
"start_asset_krw": 2182728.0,
|
"start_asset_krw": 2230083.0,
|
||||||
"end_asset_krw": 3440648.0
|
"end_asset_krw": 3515283.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-10",
|
"month": "2025-10",
|
||||||
"pnl_pct": 9.29,
|
"pnl_pct": 9.29,
|
||||||
"start_asset_krw": 3440648.0,
|
"start_asset_krw": 3515283.0,
|
||||||
"end_asset_krw": 3760442.0
|
"end_asset_krw": 3842010.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-11",
|
"month": "2025-11",
|
||||||
"pnl_pct": 11.24,
|
"pnl_pct": 11.24,
|
||||||
"start_asset_krw": 3760442.0,
|
"start_asset_krw": 3842010.0,
|
||||||
"end_asset_krw": 4183047.0
|
"end_asset_krw": 4273771.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-12",
|
"month": "2025-12",
|
||||||
"pnl_pct": -0.87,
|
"pnl_pct": -0.87,
|
||||||
"start_asset_krw": 4183047.0,
|
"start_asset_krw": 4273771.0,
|
||||||
"end_asset_krw": 4146498.0
|
"end_asset_krw": 4236421.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-01",
|
"month": "2026-01",
|
||||||
"pnl_pct": 33.77,
|
"pnl_pct": 33.77,
|
||||||
"start_asset_krw": 4146498.0,
|
"start_asset_krw": 4236421.0,
|
||||||
"end_asset_krw": 5546639.0
|
"end_asset_krw": 5666889.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-02",
|
"month": "2026-02",
|
||||||
"pnl_pct": 15.61,
|
"pnl_pct": 15.61,
|
||||||
"start_asset_krw": 5546639.0,
|
"start_asset_krw": 5666889.0,
|
||||||
"end_asset_krw": 6412274.0
|
"end_asset_krw": 6551242.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-03",
|
"month": "2026-03",
|
||||||
"pnl_pct": 9.12,
|
"pnl_pct": 9.12,
|
||||||
"start_asset_krw": 6412274.0,
|
"start_asset_krw": 6551242.0,
|
||||||
"end_asset_krw": 6996857.0
|
"end_asset_krw": 7148390.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-04",
|
"month": "2026-04",
|
||||||
"pnl_pct": 22.86,
|
"pnl_pct": 22.85,
|
||||||
"start_asset_krw": 6996857.0,
|
"start_asset_krw": 7148390.0,
|
||||||
"end_asset_krw": 8596129.0
|
"end_asset_krw": 8782116.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-05",
|
"month": "2026-05",
|
||||||
"pnl_pct": 47.58,
|
"pnl_pct": 47.57,
|
||||||
"start_asset_krw": 8596129.0,
|
"start_asset_krw": 8782116.0,
|
||||||
"end_asset_krw": 12686054.0
|
"end_asset_krw": 12959660.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"walk_forward_summary": {
|
"walk_forward_summary": {
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hybrid_holdout_pnl",
|
"name": "hybrid_holdout_pnl",
|
||||||
"pass": true,
|
"pass": true,
|
||||||
"value": 62.36
|
"value": 62.35
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hybrid_max_mdd",
|
"name": "hybrid_max_mdd",
|
||||||
@@ -302,19 +302,19 @@
|
|||||||
{
|
{
|
||||||
"name": "hybrid_fee_stress_pnl",
|
"name": "hybrid_fee_stress_pnl",
|
||||||
"pass": true,
|
"pass": true,
|
||||||
"value": 952.77
|
"value": 975.74
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "option_c_target_300pct",
|
"name": "option_c_target_300pct",
|
||||||
"pass": true,
|
"pass": true,
|
||||||
"value": 1120.97,
|
"value": 1147.3,
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"go_no_go_option_c_phase2": {
|
"go_no_go_option_c_phase2": {
|
||||||
"go": true,
|
"go": true,
|
||||||
"gt_capture_ratio": 0.2612,
|
"gt_capture_ratio": 0.2674,
|
||||||
"targets": {
|
"targets": {
|
||||||
"phase2_pnl_pct": 1000.0,
|
"phase2_pnl_pct": 1000.0,
|
||||||
"min_gt_capture": 0.23,
|
"min_gt_capture": 0.23,
|
||||||
@@ -328,17 +328,17 @@
|
|||||||
{
|
{
|
||||||
"name": "full_pnl_1000pct",
|
"name": "full_pnl_1000pct",
|
||||||
"pass": true,
|
"pass": true,
|
||||||
"value": 1120.97
|
"value": 1147.3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "gt_capture_23pct",
|
"name": "gt_capture_23pct",
|
||||||
"pass": true,
|
"pass": true,
|
||||||
"value": 0.2612
|
"value": 0.2674
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "holdout_pnl_positive",
|
"name": "holdout_pnl_positive",
|
||||||
"pass": true,
|
"pass": true,
|
||||||
"value": 62.36
|
"value": 62.35
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "max_mdd",
|
"name": "max_mdd",
|
||||||
@@ -348,13 +348,13 @@
|
|||||||
{
|
{
|
||||||
"name": "fee_stress_ratio",
|
"name": "fee_stress_ratio",
|
||||||
"pass": true,
|
"pass": true,
|
||||||
"value": 952.77,
|
"value": 975.74,
|
||||||
"threshold": 850.0
|
"threshold": 850.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "slippage_stress_positive",
|
"name": "slippage_stress_positive",
|
||||||
"pass": true,
|
"pass": true,
|
||||||
"value": 27.66,
|
"value": 31.58,
|
||||||
"note": "체결가 슬리피지 반영 후에도 흑자"
|
"note": "체결가 슬리피지 반영 후에도 흑자"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -434,19 +434,19 @@
|
|||||||
},
|
},
|
||||||
"sim_causal_gt": {
|
"sim_causal_gt": {
|
||||||
"initial_cash_krw": 1000000,
|
"initial_cash_krw": 1000000,
|
||||||
"final_asset_krw": 1146560.0,
|
"final_asset_krw": 1147944.0,
|
||||||
"pnl_krw": 146560.0,
|
"pnl_krw": 147944.0,
|
||||||
"pnl_pct": 14.66,
|
"pnl_pct": 14.79,
|
||||||
"total_fees_krw": 2101.0,
|
"total_fees_krw": 2025.0,
|
||||||
"cash_krw": 1146560.0,
|
"cash_krw": 1147944.0,
|
||||||
"holding_qty": 0.0,
|
"holding_qty": 0.0,
|
||||||
"holding_value_krw": 0.0,
|
"holding_value_krw": 0.0,
|
||||||
"mark_price": 487.0,
|
"mark_price": 487.0,
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"trade_count": 134,
|
"trade_count": 124,
|
||||||
"max_drawdown_pct": 0.96,
|
"max_drawdown_pct": 0.96,
|
||||||
"peak_asset_krw": 1117188.0,
|
"peak_asset_krw": 1118535.0,
|
||||||
"trough_asset_krw": 1106457.0,
|
"trough_asset_krw": 1107793.0,
|
||||||
"leg_count": 17,
|
"leg_count": 17,
|
||||||
"sizing_mode": "causal_gt_leg_engine",
|
"sizing_mode": "causal_gt_leg_engine",
|
||||||
"sizing_note": "인과 GT leg: split_buy + peak_sell, causal tier 복리 (미래 미사용)",
|
"sizing_note": "인과 GT leg: split_buy + peak_sell, causal tier 복리 (미래 미사용)",
|
||||||
@@ -461,46 +461,46 @@
|
|||||||
"use_local_trough": false
|
"use_local_trough": false
|
||||||
},
|
},
|
||||||
"alloc_stats": {
|
"alloc_stats": {
|
||||||
"buy_executed": 101,
|
"buy_executed": 91,
|
||||||
"buy_skipped": 0,
|
"buy_skipped": 0,
|
||||||
"sell_executed": 33,
|
"sell_executed": 33,
|
||||||
"sell_skipped": 0,
|
"sell_skipped": 0,
|
||||||
"buy_total_krw": 2026492.0,
|
"buy_total_krw": 1950020.0,
|
||||||
"large_leg_count": 0,
|
"large_leg_count": 0,
|
||||||
"large_tier_buy_count": 0,
|
"large_tier_buy_count": 0,
|
||||||
"buy_amount_avg_krw": 20064.0,
|
"buy_amount_avg_krw": 21429.0,
|
||||||
"buy_amount_min_krw": 5000,
|
"buy_amount_min_krw": 5000,
|
||||||
"buy_amount_max_krw": 57147.0
|
"buy_amount_max_krw": 57216.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sim_causal_hybrid": {
|
"sim_causal_hybrid": {
|
||||||
"initial_cash_krw": 1000000,
|
"initial_cash_krw": 1000000,
|
||||||
"final_asset_krw": 12209700.0,
|
"final_asset_krw": 12473032.0,
|
||||||
"pnl_krw": 11209700.0,
|
"pnl_krw": 11473032.0,
|
||||||
"pnl_pct": 1120.97,
|
"pnl_pct": 1147.3,
|
||||||
"total_fees_krw": 695439.0,
|
"total_fees_krw": 710297.0,
|
||||||
"cash_krw": 0.0,
|
"cash_krw": 0.0,
|
||||||
"holding_qty": 25071.252612,
|
"holding_qty": 25611.975633,
|
||||||
"holding_value_krw": 12209700.0,
|
"holding_value_krw": 12473032.0,
|
||||||
"mark_price": 487.0,
|
"mark_price": 487.0,
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"trade_count": 5225,
|
"trade_count": 5225,
|
||||||
"max_drawdown_pct": 19.22,
|
"max_drawdown_pct": 19.22,
|
||||||
"peak_asset_krw": 1149472.0,
|
"peak_asset_krw": 1174413.0,
|
||||||
"trough_asset_krw": 928596.0,
|
"trough_asset_krw": 948744.0,
|
||||||
"sizing_mode": "monitor_dd_tier",
|
"sizing_mode": "monitor_dd_tier",
|
||||||
"sizing_note": "monitor buy+sell + drawdown·past-leg tier (미래 미사용)",
|
"sizing_note": "monitor buy+sell + drawdown·past-leg tier (미래 미사용)",
|
||||||
"alloc_stats": {
|
"alloc_stats": {
|
||||||
"buy_executed": 1608,
|
"buy_executed": 1632,
|
||||||
"buy_skipped": 679,
|
"buy_skipped": 655,
|
||||||
"sell_executed": 2938,
|
"sell_executed": 2938,
|
||||||
"sell_skipped": 0,
|
"sell_skipped": 0,
|
||||||
"buy_total_krw": 695591657.0,
|
"buy_total_krw": 710442254.0,
|
||||||
"large_leg_count": 1598,
|
"large_leg_count": 1535,
|
||||||
"large_tier_buy_count": 1598,
|
"large_tier_buy_count": 1535,
|
||||||
"buy_amount_avg_krw": 432582.0,
|
"buy_amount_avg_krw": 435320.0,
|
||||||
"buy_amount_min_krw": 814.0,
|
"buy_amount_min_krw": 828.0,
|
||||||
"buy_amount_max_krw": 8437092.0
|
"buy_amount_max_krw": 8620153.0
|
||||||
},
|
},
|
||||||
"input_fires": 5225
|
"input_fires": 5225
|
||||||
},
|
},
|
||||||
@@ -510,32 +510,32 @@
|
|||||||
},
|
},
|
||||||
"sim_tier_enhanced": {
|
"sim_tier_enhanced": {
|
||||||
"initial_cash_krw": 1000000,
|
"initial_cash_krw": 1000000,
|
||||||
"final_asset_krw": 485326.0,
|
"final_asset_krw": 487437.0,
|
||||||
"pnl_krw": -514674.0,
|
"pnl_krw": -512563.0,
|
||||||
"pnl_pct": -51.47,
|
"pnl_pct": -51.26,
|
||||||
"total_fees_krw": 111808.0,
|
"total_fees_krw": 107068.0,
|
||||||
"cash_krw": 0.0,
|
"cash_krw": 1.0,
|
||||||
"holding_qty": 996.562036,
|
"holding_qty": 1000.895401,
|
||||||
"holding_value_krw": 485326.0,
|
"holding_value_krw": 487436.0,
|
||||||
"mark_price": 487.0,
|
"mark_price": 487.0,
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"trade_count": 5225,
|
"trade_count": 5225,
|
||||||
"max_drawdown_pct": 74.08,
|
"max_drawdown_pct": 74.08,
|
||||||
"peak_asset_krw": 1534299.0,
|
"peak_asset_krw": 1540984.0,
|
||||||
"trough_asset_krw": 397703.0,
|
"trough_asset_krw": 399432.0,
|
||||||
"sizing_mode": "monitor_tier_enhanced",
|
"sizing_mode": "monitor_tier_enhanced",
|
||||||
"sizing_note": "monitor buy+sell + past-leg·drawdown tier + conviction (미래 미사용)",
|
"sizing_note": "monitor buy+sell + past-leg·drawdown tier + conviction (미래 미사용)",
|
||||||
"alloc_stats": {
|
"alloc_stats": {
|
||||||
"buy_executed": 183,
|
"buy_executed": 279,
|
||||||
"buy_skipped": 2104,
|
"buy_skipped": 2008,
|
||||||
"sell_executed": 2938,
|
"sell_executed": 2938,
|
||||||
"sell_skipped": 0,
|
"sell_skipped": 0,
|
||||||
"buy_total_krw": 112251943.0,
|
"buy_total_krw": 107514607.0,
|
||||||
"large_leg_count": 144,
|
"large_leg_count": 136,
|
||||||
"large_tier_buy_count": 144,
|
"large_tier_buy_count": 136,
|
||||||
"buy_amount_avg_krw": 613399.0,
|
"buy_amount_avg_krw": 385357.0,
|
||||||
"buy_amount_min_krw": 5000,
|
"buy_amount_min_krw": 5000,
|
||||||
"buy_amount_max_krw": 1533530.0
|
"buy_amount_max_krw": 1540213.0
|
||||||
},
|
},
|
||||||
"input_fires": 5225
|
"input_fires": 5225
|
||||||
},
|
},
|
||||||
@@ -548,156 +548,156 @@
|
|||||||
"note": "전기간 복리(causal tier) 후 holdout 구간 자산 증감"
|
"note": "전기간 복리(causal tier) 후 holdout 구간 자산 증감"
|
||||||
},
|
},
|
||||||
"sim_hybrid_holdout": {
|
"sim_hybrid_holdout": {
|
||||||
"initial_asset_krw": 7813594.0,
|
"initial_asset_krw": 7982769.0,
|
||||||
"final_asset_krw": 12686054.0,
|
"final_asset_krw": 12959660.0,
|
||||||
"pnl_krw": 4872460.0,
|
"pnl_krw": 4976891.0,
|
||||||
"pnl_pct": 62.36,
|
"pnl_pct": 62.35,
|
||||||
"trade_count": 845,
|
"trade_count": 845,
|
||||||
"note": "전기간 복리(hybrid DD tier) 후 holdout 구간 자산 증감"
|
"note": "전기간 복리(hybrid DD tier) 후 holdout 구간 자산 증감"
|
||||||
},
|
},
|
||||||
"sim_hybrid_fee_stress": {
|
"sim_hybrid_fee_stress": {
|
||||||
"initial_cash_krw": 1000000,
|
"initial_cash_krw": 1000000,
|
||||||
"final_asset_krw": 10527724.0,
|
"final_asset_krw": 10757384.0,
|
||||||
"pnl_krw": 9527724.0,
|
"pnl_krw": 9757384.0,
|
||||||
"pnl_pct": 952.77,
|
"pnl_pct": 975.74,
|
||||||
"total_fees_krw": 1261948.0,
|
"total_fees_krw": 1289111.0,
|
||||||
"cash_krw": -0.0,
|
"cash_krw": -0.0,
|
||||||
"holding_qty": 21617.503526,
|
"holding_qty": 22089.084224,
|
||||||
"holding_value_krw": 10527724.0,
|
"holding_value_krw": 10757384.0,
|
||||||
"mark_price": 487.0,
|
"mark_price": 487.0,
|
||||||
"fee_rate": 0.001,
|
"fee_rate": 0.001,
|
||||||
"trade_count": 5225,
|
"trade_count": 5225,
|
||||||
"max_drawdown_pct": 19.37,
|
"max_drawdown_pct": 16.02,
|
||||||
"peak_asset_krw": 4363286.0,
|
"peak_asset_krw": 7104706.0,
|
||||||
"trough_asset_krw": 3518190.0
|
"trough_asset_krw": 5966238.0
|
||||||
},
|
},
|
||||||
"sim_hybrid_slippage_stress": {
|
"sim_hybrid_slippage_stress": {
|
||||||
"initial_cash_krw": 1000000,
|
"initial_cash_krw": 1000000,
|
||||||
"final_asset_krw": 1276559.0,
|
"final_asset_krw": 1315755.0,
|
||||||
"pnl_krw": 276559.0,
|
"pnl_krw": 315755.0,
|
||||||
"pnl_pct": 27.66,
|
"pnl_pct": 31.58,
|
||||||
"total_fees_krw": 695439.0,
|
"total_fees_krw": 710297.0,
|
||||||
"cash_krw": 0.0,
|
"cash_krw": -0.0,
|
||||||
"holding_qty": 2621.271075,
|
"holding_qty": 2701.755603,
|
||||||
"holding_value_krw": 1276559.0,
|
"holding_value_krw": 1315755.0,
|
||||||
"mark_price": 487.0,
|
"mark_price": 487.0,
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"trade_count": 5225,
|
"trade_count": 5225,
|
||||||
"max_drawdown_pct": 59.37,
|
"max_drawdown_pct": 59.08,
|
||||||
"peak_asset_krw": 3404596.0,
|
"peak_asset_krw": 3483599.0,
|
||||||
"trough_asset_krw": 1383204.0,
|
"trough_asset_krw": 1425625.0,
|
||||||
"slippage_pct": 0.05,
|
"slippage_pct": 0.05,
|
||||||
"sizing_mode": "hybrid_slippage_stress"
|
"sizing_mode": "hybrid_slippage_stress"
|
||||||
},
|
},
|
||||||
"hybrid_portfolio_walk_forward": [
|
"hybrid_portfolio_walk_forward": [
|
||||||
{
|
{
|
||||||
"month": "2025-06",
|
"month": "2025-06",
|
||||||
"pnl_pct": 10.39,
|
"pnl_pct": 12.78,
|
||||||
"start_asset_krw": 1000000.0,
|
"start_asset_krw": 1000000.0,
|
||||||
"end_asset_krw": 1103874.0
|
"end_asset_krw": 1127825.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-07",
|
"month": "2025-07",
|
||||||
"pnl_pct": 60.89,
|
"pnl_pct": 60.89,
|
||||||
"start_asset_krw": 1103874.0,
|
"start_asset_krw": 1127825.0,
|
||||||
"end_asset_krw": 1776051.0
|
"end_asset_krw": 1814584.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-08",
|
"month": "2025-08",
|
||||||
"pnl_pct": 22.9,
|
"pnl_pct": 22.9,
|
||||||
"start_asset_krw": 1776051.0,
|
"start_asset_krw": 1814584.0,
|
||||||
"end_asset_krw": 2182728.0
|
"end_asset_krw": 2230083.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-09",
|
"month": "2025-09",
|
||||||
"pnl_pct": 57.63,
|
"pnl_pct": 57.63,
|
||||||
"start_asset_krw": 2182728.0,
|
"start_asset_krw": 2230083.0,
|
||||||
"end_asset_krw": 3440648.0
|
"end_asset_krw": 3515283.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-10",
|
"month": "2025-10",
|
||||||
"pnl_pct": 9.29,
|
"pnl_pct": 9.29,
|
||||||
"start_asset_krw": 3440648.0,
|
"start_asset_krw": 3515283.0,
|
||||||
"end_asset_krw": 3760442.0
|
"end_asset_krw": 3842010.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-11",
|
"month": "2025-11",
|
||||||
"pnl_pct": 11.24,
|
"pnl_pct": 11.24,
|
||||||
"start_asset_krw": 3760442.0,
|
"start_asset_krw": 3842010.0,
|
||||||
"end_asset_krw": 4183047.0
|
"end_asset_krw": 4273771.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2025-12",
|
"month": "2025-12",
|
||||||
"pnl_pct": -0.87,
|
"pnl_pct": -0.87,
|
||||||
"start_asset_krw": 4183047.0,
|
"start_asset_krw": 4273771.0,
|
||||||
"end_asset_krw": 4146498.0
|
"end_asset_krw": 4236421.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-01",
|
"month": "2026-01",
|
||||||
"pnl_pct": 33.77,
|
"pnl_pct": 33.77,
|
||||||
"start_asset_krw": 4146498.0,
|
"start_asset_krw": 4236421.0,
|
||||||
"end_asset_krw": 5546639.0
|
"end_asset_krw": 5666889.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-02",
|
"month": "2026-02",
|
||||||
"pnl_pct": 15.61,
|
"pnl_pct": 15.61,
|
||||||
"start_asset_krw": 5546639.0,
|
"start_asset_krw": 5666889.0,
|
||||||
"end_asset_krw": 6412274.0
|
"end_asset_krw": 6551242.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-03",
|
"month": "2026-03",
|
||||||
"pnl_pct": 9.12,
|
"pnl_pct": 9.12,
|
||||||
"start_asset_krw": 6412274.0,
|
"start_asset_krw": 6551242.0,
|
||||||
"end_asset_krw": 6996857.0
|
"end_asset_krw": 7148390.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-04",
|
"month": "2026-04",
|
||||||
"pnl_pct": 22.86,
|
"pnl_pct": 22.85,
|
||||||
"start_asset_krw": 6996857.0,
|
"start_asset_krw": 7148390.0,
|
||||||
"end_asset_krw": 8596129.0
|
"end_asset_krw": 8782116.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "2026-05",
|
"month": "2026-05",
|
||||||
"pnl_pct": 47.58,
|
"pnl_pct": 47.57,
|
||||||
"start_asset_krw": 8596129.0,
|
"start_asset_krw": 8782116.0,
|
||||||
"end_asset_krw": 12686054.0
|
"end_asset_krw": 12959660.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hybrid_portfolio_wf_summary": {
|
"hybrid_portfolio_wf_summary": {
|
||||||
"months": 12,
|
"months": 12,
|
||||||
"positive_months": 11,
|
"positive_months": 11,
|
||||||
"positive_ratio": 0.9167,
|
"positive_ratio": 0.9167,
|
||||||
"mean_pnl_pct": 25.03
|
"mean_pnl_pct": 25.23
|
||||||
},
|
},
|
||||||
"primary_sizing": "hybrid",
|
"primary_sizing": "hybrid",
|
||||||
"sim_primary": {
|
"sim_primary": {
|
||||||
"initial_cash_krw": 1000000,
|
"initial_cash_krw": 1000000,
|
||||||
"final_asset_krw": 12209700.0,
|
"final_asset_krw": 12473032.0,
|
||||||
"pnl_krw": 11209700.0,
|
"pnl_krw": 11473032.0,
|
||||||
"pnl_pct": 1120.97,
|
"pnl_pct": 1147.3,
|
||||||
"total_fees_krw": 695439.0,
|
"total_fees_krw": 710297.0,
|
||||||
"cash_krw": 0.0,
|
"cash_krw": 0.0,
|
||||||
"holding_qty": 25071.252612,
|
"holding_qty": 25611.975633,
|
||||||
"holding_value_krw": 12209700.0,
|
"holding_value_krw": 12473032.0,
|
||||||
"mark_price": 487.0,
|
"mark_price": 487.0,
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"trade_count": 5225,
|
"trade_count": 5225,
|
||||||
"max_drawdown_pct": 19.22,
|
"max_drawdown_pct": 19.22,
|
||||||
"peak_asset_krw": 1149472.0,
|
"peak_asset_krw": 1174413.0,
|
||||||
"trough_asset_krw": 928596.0,
|
"trough_asset_krw": 948744.0,
|
||||||
"sizing_mode": "primary_hybrid_dd_tier",
|
"sizing_mode": "primary_hybrid_dd_tier",
|
||||||
"sizing_note": "권장: monitor + past-leg·drawdown tier (검증 통과, 미래 미사용)",
|
"sizing_note": "권장: monitor + past-leg·drawdown tier (검증 통과, 미래 미사용)",
|
||||||
"alloc_stats": {
|
"alloc_stats": {
|
||||||
"buy_executed": 1608,
|
"buy_executed": 1632,
|
||||||
"buy_skipped": 679,
|
"buy_skipped": 655,
|
||||||
"sell_executed": 2938,
|
"sell_executed": 2938,
|
||||||
"sell_skipped": 0,
|
"sell_skipped": 0,
|
||||||
"buy_total_krw": 695591657.0,
|
"buy_total_krw": 710442254.0,
|
||||||
"large_leg_count": 1598,
|
"large_leg_count": 1535,
|
||||||
"large_tier_buy_count": 1598,
|
"large_tier_buy_count": 1535,
|
||||||
"buy_amount_avg_krw": 432582.0,
|
"buy_amount_avg_krw": 435320.0,
|
||||||
"buy_amount_min_krw": 814.0,
|
"buy_amount_min_krw": 828.0,
|
||||||
"buy_amount_max_krw": 8437092.0
|
"buy_amount_max_krw": 8620153.0
|
||||||
},
|
},
|
||||||
"input_fires": 5225
|
"input_fires": 5225
|
||||||
},
|
},
|
||||||
@@ -706,11 +706,11 @@
|
|||||||
"sim_sized_pnl_pct": 74.69,
|
"sim_sized_pnl_pct": 74.69,
|
||||||
"gt_model_capture_ratio": 0.0019,
|
"gt_model_capture_ratio": 0.0019,
|
||||||
"causal_gt_capture_ratio": 0.0034,
|
"causal_gt_capture_ratio": 0.0034,
|
||||||
"sim_causal_gt_pnl_pct": 14.66,
|
"sim_causal_gt_pnl_pct": 14.79,
|
||||||
"causal_hybrid_capture_ratio": 0.2612,
|
"causal_hybrid_capture_ratio": 0.2674,
|
||||||
"sim_causal_hybrid_pnl_pct": 1120.97,
|
"sim_causal_hybrid_pnl_pct": 1147.3,
|
||||||
"tier_enhanced_capture_ratio": -0.012,
|
"tier_enhanced_capture_ratio": -0.0119,
|
||||||
"sim_tier_enhanced_pnl_pct": -51.47,
|
"sim_tier_enhanced_pnl_pct": -51.26,
|
||||||
"causal_gt_params": {
|
"causal_gt_params": {
|
||||||
"peak_mode": "local",
|
"peak_mode": "local",
|
||||||
"pivot_order": 8,
|
"pivot_order": 8,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
# Live Phase A — dry-run 검증
|
# Live Phase A — dry-run 검증
|
||||||
|
|
||||||
- 일시: 2026-06-01 16:37:13
|
- 일시: 2026-06-01 23:17:30
|
||||||
- 결과: **PASS**
|
- 결과: **PASS**
|
||||||
- **배포 일정:** Phase C ~6/5 (금) → Phase B-1 ~6/6 (토)
|
|
||||||
- **체크리스트:** [DEPLOYMENT_CHECKLIST.md](./DEPLOYMENT_CHECKLIST.md)
|
|
||||||
- **`.env` 권장:** [env.recommended.md](./env.recommended.md)
|
|
||||||
|
|
||||||
## Plan (목적)
|
## Plan (목적)
|
||||||
|
|
||||||
@@ -21,35 +18,59 @@ python scripts/06_execute_live.py --once
|
|||||||
|
|
||||||
## Check (점검 결과)
|
## Check (점검 결과)
|
||||||
|
|
||||||
- GT_SIGNAL_CAUSAL=True · live_sizing=ON · LIVE_TRADING_ENABLED=False
|
- GT_SIGNAL_CAUSAL=True
|
||||||
|
- LIVE_TRADING_ENABLED=False
|
||||||
- monitor_rules: buy_compound_tight, sell_mtf_cross_all_tf
|
- monitor_rules: buy_compound_tight, sell_mtf_cross_all_tf
|
||||||
- hybrid DD: dd_large=5.0%, dd_medium=2.0%
|
- hybrid DD: {'dd_large_pct': 5.0, 'dd_medium_pct': 2.0}
|
||||||
- 현재 발화: 없음 (신호 대기)
|
|
||||||
- 06 dry-run: 주문 없음, live=OFF
|
|
||||||
|
|
||||||
### hybrid tier 사이징 (시나리오)
|
|
||||||
|
|
||||||
| 시나리오 | hybrid | conviction (배포 금지) |
|
|
||||||
|----------|--------|------------------------|
|
|
||||||
| 신규·DD 1% | ₩50,000 | ₩50,000 |
|
|
||||||
| 신규·DD 6% | ₩999,500 | ₩999,500 |
|
|
||||||
| 복리 500만·large leg | ₩4,997,501 | ₩4,997,501 |
|
|
||||||
|
|
||||||
- live_trader는 `enhanced=False` → hybrid primary와 동일 경로
|
|
||||||
- conviction은 DD 10% 이상·weight 분할 생략 시 시뮬과 크게 괴리 (별도 경로)
|
|
||||||
|
|
||||||
### 실거래 한도 (중요)
|
|
||||||
|
|
||||||
- 현재 시장 DD 구간에서 hybrid 1회 매수액 ≈ **₩999,500** (현금 100만 기준)
|
|
||||||
- `LIVE_DAILY_KRW_MAX=300,000` → **일 주문 한도 초과로 매수 스킵**
|
|
||||||
- 파일럿 단계에서는 한도가 의도적으로 시뮬(+1121%)보다 보수적임
|
|
||||||
|
|
||||||
## Act (다음 단계)
|
## Act (다음 단계)
|
||||||
|
|
||||||
1. **Phase C (월~6/5 금):** `05_run_monitor.py` 상시 · `06_verify_live_dryrun.py` 1일 1회
|
1. ~~`LIVE_TRADING_ENABLED=1`~~ **적용 완료 (Phase B-1, 2026-06-01)**
|
||||||
2. **금요일 C Go/No-Go** — [DEPLOYMENT_CHECKLIST.md](./DEPLOYMENT_CHECKLIST.md) §4.4
|
2. `06_execute_live.py` 상시 루프 기동 (180초 주기)
|
||||||
3. **토 6/6~ Phase B-1:** [env.recommended.md](./env.recommended.md) 적용 후 `LIVE_TRADING_ENABLED=1`
|
3. 1~2주 실계좌 PnL·슬리피지 기록 (본 문서 갱신)
|
||||||
4. 2~4주 후 B-2 승격 검토
|
|
||||||
|
## Phase C dry-run (지금 ~ 금요일 저녁)
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|------|-----|
|
||||||
|
| `LIVE_TRADING_ENABLED` | **0** (실주문 없음) |
|
||||||
|
| 실행 | `python scripts/06_execute_live.py` (180초 주기) |
|
||||||
|
| 발화 로그 | `data/ops/paper_fires.jsonl` |
|
||||||
|
| 금요일 집계 | `python scripts/07_phase_c_paper_report.py` |
|
||||||
|
|
||||||
|
### 수익률 확인 (중요)
|
||||||
|
|
||||||
|
| 종류 | 가능? |
|
||||||
|
|------|-------|
|
||||||
|
| **실계좌 수익률** | **불가** (주문 없음) |
|
||||||
|
| **모의 forward %** | **가능** (07 스크립트, 발화 후 N봉 가격 기준 **참고용**) |
|
||||||
|
| **시뮬 hybrid +1,147%** | 과거 1년 백테스트, 이번 주 결과와 **별개** |
|
||||||
|
|
||||||
|
금요일 C Go 후 토요일~ **B-1**: `.env`에서 `LIVE_TRADING_ENABLED=1`, `LIVE_DAILY_KRW_MAX=1000000`
|
||||||
|
|
||||||
|
### 일별 기록
|
||||||
|
|
||||||
|
| 날짜 | download | verify | 06 dry | buy 발화 | sell 발화 | 메모 |
|
||||||
|
|------|----------|--------|--------|----------|-----------|------|
|
||||||
|
| 6/1 | | PASS | | | | |
|
||||||
|
| 6/2 | | | | | | |
|
||||||
|
| 6/3 | | | | | | |
|
||||||
|
| 6/4 | | | | | | |
|
||||||
|
| 6/5 | | | | | | **C Go → B-1** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase B-1 (금요일 이후 예정)
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|------|-----|
|
||||||
|
| `LIVE_TRADING_ENABLED` | 1 |
|
||||||
|
| `LIVE_DAILY_KRW_MAX` | 1,000,000 |
|
||||||
|
| `LIVE_DAILY_LOSS_LIMIT_KRW` | 100,000 |
|
||||||
|
| `06 --once` | live=ON, 발화 없음 |
|
||||||
|
| 배분 | hybrid primary (`enhanced=False`) |
|
||||||
|
|
||||||
|
**선행 조치:** `coin` 환경에 `pip install python-dotenv` (`.env` 미적용 방지). `scripts/_bootstrap.py`·`config.py`는 `load_project_env(override=True)`.
|
||||||
|
|
||||||
## Kill switch
|
## Kill switch
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
| 경로 | 용도 | Git |
|
| 경로 | 용도 | Git |
|
||||||
|------|------|-----|
|
|------|------|-----|
|
||||||
| [reference/](reference/) | 로드맵·구조·GT 가이드·기법 명세 (`trade_anaysis.html`) | 추적 |
|
| [reference/](reference/) | 로드맵·**시뮬/LIVE/RISK/OPERATIONS**·GT·기법 명세 | 추적 |
|
||||||
| [02_ground_truth/](02_ground_truth/) | 정답 차트 HTML | 추적 |
|
| [02_ground_truth/](02_ground_truth/) | 정답 차트 HTML | 추적 |
|
||||||
| [03_analysis/](03_analysis/) | enrich CSV·GT 스냅샷·점검 HTML | 추적 |
|
| [03_analysis/](03_analysis/) | enrich CSV·GT 스냅샷·점검 HTML | 추적 |
|
||||||
| [04_matching/](04_matching/) | 규칙·시뮬·유사도 산출물 | 추적 |
|
| [04_matching/](04_matching/) | 규칙·시뮬·유사도 산출물 | 추적 |
|
||||||
|
|||||||
@@ -1,48 +1,99 @@
|
|||||||
# 3단계 — 오픈 (실거래)
|
# 2~3단계 — dry-run·실거래 (hybrid primary)
|
||||||
|
|
||||||
## 정의
|
## 운영 모델 (시뮬과 동일)
|
||||||
|
|
||||||
**실제 KRW가 빗썸 주문으로 나가는 단계**입니다. 05 텔레그램 알림만으로는 3단계가 아닙니다.
|
dry-run(Phase C)과 실거래(Phase B) 모두 **시뮬 `sim_primary` = `sim_causal_hybrid`** 와 같은 경로이다.
|
||||||
|
|
||||||
## 선행 조건
|
| 구분 | 내용 |
|
||||||
|
|------|------|
|
||||||
|
| 신호 | `matched_rules.json` → `monitor_rules` 2개 |
|
||||||
|
| 매수 규칙 | `buy_compound_tight` |
|
||||||
|
| 매도 규칙 | `sell_mtf_cross_all_tf` |
|
||||||
|
| 배분 | hybrid DD tier + past-leg, **`enhanced=False`** |
|
||||||
|
| 금지 | `sim_tier_enhanced` (conviction), GT oracle 타점 |
|
||||||
|
|
||||||
1. `python scripts/04_simulation_report.py` → **Go/No-Go: GO**
|
코드: `deepcoin/ops/live_trader.py` (`GT_SIGNAL_CAUSAL=1` 시 `plan_buy_amount_krw(..., enhanced=False)`)
|
||||||
2. [DEPLOYMENT_CHECKLIST.md](../05_ops/DEPLOYMENT_CHECKLIST.md) — Phase C 완료 후 B-1
|
|
||||||
3. [env.recommended.md](../05_ops/env.recommended.md) — Phase별 `.env`
|
|
||||||
4. 본 문서·`RISK.md`·`OPERATIONS.md` 숙지
|
|
||||||
|
|
||||||
## 실행
|
## Phase C — dry-run (주문 없음, 3단계 전)
|
||||||
|
|
||||||
|
**정의:** 빗썸 **시장가 주문 없음**. 신호·tier·알림·로그만 검증.
|
||||||
|
|
||||||
|
| 항목 | 설정 |
|
||||||
|
|------|------|
|
||||||
|
| `LIVE_TRADING_ENABLED` | **0** |
|
||||||
|
| 스크립트 | `05_run_monitor.py`, `06_verify_live_dryrun.py`, `06_execute_live.py --once` |
|
||||||
|
| 기간 | 배포 체크리스트 기준 ~Phase C 종료(금요일 Go/No-Go) |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Phase A: hybrid tier·한도 점검 (주문 없음)
|
# .env: LIVE_TRADING_ENABLED=0, GT_SIGNAL_CAUSAL=1, SIM_PRIMARY_SIZING=auto
|
||||||
python scripts/06_verify_live_dryrun.py
|
python scripts/01_download.py # 1일 1회
|
||||||
|
python scripts/06_verify_live_dryrun.py # 설정·tier·규칙 점검
|
||||||
|
python scripts/05_run_monitor.py # 텔레그램 알림 (상시)
|
||||||
|
python scripts/06_execute_live.py --once # dry_run 로그만 (선택)
|
||||||
|
```
|
||||||
|
|
||||||
# 반드시 LIVE_TRADING_ENABLED=1 일 때만 주문
|
- 06은 발화 시 `dry_run (LIVE_TRADING_ENABLED=0)` 만 기록, **API 매수·매도 호출 없음**
|
||||||
python scripts/06_execute_live.py --once # 1회 점검
|
- 잔고 조회는 알림·tier 계산용으로 API를 쓸 수 있음
|
||||||
python scripts/06_execute_live.py # 상시 (알림+주문)
|
|
||||||
|
상세: [DEPLOYMENT_CHECKLIST.md](../05_ops/DEPLOYMENT_CHECKLIST.md) §4 · [env.recommended.md](../05_ops/env.recommended.md) Phase C
|
||||||
|
|
||||||
|
## Phase B — 실거래 (3단계 오픈)
|
||||||
|
|
||||||
|
**정의:** 실제 KRW가 빗썸 주문으로 나간다. 05 알림만으로는 3단계가 아니다.
|
||||||
|
|
||||||
|
### 선행 조건
|
||||||
|
|
||||||
|
1. [SIMULATION.md](SIMULATION.md) — 시뮬 **GO** (완료)
|
||||||
|
2. Phase C **GO** (5일 모니터·verify·알림 안정)
|
||||||
|
3. [env.recommended.md](../05_ops/env.recommended.md) Phase B-1 `.env`
|
||||||
|
4. [RISK.md](RISK.md), [OPERATIONS.md](OPERATIONS.md) 숙지
|
||||||
|
|
||||||
|
### 실행
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/06_verify_live_dryrun.py # B-1 당일 재확인
|
||||||
|
|
||||||
|
# LIVE_TRADING_ENABLED=1 확인 후
|
||||||
|
python scripts/06_execute_live.py --once
|
||||||
|
python scripts/06_execute_live.py # 상시
|
||||||
```
|
```
|
||||||
|
|
||||||
## 환경 변수
|
## 환경 변수
|
||||||
|
|
||||||
| 변수 | 기본 | 설명 |
|
| 변수 | Phase C | Phase B-1 (예) | 설명 |
|
||||||
|------|------|------|
|
|------|---------|----------------|------|
|
||||||
| `LIVE_TRADING_ENABLED` | 0 | **1**일 때만 실주문 |
|
| `LIVE_TRADING_ENABLED` | 0 | 1 | 1일 때만 실주문 |
|
||||||
| `LIVE_ORDER_KRW` | 100000 | 1회 주문 금액(원) |
|
| `GT_SIGNAL_CAUSAL` | 1 | 1 | hybrid sizing |
|
||||||
| `LIVE_DAILY_KRW_MAX` | 300000 | 일일 총 주문 한도 |
|
| `SIM_PRIMARY_SIZING` | auto | auto | primary=hybrid |
|
||||||
| `LIVE_COOLDOWN_MIN` | 180 | 동일 규칙 재주문 최소 간격(분) |
|
| `LIVE_ORDER_KRW` | 100000 | 100000 | 매도·비-hybrid fallback 참고 |
|
||||||
| `LIVE_MAX_TRADES_PER_DAY` | 10 | 일일 최대 체결 시도 |
|
| `LIVE_DAILY_KRW_MAX` | 300000 | 1,000,000 | **sim 대비 체결 상한** |
|
||||||
| `LIVE_DAILY_LOSS_LIMIT_KRW` | 50000 | 일 손실 한도(추가 주문 중단) |
|
| `LIVE_COOLDOWN_MIN` | 180 | 180 | 규칙별 재주문 간격 |
|
||||||
|
| `LIVE_MAX_TRADES_PER_DAY` | 10 | 10 | 일 최대 시도 |
|
||||||
|
| `LIVE_DAILY_LOSS_LIMIT_KRW` | 50000 | 50000 | 일 손실 한도 |
|
||||||
|
| `MONITOR_LOOP_SLEEP_SEC` | 180 | 180 | 05/06 루프 주기 |
|
||||||
|
| `MONITOR_ALERT_COOLDOWN_MIN` | 180 | 180 | 텔레그램 중복 방지 |
|
||||||
|
|
||||||
## 주문 규칙
|
Phase별 전체 블록: [env.recommended.md](../05_ops/env.recommended.md)
|
||||||
|
|
||||||
- `matched_rules.json`의 **`monitor_rules`** 만 사용 (매수·매도 각 1개)
|
## 주문·배분 동작
|
||||||
- 매수: 시장가 매수 (`buyCoinMarket`)
|
|
||||||
- 매도: 보유 수량 기준 시장가 매도 (`sellCoinMarket`)
|
- **매수:** EV/WF 통과 규칙만 hybrid tier 원화 산출 → `_can_trade` (일한도·쿨다운) → 시장가 매수
|
||||||
|
- **매도:** 보유 WLD 전량 기준 시장가 매도 (`LIVE_ORDER_KRW`는 매도 경로에서 수량 우선)
|
||||||
|
- **스킵:** 현금 부족, 일한도, hybrid planned > `LIVE_DAILY_KRW_MAX`, 미승인 규칙
|
||||||
|
|
||||||
## 로그
|
## 로그
|
||||||
|
|
||||||
- `data/ops/live_trades.jsonl` — 주문 시도·결과
|
| 경로 | 내용 |
|
||||||
|
|------|------|
|
||||||
|
| `data/ops/live_trades.jsonl` | 06 주문·dry-run 시도 (JSONL) |
|
||||||
|
| `docs/05_ops/live_verification_*.md` | Phase C/B 일별 기록 |
|
||||||
|
|
||||||
## 4단계 연결
|
## 4단계 연결
|
||||||
|
|
||||||
오픈 후 **1~2주** 실계좌 PnL·슬리피지·장애를 `docs/05_ops/live_verification_*.md`에 기록합니다.
|
오픈 후 **1~2주** 실계좌 PnL·슬리피지·장애를 verification 문서에 기록 → B-2 한도 검토.
|
||||||
|
|
||||||
|
## 관련 문서
|
||||||
|
|
||||||
|
- [SIMULATION.md](SIMULATION.md) — Go/No-Go·`sim_primary`
|
||||||
|
- [OPERATIONS.md](OPERATIONS.md) — 일상 루틴·장애
|
||||||
|
- [RISK.md](RISK.md) — Kill switch·한도
|
||||||
|
|||||||
@@ -1,42 +1,143 @@
|
|||||||
# 운영 가이드
|
# 운영 가이드
|
||||||
|
|
||||||
|
## 로드맵과 현재 위치 (2026-06-01)
|
||||||
|
|
||||||
|
| 순서 | 단계 | 상태 |
|
||||||
|
|------|------|------|
|
||||||
|
| 1 | 시뮬레이션 | **완료** (GO) — [SIMULATION.md](SIMULATION.md) |
|
||||||
|
| 2 | 문서화 | **본 문서군** — SIMULATION / LIVE / RISK / OPERATIONS |
|
||||||
|
| 3 | 오픈 (실거래) | Phase C 후 B-1 — [LIVE_TRADING.md](LIVE_TRADING.md) |
|
||||||
|
| 4 | 실계좌 검증 | 1~2주 |
|
||||||
|
| 5 | 지속 운영 | 06 상시 + 월간 재시뮬 |
|
||||||
|
|
||||||
|
배포 모델: **hybrid primary** (= 시뮬 `sim_causal_hybrid`). 상세: [LIVE_TRADING.md](LIVE_TRADING.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 단계별 스크립트
|
## 단계별 스크립트
|
||||||
|
|
||||||
| 단계 | 스크립트 |
|
| 단계 | 스크립트 | 산출·역할 |
|
||||||
|------|----------|
|
|------|----------|-----------|
|
||||||
| 데이터 | `01_download.py` |
|
| 01 | `01_download.py` | `coins.db` (3~1440분, 1분 제외) |
|
||||||
| GT | `02_ground_truth.py` |
|
| (자동) | 05/06·`load_frames_from_db` | API 수집 시 `MONITOR_PERSIST_CANDLES=1`이면 **즉시 DB INSERT** |
|
||||||
| 분석 | `03_analyze_enrich.py`, `03_analyze_trades.py` |
|
| 02 | `02_ground_truth.py` | GT JSON·차트 |
|
||||||
| 매칭 | `04_match_rules.py` |
|
| 03 | `03_analyze_enrich.py` | `docs/03_analysis/latest/` |
|
||||||
| 시뮬 | `04_simulation_report.py` |
|
| 03b | `03_analyze_trades.py` | GT MTF 스냅샷 CSV |
|
||||||
| 알림 | `05_run_monitor.py` |
|
| 03c | `03_gt_mtf_profile.py` | GT 프로필 (04 입력) |
|
||||||
| 실거래 | `06_execute_live.py` |
|
| 04 | `04_match_rules.py` | `matched_rules.json` |
|
||||||
|
| 04 시뮬 | `04_simulation_report.py` | Go/No-Go 리포트 |
|
||||||
|
| 05 | `05_run_monitor.py` | 알림 (주문 없음) |
|
||||||
|
| 06 | `06_execute_live.py` | 알림+주문 (LIVE=1) |
|
||||||
|
| 점검 | `06_verify_live_dryrun.py` | hybrid·한도·규칙 PASS |
|
||||||
|
| 환경 | `verify_env.py` | `.env`·경로 검증 |
|
||||||
|
|
||||||
## 일상 운영 (5단계 이후)
|
구조: [STRUCTURE.md](STRUCTURE.md)
|
||||||
|
|
||||||
1. `01_download.py` — 일 1회 권장
|
---
|
||||||
2. `06_execute_live.py` — 상시 (`LIVE_TRADING_ENABLED=1`)
|
|
||||||
3. 주간 — `04_simulation_report.py`로 EV·Go 재확인
|
## Phase C — 지금~금요일 (dry-run, 주문 없음)
|
||||||
|
|
||||||
|
### `.env` 핵심
|
||||||
|
|
||||||
|
```env
|
||||||
|
LIVE_TRADING_ENABLED=0
|
||||||
|
GT_SIGNAL_CAUSAL=1
|
||||||
|
SIM_PRIMARY_SIZING=auto
|
||||||
|
MONITOR_LOOP_SLEEP_SEC=180
|
||||||
|
MONITOR_ALERT_COOLDOWN_MIN=180
|
||||||
|
```
|
||||||
|
|
||||||
|
전체: [env.recommended.md](../05_ops/env.recommended.md)
|
||||||
|
|
||||||
|
### 매일 루틴
|
||||||
|
|
||||||
|
| 순서 | 명령 | 빈도 |
|
||||||
|
|------|------|------|
|
||||||
|
| 1 | `python scripts/01_download.py` | 1일 1회 |
|
||||||
|
| 2 | `python scripts/06_verify_live_dryrun.py` | 1일 1회 |
|
||||||
|
| 3 | `python scripts/06_execute_live.py` | 상시 (`LIVE=0`, 발화→`paper_fires.jsonl`) |
|
||||||
|
| 4 | 금요일 | `python scripts/07_phase_c_paper_report.py` (모의 forward % 참고) |
|
||||||
|
|
||||||
|
실계좌 수익률은 dry-run에서 나오지 않음. B-1 전 C Go 판정 후 `LIVE_TRADING_ENABLED=1`.
|
||||||
|
|
||||||
|
### 일별 기록
|
||||||
|
|
||||||
|
- 파일: `docs/05_ops/live_verification_20260601.md`
|
||||||
|
- 금요일: [DEPLOYMENT_CHECKLIST.md](../05_ops/DEPLOYMENT_CHECKLIST.md) §4.4 C Go/No-Go
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase B-1 — 실거래 시작 (C GO 이후)
|
||||||
|
|
||||||
|
1. [env.recommended.md](../05_ops/env.recommended.md) Phase B-1 블록 적용
|
||||||
|
2. `LIVE_TRADING_ENABLED=1` 확인
|
||||||
|
3. `06_verify_live_dryrun.py` → PASS
|
||||||
|
4. `06_execute_live.py --once` → `live_trades.jsonl` 확인
|
||||||
|
5. `06_execute_live.py` 상시
|
||||||
|
|
||||||
|
체크리스트: [DEPLOYMENT_CHECKLIST.md](../05_ops/DEPLOYMENT_CHECKLIST.md) §5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase B-2 이후 (검증 통과 시)
|
||||||
|
|
||||||
|
- `LIVE_DAILY_KRW_MAX` 등 한도 상향 (리스크 문서·본인 판단)
|
||||||
|
- 주간 `04_simulation_report.py`로 EV·hybrid Go 재확인
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 텔레그램
|
## 텔레그램
|
||||||
|
|
||||||
- 05/06: 규칙 발화·**체결 결과** (06)
|
| 스크립트 | 내용 |
|
||||||
- `MONITOR_ALERT_COOLDOWN_MIN` / `LIVE_COOLDOWN_MIN` 으로 중복 완화
|
|----------|------|
|
||||||
|
| 05 | 규칙 발화·MTF 요약 (주문 없음) |
|
||||||
|
| 06 | 발화 + 체결/dry-run 결과 (`LIVE=1` 시 체결) |
|
||||||
|
|
||||||
|
중복 완화: `MONITOR_ALERT_COOLDOWN_MIN`, `LIVE_COOLDOWN_MIN`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 장애 대응
|
## 장애 대응
|
||||||
|
|
||||||
| 증상 | 조치 |
|
| 증상 | 조치 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 주문 실패 | 로그·빗썸 API 키·잔고 확인, `LIVE_TRADING_ENABLED=0` |
|
| 주문 실패 | `live_trades.jsonl`, API 키·잔고 → `LIVE_TRADING_ENABLED=0` |
|
||||||
| 알림만 오고 주문 없음 | `LIVE_TRADING_ENABLED` 확인 |
|
| 알림만, 주문 없음 | C: 정상. B: `LIVE_TRADING_ENABLED` 확인 |
|
||||||
| 과다 알림 | 쿨다운 증가·`monitor_rules` 축소 |
|
| verify FAIL | `.env`, `matched_rules` 2개, `GT_SIGNAL_CAUSAL=1` |
|
||||||
|
| hybrid 금액 0 | 현금·EV/WF·tier 스킵 로그 확인 |
|
||||||
|
| 과다 알림 | 쿨다운 증가 |
|
||||||
|
| 시뮬과 수익 괴리 | 일한도·슬리피지 — [RISK.md](RISK.md) |
|
||||||
|
|
||||||
## 오픈 체크리스트 (3단계 당일)
|
---
|
||||||
|
|
||||||
- [ ] [DEPLOYMENT_CHECKLIST.md](../05_ops/DEPLOYMENT_CHECKLIST.md) Phase C Go
|
## 데이터·산출물 경로
|
||||||
- [ ] [env.recommended.md](../05_ops/env.recommended.md) Phase B-1 적용
|
|
||||||
- [ ] 시뮬 Go/No-Go **GO**
|
| 경로 | 용도 |
|
||||||
- [ ] `.env` 한도 확인
|
|------|------|
|
||||||
- [ ] `LIVE_TRADING_ENABLED=1` 의도적 설정
|
| `data/coins.db` 또는 루트 `coins.db` | OHLCV |
|
||||||
- [ ] `--once` 1회 테스트
|
| `data/ground_truth/ground_truth_trades.json` | GT |
|
||||||
- [ ] `live_trades.jsonl` 기록 확인
|
| `docs/04_matching/matched_rules.json` | 운영 규칙 |
|
||||||
|
| `docs/04_matching/simulation_report.html` | 시뮬 리포트 |
|
||||||
|
| `data/ops/live_trades.jsonl` | 06 로그 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 오픈 당일 체크리스트 (3단계)
|
||||||
|
|
||||||
|
- [ ] Phase C **GO**
|
||||||
|
- [ ] 시뮬·hybrid Go/No-Go **GO**
|
||||||
|
- [ ] Phase B-1 `.env`
|
||||||
|
- [ ] `06_verify_live_dryrun.py` PASS
|
||||||
|
- [ ] `LIVE_TRADING_ENABLED=1` 의도 확인
|
||||||
|
- [ ] `--once` 후 `live_trades.jsonl`
|
||||||
|
- [ ] [RISK.md](RISK.md) Kill switch 숙지
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 관련 문서
|
||||||
|
|
||||||
|
- [ROADMAP.md](ROADMAP.md)
|
||||||
|
- [SIMULATION.md](SIMULATION.md)
|
||||||
|
- [LIVE_TRADING.md](LIVE_TRADING.md)
|
||||||
|
- [RISK.md](RISK.md)
|
||||||
|
- [DEPLOYMENT_CHECKLIST.md](../05_ops/DEPLOYMENT_CHECKLIST.md)
|
||||||
|
|||||||
@@ -1,25 +1,62 @@
|
|||||||
# 리스크 — 실거래
|
# 리스크 — dry-run·실거래
|
||||||
|
|
||||||
## 원칙
|
## 모델 리스크 (배포 경로)
|
||||||
|
|
||||||
- 소액 파일럿만 허용 (`LIVE_ORDER_KRW`, `LIVE_DAILY_KRW_MAX`)
|
| 경로 | 리스크 수준 | 조치 |
|
||||||
- 손실 한도 초과 시 **당일 추가 주문 중단**
|
|------|-------------|------|
|
||||||
- API·네트워크 오류 시 주문 중단·로그 기록
|
| **hybrid primary** (`sim_causal_hybrid`) | 배포 허용 | Phase C 검증 후 B-1 소액 |
|
||||||
|
| GT oracle (+4,291%) | **운영 금지** | 벤치마크만 참고 (미래 정보) |
|
||||||
|
| conviction (`sim_tier_enhanced`) | **코드·env 모두 금지** | `enhanced=False` 고정 |
|
||||||
|
| sim 고수익 ≠ 실현 | **구조적 갭** | `LIVE_DAILY_KRW_MAX`·슬리피지·부분 체결 |
|
||||||
|
|
||||||
|
시뮬 Option C **GO**는 과거 데이터·가정 하의 결과이며, 실계좌 수익을 보장하지 않는다.
|
||||||
|
|
||||||
|
## 자금·한도
|
||||||
|
|
||||||
|
### 원칙
|
||||||
|
|
||||||
|
- 파일럿만 허용: Phase B-1은 **소액** (`LIVE_ORDER_KRW`, `LIVE_DAILY_KRW_MAX` 보수적)
|
||||||
|
- hybrid 1회 planned 매수가 `LIVE_DAILY_KRW_MAX`를 넘으면 **주문 스킵** (시뮬은 제한 없음)
|
||||||
|
- 일 손실 한도 초과 시 **당일 추가 주문 중단**
|
||||||
|
|
||||||
|
### Phase별 한도 예 (`.env` 조정 필수)
|
||||||
|
|
||||||
|
| Phase | `LIVE_TRADING_ENABLED` | 일한도 예 | 비고 |
|
||||||
|
|-------|------------------------|-----------|------|
|
||||||
|
| C (dry-run) | 0 | 30만 (dry-run 참고) | 주문 없음 |
|
||||||
|
| B-1 | 1 | 100만 | sim 대비 보수 |
|
||||||
|
| B-2 | 1 | 500만+ | B-1 검증 후만 |
|
||||||
|
|
||||||
|
본인 자금·위험 성향에 맞게 **반드시** 낮춰 시작한다.
|
||||||
|
|
||||||
|
## 시장·기술 리스크
|
||||||
|
|
||||||
|
| 리스크 | 영향 | 완화 |
|
||||||
|
|--------|------|------|
|
||||||
|
| API 장애·레이트리밋 | 시세·주문 실패 | 재시도·`LIVE_TRADING_ENABLED=0` |
|
||||||
|
| 슬리피지·호가 | sim 대비 수익 하락 | B-1 소액·verification 기록 |
|
||||||
|
| DB·봉 지연 | 규칙 오판 | `01_download` 일 1회 |
|
||||||
|
| 과다 발화 | 수수료·알림 피로 | 쿨다운·규칙 수(`MATCH_MONITOR_MAX_PER_SIDE`) |
|
||||||
|
| 단일 종목(WLD) | 집중 리스크 | 포지션·일한도 상한 |
|
||||||
|
|
||||||
## Kill switch
|
## Kill switch
|
||||||
|
|
||||||
| 방법 | 동작 |
|
| 방법 | 동작 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `.env` | `LIVE_TRADING_ENABLED=0` 설정 후 프로세스 재시작 |
|
| `.env` | `LIVE_TRADING_ENABLED=0` 후 05/06 프로세스 재시작 |
|
||||||
| 프로세스 | `06_execute_live.py` 중지 |
|
| 프로세스 | `06_execute_live.py` 중지 (05만 남기면 알림만) |
|
||||||
| 빗썸 | 앱/웹에서 수동 청산 |
|
| 빗썸 | 앱/웹 수동 청산 |
|
||||||
|
|
||||||
## 한도 (기본값 예시)
|
긴급 시 **주문 프로세스 중지 → LIVE_TRADING_ENABLED=0** 순서를 권장한다.
|
||||||
|
|
||||||
- 1회 10만 원 · 일 30만 원 · 일 손실 5만 원 초과 시 중단
|
## 검증·재평가
|
||||||
|
|
||||||
운영 전 본인 자금에 맞게 **반드시** 조정하세요.
|
| 주기 | 작업 |
|
||||||
|
|------|------|
|
||||||
|
| Phase C (~5일) | 발화·알림·verify PASS — [DEPLOYMENT_CHECKLIST](../05_ops/DEPLOYMENT_CHECKLIST.md) |
|
||||||
|
| B-1 (1~2주) | `live_verification_*.md` PnL·MDD·슬리피지 |
|
||||||
|
| 월 1회 | `04_simulation_report.py` Go 재확인 |
|
||||||
|
|
||||||
## 면책
|
## 면책
|
||||||
|
|
||||||
실거래 손익은 전적으로 운영자 책임입니다. 본 저장소는 투자 자문이 아닙니다.
|
실거래·dry-run 관찰 손익은 전적으로 운영자 책임이다. 본 저장소는 투자 자문이 아니다.
|
||||||
|
|||||||
@@ -4,21 +4,33 @@
|
|||||||
|
|
||||||
| 단계 | 내용 | 실행 |
|
| 단계 | 내용 | 실행 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 01~03c | DB, GT, enrich, GT MTF 스냅샷, **전 TF 프로필(매수/매도 대조)** | `01`~`03_gt_mtf_profile.py` |
|
| 01~03c | DB, GT, enrich, GT MTF 스냅샷, 전 TF 프로필 | `01`~`03_gt_mtf_profile.py` |
|
||||||
| 04 | GT 프로필 + leg_gt EV + holdout | `04_match_rules.py` |
|
| 04 | GT 프로필 + leg_gt EV + holdout | `04_match_rules.py` |
|
||||||
| 05 | 텔레그램 알림 (주문 없음) | `05_run_monitor.py` |
|
| 05 (기능) | 텔레그램 알림 스크립트 | `05_run_monitor.py` |
|
||||||
|
| **1** | **시뮬레이션** walk-forward·Go/No-Go | `04_simulation_report.py` — **GO** |
|
||||||
|
| **2** | **문서화** | [SIMULATION.md](SIMULATION.md), [LIVE_TRADING.md](LIVE_TRADING.md), [RISK.md](RISK.md), [OPERATIONS.md](OPERATIONS.md) |
|
||||||
|
|
||||||
## 남은 작업 (합의 순서)
|
## 남은 작업 (합의 순서)
|
||||||
|
|
||||||
| 순서 | 단계 | 내용 | 실행 |
|
| 순서 | 단계 | 내용 | 실행 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| **1** | 시뮬레이션 | walk-forward·민감도·Go/No-Go | `04_simulation_report.py` |
|
| **3** | 오픈 (B-1) | 실거래 소액 · hybrid | `06_execute_live.py` — **기동** (`LIVE_TRADING_ENABLED=1`) |
|
||||||
| **2** | 문서화 | SIMULATION, LIVE, RISK, OPERATIONS | `docs/reference/` |
|
| **(병행)** | Phase C 알림 | 선택 | `05_run_monitor.py` |
|
||||||
| **3** | 오픈 | **실거래** (소액) | `06_execute_live.py` |
|
|
||||||
| **4** | 검증 | 실계좌 1~2주 | `docs/05_ops/live_verification_*.md` |
|
| **4** | 검증 | 실계좌 1~2주 | `docs/05_ops/live_verification_*.md` |
|
||||||
| **5** | 지속 | 실거래 유지·월간 재시뮬 | 06 상시 |
|
| **5** | 지속 | 실거래·월간 재시뮬 | 06 상시 |
|
||||||
|
|
||||||
가이드: [SIMULATION.md](SIMULATION.md) · [LIVE_TRADING.md](LIVE_TRADING.md)
|
운영 모델: **sim_primary = hybrid** (`sim_causal_hybrid`). [LIVE_TRADING.md](LIVE_TRADING.md)
|
||||||
|
|
||||||
|
## 가이드 맵
|
||||||
|
|
||||||
|
| 문서 | 용도 |
|
||||||
|
|------|------|
|
||||||
|
| [SIMULATION.md](SIMULATION.md) | 1단계 결과·Go/No-Go·portfolio_compare |
|
||||||
|
| [LIVE_TRADING.md](LIVE_TRADING.md) | Phase C dry-run · Phase B live |
|
||||||
|
| [RISK.md](RISK.md) | 한도·Kill switch·sim vs 실현 갭 |
|
||||||
|
| [OPERATIONS.md](OPERATIONS.md) | 일상 루틴·장애·체크리스트 |
|
||||||
|
| [DEPLOYMENT_CHECKLIST.md](../05_ops/DEPLOYMENT_CHECKLIST.md) | C→B 일정·Go 기준 |
|
||||||
|
| [env.recommended.md](../05_ops/env.recommended.md) | Phase별 `.env` |
|
||||||
|
|
||||||
## 디렉터리
|
## 디렉터리
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
# 1단계 — 시뮬레이션
|
# 1단계 — 시뮬레이션
|
||||||
|
|
||||||
|
**상태 (2026-06-01):** `04_simulation_report.py` 실행 완료 · 규칙·hybrid·Option C 2차 **GO**
|
||||||
|
|
||||||
## 목적
|
## 목적
|
||||||
|
|
||||||
`monitor_rules`가 과적합이 아닌지 검증하고, **Ground Truth와 동일한 자본 배분 원칙**으로 holdout 체결 수익을 비교합니다.
|
1. `monitor_rules`가 holdout·walk-forward·수수료 스트레스를 통과하는지 검증한다.
|
||||||
|
2. 운영에 쓸 **배분 경로(primary)** 를 정한다. (실전 = **hybrid**, GT oracle 아님)
|
||||||
|
|
||||||
## 실행
|
## 실행
|
||||||
|
|
||||||
@@ -15,40 +18,95 @@ python scripts/04_simulation_report.py
|
|||||||
|
|
||||||
| 파일 | 내용 |
|
| 파일 | 내용 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `docs/04_matching/simulation_report.json` | Go/No-Go, `portfolio_compare`, `gt_model` |
|
| `docs/04_matching/simulation_report.json` | Go/No-Go, `portfolio_compare`, walk-forward |
|
||||||
| `docs/04_matching/simulation_report.html` | 카드 3줄: **GT · 시뮬(총자산%) · 시뮬(고정₩/회)** |
|
| `docs/04_matching/simulation_report.html` | GT·시뮬 경로별 카드·차트 |
|
||||||
|
|
||||||
## 포트폴리오 비교 (`portfolio_compare`)
|
## 배포 모델 (primary)
|
||||||
|
|
||||||
|
| 경로 | `portfolio_compare` 키 | 전기간 PnL (최근 실행) | 운영 |
|
||||||
|
|------|------------------------|------------------------|------|
|
||||||
|
| GT oracle (사후 ZigZag) | `ground_truth_chrono` | +4,291% | **미사용** (미래 허용 벤치마크) |
|
||||||
|
| **권장 primary** | `sim_primary` = `sim_causal_hybrid` | **+1,147%** | **dry-run·live 배분** |
|
||||||
|
| causal tier only | `sim_sized` | +75% | 미사용 |
|
||||||
|
| 인과 GT leg 엔진 | `sim_causal_gt` | +15% | 미사용 |
|
||||||
|
| conviction tier | `sim_tier_enhanced` | -51% | **금지** |
|
||||||
|
| 고정 금액 baseline | `sim_fixed_order` | -94% | 비교용 |
|
||||||
|
|
||||||
|
- `primary_sizing`: **hybrid** (`go_no_go_hybrid.primary_sizing`)
|
||||||
|
- hybrid: monitor 발화 + **DD tier + past-leg tier**, `enhanced=False`
|
||||||
|
- 코드: `deepcoin/ground_truth/causal_gt_hybrid.py`, `deepcoin/matching/simulation.py`
|
||||||
|
|
||||||
|
## Go/No-Go (최근 실행 요약)
|
||||||
|
|
||||||
|
### 규칙 (`go_no_go`)
|
||||||
|
|
||||||
|
| rule_id | holdout EV | WF+ 비율 | fee 2× EV | 결과 |
|
||||||
|
|---------|------------|----------|-----------|------|
|
||||||
|
| `buy_compound_tight` | 5.66 | 0.75 | 4.99 | PASS |
|
||||||
|
| `sell_mtf_cross_all_tf` | 7.13 | 1.00 | 7.12 | PASS |
|
||||||
|
|
||||||
|
→ **GO**
|
||||||
|
|
||||||
|
### hybrid primary (`go_no_go_hybrid`)
|
||||||
|
|
||||||
|
| 검사 | 값 | 결과 |
|
||||||
|
|------|-----|------|
|
||||||
|
| monitor_rules_go | - | PASS |
|
||||||
|
| hybrid_holdout_pnl | +62.35% | PASS |
|
||||||
|
| hybrid_max_mdd | 19.22% | PASS |
|
||||||
|
| hybrid_fee_stress_pnl | +975.74% | PASS |
|
||||||
|
| option_c_target_300pct (optional) | +1,147% | PASS |
|
||||||
|
|
||||||
|
→ **GO** · `primary_sizing=hybrid`
|
||||||
|
|
||||||
|
### Option C 2차 (`go_no_go_option_c_phase2`)
|
||||||
|
|
||||||
|
- 전기간 +1,000% 목표, GT capture ≥23%, WF 양수 월 비율, 슬리피지 스트레스 등 → **GO**
|
||||||
|
|
||||||
|
재실행 후 수치는 `simulation_report.json`을 기준으로 한다.
|
||||||
|
|
||||||
|
## 포트폴리오 비교 (`portfolio_compare`) — 읽는 법
|
||||||
|
|
||||||
| 키 | 설명 |
|
| 키 | 설명 |
|
||||||
|----|------|
|
|----|------|
|
||||||
| `ground_truth_chrono` | GT 타점 + `amount_krw` 시각순 체결 |
|
| `ground_truth_chrono` | GT 타점·`amount_krw` 시각순 체결 (상한 벤치마크) |
|
||||||
| `sim_sized` | holdout 발화 + **총자산×비중×EV/WF·leg상위** (`position_sizing`) |
|
| `sim_primary` / `sim_causal_hybrid` | **운영 배분과 동일** (monitor + hybrid tier) |
|
||||||
| `sim_fixed_order` | 동일 발화 + **고정 `LIVE_ORDER_KRW`/회** (baseline) |
|
| `sim_sized` | EV/WF·leg 가중 복리 (구 경로) |
|
||||||
|
| `sim_hybrid_holdout` | hybrid 전기간 복리 후 **holdout 구간** 자산 증감 |
|
||||||
## 시뮬 매수 배분 (GT와 동일 원칙)
|
| `sim_hybrid_fee_stress` | 수수료 스트레스 hybrid |
|
||||||
|
| `hybrid_dd_params` | `dd_large_pct`, `dd_medium_pct` (캘리브 JSON과 동기) |
|
||||||
- **통과 규칙만** 대형: holdout EV·PF, walk-forward, 수수료 2× 스트레스 (`load_ev_wf_approved_rule_ids`)
|
|
||||||
- **leg 상위** `GT_LARGE_LEG_TOP_PCT` + 근접 GT leg 매칭 → `LIVE_BUY_PCT_LARGE`
|
|
||||||
- 그 외 → `LIVE_BUY_PCT_SMALL`
|
|
||||||
- 일한도·일최대거래: `select_capped_fires` (동적 planned 원화로 `LIVE_DAILY_KRW_MAX` 적용)
|
|
||||||
|
|
||||||
## 검증 항목
|
## 검증 항목
|
||||||
|
|
||||||
| 항목 | 설명 |
|
| 항목 | 설명 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| Holdout | EV≥0, PF≥1 |
|
| Holdout | 규칙별 EV≥0, PF≥1 |
|
||||||
| Walk-forward | 양수 월 비율 ≥ `SIM_GO_WF_POSITIVE_RATIO` |
|
| Walk-forward | 월별 EV·`SIM_GO_WF_POSITIVE_RATIO` |
|
||||||
| 수수료 스트레스 | 수수료 2× 후 EV≥0 |
|
| 수수료 스트레스 | `SIM_FEE_STRESS_MULT` (기본 2×) |
|
||||||
| 실거래 한도 | 동적 매수액 기준 일한도 시뮬 |
|
| hybrid | holdout PnL, MDD, fee stress, (선택) +300% |
|
||||||
|
| 슬리피지 | Option C 2차 — 체결가 불리 가정 후 흑자 여부 |
|
||||||
|
|
||||||
## Go/No-Go
|
## 시뮬 vs 실운영 (기대 갭)
|
||||||
|
|
||||||
- **GO**: monitor_rules 전 규칙 checks 통과
|
시뮬 hybrid는 **일한도 없이** 복리·전액 배분을 가정한다. 실거래는 `LIVE_DAILY_KRW_MAX` 등으로 체결이 잘리므로 **수익률이 sim_primary와 같지 않을 수 있다**. (배포 체크리스트 D6: 실현=sim 미달)
|
||||||
- **NO-GO**: 04 재선별 후 재실행
|
|
||||||
|
|
||||||
## 환경 변수
|
## 환경 변수
|
||||||
|
|
||||||
- `SIM_GO_*`, `SIM_WALK_FORWARD_MIN_MONTHS`, `SIM_FEE_STRESS_MULT`
|
| 변수 | 용도 |
|
||||||
- `LIVE_ORDER_KRW`, `LIVE_DAILY_KRW_MAX` (고정 baseline·한도)
|
|------|------|
|
||||||
- `LIVE_BUY_PCT_LARGE`, `LIVE_BUY_PCT_SMALL` (시뮬·실거래 비율 매수)
|
| `SIM_GO_*`, `SIM_FEE_STRESS_MULT`, `SIM_WALK_FORWARD_MIN_MONTHS` | 규칙 Go/No-Go |
|
||||||
|
| `SIM_PRIMARY_SIZING` | `auto` \| `hybrid` \| `causal_tier` (권장: **auto** → hybrid) |
|
||||||
|
| `GT_SIGNAL_CAUSAL` | 1 — hybrid live sizing 활성 |
|
||||||
|
| `CAUSAL_GT_DD_LARGE_PCT`, `CAUSAL_GT_DD_MEDIUM_PCT` | hybrid tier (캘리브와 동기) |
|
||||||
|
| `GT_BUY_PCT_*`, `GT_LARGE_LEG_TOP_PCT` | tier 비중 |
|
||||||
|
|
||||||
|
## NO-GO 시
|
||||||
|
|
||||||
|
1. `04_match_rules.py` 재실행 (프로필·선별)
|
||||||
|
2. `04_simulation_report.py` 재실행
|
||||||
|
3. `go_no_go` / `go_no_go_hybrid` checks 확인
|
||||||
|
|
||||||
|
## 다음 단계
|
||||||
|
|
||||||
|
- **2단계 문서화:** 본 문서 + [LIVE_TRADING.md](LIVE_TRADING.md), [RISK.md](RISK.md), [OPERATIONS.md](OPERATIONS.md)
|
||||||
|
- **3단계 이전:** [DEPLOYMENT_CHECKLIST.md](../05_ops/DEPLOYMENT_CHECKLIST.md) Phase C (dry-run)
|
||||||
|
|||||||
165
scripts/07_phase_c_paper_report.py
Normal file
165
scripts/07_phase_c_paper_report.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Phase C dry-run 종료 후 모의 수익률(참고) 집계.
|
||||||
|
|
||||||
|
- 입력: data/ops/paper_fires.jsonl (06 dry-run 발화 로그)
|
||||||
|
- 출력: docs/05_ops/phase_c_paper_report.json + 콘솔 요약
|
||||||
|
|
||||||
|
주의: 실계좌 수익이 아님. 발화가 N봉 후 가격으로 계산한 forward % 합산(참고).
|
||||||
|
hybrid 복리 PnL은 simulation_report.html 과 다릅니다.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import runpy
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
runpy.run_path(str(Path(__file__).resolve().parent / "_bootstrap.py"))
|
||||||
|
|
||||||
|
from config import ( # noqa: E402
|
||||||
|
MATCH_FORWARD_BARS,
|
||||||
|
MATCH_PRIMARY_INTERVAL,
|
||||||
|
SYMBOL,
|
||||||
|
TRADING_FEE_RATE,
|
||||||
|
)
|
||||||
|
from deepcoin.matching.label_outcomes import _forward_ret_vectorized # noqa: E402
|
||||||
|
from deepcoin.ops.monitor import Monitor # noqa: E402
|
||||||
|
from deepcoin.paths import PAPER_FIRES_LOG, PAPER_WEEKLY_REPORT_JSON # noqa: E402
|
||||||
|
|
||||||
|
_FEE_PCT = TRADING_FEE_RATE * 2 * 100
|
||||||
|
|
||||||
|
|
||||||
|
def load_paper_fires(path: Path) -> pd.DataFrame:
|
||||||
|
"""paper_fires.jsonl → DataFrame."""
|
||||||
|
if not path.is_file():
|
||||||
|
return pd.DataFrame()
|
||||||
|
rows = []
|
||||||
|
for line in path.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
rows.append(json.loads(line))
|
||||||
|
if not rows:
|
||||||
|
return pd.DataFrame()
|
||||||
|
return pd.DataFrame(rows)
|
||||||
|
|
||||||
|
|
||||||
|
def attach_forward_returns(fires: pd.DataFrame, close_df: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
would_trade=True 발화에 MATCH_FORWARD_BARS 기준 forward 수익률(%) 부여.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fires: paper 발화.
|
||||||
|
close_df: 3분 종가 (datetime index).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
forward_ret_pct 컬럼 추가.
|
||||||
|
"""
|
||||||
|
if fires.empty or close_df.empty:
|
||||||
|
fires["forward_ret_pct"] = np.nan
|
||||||
|
return fires
|
||||||
|
|
||||||
|
close_df = close_df.sort_index()
|
||||||
|
if not isinstance(close_df.index, pd.DatetimeIndex):
|
||||||
|
close_df.index = pd.to_datetime(close_df.index)
|
||||||
|
close_ts_ns = close_df.index.astype(np.int64).values
|
||||||
|
close_px = close_df["Close"].astype(float).values
|
||||||
|
|
||||||
|
sub = fires[fires["would_trade"] == True].copy() # noqa: E712
|
||||||
|
if sub.empty:
|
||||||
|
fires["forward_ret_pct"] = np.nan
|
||||||
|
return fires
|
||||||
|
|
||||||
|
sig = pd.to_datetime(sub["signal_dt"])
|
||||||
|
fire_ns = sig.astype(np.int64).values
|
||||||
|
c0 = sub["close"].astype(float).values
|
||||||
|
side = sub["side"].astype(str).values
|
||||||
|
ret, valid = _forward_ret_vectorized(
|
||||||
|
fire_ns, c0, close_ts_ns, close_px, side, MATCH_FORWARD_BARS, _FEE_PCT
|
||||||
|
)
|
||||||
|
fires = fires.copy()
|
||||||
|
fires["forward_ret_pct"] = np.nan
|
||||||
|
idx = sub.index
|
||||||
|
fires.loc[idx, "forward_ret_pct"] = np.where(valid, ret, np.nan)
|
||||||
|
return fires
|
||||||
|
|
||||||
|
|
||||||
|
def summarize(fires: pd.DataFrame) -> dict:
|
||||||
|
"""집계 dict."""
|
||||||
|
traded = fires[fires["would_trade"] == True] # noqa: E712
|
||||||
|
with_ret = traded[traded["forward_ret_pct"].notna()]
|
||||||
|
out: dict = {
|
||||||
|
"generated_at": datetime.now().isoformat(timespec="seconds"),
|
||||||
|
"symbol": SYMBOL,
|
||||||
|
"forward_bars": MATCH_FORWARD_BARS,
|
||||||
|
"fee_round_trip_pct": _FEE_PCT,
|
||||||
|
"total_signals": int(len(fires)),
|
||||||
|
"would_trade_count": int(len(traded)),
|
||||||
|
"skipped_count": int(len(fires) - len(traded)),
|
||||||
|
"labeled_count": int(len(with_ret)),
|
||||||
|
"note": (
|
||||||
|
"모의 forward 수익률. 실계좌·hybrid 복리 PnL 아님. "
|
||||||
|
"매수·매도 leg 미결합 단순 합산."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
if not with_ret.empty:
|
||||||
|
out["mean_forward_ret_pct"] = round(float(with_ret["forward_ret_pct"].mean()), 4)
|
||||||
|
out["sum_forward_ret_pct"] = round(float(with_ret["forward_ret_pct"].sum()), 4)
|
||||||
|
by_side = (
|
||||||
|
with_ret.groupby("side")["forward_ret_pct"]
|
||||||
|
.agg(["count", "mean", "sum"])
|
||||||
|
.round(4)
|
||||||
|
)
|
||||||
|
out["by_side"] = {k: v.to_dict() for k, v in by_side.iterrows()}
|
||||||
|
by_rule = (
|
||||||
|
traded.groupby("rule_id")
|
||||||
|
.size()
|
||||||
|
.to_dict()
|
||||||
|
if not traded.empty
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
out["fires_by_rule"] = by_rule
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""paper_fires 로드 → forward % → 리포트 저장."""
|
||||||
|
fires = load_paper_fires(PAPER_FIRES_LOG)
|
||||||
|
if fires.empty:
|
||||||
|
print(f"[07] 발화 로그 없음: {PAPER_FIRES_LOG}")
|
||||||
|
print(" Phase C 기간 06_execute_live.py (LIVE=0) 상시 실행 후 재시도")
|
||||||
|
return
|
||||||
|
|
||||||
|
mon = Monitor(cooldown_file=None)
|
||||||
|
df = mon.read_candles_from_db(SYMBOL, MATCH_PRIMARY_INTERVAL, max_rows=50000)
|
||||||
|
if df.empty:
|
||||||
|
df = mon.get_coin_some_data(SYMBOL, MATCH_PRIMARY_INTERVAL)
|
||||||
|
if not isinstance(df.index, pd.DatetimeIndex):
|
||||||
|
df = df.set_index(pd.to_datetime(df["datetime"]))
|
||||||
|
|
||||||
|
fires = attach_forward_returns(fires, df)
|
||||||
|
report = summarize(fires)
|
||||||
|
PAPER_WEEKLY_REPORT_JSON.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
PAPER_WEEKLY_REPORT_JSON.write_text(
|
||||||
|
json.dumps(report, ensure_ascii=False, indent=2),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"[07] 저장: {PAPER_WEEKLY_REPORT_JSON}")
|
||||||
|
print(f" 기간 로그: {fires['ts'].min()} ~ {fires['ts'].max()}")
|
||||||
|
print(f" 발화 {report['total_signals']} · 체결가정(would_trade) {report['would_trade_count']}")
|
||||||
|
if "sum_forward_ret_pct" in report:
|
||||||
|
print(
|
||||||
|
f" 모의 forward 합산: {report['sum_forward_ret_pct']}% "
|
||||||
|
f"(평균 {report['mean_forward_ret_pct']}%, "
|
||||||
|
f"{MATCH_FORWARD_BARS}봉 후, 참고용)"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(" forward 라벨 가능 건 없음 (봉 데이터 부족 또는 발화 없음)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -8,4 +8,5 @@ if str(ROOT) not in sys.path:
|
|||||||
# .env → config 일관 로드
|
# .env → config 일관 로드
|
||||||
from deepcoin.env_loader import load_project_env # noqa: E402
|
from deepcoin.env_loader import load_project_env # noqa: E402
|
||||||
|
|
||||||
load_project_env()
|
# 프로젝트 .env가 OS에 남은 LIVE_* 등보다 우선 (실거래 설정 일치)
|
||||||
|
load_project_env(override=True)
|
||||||
|
|||||||
17
scripts/run_phase_c.sh
Executable file
17
scripts/run_phase_c.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Phase C dry-run (~금요일 저녁). 프로젝트 루트에서 실행.
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
PY="${PY:-/Users/dsyoon/opt/anaconda3/envs/coin/bin/python}"
|
||||||
|
|
||||||
|
echo "=== Phase C: LIVE_TRADING_ENABLED=0 확인 ==="
|
||||||
|
$PY -c "import runpy; from pathlib import Path; runpy.run_path('scripts/_bootstrap.py'); import config; assert not config.LIVE_TRADING_ENABLED, 'LIVE must be 0'"
|
||||||
|
|
||||||
|
echo "=== 01 봉 갱신 (1일 1회 권장) ==="
|
||||||
|
$PY scripts/01_download.py
|
||||||
|
|
||||||
|
echo "=== verify ==="
|
||||||
|
$PY scripts/06_verify_live_dryrun.py
|
||||||
|
|
||||||
|
echo "=== 06 dry-run 상시 (Ctrl+C 종료) ==="
|
||||||
|
exec $PY scripts/06_execute_live.py
|
||||||
Reference in New Issue
Block a user