active.json (파라미터)

{
  "rsi_buy_below": 35,
  "rsi_sell_above": 70,
  "max_position_change_pct": 20,
  "max_concurrent_positions": 4,
  "min_volume_krw_24h": 1000000000,
  "stop_loss_pct": 3.0,
  "momentum_vol_ratio": 2.5,
  "momentum_change_pct": 3.0,
  "momentum_change_pct_max": 8.0,
  "recent_decline_skip_pct": 5.0,
  "trend_exit_buffer_pct": 0.3,
  "stop_cooldown_h": 24,
  "btc_riskoff_decline_pct": 1.5,
  "_version": 23,
  "_updated": "2026-06-23",
  "_note": "v23 = v22 code FROZEN, watchlist-only rotation. Yesterday live -1.40% (-14,462 KRW) driven by one normal stop-loss (MET2 -3.6%) plus heavy RSI round-trip churn on BTC/PYTH/SOL/ENA/IP -- expected take-profit behavior, not a logic defect. Macro neutral (kimchi -0.08%, funding 0.004%/8h), so risk-off gate stays dormant; no param change. Calibration: backtests still overstate realized by ~8-15pp (e.g. 06-21 9.25 pred vs -1.51 real), so the +7.8% 168h backtest is treated as inflated and NOT chased. A dormant macro gate was considered and rejected: it would be invisible in the neutral backtest, so it cannot 'clearly beat baseline' to promote, and adds bug risk for zero current benefit. All params FROZEN. Dropped CHZ/OPEN/ZAMA/ETHFI/HOLO (no recent trades or top-mover appearances); added AQT/MMT/AXS/ZRO/BREV (high-volume missed movers)."
}

watchlist.json (감시 종목)

{
  "tickers": [
    "KRW-BTC",
    "KRW-ETH",
    "KRW-XRP",
    "KRW-SOL",
    "KRW-ADA",
    "KRW-LINK",
    "KRW-BCH",
    "KRW-XLM",
    "KRW-INJ",
    "KRW-PYTH",
    "KRW-SEI",
    "KRW-ENA",
    "KRW-WLD",
    "KRW-AERO",
    "KRW-ORCA",
    "KRW-MET2",
    "KRW-ANIME",
    "KRW-IP",
    "KRW-MANA",
    "KRW-AQT",
    "KRW-MMT",
    "KRW-AXS",
    "KRW-ZRO",
    "KRW-BREV"
  ],
  "_updated": "2026-06-23"
}

active.py (현재 매매 로직)

# Active trading strategy v23 - code FROZEN (identical to v21/v22). Watchlist-only rotation.
# v21 logic: v20 + D1 falling-knife defense (BTC risk-off gate, btc_riskoff_decline_pct=1.5).
# Evidence from 178 paper round-trips (2026-05-22..06-10):
#   rsi_dip -> rsi_sell : 129 trades, 64% win, +0.72%/trade
#   rsi_dip -> stop_loss:  44 trades,  0% win, -5.57%/trade  <- killed by v17 trend filters
#   avg win +2.06% vs avg loss -3.56%
# v17 core (keep): uptrend-only dip buys, BTC regime gate, trend-break exit,
#   quick RSI>=70 take-profit (delaying exits to MA20-fade lost -7.9% vs +2.2%).
# v18 adds cooldown: skip buys for tickers in portfolio['recent_stops'] < stop_cooldown_h.
# Exits (stop-loss / RSI sell / trend break) are NEVER gated by volume or regime.
from __future__ import annotations


def _vol_krw_24h(m: dict) -> float:
    bars = (m.get('ohlcv_1h') or [])[-24:]
    return sum(b['close'] * b['volume'] for b in bars)


def _recent_decline_pct(m: dict, lookback: int = 8) -> float:
    # Drop from the 8h rolling high; proxies post-stop-loss cooldown
    bars = (m.get('ohlcv_1h') or [])[-lookback:]
    if len(bars) < 2:
        return 0.0
    recent_high = max(float(b['high']) for b in bars)
    current = float(bars[-1]['close'])
    if recent_high <= 0:
        return 0.0
    return (recent_high - current) / recent_high * 100.0


def _uptrend(ind: dict, price: float) -> bool:
    ma20 = float(ind.get('ma_20') or 0)
    ma60 = float(ind.get('ma_60') or 0)
    if ma20 <= 0 or ma60 <= 0 or price <= 0:
        return False
    return ma20 > ma60 and price > ma60


def _trend_broken(ind: dict, buffer_pct: float) -> bool:
    ma20 = float(ind.get('ma_20') or 0)
    ma60 = float(ind.get('ma_60') or 0)
    if ma20 <= 0 or ma60 <= 0:
        return False
    return ma20 < ma60 * (1 - buffer_pct / 100.0)


def decide(market: dict, portfolio: dict, params: dict) -> list[dict]:
    orders: list[dict] = []
    max_pct = int(params.get('max_position_change_pct', 20))
    rsi_buy = float(params.get('rsi_buy_below', 35))
    rsi_sell = float(params.get('rsi_sell_above', 70))
    max_pos = int(params.get('max_concurrent_positions', 4))
    stop_loss_pct = float(params.get('stop_loss_pct', 3.0))
    min_vol_krw = float(params.get('min_volume_krw_24h', 1_000_000_000))
    mom_vol_ratio = float(params.get('momentum_vol_ratio', 2.5))
    mom_change_pct = float(params.get('momentum_change_pct', 3.0))
    mom_change_pct_max = float(params.get('momentum_change_pct_max', 8.0))
    recent_decline_skip = float(params.get('recent_decline_skip_pct', 5.0))
    trend_exit_buffer = float(params.get('trend_exit_buffer_pct', 0.3))
    stop_cooldown_h = float(params.get('stop_cooldown_h', 24))
    btc_riskoff_decline_pct = float(params.get('btc_riskoff_decline_pct', 0) or 0)
    min_krw = 6000

    recent_stops = portfolio.get('recent_stops') or {}

    # Market regime: BTC 1h MA20 vs MA60. Down regime = exits only, hold cash.
    btc_ind = (market.get('KRW-BTC') or {}).get('indicators') or {}
    btc_price = float((market.get('KRW-BTC') or {}).get('price') or 0)
    regime_up = _uptrend(btc_ind, btc_price)
    btc_decline = _recent_decline_pct(market.get('KRW-BTC') or {})
    risk_off = btc_riskoff_decline_pct > 0 and btc_decline > btc_riskoff_decline_pct

    active_pos = sum(
        1 for h in portfolio['holdings'].values()
        if h['value_krw'] > min_krw
    )

    for ticker, m in market.items():
        ind = m.get('indicators') or {}
        rsi = ind.get('rsi_14')
        vol_ratio = float(ind.get('vol_ratio') or 1.0)
        change_24h = float(ind.get('change_24h_pct') or 0.0)
        price = float(m.get('price') or 0.0)

        holding = portfolio['holdings'].get(ticker, {})
        h_amount = float(holding.get('amount') or 0)
        h_value = float(holding.get('value_krw') or 0)
        avg_buy = float(holding.get('avg_buy_price') or 0)
        has_pos = h_value > min_krw

        # --- exits first, always reachable ---
        if has_pos and avg_buy > 0 and price > 0:
            drop_pct = (avg_buy - price) / avg_buy * 100.0
            if drop_pct >= stop_loss_pct:
                orders.append({
                    'ticker': ticker,
                    'side': 'sell',
                    'percentage': 100,
                    'reason': f'stop_loss {drop_pct:.1f}% below avg {avg_buy:.0f}',
                })
                active_pos -= 1
                continue
            if _trend_broken(ind, trend_exit_buffer):
                orders.append({
                    'ticker': ticker,
                    'side': 'sell',
                    'percentage': 100,
                    'reason': 'trend_break MA20 < MA60',
                })
                active_pos -= 1
                continue

        if rsi is None:
            continue

        # quick overbought take-profit (let-winners-run lost -7.9% vs +2.2%)
        if rsi >= rsi_sell and h_amount > 0:
            orders.append({
                'ticker': ticker,
                'side': 'sell',
                'percentage': 100,
                'reason': f'RSI {rsi:.1f} >= {rsi_sell}',
            })
            active_pos -= 1
            continue

        # --- entries: only in BTC up-regime, only liquid names ---
        if not regime_up:
            continue
        if risk_off:
            continue
        if _vol_krw_24h(m) < min_vol_krw:
            continue
        # skip re-entry into recently stopped tickers
        if recent_stops.get(ticker, 1e9) < stop_cooldown_h:
            continue

        krw = float(portfolio.get('krw_balance') or 0)
        can_buy = (not has_pos) and krw > min_krw * 2 and active_pos < max_pos

        # pullback buy: oversold dip WITHIN an uptrend, not a falling knife
        if can_buy and rsi <= rsi_buy and _uptrend(ind, price):
            if _recent_decline_pct(m) > recent_decline_skip:
                continue
            orders.append({
                'ticker': ticker,
                'side': 'buy',
                'percentage': max_pct,
                'reason': f'uptrend pullback RSI {rsi:.1f} <= {rsi_buy}',
            })
            active_pos += 1
            continue

        # momentum buy: volume surge with banded 24h change (kept from v16)
        if (
            can_buy
            and vol_ratio >= mom_vol_ratio
            and mom_change_pct <= change_24h <= mom_change_pct_max
            and rsi < rsi_sell - 5.0
        ):
            orders.append({
                'ticker': ticker,
                'side': 'buy',
                'percentage': max_pct,
                'reason': f'momentum vol_ratio={vol_ratio:.1f} chg24h={change_24h:.1f}%',
            })
            active_pos += 1

    return orders

notes.md (회고 노트)

# Strategy Notes

매일 reflector가 회고를 여기에 append 합니다. 사람도 직접 적어도 됨.

---

## 2026-05-22 v1.1 (매도 로직 전량 청산으로 변경)

- 이전 v1에서 매도가 보유의 20%씩이라 슬롯이 안 비어 새 종목 매수가 막힘.
- 변경: RSI ≥ 70 신호 시 percentage=100 (전량 매도) → 슬롯 즉시 해제.
- reflector는 향후 손절 / 익절 / 트레일링 등 자유롭게 sell trigger 추가 가능
  (decide() 시그니처와 order-shape만 유지하면 OK).

---

## 2026-05-22 v1 (초기 baseline 가동)

전략 v1 — 단순 RSI 평균회귀.
- 매수: RSI(14, 5분봉) ≤ 30, 현재 포지션 없음, 동시 보유 ≤ 5
- 매도: RSI ≥ 70, 보유 중
- 거래량: 한 번에 잔고/보유의 20%

가설: KRW 상위 20개에서 단기 과매도/과매수 평균회귀 기회 포착.
다음 회고에서 확인할 것: 진입 이후 평균 보유 시간, 승률, 미체결로 놓친 기회.

## 2026-05-23 (v2)

## 2026-05-23 v2 (stop-loss + momentum signal + watchlist rotation)
- Live PnL -2.45% (-24,509 KRW); root cause: positions bought 22:23-23:38 UTC sat in drawdown 5+ hours with no exit trigger (RSI never hit 70 on the way down).
- Fix 1: added **stop-loss at -3%** from avg_buy_price — prevents open-ended overnight drawdowns.
- Fix 2: **volume gate now enforced in code** via ohlcv_1h sum (was params-only; untrusted in decide()).
- Fix 3: **momentum breakout buy** — vol_ratio >= 2.5 AND 24h change >= 3% AND RSI < 65 → enters position; targets movers like KRW-IN (+44%), KRW-MTL (+17%), KRW-ALT (+4.4%, 61.8B KRW vol) that pure mean-reversion missed entirely.
- Watchlist: removed KRW-ALGO (sold, flat signals), KRW-SAND (no activity); added KRW-IN, KRW-MTL, KRW-ALT, KRW-ZBT, KRW-HP (all top-24h movers with >= 8.9B KRW volume).
- RSI thresholds unchanged (30/70) — signal quality was fine; missing stop-loss was the structural gap.

## 2026-05-25 (v3)

## 2026-05-25 (v2 code unchanged, watchlist rotation)
- Live PnL +2.56% (+24,849 KRW): RSI mean-reversion + stop-loss performing as designed; no structural gaps.
- Backtest 48h: +1.51% / 102 trades — healthy cadence, no overfit signals.
- BCH round-trip loss (-1.42%): bought RSI=30.0 boundary at 528,000, sold RSI=79.2 at 520,500; price trended down despite RSI recovery; loss contained, stop-loss not needed.
- Missed: KRW-VVV (+5.89%, 20.7B KRW), KRW-TRUST (+5.77%, 21.2B KRW), KRW-POLYX (+8.23%, 3.6B KRW), KRW-JTO (+1.07%, 10.2B KRW) — all absent from watchlist; momentum signal would have entered VVV/TRUST at vol+change thresholds.
- Watchlist: **added** KRW-VVV, KRW-TRUST, KRW-JTO, KRW-POLYX; **removed** KRW-ZBT, KRW-DOT, KRW-MATIC (no trades in 24h, likely flat volume).
- Params and code frozen at v2 — signal quality confirmed positive; no evidence for threshold adjustment.

## 2026-05-26 (v4)

## 2026-05-26 (v4)
- Live PnL +1.86% (+18,476 KRW): RSI mean-reversion + stop-loss performing cleanly; VVV stop-loss at -3.6% fired and capped loss, then RSI re-entry recycled the slot profitably.
- Backtest 48h: -1.30% / 87 trades — diverges from live result; likely intra-bar timing artifact; live signal quality confirmed positive so all params frozen.
- Missed: ERA (+17.46%, 36B KRW vol) and LA (+3.53%, 6.7B KRW vol) — absent from watchlist; IN (+19.47%) was listed but momentum RSI gate (< 65) likely blocked entry once the vol spike arrived.
- Watchlist: **added** KRW-ERA (36B vol, top-2 mover), KRW-LA (6.7B vol, +3.5%); **removed** KRW-ALT, KRW-JTO, KRW-HP (zero trades in 24h, volume too thin to trigger any signal).
- Strategy code and all thresholds frozen at v2 baseline — no structural gaps identified.

## 2026-05-27 (v5)

## 2026-05-27 (v4 — watchlist rotation)
- Live +1.83% (+14,604 KRW); 3rd consecutive positive day — RSI mean-reversion and stop-loss operating as designed.
- Stop-losses fired cleanly: TRUST -3.9%, ETC -3.2%; losses capped within tolerance.
- Profitable round-trips: BTC (RSI 27.9→77.1), ATOM (RSI 26.7→72.1), VVV (RSI 29.0→81.6), IN (RSI 22.2→80.0).
- Missed: AZTEC (+11.3%, 22B vol), SEI (+7.7%, 10B vol), VIRTUAL (+5.0%, 27B vol), RENDER (+4.5%, 22B vol) — all absent from watchlist; adding now.
- Watchlist: **added** AZTEC, SEI, VIRTUAL, RENDER; **removed** ERA, LA (zero trades in 24h since added), UNI, LTC (no RSI signals, consistently inactive).
- Code and all params frozen at v4 baseline — signal quality confirmed positive for 3rd day; no structural gaps.

## 2026-05-28 (v6)

## 2026-05-28 (v6 — watchlist rotation)
- Live -3.38% (-27,357 KRW): stop-losses on NEAR (-3.9% below avg 3799) and AZTEC (-3.2% below avg 36) were the primary drag; RSI exits on XRP/ATOM/ETH/ETC/BTC all remained profitable.
- Backtest 48h: +0.75% / 121 trades — logic still valid; live/backtest divergence attributed to volatile watchlist entries, not strategy structure.
- Macro: BTC -3.35% over 7 days; mean-reversion entries carry higher stop-loss risk in sustained downtrend — monitor closely.
- Code and all params frozen (RSI 30/70, stop-loss 3%) — no structural gaps; single bad day insufficient evidence to adjust thresholds.
- Missed: FF (+9.4%, 21B vol), ALT (+8.7%, 29B vol), ICP (+4.2%, 29B vol), PROS (+3.1%, 17B vol), JTO (+3.1%, 8.5B vol) — all absent from watchlist; adding now.
- Watchlist: **added** FF, ALT, ICP, PROS, JTO; **removed** AZTEC (stop-loss, thin liquidity), SEI, MTL, VVV, TRUST, DOGE, TRX, AVAX (zero trades in 24h).

## 2026-05-29 (v7)

## 2026-05-29 (v7 — macro guard + watchlist rotation)
- Live -4.80% (-29,049 KRW): 2nd consecutive loss day; 4 stop-losses (JTO -6.9%, BCH -5.3%, RENDER -3.7%, ATOM -3.0%) dominated; backtest also -4.34% — both aligned, confirms downtrend exposure not a one-off.
- Root cause: BTC 7d -5.5%; mean-reversion buys in correlated altcoins repeatedly trigger stop-loss when the whole market drifts down between candles.
- Code change: macro guard added — if BTC 24h change < -1.5%, cap `max_pos` at 3 and disable momentum buys; limits simultaneous correlated entries in sustained downtrends.
- Param change: `max_concurrent_positions` 5→4 as conservative baseline (fewer simultaneous bets on bad days).
- Watchlist: **removed** JTO, RENDER, ATOM (stop-loss victims), ALT (zero activity); **added** HBAR (+7.14%, 21B vol), ZAMA (+5.79%, 14B vol), MON (+5.33%, 13B vol), PROVE (+2.1%, 5B vol).
- RSI thresholds (30/70) and stop-loss (3%) unchanged — signal quality valid; regime mismatch was the structural gap.

## 2026-05-30 (v8)

## 2026-05-30 (v8 — watchlist rotation, code frozen)
- Live +8.24% (+38,967 KRW); backtest 48h +5.89% / 94 trades — both strongly positive. v7 macro guard + 4-position baseline now validated after the two prior loss days that motivated it.
- Clean RSI round-trips: ETH (29.1→75.0), XLM (25.9→76.9), VIRTUAL (20.7→72.0), plus FF/ADA/BTC; HBAR oversold buys (RSI 14.3/16.7) rode a +12.1% move on 28B vol.
- No structural gaps. RSI 30/70, stop-loss 3%, momentum gate, and macro guard all FROZEN — a single strong day is not evidence to loosen risk controls, especially with BTC 7d still -5.7%.
- Missed: INJ (+13.2%, 30.7B vol), IOTA (+8.4%, 7.5B vol), PYTH (+4.2%, 10.8B vol) — absent from watchlist; all high-volume, adding.
- Watchlist: added INJ, IOTA, PYTH; removed ZAMA, MON, PROVE (zero trades since v7 add, liquidity too thin to trigger any signal).

## 2026-05-31 (v9)

## 2026-05-31 (v9 — momentum upper band)
- Live -0.58% (-2,917 KRW); backtest 48h +9.66% / 100 trades — large live/backtest divergence continues, weighting the live result.
- Primary drag: HBAR churned 3x — momentum buy at chg24h=+19.5% then stop-loss -3.9%, plus two RSI re-entries stopping at -4.1%; INJ (-3.1%) and an XLM (-3.3%) stop-loss also fired.
- Code change: momentum entries now require 3% <= chg24h <= 12% (new momentum_change_pct_max=12). Buying names already up ~20% chases blow-off tops that reverse into stop-loss; this guard would have skipped the HBAR momentum entry.
- RSI 30/70, stop-loss 3%, macro guard, 4-position baseline all FROZEN — change tightens risk, not loosens it.
- Watchlist: added IO (18B vol, +12.7%), POKT (21B vol, +10.5%) from missed movers; removed HBAR (repeated whipsaw stop-losses).

## 2026-06-02 (v10)

## 2026-06-02 (v10 - bear-regime deeper RSI gate)
- Live -6.97% (-35,087 KRW); backtest 48h flat (-0.05% / 17 trades) - divergence persists, weighting live.
- Primary drag: mean-reversion whipsaw in a BTC downtrend (7d -4.94%). XLM bought RSI 29.6 stopped -4.4%, re-bought RSI 26.3 stopped -4.2%; BTC stop -3.0%; ADA/XRP/PROS marginal 26-29 entries bled.
- Code change: when in_bear (BTC 24h < -1.5%), RSI buy gate deepens 30->25 (new rsi_buy_bear=25). Marginal 26-30 oversold reads are falling knives in a drift; the deepest names (ETH 17.6, IN 20.0, BTC 22.6) still qualify.
- RSI sell 70, stop-loss 3%, momentum band 3-12%, 4-position baseline all FROZEN - this tightens entry risk, does not loosen it.
- Missed: NEAR +15.3% (watchlisted but never oversold/in-band), JTO +7.3% (13.8B vol), SEI +5.0% (10.4B vol), CHZ +8.8% (6.4B vol).
- Watchlist: added JTO, SEI, CHZ (high-volume recurring movers); removed POLYX (3.6B thin), ICP (no trades since add).

## 2026-06-03 (v11)

## 2026-06-03 (v11 - bear-regime bounce confirmation)
- Live -5.59% (-25,996 KRW); backtest 48h -1.70% / 74 trades - both negative, downtrend exposure confirmed not a one-off.
- Root cause: six stop-losses (BCH -3.2%, FF -3.6%, SOL -3.5%, BTC -3.2%, PROS -4.0%, IN -3.2%), ~0.7% drag each, account for the whole loss; v10's RSI<=25 deep gate fired but the oversold names were bought mid-fall and stopped before reverting.
- Code change: in_bear mean-reversion buys now require a one-bar bounce (last closed 1h close > prior close, new bear_buy_confirm=true); skips knives still falling, keeps oversold names that have begun to turn.
- RSI sell 70, stop-loss 3%, momentum band 3-12%, 4-position baseline, macro cut all FROZEN - tightens entry risk only.
- Missed: PIEVERSE +18.1% (70B vol), ENA +11.1% (13.8B), ZORA +8.0% (16.3B), ICP +6.8% (8.97B, left out to avoid re-add churn).
- Watchlist: added PIEVERSE, ENA, ZORA (high-volume movers); removed POKT (526M vol, below 1B trigger floor) and PROS (repeat stop-loss victim).

## 2026-06-04 (v12)

## 2026-06-04 (v12 - watchlist rotation, code frozen)
- Live +0.39% (+1,730 KRW); first positive day since v8, validating v11's bear bounce-confirm gate. FF round-trip 139->153 (+10%) plus RSI exits (SOL 86, IN 75, BCH 74.6, CHZ 80) offset three stops (BTC -3.1%, ZORA -4.7%, FF -3.5%).
- Backtest 48h -4.99% / 108 trades; live/backtest divergence persists, weighting live as established.
- Code and ALL params FROZEN - RSI 30/70, stop 3%, rsi_buy_bear 25, bounce-confirm, momentum band 3-12%, 4-pos baseline. One good day is not evidence to touch risk controls; the v11 change is working.
- Missed: WLD +37.0% (213B vol!), ARKM +10.6% (3.9B), XPL +8.6% (9.8B) - all absent from watchlist, high volume, adding.
- Watchlist: added WLD, XPL, ARKM; removed ZORA (immediate -4.7% stop-loss victim, re-add if it stabilizes).

## 2026-06-05 (v13)

## 2026-06-05 (v13 - watchlist rotation, code frozen)
- Live -0.94% (-4,134 KRW); mild loss, no stop-loss cluster. Three RSI buys (XRP 16.7, SOL 29.4, BCH 28.3) opened near cutoff and are still unrealized - drag is small and not locked in.
- Backtest 48h flipped strongly positive: +5.55% / 78 trades (vs -4.99% prior cycle); BTC +3.3% (non-bear). Framework performing in sim.
- Code and ALL params FROZEN - RSI 30/70, stop 3%, rsi_buy_bear 25, bounce-confirm, momentum band 3-12%, 4-pos baseline. A sub-1% down day against a positive backtest is not evidence to touch risk controls.
- Missed: ESP +18.9% (2.2B vol, too thin to chase), ZBT +8.5% (14.1B vol), SAHARA +2.1% (17.7B vol).
- Watchlist: added ZBT, SAHARA (high-volume movers); removed IO, ARKM (no trades since add, lowest conviction).

## 2026-06-06 (v14)

## 2026-06-06 (v14 - deeper bear position cap)
- Live -4.99% (-21,265 KRW); backtest 48h -0.92% / 68 trades. Loss driven by a 5-name stop-loss cluster in a mild BTC downtrend (-1.86%): BCH -6.5%, XRP -4.0%, XLM -3.4%, IN -3.3%, BTC -3.2%.
- Recurring root cause: bear-regime mean-reversion entries cluster and stop together. This is the 4th bear-ish loss day in 6 cycles; v11 bounce-confirm + the original max_pos-1 cut were not enough.
- Code change: in_bear now caps concurrent positions at 2 (new max_concurrent_positions_bear=2) instead of 3, shrinking correlated downtrend exposure. Tightens risk only; RSI 30/70, stop 3%, rsi_buy_bear 25, bounce-confirm, momentum band 3-12% all FROZEN.
- BCH stopped at 6.5% (gapped past the 3% stop between checks) is slippage, not a logic gap; fewer concurrent bear positions is the only lever decide() controls.
- Missed: PROS +8.5% (9.1B vol, skipped - repeat stop-loss victim), JST +6.9% (1.9B), KITE +3.5% (4.9B).
- Watchlist: added KITE, JST (high-volume missed movers); removed ETC, IOTA (no trades/movement, lowest conviction).

## 2026-06-08 (v15)

## 2026-06-08 (v15)

## 2026-06-08 (v15 - recent-decline re-entry guard + momentum upper band cut)
- Live -4.97% (-19,289 KRW); backtest 48h +12.9% / 88 trades. Loss driven by 3 stop-losses (SAHARA -3.8%, FF -3.5%, IN -3.2%) plus same-ticker re-entry churn: IN re-bought immediately after stop-loss, PIEVERSE x3 and CHZ x2 cycles in one day.
- Root cause: no cooldown after stop-loss. RSI stays oversold after a -3% drop, so decide() re-enters the same still-falling position and churns losses.
- Code change 1: new `_recent_decline_pct` helper skips RSI mean-reversion buys when price is >5% below its 8h rolling high (param `recent_decline_skip_pct=5.0`). Blocks re-entry into tickers still actively falling without requiring trade history in the function.
- Code change 2: `momentum_change_pct_max` 12% -> 8%; SAHARA momentum-bought at +8.8% reversed immediately into stop-loss — names already up 8-12% are more likely blow-off tops.
- RSI thresholds 30/70, stop_loss 3%, rsi_buy_bear 25, bear bounce-confirm, 4-pos non-bear baseline all FROZEN.
- Missed: OPEN +12.7% (4.7B vol), MEGA +4.7% (3.4B vol); both absent from watchlist.
- Watchlist: added OPEN, MEGA (high-volume missed movers); removed NEAR, JST (no trades in recent cycles, lowest conviction).

## 2026-06-10 (v16)

## 2026-06-10 (v16 - code frozen, watchlist rotation)
- Live +3.31% (+12,145 KRW); backtest 48h +5.26% / 68 trades. First cycle where live and backtest agree positive - v15's recent-decline guard and 8% momentum cap are doing their job (zero same-ticker re-entry churn; CHZ round-trip 38.2->42.2 +10.5%, clean RSI 27->72 cycle).
- Two stops (VIRTUAL -3.5%, ENA -5.5%) were normal risk control, not clusters, and neither was re-bought - exactly the behavior v15 targeted.
- Code and ALL params FROZEN: RSI 30/70, stop 3%, rsi_buy_bear 25, bounce-confirm, momentum 3-8%, recent_decline_skip 5%, 4/2 position caps. One good day is not evidence to touch risk controls (v12 precedent).
- Missed: KAT +16.3% (6.8B vol), CHIP +15.5% (11.3B), SLX +5.6% (51.6B) - all absent from watchlist with strong volume, adding all three.
- Watchlist: removed IN and FF (repeat stop-loss/churn victims v14-v15) and SAHARA (v15 momentum blow-off stop); count stays at 25.


## 2026-06-11 (v17) — manual rewrite by Claude (사용자 요청)

- 실현 손익 분석(178 라운드트립): rsi_dip→stop_loss 44회 전패, 평균 -5.57%, 누적 -245%. 손실 전부가 하락추세 딥매수.
- v17: (1) 코인 자체 상승추세(1h MA20>MA60, price>MA60)에서만 눌림목 매수 (2) BTC 레짐 다운이면 신규진입 전면 금지 (3) MA20<MA60 추세붕괴 시 즉시 이탈 (4) rsi_buy 30→35
- 주의: 백테스트는 watchlist 선택편향(최근 상승 코인으로 구성)으로 딥매수 수익을 체계적으로 과대평가함. v16 7d bt +15.4%였지만 실현은 20일 -24%. v17 평가는 실현 paper PnL과 calibration 표로 할 것.


## 2026-06-11 (v18) — manual, 인프라 대수술과 함께

- v18 = v17 + 손절 쿨다운 24h (`portfolio['recent_stops']`). IN/FF/SAHARA 반복 손절 출혈 차단.
- **기각된 실험**: RSI70 즉시익절 대신 MA20 페이드까지 보유(let winners run) → 중립 유니버스 72h에서 -7.92% vs 즉시익절 +2.22%. 즉시익절이 수익 원천임이 재확인됨. 새 증거 없이 지연익절 재제안 금지.
- 인프라: trader 5분 주기(손절 슬리피지 -5.57%→개선 기대), 백테스트 5분 스텝 정렬, promote 평가를 거래대금 상위 중립 유니버스로(워치리스트 선택편향 제거), 전/후반 -3% 로버스트니스 게이트, 24h -5% 드로다운 가드(전략 밖 하드 컨트롤), 백테스터 BTC fetch 실패 시 명시적 에러(조용히 0거래 평가되던 잠복 버그).

## 2026-06-17 (v19)

## 2026-06-17 (v19 — watchlist rotation, code frozen)
- Live -1.13% (-12,172 KRW); 3 stop-losses (ENA -3.1%, PIEVERSE -3.1%, BCH -3.1%) partially offset by profitable RSI-exit round-trips on XRP, ETH, BTC, PYTH, ZBT, INJ — cost of normal risk control, not a logic defect.
- Macro neutral: kimchi premium -0.06%, BTC funding 0.002%/8h — no crowding or retail euphoria signals; no regime-gate adjustment needed.
- 7d backtest +23.1% is almost certainly inflated given calibration divergence history (avg backtest overstates realized by ~8pp); no param changes warranted.
- Missed: JTO +11.0% (in watchlist — uptrend with no RSI≤35 dip triggered; no fix needed), AERO +10.6% (5.6B), CFG +5.9% (4.8B), MMT +9.3% (2.1B) — all absent from watchlist.
- Watchlist: removed VIRTUAL (v16 stop victim, inactive), PIEVERSE (stopped out yesterday), KAT/CHIP/XPL (no trades or top-mover appearances in recent cycles); added AERO, CFG, MMT, CTC (missed movers, all ≥2B KRW daily volume).
- All params frozen: RSI 35/70, stop 3%, cooldown 24h, momentum 3-8%, recent_decline_skip 5%, max_pos 4.

## 2026-06-18 (v20)

## 2026-06-18 (v20 — watchlist rotation, code frozen)
- Live -0.47% (-4,954 KRW); 2 stop-losses (ZBT 3.2%, CTC 3.9%), LINK/PYTH trend-break exits, profitable RSI exits on WLD and INJ — normal risk control, no logic defect.
- Macro neutral: kimchi premium 0.01%, BTC funding 0.0026%/8h — no crowding or retail euphoria; no regime adjustment needed.
- Calibration: backtests continue to overstate realized by 8-15pp; 7d bt +10.6% treated as inflated. No param changes.
- Missed: ETHFI +12.9% (1.5B vol), HOLO +7.0% (1.6B), ORCA +5.6% (17.5B), ZAMA +5.3% (8.5B) — all absent from watchlist. ENA +10.8% was in watchlist but no RSI<=35 dip triggered; expected behavior.
- Watchlist: removed ZBT/CTC (stopped out today, cooldown active), MEGA (no conviction, same-session round-trip), KITE (no recent appearances); added ORCA, ZAMA, ETHFI, HOLO (missed movers, all >=1.5B vol).
- All params frozen: RSI 35/70, stop 3%, cooldown 24h, momentum 3-8%, recent_decline_skip 5%, max_pos 4.

## 2026-06-18 (v21)

v21 - D1 falling-knife defense (BTC risk-off gate, threshold 1.5%): skip new buys when BTC fell >1.5% from 8h high. Manual candidate, passed neutral-universe gate (cand 15.54% vs base 14.82%, edge +0.72%p, halves [10.73, 4.35]). Conditional/dormant in uptrends; addresses 06-16 selloff. D2 (5m green) was tested-and-rejected: -1.65%p on same gate.

## 2026-06-21 (v22)

## 2026-06-21 (v22 — watchlist rotation, code frozen)
- Live -1.38% (-14,651 KRW); 2 stop-losses (CFG -3.7%, JTO -3.3%) plus RSI round-trip churn on BTC/ETH/XRP/ORCA — normal risk control, no logic defect. SOL bought into close is the only carry.
- Macro neutral: kimchi premium 0.1%, BTC funding 0.0008%/8h — no euphoria/crowding; risk-off gate and any macro overlay stay dormant. No param change.
- Calibration: 168h backtest +8.3% treated as inflated (history overstates realized by ~8-15pp); no chasing. All params FROZEN (RSI 35/70, stop 3%, cooldown 24h, momentum 3-8%, recent_decline 5%, risk-off 1.5%, max_pos 4).
- Missed (all absent from watchlist, strong volume): MANA +14.6% (6.0B), MET2 +10.0% (22.6B), IP +4.6% (7.4B), ANIME +6.2% (12.9B).
- Watchlist: removed CFG/JTO (stopped out today, cooldown active) and SLX/MMT (no recent trades/appearances); added MET2, ANIME, IP, MANA. Count 24.

## 2026-06-23 (v23)

## 2026-06-23 (v23 — watchlist rotation, code frozen)
- Live -1.40% (-14,462 KRW); one stop-loss (MET2 -3.6%) plus RSI round-trip churn on BTC/PYTH/SOL/ENA/IP — normal take-profit cycling, no logic defect. Currently carry BTC + PYTH.
- Macro neutral: kimchi -0.08%, funding 0.004%/8h — no euphoria/crowding; risk-off gate stays dormant. No param change.
- Calibration: 168h backtest +7.8% (both halves positive) treated as inflated; recent days show backtest overstating realized by ~8-15pp. No chasing. All params FROZEN (RSI 35/70, stop 3%, cooldown 24h, momentum 3-8%, recent_decline 5%, risk-off 1.5%, max_pos 4).
- Rejected: a dormant macro euphoria gate — invisible in the neutral backtest so it can't beat baseline to promote, and adds bug risk for zero current benefit. Code stays identical to v21/v22.
- Missed (absent, strong volume): AQT +6.5% (40.2B), MMT +11.6% (8.0B), AXS +3.1% (9.4B), ZRO +4.8% (2.5B), BREV +7.4% (2.2B).
- Watchlist: dropped CHZ/OPEN/ZAMA/ETHFI/HOLO (no recent trades or top-mover hits); added AQT/MMT/AXS/ZRO/BREV. Count 24.

전략 백업 (30개)

20260623-090529-watchlist.json
20260623-090529-active.py
20260623-090529-active.json
20260621-090543-watchlist.json
20260621-090543-active.py
20260621-090543-active.json
20260618-231151-watchlist.json
20260618-231151-active.py
20260618-231151-active.json
20260618-090706-watchlist.json
20260618-090706-active.py
20260618-090706-active.json
20260617-090734-watchlist.json
20260617-090734-active.py
20260617-090734-active.json
20260611-champion-v1.py
20260611-005700-v17-active.py
20260611-004700-v16-active.py
20260610-090159-watchlist.json
20260610-090159-active.py
20260610-090159-active.json
20260608-090551-watchlist.json
20260608-090551-active.py
20260608-090551-active.json
20260606-090244-watchlist.json
20260606-090244-active.py
20260606-090244-active.json
20260605-090222-watchlist.json
20260605-090222-active.py
20260605-090222-active.json
📊대시보드 🏆챔피언 📋거래 🔍회고 전략