Relax LLM entry filters, show 1h-bullish HOLD reasons in cycle report

- llm_analyzer.py: change 5m filters from hard requirements to confidence
  adjustments (volume, ADX<15, OBV direction now ±0.1 instead of blocking);
  1h ADX<20 lowers confidence instead of preventing entry
- slack_notifier.py: when all symbols HOLD, show 1h-bullish symbols with
  their HOLD reason instead of generic "no action" message
- main.py: log all 15 HOLD reasons instead of first 3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kroutony 2026-03-24 01:32:51 +00:00
parent d261b36460
commit b2d7495ec0
3 changed files with 32 additions and 9 deletions

View File

@ -26,7 +26,7 @@ def analyze_market(indicator_summary: str, account_status: str) -> list[dict]:
### 最高優先:多時間框架過濾(現貨,只做多) ### 最高優先:多時間框架過濾(現貨,只做多)
- 1小時趨勢為多頭EMA9 > EMA21才考慮 BUY - 1小時趨勢為多頭EMA9 > EMA21才考慮 BUY
- 1小時為空頭時只持有或平倉SELL不開新倉 - 1小時為空頭時只持有或平倉SELL不開新倉
- 1小時 ADX < 20盤整避免進場 - 1小時 ADX < 20盤整降低信心但不禁止進場
- 1小時 ADX > 25 且方向一致 = 高信心交易 - 1小時 ADX > 25 且方向一致 = 高信心交易
### 進場訊號 (BUY) — 需至少2個確認 ### 進場訊號 (BUY) — 需至少2個確認
@ -42,11 +42,12 @@ def analyze_market(indicator_summary: str, account_status: str) -> list[dict]:
3. **趨勢反轉確認**: MACD 死叉 + EMA9 下穿 EMA21 + 1h 趨勢也轉空 3. **趨勢反轉確認**: MACD 死叉 + EMA9 下穿 EMA21 + 1h 趨勢也轉空
- 1h 趨勢仍為多頭即使 5m 出現賣出訊號也應降低信心或 HOLD - 1h 趨勢仍為多頭即使 5m 出現賣出訊號也應降低信心或 HOLD
### 過濾條件(必須全部滿足 ### 輔助條件(加減分,非硬性過濾
- 成交量需高於 20 期平均確認動能 - 成交量高於 MA20 信心 +0.1遠低於 MA20 信心 -0.1
- 5分鐘 ADX > 15排除極度盤整 - 5分鐘 ADX > 15 正常ADX < 15 信心 -0.1但不禁止進場
- OBV 方向需與交易方向一致量價確認 - OBV 方向與交易一致 信心 +0.1相反 信心 -0.1
- ATR 過高時降低 suggested_amount_pct波動風控 - ATR 過高時降低 suggested_amount_pct波動風控
- 這些條件用於調整信心分數不應單獨阻擋進場
### 信心分數指引 ### 信心分數指引
- 0.8+: 多時間框架對齊 + 3個以上確認指標 - 0.8+: 多時間框架對齊 + 3個以上確認指標

16
main.py
View File

@ -121,11 +121,12 @@ def run_cycle():
if o.get("type") == "EXCHANGE STOP": if o.get("type") == "EXCHANGE STOP":
stop_orders_by_sym.setdefault(o["symbol"], []).append(o) 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 = {} wallet_balances = {}
for w in account_status.get("wallets", []): for w in account_status.get("wallets", []):
if w.get("type") == "exchange": 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 # Load tracked stops early — needed for both stop sync and fill detection
tracked_stops = _load_tracked_stops() tracked_stops = _load_tracked_stops()
@ -435,6 +436,17 @@ def run_cycle():
time.sleep(1.0) # Wait for Bitfinex to release locked balance time.sleep(1.0) # Wait for Bitfinex to release locked balance
# Place new stop-loss for TOTAL position amount at new avg entry # Place new stop-loss for TOTAL position amount at new avg entry
# 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) total_amount = pos.get("amount", amount)
entry_price = pos.get("entry_price", price) entry_price = pos.get("entry_price", price)

View File

@ -103,7 +103,17 @@ def send_cycle_report(
if not llm_ok: if not llm_ok:
lines.append(" ⚠️ LLM 分析失敗,本次跳過。") lines.append(" ⚠️ LLM 分析失敗,本次跳過。")
else: 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("") lines.append("")
# --- Executed trades --- # --- Executed trades ---