bifitnex-trading/llm_analyzer.py
kroutony 17303d5d3d Add deposit-based total return, session persistence flag, dual return rates
- Add fetch_total_deposits() with hourly local cache (deposit_cache.json)
- Use deposit total as capital base for accurate total return calculation
- Add --no-session-persistence to claude CLI subprocess calls
- Show both total return (deposit-based) and change rate (cost-based) in reports
- Update portfolio summary with Total Return line

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 10:21:39 +00:00

121 lines
4.0 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 re
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
### 過濾條件(必須全部滿足)
- 成交量需高於 20 期平均(確認動能)
- 5分鐘 ADX > 15排除極度盤整
- OBV 方向需與交易方向一致(量價確認)
- 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不要其他文字。"""
try:
result = subprocess.run(
["claude", "-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 []