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>
This commit is contained in:
parent
ddc1b9e3eb
commit
17303d5d3d
@ -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
|
||||
}
|
||||
@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -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,
|
||||
|
||||
16
main.py
16
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} 筆"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user