bifitnex-trading/llm_analyzer.py
kroutony b2d7495ec0 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>
2026-03-24 01:32:51 +00:00

126 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import logging
import os
import re
import shutil
import subprocess
logger = logging.getLogger(__name__)
def analyze_market(indicator_summary: str, account_status: str) -> list[dict]:
"""Call Claude CLI to analyze market data and return trade signals."""
prompt = f"""你是一位專業的加密貨幣短線交易分析師。
## 帳戶狀態
{account_status}
## 目前市場指標摘要
{indicator_summary}
請根據以下策略分析每個幣種並給出交易建議:
## 交易策略
### 最高優先:多時間框架過濾(現貨,只做多)
- 1小時趨勢為多頭EMA9 > EMA21才考慮 BUY
- 1小時為空頭時只持有或平倉SELL不開新倉
- 1小時 ADX < 20盤整降低信心但不禁止進場
- 1小時 ADX > 25 且方向一致 = 高信心交易
### 進場訊號 (BUY) — 需至少2個確認
1. **超賣反彈**: StochRSI K < 0.2 且 K 上穿 D + RSI < 40
2. **均值回歸**: 價格觸及 BB 下軌 + RSI < 40 + CMF > 0有買壓
3. **趨勢啟動**: MACD 金叉 + EMA9 上穿 EMA21 + ADX > 20有趨勢
4. **支撐反彈**: 價格接近樞紐支撐位S1/S2+ OBV 流入 + RSI < 45
### 出場訊號 (SELL) — 需至少2個確認不輕易賣出
⚠️ 現貨多單應讓利潤奔跑,除非有明確反轉訊號,否則傾向持有。
1. **強烈超買反轉**: StochRSI K > 0.8 且 K 下穿 D + RSI > 70 + MACD histogram 轉負
2. **阻力拒絕+量縮**: 價格接近樞紐阻力位R1/R2+ OBV 流出 + CMF < 0
3. **趨勢反轉確認**: MACD 死叉 + EMA9 下穿 EMA21 + 1h 趨勢也轉空
- 若 1h 趨勢仍為多頭,即使 5m 出現賣出訊號,也應降低信心或 HOLD
### 輔助條件(加減分,非硬性過濾)
- 成交量高於 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個以上確認指標
- 0.6-0.8: 多時間框架對齊 + 2個確認
- 0.4-0.6: 單一時間框架訊號,降低倉位
- < 0.4: 不建議交易,回傳 HOLD
請以 JSON 格式回傳,每個幣種一個物件:
```json
[
{{
"symbol": "tBTCUST",
"action": "BUY" | "SELL" | "HOLD",
"confidence": 0.0-1.0,
"reason": "簡短理由(含觸發的指標)",
"suggested_amount_pct": 0.05-0.20
}}
]
```
只回傳 JSON不要其他文字。"""
claude_bin = shutil.which("claude") or os.path.expanduser("~/.local/bin/claude")
try:
result = subprocess.run(
[claude_bin, "-p", prompt, "--output-format", "json", "--no-session-persistence"],
capture_output=True,
text=True,
timeout=120,
)
if result.returncode != 0:
logger.error("Claude CLI failed (rc=%d): %s", result.returncode, result.stderr)
return []
return _parse_llm_response(result.stdout)
except subprocess.TimeoutExpired:
logger.error("Claude CLI timed out")
return []
except FileNotFoundError:
logger.error("Claude CLI not found — is 'claude' installed and in PATH?")
return []
except Exception as e:
logger.error("LLM analysis error: %s", e)
return []
def _parse_llm_response(raw: str) -> list[dict]:
"""Extract the JSON array from Claude's response."""
# First try: the output-format json wraps response in {"result": "..."}
try:
wrapper = json.loads(raw)
if isinstance(wrapper, dict) and "result" in wrapper:
raw = wrapper["result"]
except (json.JSONDecodeError, TypeError):
pass
# Try direct parse
if isinstance(raw, list):
return raw
try:
parsed = json.loads(raw)
if isinstance(parsed, list):
return parsed
except (json.JSONDecodeError, TypeError):
pass
# Try extracting JSON array from markdown code block or mixed text
match = re.search(r'\[[\s\S]*\]', raw)
if match:
try:
return json.loads(match.group())
except json.JSONDecodeError:
pass
logger.warning("Could not parse LLM response as JSON array")
return []