Erster Teil von #485. Der PositionManager im Core beantwortet drei Fragen, die sich beim Debuggen sofort stellen: Wem gehoert eine Position, was sagt der Broker dazu, und was ist gesperrt. Drei Zugaenge auf denselben Dienst - das Fenster der Shell, --positions und --reassign im kopflosen Betrieb, GET /api/positions. Der Testbetrieb laeuft als systemd-Dienst; ein Werkzeug, das nur in der Oberflaeche existiert, fehlt genau dann, wenn man es braucht. Entscheidungen, die im Code begruendet stehen: - BrokerQuantity ist nullable und heisst "nicht bekannt", nicht "null Stueck". Ohne Verbindung sieht ein leergeraeumtes Konto genauso aus wie eine Stoerung; aus dem einen darf nicht auf das andere geschlossen werden. - Umbuchen aendert die Zuordnung, nie die Menge. Eine Umbuchung, die eine Stueckzahl bewegt, waere ein Handel ohne Order und stuende in keiner Historie. - Gegen eine entgegengesetzte Position wird abgelehnt: Das waere eine Glattstellung und gehoert ueber eine Order gemacht, mit Preis und Historie. - Das Quellbuch ist Pflicht - Schutz gegen die falsche Zeile -, ebenso eine Begruendung, sonst ist der Eingriff spaeter nicht mehr erklaerbar. - Das Cap des Zielbuchs sperrt NICHT. Die Position existiert bereits; sie zu benennen erzeugt kein neues Risiko. Wuerde die Grenze das verhindern, bliebe jede zu grosse Position fuer immer in UNASSIGNED. - Der REST-Endpunkt ist lesend. Der Listener kennt keine Anmeldung; ein schreibender Zugriff waere ein Eingriff in die Buecher, den niemand belegen kann. Beim ersten Testlauf gefunden: (Module, ContractKey) ist der Primaerschluessel von core_position, das Buch laesst sich in der Zeile nicht ueberschreiben. Umgebucht wird deshalb durch Loeschen und Neuanlegen. 471 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
61 lines
3.1 KiB
C#
61 lines
3.1 KiB
C#
using Avalonia.Media;
|
||
|
||
namespace IBKRTrader.App.ViewModels;
|
||
|
||
/// <summary>
|
||
/// Zeilentypen für die DataGrids der Core-Ansichten.
|
||
///
|
||
/// <para>Bewusst benannte Records statt der anonymen Typen, die die WinForms-Fassung an
|
||
/// <c>DataGridView.DataSource</c> gehängt hat: Avalonias <c>DataGrid</c> bindet über
|
||
/// kompilierte Bindings gegen einen bekannten Typ. Anonyme Typen sind <c>internal</c> und
|
||
/// funktionieren dort nur über Reflexion – mit benannten Records bleibt die Spaltendefinition
|
||
/// im XAML prüfbar.</para>
|
||
/// </summary>
|
||
public sealed record ModuleRow(string Name, string Prefix, string Status);
|
||
|
||
/// <summary>
|
||
/// Eine Zeile im Live-Log. Die Farbe hängt am Eintrag statt an einer Selektion – WinForms färbte
|
||
/// über <c>SelectionColor</c> der RichTextBox ein, in Avalonia wird je Element gebunden.
|
||
/// </summary>
|
||
public sealed record LogRow(string Text, IBrush Color);
|
||
|
||
/// <summary>Eine offene Position im Modul-Fenster.</summary>
|
||
public sealed record PositionRow(string Symbol, int Quantity, decimal AvgPrice, decimal Notional);
|
||
|
||
/// <summary>
|
||
/// Eine Zeile der Positionsverwaltung (#485). Zahlen kommen als fertiger Text: Die Formatierung
|
||
/// gehoert an eine Stelle, und „Broker“ muss zwischen „null Stück“ und „keine Auskunft“
|
||
/// unterscheiden koennen - ein Zahlentyp kann das nicht.
|
||
/// </summary>
|
||
public sealed record ManagedPositionRow(
|
||
string Book, string Origin, string Instrument, string Quantity, string AvgPrice,
|
||
string Bound, string Broker, string Restrictions);
|
||
|
||
/// <summary>Bestand, den der Broker fuehrt und den kein Buch beansprucht.</summary>
|
||
public sealed record UnknownPositionRow(
|
||
string Symbol, string SecType, string Currency, string Quantity, string AvgCost, string Note);
|
||
|
||
/// <summary>Eine Zeile der Supervisor-Berichtsliste. Tokens als fertiger Text (Prompt / Completion).</summary>
|
||
public sealed record SupervisorReportRow(
|
||
DateTime CreatedAt, string Profile, string Model, string Question, int ToolCallCount, string Tokens);
|
||
|
||
// ─── Accounting ──────────────────────────────────────────────────────────────
|
||
// Beträge als bereits formatierter Text: die Umrechnung in die Anzeigewährung passiert beim
|
||
// Laden, und das Format ist damit an einer Stelle festgelegt statt in jeder Spaltendefinition.
|
||
|
||
/// <summary>Eine Monatszeile des Monatsvergleichs (BWA).</summary>
|
||
public sealed record MonthlyRow(
|
||
string Month, string Opening, string Deposits, string Withdrawals,
|
||
string Volume, string Fees, string Result, string Closing);
|
||
|
||
/// <summary>Eine Buchung im neutralen Ledger.</summary>
|
||
public sealed record LedgerRow(
|
||
DateTime Time, string AccountId, string EventType, string Side, string Symbol,
|
||
string Currency, decimal Quantity, decimal Price,
|
||
decimal Gross, decimal Fee, decimal Net, string TransactionId);
|
||
|
||
/// <summary>Ein Ingest-Lauf des Flex-Query-Abrufs.</summary>
|
||
public sealed record IngestRunRow(
|
||
string AccountId, DateTime Started, DateTime? Finished, bool Backfill,
|
||
int NewEntries, int DuplicateEntries, bool Success, string Delta, string Message);
|