bifitnex-trading/check_errors.py
kroutony b6bd45b151 Fix SELL P&L calculation, add debug logging, and multiple improvements
- Fix realized_pnl always being 0 on SELL (use amount*price instead of amount_usdt)
- Add debug logging for all-HOLD LLM cycles (log sample HOLD reasons)
- Fix nonce collision in Bitfinex API auth (add counter)
- Fix timezone to UTC+8 in main, trade_logger, sync_cost_basis, check_errors
- Fix stop-loss retry with longer delay after cancel
- Add min order amount check in trader before BUY
- Fix risk_manager to skip positions with amount <= 0
- Cap trade_history to 500 entries to prevent unbounded growth
- Fix greedy regex in LLM response parser
- Reset cost_tracking start dates to null

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 06:23:54 +00:00

161 lines
4.4 KiB
Python
Executable File
Raw Permalink 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.

#!/usr/bin/env python3
"""Check API errors in the last hour, attempt auto-fix via Claude, and report to Slack."""
import json
import os
import shutil
import subprocess
import sys
from datetime import datetime, timedelta, timezone
sys.path.insert(0, os.path.dirname(__file__))
import slack_notifier
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
LOG_FILES = [
"trading.log",
"sync_cost_basis.log",
"cron.log",
]
LOOKBACK_MINUTES = 60
def collect_recent_errors() -> list[str]:
_TZ_UTC8 = timezone(timedelta(hours=8))
since = datetime.now(_TZ_UTC8) - timedelta(minutes=LOOKBACK_MINUTES)
since_str = since.strftime("%Y-%m-%d %H:%M:%S")
errors = []
for logfile in LOG_FILES:
path = os.path.join(PROJECT_DIR, logfile)
if not os.path.exists(path):
continue
with open(path) as f:
for line in f:
if "[ERROR]" not in line:
continue
if not line[:4].isdigit():
continue
if line[:19] >= since_str:
errors.append(line.rstrip())
return errors
def attempt_auto_fix(errors: list[str]) -> str | None:
"""Ask Claude to diagnose and fix the errors. Returns a summary or None on failure."""
error_text = "\n".join(errors[:20])
prompt = f"""你是 bifitnex-trading 專案的維運工程師。
專案目錄: {PROJECT_DIR}
以下是最近一小時的錯誤 log
```
{error_text}
```
請:
1. 診斷錯誤的根因
2. 如果是程式碼問題,直接修復(編輯檔案)
3. 如果是外部問題API 暫時不可用、網路問題等),不需要改程式碼,只需說明
最後用以下 JSON 格式回覆:
```json
{{
"diagnosis": "錯誤根因簡述",
"fixed": true/false,
"changes": "修改了什麼(沒改就寫 null",
"suggestion": "如需人工介入的建議(不需要就寫 null"
}}
```
只回傳 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", "--allowedTools", "Read,Edit,Glob,Grep"],
capture_output=True,
text=True,
timeout=180,
cwd=PROJECT_DIR,
)
if result.returncode != 0:
return None
return _parse_fix_response(result.stdout)
except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
return None
def _parse_fix_response(raw: str) -> dict | None:
"""Parse Claude's JSON response."""
try:
wrapper = json.loads(raw)
text = wrapper.get("result", raw) if isinstance(wrapper, dict) else raw
except json.JSONDecodeError:
text = raw
# Try to extract JSON from the text
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# Try finding JSON block in text
start = text.find("{")
end = text.rfind("}") + 1
if start >= 0 and end > start:
try:
return json.loads(text[start:end])
except json.JSONDecodeError:
pass
return None
def main():
errors = collect_recent_errors()
if not errors:
return
unique = list(dict.fromkeys(errors))
display = unique[:20]
remaining = len(unique) - len(display)
# Try auto-fix
fix_result = attempt_auto_fix(unique)
# Build Slack message
lines = [
"<!channel>",
f"⚠️ *API 錯誤警報* (最近 {LOOKBACK_MINUTES} 分鐘,共 {len(unique)} 筆)",
"",
]
for e in display:
lines.append(f"• `{e[:200]}`")
if remaining > 0:
lines.append(f"\n...另有 {remaining} 筆錯誤")
# Append fix report
lines.append("")
if fix_result:
lines.append("🔧 *自動修復報告:*")
lines.append(f" 診斷:{fix_result.get('diagnosis', '未知')}")
if fix_result.get("fixed"):
lines.append(f" ✅ 已修復:{fix_result.get('changes', '-')}")
else:
lines.append(" ❌ 未自動修復")
if fix_result.get("suggestion"):
lines.append(f" 💡 建議:{fix_result.get('suggestion')}")
else:
lines.append("🔧 *自動修復:* Claude 分析失敗,請人工檢查")
slack_notifier._send({"text": "\n".join(lines)})
if __name__ == "__main__":
main()