Files
Encelado/Encelado/src/Encelado.Core/Baskets/PendingEntry.cs
T
Alby96andClaude Fable 5.1 13d64d1830 5.0 Fase 1: registro persistente degli ordini, stati Pending, orfane adottate e chiuse
Un ordine dall'esito ignoto non viene più abbandonato: entra in
data/state/pending_orders.json prima della chiamata HTTP, l'esito si legge per
orderId (il server non registra il referenceId degli ordini v2) e in mancanza
si ricostruisce dalla posizione comparsa sul conto. Una gamba senza esito porta
il basket in PendingA/PendingB invece di rifiutarlo; alla risoluzione parte la
gamba B, ridimensionata sulle unità eseguite, oppure la gamba A viene richiusa.
Ogni posizione del conto è classificata basket / orfana-bot / esterna: le orfane
del bot vengono adottate e chiuse, le esterne contate e mai toccate. Il picco di
equity ignora i movimenti di cassa. Bonifica da headless, orders.jsonl, contatori
in dashboard, bandit che propone e non applica. ADR-0009, test (m)-(q).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 10:54:50 +02:00

103 lines
3.9 KiB
C#

using System.Globalization;
using System.Text.Json;
namespace Encelado.Core.Baskets;
/// <summary>
/// An entry the decider approved whose execution is not finished: leg A is on its way
/// (state <c>PendingA</c>) or filled while leg B is on its way (<c>PendingB</c>). Holds
/// everything needed to finish the basket later — or to undo leg A — without the
/// original decision object, which does not survive a restart.
/// </summary>
public sealed class PendingEntry
{
public required string BasketId { get; init; }
public required bool BuyCross { get; init; }
public required double EntryZ { get; init; }
public required double UnitsA { get; init; }
public required double UnitsB { get; init; }
public required double TpPips { get; init; }
public required double MaxLossUsd { get; init; }
public double EntryCostPips { get; init; } = double.NaN;
public required double EquityAtEntry { get; init; }
public string Motivazione { get; init; } = string.Empty;
public required DateTime DecidedUtc { get; init; }
public string ClientRefA { get; set; } = string.Empty;
public string ClientRefB { get; set; } = string.Empty;
/// <summary>The quotes seen at decision time, for the slippage of each leg.</summary>
public double QuoteA { get; init; }
public double QuoteB { get; init; }
/// <summary>Leg A as filled, once it is.</summary>
public BasketLeg? LegA { get; set; }
public void Write(Utf8JsonWriter w)
{
ArgumentNullException.ThrowIfNull(w);
w.WriteStartObject("pending");
w.WriteString("basketId", BasketId);
w.WriteBoolean("buyCross", BuyCross);
w.WriteNumber("entryZ", EntryZ);
w.WriteNumber("unitsA", UnitsA);
w.WriteNumber("unitsB", UnitsB);
w.WriteNumber("tpPips", TpPips);
w.WriteNumber("maxLossUsd", MaxLossUsd);
w.WriteNumber("entryCostPips", double.IsFinite(EntryCostPips) ? EntryCostPips : 0);
w.WriteNumber("equityAtEntry", EquityAtEntry);
w.WriteString("motivazione", Motivazione);
w.WriteString("decidedUtc", DecidedUtc.ToString("O", CultureInfo.InvariantCulture));
w.WriteString("clientRefA", ClientRefA);
w.WriteString("clientRefB", ClientRefB);
w.WriteNumber("quoteA", QuoteA);
w.WriteNumber("quoteB", QuoteB);
if (LegA is { } leg)
{
BasketLeg.Write(w, "legA", leg);
}
w.WriteEndObject();
}
public static PendingEntry Read(JsonElement e)
{
PendingEntry p = new()
{
BasketId = e.GetProperty("basketId").GetString() ?? string.Empty,
BuyCross = e.GetProperty("buyCross").GetBoolean(),
EntryZ = e.GetProperty("entryZ").GetDouble(),
UnitsA = e.GetProperty("unitsA").GetDouble(),
UnitsB = e.GetProperty("unitsB").GetDouble(),
TpPips = e.GetProperty("tpPips").GetDouble(),
MaxLossUsd = e.GetProperty("maxLossUsd").GetDouble(),
EntryCostPips = e.GetProperty("entryCostPips").GetDouble(),
EquityAtEntry = e.GetProperty("equityAtEntry").GetDouble(),
Motivazione = e.GetProperty("motivazione").GetString() ?? string.Empty,
DecidedUtc = DateTime.Parse(e.GetProperty("decidedUtc").GetString()!, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal),
QuoteA = e.TryGetProperty("quoteA", out JsonElement qa) ? qa.GetDouble() : 0,
QuoteB = e.TryGetProperty("quoteB", out JsonElement qb) ? qb.GetDouble() : 0,
};
p.ClientRefA = e.TryGetProperty("clientRefA", out JsonElement ca) ? ca.GetString() ?? string.Empty : string.Empty;
p.ClientRefB = e.TryGetProperty("clientRefB", out JsonElement cb) ? cb.GetString() ?? string.Empty : string.Empty;
if (e.TryGetProperty("legA", out JsonElement la))
{
p.LegA = BasketLeg.Read(la);
}
return p;
}
}