diff --git a/llm_analyzer.py b/llm_analyzer.py index 4ff4708..d40f4d6 100644 --- a/llm_analyzer.py +++ b/llm_analyzer.py @@ -26,7 +26,7 @@ def analyze_market(indicator_summary: str, account_status: str) -> list[dict]: ### 最高優先:多時間框架過濾(現貨,只做多) - 1小時趨勢為多頭(EMA9 > EMA21)時,才考慮 BUY - 1小時為空頭時,只持有或平倉(SELL),不開新倉 -- 1小時 ADX < 20(盤整)時,避免進場 +- 1小時 ADX < 20(盤整)時,降低信心但不禁止進場 - 1小時 ADX > 25 且方向一致 = 高信心交易 ### 進場訊號 (BUY) — 需至少2個確認 @@ -42,11 +42,12 @@ def analyze_market(indicator_summary: str, account_status: str) -> list[dict]: 3. **趨勢反轉確認**: MACD 死叉 + EMA9 下穿 EMA21 + 1h 趨勢也轉空 - 若 1h 趨勢仍為多頭,即使 5m 出現賣出訊號,也應降低信心或 HOLD -### 過濾條件(必須全部滿足) -- 成交量需高於 20 期平均(確認動能) -- 5分鐘 ADX > 15(排除極度盤整) -- OBV 方向需與交易方向一致(量價確認) +### 輔助條件(加減分,非硬性過濾) +- 成交量高於 MA20 → 信心 +0.1;遠低於 MA20 → 信心 -0.1 +- 5分鐘 ADX > 15 → 正常;ADX < 15 → 信心 -0.1(但不禁止進場) +- OBV 方向與交易一致 → 信心 +0.1;相反 → 信心 -0.1 - ATR 過高時降低 suggested_amount_pct(波動風控) +- 這些條件用於調整信心分數,不應單獨阻擋進場 ### 信心分數指引 - 0.8+: 多時間框架對齊 + 3個以上確認指標 diff --git a/main.py b/main.py index a4dfa40..a9e2346 100644 --- a/main.py +++ b/main.py @@ -121,11 +121,12 @@ def run_cycle(): if o.get("type") == "EXCHANGE STOP": stop_orders_by_sym.setdefault(o["symbol"], []).append(o) - # Build map: currency → wallet balance (for accurate stop-loss amounts) + # Build map: currency → wallet available balance (for accurate stop-loss amounts) + # Use "available" (not "balance") to exclude coins locked by existing orders wallet_balances = {} for w in account_status.get("wallets", []): if w.get("type") == "exchange": - wallet_balances[w["currency"]] = w.get("balance", 0) + wallet_balances[w["currency"]] = w.get("available", 0) or w.get("balance", 0) # Load tracked stops early — needed for both stop sync and fill detection tracked_stops = _load_tracked_stops() @@ -435,7 +436,18 @@ def run_cycle(): time.sleep(1.0) # Wait for Bitfinex to release locked balance # Place new stop-loss for TOTAL position amount at new avg entry - total_amount = pos.get("amount", amount) + # Refresh wallet balance to get actual available amount (handles partial fills, fees) + try: + refreshed_status = data_fetcher.fetch_account_status() + currency = sym[1:].replace(":UST", "").replace("UST", "") + wallet_amt = 0 + for w in refreshed_status.get("wallets", []): + if w.get("type") == "exchange" and w.get("currency") == currency: + wallet_amt = w.get("available", 0) or w.get("balance", 0) + break + total_amount = wallet_amt if wallet_amt > 0 else pos.get("amount", amount) + except Exception: + total_amount = pos.get("amount", amount) entry_price = pos.get("entry_price", price) # ATR-based dynamic stop price diff --git a/slack_notifier.py b/slack_notifier.py index 2183a1f..57ca469 100644 --- a/slack_notifier.py +++ b/slack_notifier.py @@ -103,7 +103,17 @@ def send_cycle_report( if not llm_ok: lines.append(" ⚠️ LLM 分析失敗,本次跳過。") else: - lines.append(" All symbols → HOLD, no action this cycle.") + # Show why 1h-bullish symbols didn't enter + bullish_holds = [s for s in holds if "多頭" in s.get("reason", "")] + if bullish_holds: + for s in bullish_holds: + name = config.SYMBOL_NAMES.get(s["symbol"], s["symbol"]) + lines.append(f" 🟡 {name} (1h多頭) HOLD — {s.get('reason', '')}") + other_count = len(holds) - len(bullish_holds) + if other_count > 0: + lines.append(f" 🔴 其餘 {other_count} 幣種 1h 空頭 HOLD") + else: + lines.append(" 🔴 全部 1h 空頭,無進場機會") lines.append("") # --- Executed trades ---