f151316bdf
Fonte: articolo ufficiale Deribit "Yield/reward bearing coins" (WebFetch lo prende
403, si legge dall'API Help Center in JSON). Tre correzioni alle conclusioni di
ieri.
1) L'ITALIA e' nella lista delle giurisdizioni escluse dai reward USDC. Lo zero
misurato e' confermato dalla fonte, ma la mia ipotesi MiCA era SBAGLIATA: la
lista contiene Canada e Giappone, non e' il perimetro MiCA. E' policy di
giurisdizione Deribit. M27: una fonte normativa si verifica, non si deduce.
2) La finestra di pagamento USDC e' di DUE SETTIMANE ("within the first two
weeks of the following month"), non tre giorni. Avevo verificato su 8/14 e 3/14
giorni. Rifatto sulle finestre vere: LUGLIO conclusivo (atteso $1,72 contro
un'escursione TOTALE dell'equity di $0,48 in 1-16/08, zero scalini compatibili),
GIUGNO no (il libro opera da meta' mese, rumore della taglia del segnale). La
conclusione non cambia, ma l'evidenza e' UNA finestra piu' la lista ufficiale,
non due: il "172x" del 30/08 era sovra-affermato.
3) IL TETTO NON E' UN LIVELLO, E' UNA FRAZIONE. L'articolo documenta un Cap
ETHENA che diluisce il TASSO a livello di exchange e nessun limite sulle
quantita' detenibili: il muro non aveva base documentale e andava ri-sondato.
Fatto il 31/08 (giorno UTC nuovo -> non e' un limite giornaliero): ieri si
tornava a 644,18, oggi no, con l'equity scesa di $8.
30/08 tetto [644,18 · 645,18) equity $2.063,79 = 31,21-31,26%
31/08 tetto [643,18 · 644,18) equity $2.055,56 = 31,29-31,34%
0,05pp di scarto, dentro il rumore dell'equity (+-$2-8/ora). Candidato pulito
5/16 = 31,25%, ma a questa risoluzione non si distingue da una regola sul
collaterale scontato dell'haircut (~29%): si cita la banda (M25).
=> Il tetto SCALA col conto: la quota resta ~31%, il valore in dollari cresce
col capitale, il 70% non e' raggiungibile ne' ora ne' mai. E, essendo pinnati
al tetto, il rischio emittente resta una frazione COSTANTE del conto.
Cablato (rispondeva a "dove e' scritto il valore del tetto": in tre note di
testo e in nessun posto che il codice leggesse, tanto che usde_convert --quota
0.70 dichiarava "piano valido" per un ordine che il venue rifiuta):
- config/live.json usde.venue_cap_frac 0.312 + venue_cap_misurato
- src/live/usde.py lo porta nei default (unica autorita', P1)
- usde_convert.piano() rifiuta il bersaglio sopra il tetto PRIMA di sparare
e stampa il massimo raggiungibile. Verificato su entrambi i rami.
Anche: l'USDe ha un fee Deribit del 5% mai nominato prima. Il nostro misurato
(~4,5%/anno) e' gia' netto: e' l'unico numero da citare.
Stato: USDE 643,175691 (31,29%, al tetto), USDC $1.412,40, totale $2.055,57.
Suite: 795 passati.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
303 lines
17 KiB
Python
303 lines
17 KiB
Python
#!/usr/bin/env python
|
|
"""usde_convert.py — porta la QUOTA di collaterale USDE a un bersaglio dichiarato. INVIA ORDINI VERI.
|
|
|
|
PERCHE' ESISTE. La conversione del 26/08 (test di eligibilita', 500 USDE) fu una chiamata a mano al
|
|
gateway: nessuno script, nessuna guardia, nessuna traccia se non il diario. Il gate USDE-01 prevede
|
|
entrambe le direzioni — IDONEO -> si decide la quota; NON IDONEO -> "si riconverte" — e nessuna delle
|
|
due aveva un attrezzo. Qui c'e', e vale in tutti e due i versi (quota piu' alta = compra USDE, piu'
|
|
bassa = vende).
|
|
|
|
PERCHE' NON PASSA DA DeribitTrader. `execution.ALLOWED` ammette solo i due perp del libro: e' il
|
|
guardrail anti-fat-finger del percorso soldi del book e NON va allargato per far entrare uno spot che
|
|
col libro non c'entra. Questo script parla al gateway per conto suo e porta le proprie guardie.
|
|
|
|
LE GUARDIE (tutte BLOCCANTI, tutte dichiarate qui):
|
|
* banda di prezzo [0.995, 1.005] — la stessa del 26/08. Fuori banda non si converte: un USDE a
|
|
0.98 e' esattamente il momento in cui NON si compra, e uno a 1.02 non esiste (si clampa a 1.0).
|
|
* quota bersaglio in [0, QUOTA_HARD_MAX] — il gate USDE-01 dice "mai 100%: R1 emittente non
|
|
recuperabile". Il tetto e' HARD e non si passa da riga di comando.
|
|
* TETTO DEL VENUE (`usde.venue_cap_frac`). Deribit rifiuta gli acquisti oltre una frazione
|
|
dell'equity totale — ~31,2%, MISURATA il 30-31/08 e non documentata da nessuna parte, col
|
|
messaggio fuorviante `not_enough_funds_in_currency`. E' una frazione, non un livello: fatta
|
|
scendere l'equity di $8, il tetto e' sceso con lei. Senza questa guardia il piano si dichiara
|
|
VALIDO e poi il venue lo rifiuta a raffica — che e' esattamente il giro di venti minuti costato
|
|
il 30/08. Il valore vive in config, non qui (P1: il bersaglio si deriva, non si ridichiara).
|
|
* CUSCINO DI REGOLAMENTO. Il P&L e il funding dei perp USDC-lineari si regolano in USDC, non nel
|
|
collaterale: a quota alta il saldo USDC va negativo alla prima perdita del libro e Deribit lo
|
|
finanzia a interesse. Il margine NON e' il vincolo (r0830_usde_quota: l'haircut non morde fino
|
|
al 90%) — il vincolo e' questo. Il cuscino richiesto e' il disaster-SL sulla MASSIMA esposizione
|
|
che il libro puo' prendere: n_asset * frac * disaster_sl_pct * equity, tutto letto da
|
|
config/live.json (P1: il bersaglio si deriva, non si ridichiara).
|
|
* dry-run DI DEFAULT: senza --esegui stampa il piano e non manda niente.
|
|
|
|
IL TETTO PER-ORDINE (misurato il 30/08, non documentato da nessuna parte). Il venue rifiuta gli
|
|
ordini spot grandi con `not_enough_funds_in_currency` ANCHE quando i fondi ci sono: 100 accettato
|
|
e 300 rifiutato con $1.442 disponibili (e 500 era passato il 26/08 con piu' saldo). Il gateway non
|
|
espone `available_withdrawal_funds` ne' `get_order_book`, quindi la causa vera NON e' leggibile da
|
|
qui e il messaggio del venue e' fuorviante: si tratta il tetto come ignoto e si CHUNKA, dimezzando
|
|
il blocco a ogni rifiuto. Costa zero: il book ha 3.990 unita' al miglior ask e la commissione spot
|
|
e' 0, quindi N fill piccoli e un fill grande pagano lo stesso prezzo.
|
|
|
|
NON E' NE' LA TAGLIA NE' IL TEMPO: E' IL PREZZO. Diagnosi finale (30/08 20:02, tre ordini da 1
|
|
USDE): SELL @1.0000 passa, BUY @1.0003 passa, BUY @1.0002 e BUY market falliscono. L'ask si era
|
|
mosso da 1.0002 a 1.0003 e l'ordine aveva smesso di INCROCIARE: Deribit rifiuta un limite BUY non
|
|
marcabile sullo spot con `not_enough_funds_in_currency`, che e' un messaggio fuorviante e ha fatto
|
|
inseguire per venti minuti due ipotesi sbagliate (tetto di size, rate-limit). Il difetto vero era
|
|
qui: il prezzo dell'ordine si derivava dall'INDICE (1.0001 + 1 tick) invece che dal BOOK. Sono due
|
|
prezzi diversi con due mestieri diversi — l'indice MARCA il collaterale (usde.valuta, mediana
|
|
multi-exchange: la lezione Binance 10/10), il book PREZZA lo scambio. Confonderli non sbaglia la
|
|
valutazione, sbaglia l'esecuzione.
|
|
|
|
LO STORICO DELLE IPOTESI SBAGLIATE, tenuto apposta: 943.98 rifiutato per "Invalid params" (vero:
|
|
min_trade_amount=1, il passo e' intero) -> 933 rifiutato per fondi con $1.541 disponibili (falso
|
|
indizio) -> 100 passa e 300 no (sembrava un tetto di size: era l'ask che si muoveva in mezzo) ->
|
|
anche 1 rifiutato otto volte (sembrava rate-limit: era sempre l'ask). Chi rilegge non deve rifare
|
|
questo giro. Misurato subito dopo: a raffica il venue rifiuta anche 1 USDE,
|
|
otto volte di fila, mentre 100 era appena passato. Il messaggio `not_enough_funds_in_currency` e'
|
|
FUORVIANTE — il vincolo e' la cadenza (regolamento spot asincrono / rate-limit), non il saldo ne'
|
|
la size. Percio' si SPAZIANO gli ordini (`--pausa`) e si fa backoff sul TEMPO, riducendo il blocco
|
|
solo dopo rifiuti ripetuti. Dimezzare la size a ogni rifiuto, come faceva la prima versione, e' la
|
|
reazione sbagliata al sintomo giusto: consuma i tentativi senza toccare la causa.
|
|
|
|
uv run python scripts/live/usde_convert.py --quota 0.70 # piano, nessun ordine
|
|
uv run python scripts/live/usde_convert.py --quota 0.70 --esegui # manda l'ordine
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import math
|
|
import sys
|
|
import time
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from src.live import usde as U # noqa: E402
|
|
from src.live.deribit import DeribitRead # noqa: E402
|
|
|
|
LOG = ROOT / "data" / "live" / "usde_convert.jsonl"
|
|
PX_MIN, PX_MAX = 0.995, 1.005 # banda di sicurezza sul prezzo (26/08)
|
|
QUOTA_HARD_MAX = 0.95 # gate USDE-01: "mai 100%"
|
|
MIN_ORDER_USD = 5.0
|
|
TOLL = 0.005 # entro mezzo punto di quota si considera gia' a bersaglio
|
|
EPS_CUSCINO = 0.01 # 1 centesimo: il criterio e' ">= cuscino", e una quota derivata DAL
|
|
# cuscino ci cade sopra esatta (0.30*E da entrambi i lati). Senza
|
|
# questo epsilon il rumore float boccia il caso di pareggio.
|
|
|
|
|
|
def cuscino_richiesto_usd(equity_tot: float) -> tuple[float, str]:
|
|
"""Cuscino USDC di REGOLAMENTO = disaster-SL sulla massima esposizione lorda del libro.
|
|
Derivato da config/live.json + src/live/book.py, mai ridichiarato qui."""
|
|
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
|
sl = float(cfg["disaster_sl_pct"])
|
|
from src.live import book as B
|
|
frac, n_asset = float(B.WEIGHT), 2
|
|
lordo_max = equity_tot * frac * n_asset
|
|
return lordo_max * sl, f"{n_asset} asset x frac {frac} x SL {sl:.0%} su ${equity_tot:,.2f}"
|
|
|
|
|
|
def book_top(timeout: float = 5.0) -> tuple[float | None, float | None]:
|
|
"""Miglior bid/ask dello spot USDE_USDC dall'API PUBBLICA Deribit (tokenless: il gateway non
|
|
espone get_order_book). -> (bid, ask), (None, None) se non leggibile. Mai solleva.
|
|
|
|
NON e' il prezzo di `usde.valuta`: quello e' l'INDICE e serve a marcare il collaterale. Questo
|
|
serve a far INCROCIARE l'ordine, ed e' l'unico che il matching engine guarda."""
|
|
try:
|
|
import requests
|
|
r = requests.get("https://www.deribit.com/api/v2/public/get_order_book",
|
|
params={"instrument_name": U.SPOT, "depth": 5}, timeout=timeout).json()
|
|
res = r.get("result", r)
|
|
bid = float(res["bids"][0][0]) if res.get("bids") else None
|
|
ask = float(res["asks"][0][0]) if res.get("asks") else None
|
|
return bid, ask
|
|
except Exception:
|
|
return None, None
|
|
|
|
|
|
def piano(eq_usdc: float, eq_usde: float, px: float, quota_target: float,
|
|
equity_tot: float, bid: float | None = None, ask: float | None = None) -> dict:
|
|
"""PURA. -> piano di conversione, con verdetto e motivo. Non manda niente."""
|
|
val_usde, _ = U.valuta(eq_usde, px)
|
|
quota_ora = val_usde / equity_tot if equity_tot > 0 else 0.0
|
|
val_target = quota_target * equity_tot
|
|
delta_usd = val_target - val_usde # >0 compra USDE, <0 vende
|
|
side = "buy" if delta_usd > 0 else "sell"
|
|
# MARCABILE CON MARGINE. Il book REST pubblico e' in RITARDO sul matching engine: misurato il
|
|
# 30/08, un BUY a ask+1tick (1.0003) e' stato rifiutato e uno a 1.0004 accettato un minuto dopo,
|
|
# col book che riportava ask 1.0002x3855 in entrambi i casi. Percio' si incrocia con 5 tick di
|
|
# margine e un TETTO DURO: il fill avviene al prezzo del LIBRO, non al limite, quindi il margine
|
|
# non si paga se il book non si e' mosso, e se si e' mosso il tetto limita il danno a ~9 bps.
|
|
# Senza book leggibile si RINUNCIA — non si tira a indovinare (P5).
|
|
TICK, MARGINE = 0.0001, 5
|
|
CAP_BUY, CAP_SELL = 1.0010, 0.9990
|
|
rif = ask if side == "buy" else bid
|
|
if rif is None:
|
|
px_ord = None
|
|
elif side == "buy":
|
|
px_ord = round(min(rif + MARGINE * TICK, CAP_BUY), 4)
|
|
else:
|
|
px_ord = round(max(rif - MARGINE * TICK, CAP_SELL), 4)
|
|
# PASSO DI SIZE = 1 USDE, misurato sul venue (30/08): 500 e 10 accettati, 943.98 e 933.99
|
|
# rifiutati con "Invalid params". `get_instrument` non e' esposto dal gateway (404), quindi il
|
|
# passo e' EMPIRICO e dichiarato tale. Si quantizza per DIFETTO: meno USDE = piu' USDC, cioe'
|
|
# dal lato del cuscino di regolamento. Sul lato vendita il difetto va nello stesso verso.
|
|
amount = float(math.floor(abs(delta_usd) / (px_ord or px)))
|
|
|
|
cfg = U.cfg()
|
|
cap_frac = float(cfg.get("venue_cap_frac") or 0)
|
|
cap_usd = cap_frac * equity_tot if cap_frac else None
|
|
cuscino, come = cuscino_richiesto_usd(equity_tot)
|
|
usdc_dopo = eq_usdc - delta_usd
|
|
motivi = []
|
|
if px_ord is None:
|
|
motivi.append("book USDE_USDC non leggibile — non si prezza l'ordine sull'indice (l'indice"
|
|
" marca il collaterale, il book prezza lo scambio)")
|
|
elif not (PX_MIN <= px_ord <= PX_MAX):
|
|
motivi.append(f"prezzo d'ordine {px_ord:.4f} fuori banda [{PX_MIN}, {PX_MAX}]")
|
|
if not (PX_MIN <= px <= PX_MAX):
|
|
motivi.append(f"prezzo {px:.4f} fuori banda [{PX_MIN}, {PX_MAX}]")
|
|
if not (0.0 <= quota_target <= QUOTA_HARD_MAX):
|
|
motivi.append(f"quota {quota_target:.0%} fuori dal tetto HARD {QUOTA_HARD_MAX:.0%} (gate USDE-01)")
|
|
if abs(quota_ora - quota_target) <= TOLL:
|
|
motivi.append(f"gia' a bersaglio ({quota_ora:.1%} vs {quota_target:.0%}, toll {TOLL:.1%})")
|
|
if abs(delta_usd) < MIN_ORDER_USD:
|
|
motivi.append(f"delta ${abs(delta_usd):,.2f} sotto il minimo ${MIN_ORDER_USD:,.2f}")
|
|
if cap_usd is not None and val_target > cap_usd + EPS_CUSCINO:
|
|
motivi.append(
|
|
f"TETTO DEL VENUE: bersaglio ${val_target:,.2f} sopra ${cap_usd:,.2f} "
|
|
f"({cap_frac:.1%} dell'equity, misurato {cfg.get('venue_cap_misurato')}). "
|
|
f"Massimo raggiungibile: quota {cap_frac:.1%}")
|
|
if usdc_dopo < cuscino - EPS_CUSCINO:
|
|
motivi.append(f"cuscino di regolamento: USDC dopo ${usdc_dopo:,.2f} < ${cuscino:,.2f} richiesti ({come})")
|
|
|
|
return {"cap_usd": cap_usd, "cap_frac": cap_frac,
|
|
"quota_ora": quota_ora, "quota_target": quota_target, "val_usde": val_usde,
|
|
"equity_tot": equity_tot, "delta_usd": delta_usd, "side": side, "amount": amount,
|
|
"px": px, "px_ordine": px_ord, "usdc_dopo": usdc_dopo, "cuscino": cuscino,
|
|
"cuscino_come": come, "ok": not motivi, "motivi": motivi}
|
|
|
|
|
|
def leggi_stato(c: DeribitRead) -> tuple[float, float, float | None, str]:
|
|
eq_usdc = float(c.account_summary("USDC")["equity"])
|
|
try:
|
|
eq_usde = float(c.account_summary("USDE")["equity"])
|
|
except Exception:
|
|
eq_usde = 0.0
|
|
px, fonte = U.prezzo(client=c)
|
|
return eq_usdc, eq_usde, px, fonte
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--quota", type=float, required=True, help="quota USDE bersaglio, es. 0.70")
|
|
ap.add_argument("--esegui", action="store_true", help="manda l'ordine (senza: solo il piano)")
|
|
ap.add_argument("--chunk", type=float, default=100.0, help="blocco massimo per ordine (USDE)")
|
|
ap.add_argument("--pausa", type=float, default=12.0, help="secondi fra un ordine e il successivo")
|
|
a = ap.parse_args()
|
|
|
|
c = DeribitRead()
|
|
eq_usdc, eq_usde, px, fonte = leggi_stato(c)
|
|
if px is None:
|
|
print(" ✗ prezzo USDE non leggibile — non si converte al buio (P5)")
|
|
return 2
|
|
val_usde, _ = U.valuta(eq_usde, px)
|
|
equity_tot = eq_usdc + val_usde
|
|
bid, ask = book_top()
|
|
p = piano(eq_usdc, eq_usde, px, a.quota, equity_tot, bid, ask)
|
|
|
|
print("=" * 78)
|
|
print(f" USDE CONVERT — {datetime.now(timezone.utc):%Y-%m-%dT%H:%M:%SZ}"
|
|
f" {'ESECUZIONE' if a.esegui else 'PIANO (dry-run)'}")
|
|
print("=" * 78)
|
|
print(f" prezzo : indice {px:.4f} ({fonte}) · book bid {bid} / ask {ask}"
|
|
f" -> ordine @ {p['px_ordine']}, banda [{PX_MIN}, {PX_MAX}]")
|
|
print(f" equity : USDC ${eq_usdc:,.2f} + USDE {eq_usde:,.4f} (${val_usde:,.2f})"
|
|
f" = ${equity_tot:,.2f}")
|
|
print(f" quota : {p['quota_ora']:.1%} -> {p['quota_target']:.0%}"
|
|
+ (f" · tetto venue {p['cap_frac']:.1%} = ${p['cap_usd']:,.2f}" if p.get("cap_usd") else ""))
|
|
print(f" ordine : {p['side'].upper()} {p['amount']:,.2f} USDE_USDC @ limit {p['px_ordine']:.4f}"
|
|
f" (${abs(p['delta_usd']):,.2f})")
|
|
slack = p["usdc_dopo"] - p["cuscino"]
|
|
print(f" USDC dopo : ${p['usdc_dopo']:,.2f} · cuscino richiesto ${p['cuscino']:,.2f}"
|
|
f" ({p['cuscino_come']})")
|
|
print(f" margine : ${slack:+,.2f} di slack sul cuscino"
|
|
+ (" ⚠️ la quota e' ESATTAMENTE sul limite: zero slack" if abs(slack) <= EPS_CUSCINO else ""))
|
|
for m in p["motivi"]:
|
|
print(f" ✗ {m}")
|
|
if not p["ok"]:
|
|
print(" → NON si converte.")
|
|
return 1
|
|
if not a.esegui:
|
|
print(" → piano valido. Rilancia con --esegui per mandarlo.")
|
|
return 0
|
|
|
|
LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
chunk, fatti, tot_filled, prezzi, falliti = a.chunk, [], 0.0, [], 0
|
|
for _ in range(60):
|
|
eq_usdc, eq_usde, px, _f = leggi_stato(c)
|
|
if px is None:
|
|
print(" ✗ prezzo non piu' leggibile — mi fermo qui"); break
|
|
val, _ = U.valuta(eq_usde, px)
|
|
bid, ask = book_top()
|
|
pp = piano(eq_usdc, eq_usde, px, a.quota, eq_usdc + val, bid, ask)
|
|
if not pp["ok"]:
|
|
print(f" · fine: {pp['motivi'][0]}")
|
|
break
|
|
amt = float(min(pp["amount"], math.floor(chunk)))
|
|
if amt < 1:
|
|
print(" ✗ blocco sceso sotto 1 USDE — mi fermo"); break
|
|
resp = c._unwrap(c._post("/mcp-deribit/tools/place_order",
|
|
{"instrument_name": U.SPOT, "side": pp["side"], "amount": amt,
|
|
"type": "limit", "price": pp["px_ordine"],
|
|
"label": f"usde-quota-{a.quota:.2f}"})) or {}
|
|
if not isinstance(resp, dict) or resp.get("error") or "order" not in resp:
|
|
err = resp.get("error", resp) if isinstance(resp, dict) else resp
|
|
falliti += 1
|
|
attesa = min(a.pausa * (2 ** falliti), 90.0) # backoff sul TEMPO, non sulla taglia
|
|
if falliti >= 3: # solo allora sospetto la size
|
|
chunk = max(1.0, math.floor(chunk / 2))
|
|
print(f" · rifiutato {amt:,.0f}: {err} -> attendo {attesa:.0f}s"
|
|
+ (f", blocco a {chunk:,.0f}" if falliti >= 3 else ""))
|
|
time.sleep(attesa)
|
|
with LOG.open("a") as f:
|
|
f.write(json.dumps({"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
"rifiutato": err, "amount": amt, "nuovo_chunk": chunk}) + "\n")
|
|
if falliti >= 8:
|
|
print(" ✗ troppi rifiuti consecutivi — mi fermo"); break
|
|
continue
|
|
falliti = 0
|
|
order, trades = resp["order"], resp.get("trades", []) or []
|
|
filled = float(order.get("filled_amount") or 0) or sum(float(t.get("amount", 0) or 0) for t in trades)
|
|
tot_filled += filled
|
|
prezzi += [(float(t["price"]), float(t["amount"])) for t in trades if t.get("price") and t.get("amount")]
|
|
fatti.append({"order_id": order.get("order_id"), "state": order.get("order_state"),
|
|
"amount": amt, "filled": filled})
|
|
print(f" · {pp['side']} {amt:,.0f} -> {order.get('order_state')} filled {filled:,.0f}"
|
|
f" (quota {pp['quota_ora']:.1%} -> bersaglio {a.quota:.0%})")
|
|
if filled <= 0:
|
|
print(" ✗ ordine accettato ma non fillato — mi fermo"); break
|
|
time.sleep(a.pausa)
|
|
|
|
px_med = sum(q * v for q, v in prezzi) / sum(v for _, v in prezzi) if prezzi else None
|
|
eq_usdc, eq_usde, px, _f = leggi_stato(c)
|
|
val, _ = U.valuta(eq_usde, px)
|
|
tot = eq_usdc + val
|
|
print(f"\n ESITO : {tot_filled:,.0f} USDE in {len(fatti)} ordini"
|
|
+ (f" @ {px_med:.4f} medio" if px_med else ""))
|
|
print(f" conto : USDC ${eq_usdc:,.2f} + USDE {eq_usde:,.4f} (${val:,.2f}) = ${tot:,.2f}")
|
|
print(f" quota : {val / tot:.2%} (bersaglio {a.quota:.0%})")
|
|
with LOG.open("a") as f:
|
|
f.write(json.dumps({"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
"quota_target": a.quota, "tot_filled": tot_filled, "px_medio": px_med,
|
|
"ordini": fatti, "quota_finale": val / tot if tot else None,
|
|
"usdc": eq_usdc, "usde": eq_usde}) + "\n")
|
|
print(f" registrato : {LOG.relative_to(ROOT)}")
|
|
return 0 if tot_filled > 0 else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|