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,
|
"tETHUST": 1773352562967,
|
||||||
"tSUIUST": 1773362755397,
|
"tSUIUST": null,
|
||||||
"tSOLUST": null,
|
"tSOLUST": 1773363657396,
|
||||||
"tLTCUST": null,
|
"tLTCUST": null,
|
||||||
"tADAUST": null,
|
"tADAUST": null,
|
||||||
"tAVAX:UST": 1773380455009,
|
"tAVAX:UST": null,
|
||||||
"tUNIUST": 1773380455473,
|
"tUNIUST": null,
|
||||||
"tXRPUST": null,
|
"tXRPUST": 1773363657836,
|
||||||
"tDOGE:UST": 1773407762194,
|
"tDOGE:UST": null,
|
||||||
"tSHIB:UST": null,
|
"tSHIB:UST": null,
|
||||||
"tBTCUST": null,
|
"tBTCUST": 1773345335546,
|
||||||
"tXLMUST": null,
|
"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()
|
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
|
# Public endpoints
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -68,7 +68,7 @@ def analyze_market(indicator_summary: str, account_status: str) -> list[dict]:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["claude", "-p", prompt, "--output-format", "json"],
|
["claude", "-p", prompt, "--output-format", "json", "--no-session-persistence"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=120,
|
timeout=120,
|
||||||
|
|||||||
16
main.py
16
main.py
@ -99,6 +99,12 @@ def run_cycle():
|
|||||||
port["available_usdt"] = 10000
|
port["available_usdt"] = 10000
|
||||||
logger.info("Paper trading: using default 10000 USDT balance")
|
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)
|
# 2b. Sync stop-loss orders with exchange (real-time, not local memory)
|
||||||
try:
|
try:
|
||||||
active_orders = data_fetcher.fetch_active_orders()
|
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
|
total_cost += amount * entry
|
||||||
if current > 0:
|
if current > 0:
|
||||||
total_market_value += amount * current
|
total_market_value += amount * current
|
||||||
unrealized = total_market_value - total_cost
|
|
||||||
total_value = available + total_market_value
|
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_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 (
|
return (
|
||||||
f"總值 {total_value:.2f} USDT | 總收益 {total_return_pct:+.2f}% | "
|
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("")
|
||||||
|
|
||||||
lines.append(f"*Total Portfolio Value:* {total_value:.2f} USDT")
|
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", [])
|
history = portfolio.get("trade_history", [])
|
||||||
if history:
|
if history:
|
||||||
|
|||||||
@ -1,26 +1,38 @@
|
|||||||
{
|
{
|
||||||
"tDOGE:UST": {
|
"tDOTUST": {
|
||||||
"order_id": 233282701621,
|
"order_id": 233309387815,
|
||||||
"stop_price": 0.093946,
|
"stop_price": 1.3849,
|
||||||
"entry_price": 0.09688198941787385,
|
"entry_price": 1.4287,
|
||||||
"amount": 144.10790954
|
"amount": 10.03723093
|
||||||
},
|
},
|
||||||
"tSUIUST": {
|
"tXRPUST": {
|
||||||
"order_id": 233265250698,
|
"order_id": 233329683966,
|
||||||
"stop_price": 0.97101,
|
"stop_price": 1.372,
|
||||||
"entry_price": 1.0009320066822542,
|
"entry_price": 1.4153,
|
||||||
"amount": 54.57098037
|
"amount": 12.66451728
|
||||||
},
|
},
|
||||||
"tAVAX:UST": {
|
"tSOLUST": {
|
||||||
"order_id": 233268826282,
|
"order_id": 233329069994,
|
||||||
"stop_price": 9.4454,
|
"stop_price": 85.644,
|
||||||
"entry_price": 9.734478498389963,
|
"entry_price": 88.367,
|
||||||
"amount": 4.08390185
|
"amount": 0.16229378
|
||||||
},
|
},
|
||||||
"tUNIUST": {
|
"tETHUST": {
|
||||||
"order_id": 233279675016,
|
"order_id": 233338658543,
|
||||||
"stop_price": 3.8778,
|
"stop_price": 2048,
|
||||||
"entry_price": 3.9954999999999994,
|
"entry_price": 2110.9826633288,
|
||||||
"amount": 4.55798027
|
"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