Commit Graph

30 Commits

Author SHA1 Message Date
joungmin
27189b1ad9 feat: add Fear & Greed filter to entry logic
- core/fng.py: F&G API wrapper with 1h cache (alternative.me)
  - FNG_MIN_ENTRY=41 (env-configurable), blocks entry below threshold
- core/strategy.py: call is_entry_allowed() before volume/regime checks
- daemon/runner.py: log F&G status on every scan cycle
- core/notify.py: include F&G value in buy/signal/status notifications
- core/trader.py: pass current F&G value to notify_buy

Backtest evidence (1y / 18 tickers / 1h candles):
  - No filter:   820 trades, 32.7% WR, avg +0.012%, KRW +95k
  - F&G >= 41:   372 trades, 39.5% WR, avg +0.462%, KRW +1.72M
  - Blocked 452 trades (avg -0.372%, saved ~1.68M KRW loss)

Also add:
- backtest_db.py: Oracle DB storage for backtest runs/results/trades
- fng_1y_backtest.py, fng_adaptive_backtest.py, fng_sim_comparison.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 15:56:17 +09:00
joungmin
673ce08d84 feat: add velocity entry, fast-poll thread, tighten BEAR threshold
- Add velocity-based entry signal in strategy.py (VELOCITY_THRESHOLD=0.10,
  VELOCITY_MIN_MOVE=0.5%, VELOCITY_MIN_AGE_M=5)
- Add fast-poll thread in daemon/runner.py (SIGNAL_POLL_INTERVAL=15s)
  for sub-minute velocity event detection
- Add vol_ratio tiered condition and get_active_signals() to strategy.py
- Change BEAR_THRESHOLD -1.0 → -0.5 in market_regime.py to catch
  slow downtrends earlier (weighted 2h score)
- Expand sell_reason VARCHAR2(500) in price_db.py DDL
- Add velocity_backtest.py and sim10m.py for strategy experimentation
- Update STRATEGY.md: correct regime algorithm description (weighted 2h
  score, not BTC 1h ±5%), add fast-poll/velocity sections, add backtest
  section D, add change history table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 10:17:08 +09:00
joungmin
612055162e docs: update backtest results and WF_WINDOW param after tuning 2026-03-02 15:16:00 +09:00
joungmin
3e2cdeb2c5 feat: relax WF filter WF_WINDOW 2→4 for 40min candle strategy
40min candles generate signals more frequently, making 2-consecutive-loss
blocking too aggressive. Analysis showed WF was blocking trades with 55.9%
win rate vs 43.9% for accepted trades.

WF_WINDOW=4 (4연패 시 차단) reduces blocked trades from 34→3 out of 91,
improving 45-day return from +14.87% to +44.56% with lower drawdown (-3.90%).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 15:15:37 +09:00
joungmin
6a685a7852 feat: add sim_45m40.py and update STRATEGY.md with 40min backtest results 2026-03-02 15:02:45 +09:00
joungmin
c6c6b0020f docs: clarify backtest sections with candle unit and data source 2026-03-02 14:58:26 +09:00
joungmin
bd802fb896 docs: update STRATEGY.md to 40min candle strategy 2026-03-02 14:55:06 +09:00
joungmin
a479bccee6 feat: switch vol-lead strategy from 1h to 40min candles
Simulation sweep showed 40min candles outperform 1h:
- 40min: 91 trades, 48.4% WR, +119% PnL, -11% DD
- 60min: 65 trades, 50.8% WR, +88% PnL, -12% DD

Changes:
- strategy.py: fetch minute10, resample to 40min for vol spike detection
  - LOCAL_VOL_CANDLES=7 (was LOCAL_VOL_HOURS=5, 5h/40min = 7 candles)
- monitor.py: ATR calculated from 40min candles
  - ATR_CANDLES=7 (was 5, now 5h in 40min units)
  - ATR_CACHE_TTL=2400s (was 600s, aligned to 40min candle)
- interval_sweep.py: new interval comparison tool (10/20/30/40/50/60min)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 14:52:48 +09:00
joungmin
4b6cb8ca0e 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>
2026-03-02 13:49:32 +09:00
joungmin
324d69dde0 feat: volume-lead strategy with compounding, WF filter, and DB-backed simulation
- core/strategy.py: replace trend strategy with volume-lead accumulation
  (vol spike + 2h quiet → signal, +4.8% rise → entry)
- core/trader.py: compound budget adjusts on both profit and loss (floor 30%)
- core/notify.py: add accumulation signal telegram notification
- ohlcv_db.py: Oracle ADB OHLCV cache (insert, load, incremental update)
- sim_365.py: 365-day compounding simulation loading from DB
- krw_sim.py: KRW-based simulation with MAX_POSITIONS constraint
- ticker_sim.py: ticker count expansion comparison
- STRATEGY.md: full strategy documentation
- .gitignore: exclude *.pkl cache files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 01:46:03 +09:00
joungmin
7c7fb08693 feat: replace trend strategy with volume-lead accumulation strategy
- strategy.py: rewrite should_buy() with volume-lead logic
  - detect accumulation: vol spike + 2h quiet price → record signal_price
  - entry: price rises ≥ TREND_AFTER_VOL% from signal_price
  - signal reset: timeout (8h) or price drops below signal_price
- .env: add PRICE_QUIET_PCT=2.0, TREND_AFTER_VOL=4.8, SIGNAL_TIMEOUT_H=8.0
- vol_lead_sim.py: add parameter sweep 0.5~5.0% + fine sweep 4.0~5.0%

Backtest result (9 tickers, 2026-01-15~): +4.8% threshold
  26 trades | 69% win rate | +73.38% cumulative (vs A 33 trades 45% +24.25%)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:22:20 +09:00
joungmin
54ce327c50 chore: add WF/shadow/momentum analysis simulation scripts
Scripts used to analyze and validate strategy changes:
- wf_cmp.py: WF window size comparison on 42 real trades
- wf_cmp2.py: WF comparison extended with price_history simulation
- shadow_sim.py: shadow rehabilitation sim without strategy filters
- shadow_sim2.py: post-rehabilitation performance simulation
- shadow_sim3.py: shadow rehabilitation sim with full strategy filters
- momentum_cmp.py: momentum filter A/B comparison
- trend_check.py: 2h price gain distribution analysis per ticker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 23:58:42 +09:00
joungmin
29d48f0fe9 feat: add shadow trading rehabilitation for WF-blocked tickers
When WF filter blocks a ticker, automatically start a virtual (shadow)
position with the same trailing/time stop logic. After WF_SHADOW_WINS
consecutive shadow wins, reset WF history to re-enable real trading.

- trader.py: add _shadow_positions, _shadow_cons_wins state;
  _shadow_enter(), get_shadow_positions(), update_shadow_peak(),
  close_shadow() functions; trigger shadow entry on WF block in buy()
- monitor.py: add _check_shadow_position() with ATR trailing + time stop;
  check shadow positions every 10s in run_monitor()
- Env: WF_SHADOW_WINS=2 (2 consecutive wins to rehabilitate)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 23:57:45 +09:00
joungmin
16b4c932a2 feat: bear block, trend-continuation entry, partial TP backtest
1. daemon/runner.py: skip scan entirely in bear regime
   - calls get_regime() at start of each scan loop
   - logs bear block with score before sleeping

2. core/strategy.py: trend-continuation entry filter
   - check_trend_6h(): 6h price trend >= 1% (rejects flash spikes)
   - 15-min confirmation watchlist (_watchlist dict)
   - should_buy() adds watchlist to existing 12h+regime+momentum logic
   - CONFIRM_SECONDS env var (default 900 = 15min)
   - TREND_6H_MIN_PCT env var (default 1.0%)

3. backtest.py: partial take-profit scenario comparison (--tp-cmp)
   - simulate(): partial_tp_pct / partial_tp_ratio params
   - blended pnl = ratio * partial_pnl + (1-ratio) * remaining_pnl
   - main_tp_cmp(): 3 scenarios A/B/C (none / +5% 50% / +3% 50%)
   - result: partial TP reduces cumulative return (-56% → -63%)
     big winners carry the strategy; trimming them hurts expected value

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 10:51:02 +09:00
joungmin
b0f0b3e82a feat: ATR adaptive trailing stop and 2-decimal formatting
- monitor.py: replace fixed 1.5% stop with ATR-based adaptive stop
  recent 5x 1h candles avg range × 1.5 mult, clamped 1.0%~4.0%
  10min cache per ticker to minimize API calls
  all log numbers formatted to 2 decimal places
- notify.py: apply 2 decimal places to price, P&L, fee, cum_profit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 10:32:24 +09:00
joungmin
83a229dd26 feat: add market regime filter and compound reinvestment
- Add market_regime.py: BTC/ETH/SOL/XRP weighted 2h trend score
  Bull(≥+1.5%) / Neutral / Bear(<-1%) regime detection with 10min cache
- strategy.py: dynamic TREND/VOL thresholds based on current regime
  Bull: 3%/1.5x, Neutral: 5%/2.0x, Bear: 8%/3.5x
- price_collector.py: always include leader coins in price history
- trader.py: compound reinvestment (profit added to budget, floor at initial)
- notify.py: regime info in hourly report, P&L icons (/, 💚/🔴)
- main.py: hourly status at top-of-hour, filter positions held 1h+
- backtest.py: timestop/combo comparison modes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 10:14:36 +09:00
joungmin
035b3e2f30 fix: use actual order fills for weighted avg sell price
After sell_market_order, query Upbit /v1/order API to get actual
trade fills. If split across multiple fills, compute weighted average
price and use actual paid_fee instead of estimate.

Falls back to get_current_price if order query fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 06:11:29 +09:00
joungmin
bcef128155 feat: add trade_id + full trade record to trade_results
Each buy generates a UUID trade_id stored in positions table.
Each sell links via same trade_id in trade_results, enabling
round-trip grouping of buy→sell pairs.

Additional fields saved per trade:
- fee_krw: commission amount (0.05% each side)
- krw_profit: net KRW profit/loss after fees
- buy_price / sell_price: exact prices
- invested_krw: capital deployed
- sell_reason: trailing stop / time stop / etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 05:54:06 +09:00
joungmin
60e739d18b fix: use local 5h volume baseline instead of 23h global average
23h average includes high-volume daytime periods, causing false negatives
at early morning hours. Now compare last 1h candle against the previous
5h local average (same time-of-day context) with 1.2x multiplier.

Also add momentum failure debug logs to show exact reason for rejection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 05:46:25 +09:00
joungmin
5df56a933e feat: use 60min volume, add KRW P&L log, relax re-entry after win
- strategy: replace daily volume check with 60-min candle volume
  (daily volume at 5am is tiny -> BTC/ETH never matched; now uses
  last 1h candle vs previous 23h avg × 2)
- trader: log actual KRW net profit and fee on every sell
- trader: skip re-entry +1% block when last trade was a win
  (allow re-entry on new trend signal even below last sell price)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 05:38:37 +09:00
joungmin
d2a5c3ae9e fix: persist sell prices to DB and add WF filter bootstrap
- price_db: add sell_prices table (ensure/upsert/load/delete)
- trader: restore _last_sell_prices from DB on startup so re-entry
  block survives restarts; persist each sell price immediately
- market: retry chunk requests up to 3 times with backoff on 429

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 05:19:22 +09:00
joungmin
0b264b304c feat: add backtest module with DB cache and scenario comparison
Backtest improvements:
- Add backtest.py with Oracle DB-backed OHLCV cache (no repeated API calls)
- Add backtest_trades table to cache simulation results by params hash
  (same params -> instant load, skip re-simulation)
- Add walk-forward scenario comparison (--walkforward-cmp)
- Add trend ceiling filter (--trend-cmp, max gain threshold)
- Add ticker win-rate filter (--ticker-cmp, SQL-based instant analysis)
- Precompute daily_features once per data load (not per scenario)

Live bot fixes:
- monitor: add hard stop-loss from buy price (in addition to trailing)
- strategy: fix re-entry condition to require +1% above last sell price
- price_collector: add 48h backfill on startup for trend calculation
- main: call backfill_prices() at startup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:28:27 +09:00
joungmin
4888aa0faa feat: add walk-forward trade filter to prevent re-entry on losing tickers
- Add trade_results table to Oracle DB for persistent trade history
- Record win/loss after each sell with pnl_pct
- Load last N trades per ticker from DB on startup (survives restarts)
- Block buy() when recent win rate (last 5 trades) < 40% threshold
- Configurable via WF_WINDOW and WF_MIN_WIN_RATE env vars
- Backtest showed improvement from -7.5% to +37.4% cumulative return

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:28:07 +09:00
joungmin
80ab004eba feat: replace volatility breakout with DB-backed real-time trend check
- price_history table on Oracle ADB stores prices every 10 minutes
- check_trend(): current price vs N hours ago (default 1h, +3% threshold)
- check_momentum(): unchanged (MA20 + 2x volume still applies)
- Ticker list cached 5 minutes to avoid 429 rate limits
- Collector starts 30s after boot to avoid simultaneous API calls
- Configurable: TREND_HOURS, TREND_MIN_GAIN_PCT in .env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 11:26:26 +09:00
joungmin
9fe3ce488e fix: auto-restart scanner on crash with Telegram notification
Wrap run_scanner() in while True loop so main thread recovers from
unhandled exceptions. Sends Telegram alert on crash before restarting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:42:05 +09:00
joungmin
2585d47140 feat: add hourly position status report via Telegram
Run status reporter thread every 60 minutes, sends current price,
PnL, drop from peak, and holding time for each position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:39:30 +09:00
joungmin
a287e48522 fix: restore positions on restart and fix notify env loading
- restore_positions(): read Upbit balances on startup to prevent
  double-buying after restart
- notify.py: read TOKEN/CHAT_ID inside _send() to avoid empty values
  when module is imported before load_dotenv()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:36:17 +09:00
joungmin
a799fbebbd feat: add Telegram notifications and configurable stop loss
- notify.py: buy/sell/error alerts via upbit_trading_jm_bot
- STOP_LOSS_PCT: trailing stop configurable via .env (default -5%)
- notify_buy/notify_sell called on every trade execution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:31:03 +09:00
joungmin
0713fb1e11 feat: add time stop rule for stale positions
Sell position if held for TIME_STOP_HOURS (default 24h) with less than
TIME_STOP_MIN_GAIN_PCT (default +3%) gain. Frees up capital for
fresh momentum opportunities.

Priority: trailing stop (-10%) checked first, then time stop.
Both thresholds configurable via .env.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:23:32 +09:00
joungmin
83bd51117f feat: initial upbit auto-trader implementation
Strategy C: volatility breakout (Larry Williams K=0.5) AND momentum
(MA20 + 2x volume surge) must both trigger for a buy signal.

Hard rules:
- Trailing stop: sell when price drops -10% from peak
- Max budget: 1,000,000 KRW total, up to 3 positions (333,333 KRW each)
- Scan top 20 KRW tickers by 24h trading volume every 60s
- Monitor positions every 10s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:20:02 +09:00