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:
parent
d261b36460
commit
b2d7495ec0
@ -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
16
main.py
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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 ---
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user