fix: persist WF shadow state to DB and tighten ATR max stop

- core/price_db.py: add wf_state table CRUD (ensure/upsert/load/delete)
  to persist shadow_cons_wins across restarts
- core/trader.py: save WF blocked state on shadow enter/close,
  restore shadow_cons_wins on startup from DB
- core/monitor.py: lower ATR_MAX_STOP 4.0% → 2.0% based on sweep results
- atr_sweep.py: new ATR_MAX_STOP sweep tool using real ATR calc from DB

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-03-02 13:49:32 +09:00
parent 324d69dde0
commit 4b6cb8ca0e
4 changed files with 310 additions and 2 deletions

View File

@@ -18,6 +18,7 @@ from .price_db import (
ensure_trade_results_table, record_trade, load_recent_wins,
ensure_sell_prices_table, upsert_sell_price, load_sell_prices,
get_cumulative_krw_profit,
ensure_wf_state_table, upsert_wf_state, load_wf_states, delete_wf_state,
)
load_dotenv()
@@ -148,6 +149,10 @@ def _shadow_enter(ticker: str) -> None:
}
cons = _shadow_cons_wins.get(ticker, 0)
try:
upsert_wf_state(ticker, is_blocked=True, shadow_cons_wins=cons)
except Exception as e:
logger.error(f"WF 상태 DB 저장 실패 {ticker}: {e}")
logger.info(
f"[Shadow진입] {ticker} @ {price:,.0f}"
f"(가상 — WF 재활 {cons}/{WF_SHADOW_WINS}연승 필요)"
@@ -185,6 +190,15 @@ def close_shadow(ticker: str, sell_price: float, pnl_pct: float, reason: str) ->
if do_wf_reset:
_shadow_cons_wins.pop(ticker, None)
# shadow 상태 DB 갱신 (_shadow_lock 해제 후)
try:
if do_wf_reset:
delete_wf_state(ticker)
else:
upsert_wf_state(ticker, is_blocked=True, shadow_cons_wins=cons)
except Exception as e:
logger.error(f"WF 상태 DB 갱신 실패 {ticker}: {e}")
mark = "" if is_win else ""
logger.info(
f"[Shadow청산] {ticker} {spos['buy_price']:,.0f}{sell_price:,.0f}"
@@ -239,7 +253,7 @@ def restore_positions() -> None:
DB에 저장된 실제 매수가를 복원하고, Upbit 잔고에 없으면 DB에서도 삭제한다.
"""
# trade_results / sell_prices 테이블 초기화
# trade_results / sell_prices / wf_state 테이블 초기화
try:
ensure_trade_results_table()
except Exception as e:
@@ -248,6 +262,21 @@ def restore_positions() -> None:
# 시작 시 복리 예산 복원 (이전 세션 수익 반영)
_recalc_compound_budget()
# WF 상태 복원 (shadow 연속승 횟수 유지)
try:
ensure_wf_state_table()
wf_states = load_wf_states()
for ticker, state in wf_states.items():
if state["is_blocked"]:
_shadow_cons_wins[ticker] = state["shadow_cons_wins"]
if wf_states:
logger.info(
f"[복원] WF 차단 상태 {len(wf_states)}건 복원: "
+ ", ".join(f"{t}(shadow={s['shadow_cons_wins']})" for t, s in wf_states.items())
)
except Exception as e:
logger.warning(f"WF 상태 복원 실패 (무시): {e}")
try:
ensure_sell_prices_table()
except Exception as e: