From 17303d5d3dc55a518c7933db5e1ee43ee4a58d11 Mon Sep 17 00:00:00 2001 From: kroutony Date: Sun, 15 Mar 2026 10:21:39 +0000 Subject: [PATCH] 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) --- cost_tracking.json | 19 ++++++++-------- data_fetcher.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++ llm_analyzer.py | 2 +- main.py | 16 +++++++++++--- portfolio.py | 9 +++++++- stop_orders.json | 52 +++++++++++++++++++++++++++----------------- 6 files changed, 118 insertions(+), 34 deletions(-) diff --git a/cost_tracking.json b/cost_tracking.json index 1f77574..ec4293b 100644 --- a/cost_tracking.json +++ b/cost_tracking.json @@ -1,15 +1,16 @@ { - "tETHUST": null, - "tSUIUST": 1773362755397, - "tSOLUST": null, + "tETHUST": 1773352562967, + "tSUIUST": null, + "tSOLUST": 1773363657396, "tLTCUST": null, "tADAUST": null, - "tAVAX:UST": 1773380455009, - "tUNIUST": 1773380455473, - "tXRPUST": null, - "tDOGE:UST": 1773407762194, + "tAVAX:UST": null, + "tUNIUST": null, + "tXRPUST": 1773363657836, + "tDOGE:UST": null, "tSHIB:UST": null, - "tBTCUST": null, + "tBTCUST": 1773345335546, "tXLMUST": null, - "tLINK:UST": null + "tLINK:UST": 1773361253747, + "tDOTUST": 1773362453371 } \ No newline at end of file diff --git a/data_fetcher.py b/data_fetcher.py index ece87d1..699b69f 100644 --- a/data_fetcher.py +++ b/data_fetcher.py @@ -52,6 +52,60 @@ def _auth_post(path: str, body: dict | None = None) -> dict | list: return resp.json() +# --------------------------------------------------------------------------- +# Deposit history (cached locally, refreshed hourly) +# --------------------------------------------------------------------------- + +# 2026-03-10 00:00:00 UTC in milliseconds +_DEPOSIT_START_MS = 1773100800000 +_DEPOSIT_CACHE_FILE = "deposit_cache.json" +_DEPOSIT_CACHE_TTL = 3600 # 1 hour + + +def fetch_total_deposits() -> float: + """Return net USDT deposits since 2026-03-10. Uses local cache, refreshes hourly.""" + # Try cache first + if os.path.exists(_DEPOSIT_CACHE_FILE): + try: + with open(_DEPOSIT_CACHE_FILE) as f: + cache = json.load(f) + if time.time() - cache.get("ts", 0) < _DEPOSIT_CACHE_TTL: + return cache["total"] + except (json.JSONDecodeError, IOError, KeyError): + pass + + # Cache miss or expired — fetch from API + total = _fetch_deposits_from_api() + if total > 0: + try: + with open(_DEPOSIT_CACHE_FILE, "w") as f: + json.dump({"total": total, "ts": time.time()}, f) + except IOError: + pass + return total + + +def _fetch_deposits_from_api() -> float: + """Fetch net USDT deposits from Bitfinex API.""" + try: + raw = _auth_post("/v2/auth/r/movements/UST/hist", { + "start": _DEPOSIT_START_MS, + "limit": 100, + }) + except Exception as e: + logger.error("Failed to fetch deposit history: %s", e) + return 0.0 + + total = 0.0 + for m in raw: + status = m[9] if len(m) > 9 else "" + if status != "COMPLETED": + continue + amount = float(m[12]) if len(m) > 12 else 0 + total += amount # positive = deposit, negative = withdrawal + return total + + # --------------------------------------------------------------------------- # Public endpoints # --------------------------------------------------------------------------- diff --git a/llm_analyzer.py b/llm_analyzer.py index 4ddc777..204a794 100644 --- a/llm_analyzer.py +++ b/llm_analyzer.py @@ -68,7 +68,7 @@ def analyze_market(indicator_summary: str, account_status: str) -> list[dict]: try: result = subprocess.run( - ["claude", "-p", prompt, "--output-format", "json"], + ["claude", "-p", prompt, "--output-format", "json", "--no-session-persistence"], capture_output=True, text=True, timeout=120, diff --git a/main.py b/main.py index dd46e96..e0b3e7f 100644 --- a/main.py +++ b/main.py @@ -99,6 +99,12 @@ def run_cycle(): port["available_usdt"] = 10000 logger.info("Paper trading: using default 10000 USDT balance") + # 2a. Fetch initial capital from deposit history + try: + port["initial_capital"] = data_fetcher.fetch_total_deposits() + except Exception as e: + logger.warning("Failed to fetch deposit history: %s", e) + # 2b. Sync stop-loss orders with exchange (real-time, not local memory) try: active_orders = data_fetcher.fetch_active_orders() @@ -503,13 +509,17 @@ def _build_portfolio_one_liner(port: dict, current_prices: dict) -> str: total_cost += amount * entry if current > 0: total_market_value += amount * current - unrealized = total_market_value - total_cost total_value = available + total_market_value + # 總收益率(基於入金) + initial_capital = port.get("initial_capital", 0) + total_return_pct = ((total_value / initial_capital - 1) * 100) if initial_capital > 0 else 0.0 + # 變動收益率(基於持倉成本) + unrealized = total_market_value - total_cost total_capital = available + total_cost - total_return_pct = ((total_value / total_capital - 1) * 100) if total_capital > 0 else 0.0 + change_pct = ((total_value / total_capital - 1) * 100) if total_capital > 0 else 0.0 return ( f"總值 {total_value:.2f} USDT | 總收益 {total_return_pct:+.2f}% | " - f"{available:.2f} 可用 | 持倉 {pos_count} 筆" + f"變動 {change_pct:+.2f}% | {available:.2f} 可用 | 持倉 {pos_count} 筆" ) diff --git a/portfolio.py b/portfolio.py index 835e5eb..1ae439c 100644 --- a/portfolio.py +++ b/portfolio.py @@ -135,7 +135,14 @@ def get_portfolio_summary(portfolio: dict, current_prices: dict) -> str: lines.append("") lines.append(f"*Total Portfolio Value:* {total_value:.2f} USDT") - lines.append(f"*Total Unrealized P&L:* {total_unrealized:+.2f} USDT") + lines.append(f"*Total Unrealized P&L:* {total_unrealized:+.2f} USDT ({total_unrealized / (total_value - total_unrealized) * 100:+.2f}%)" if (total_value - total_unrealized) > 0 else f"*Total Unrealized P&L:* {total_unrealized:+.2f} USDT") + + initial_capital = portfolio.get("initial_capital", 0) + if initial_capital > 0: + total_return = total_value - initial_capital + total_return_pct = (total_value / initial_capital - 1) * 100 + sign = "+" if total_return >= 0 else "" + lines.append(f"*Total Return:* {sign}{total_return:.2f} USDT ({sign}{total_return_pct:.2f}%)") history = portfolio.get("trade_history", []) if history: diff --git a/stop_orders.json b/stop_orders.json index 88ebf56..42d770d 100644 --- a/stop_orders.json +++ b/stop_orders.json @@ -1,26 +1,38 @@ { - "tDOGE:UST": { - "order_id": 233282701621, - "stop_price": 0.093946, - "entry_price": 0.09688198941787385, - "amount": 144.10790954 + "tDOTUST": { + "order_id": 233309387815, + "stop_price": 1.3849, + "entry_price": 1.4287, + "amount": 10.03723093 }, - "tSUIUST": { - "order_id": 233265250698, - "stop_price": 0.97101, - "entry_price": 1.0009320066822542, - "amount": 54.57098037 + "tXRPUST": { + "order_id": 233329683966, + "stop_price": 1.372, + "entry_price": 1.4153, + "amount": 12.66451728 }, - "tAVAX:UST": { - "order_id": 233268826282, - "stop_price": 9.4454, - "entry_price": 9.734478498389963, - "amount": 4.08390185 + "tSOLUST": { + "order_id": 233329069994, + "stop_price": 85.644, + "entry_price": 88.367, + "amount": 0.16229378 }, - "tUNIUST": { - "order_id": 233279675016, - "stop_price": 3.8778, - "entry_price": 3.9954999999999994, - "amount": 4.55798027 + "tETHUST": { + "order_id": 233338658543, + "stop_price": 2048, + "entry_price": 2110.9826633288, + "amount": 0.01357354 + }, + "tLINK:UST": { + "order_id": 233331464285, + "stop_price": 8.9428, + "entry_price": 9.2243, + "amount": 1.16577353 + }, + "tBTCUST": { + "order_id": 233335923158, + "stop_price": 69413, + "entry_price": 71556.91752086183, + "amount": 0.00065071 } } \ No newline at end of file