using System.Globalization; using System.Text.Json; namespace Encelado.Core.Baskets; /// /// An entry the decider approved whose execution is not finished: leg A is on its way /// (state PendingA) or filled while leg B is on its way (PendingB). Holds /// everything needed to finish the basket later — or to undo leg A — without the /// original decision object, which does not survive a restart. /// 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; /// The quotes seen at decision time, for the slippage of each leg. public double QuoteA { get; init; } public double QuoteB { get; init; } /// Leg A as filled, once it is. 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; } }