Abweichung wird je Instrument bestimmt, Sperren auch kopflos setzbar

Der erste Lauf gegen die Testmaschine zeigte den Fehler sofort: 2 Stueck SAP in
DIAG, 2 in UNASSIGNED, der Broker fuehrt 4 - alles richtig, aber zeilenweise
verglichen meldete die Ansicht zwei Fehlbestaende. Der Broker kennt unsere
Buecher nicht; vergleichbar ist nur die Summe ueber alle. ManagedPosition traegt
jetzt OwnTotalQuantity, und Diverges misst daran. Zwei Tests halten beide
Richtungen fest: eine aufgeteilte Position ist keine Abweichung, eine echte
verschwindet nicht in der Summe.

Dazu --lock und --instrument-rule fuer den kopflosen Betrieb. Beide mit
Trockenlauf; die Stufe "Locked" schreibt vor dem Setzen aus, dass sie auch das
SCHLIESSEN durch Module verhindert - das gehoert dorthin, wo entschieden wird,
nicht in den Stoerungsfall.

Auf der Testmaschine durchgespielt: GOOG von UNASSIGNED nach MANUAL umgebucht
(Menge und Einstand unveraendert), dann auf CloseOnly gesetzt - beides steht in
der Uebersicht.

473 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-06 12:45:46 +02:00
co-authored by Claude Opus 5
parent 8fd99ee6fa
commit 834f035ff6
4 changed files with 211 additions and 9 deletions
@@ -248,4 +248,56 @@ public class PositionManagerTests
overview.Positions[0].Lock.Should().Be(PositionLock.CloseOnly);
overview.Positions[0].IsRestricted.Should().BeTrue();
}
[Fact]
public async Task Overview_DoesNotCallASplitPositionADivergence()
{
// Am 2026-09-06 auf der Test-VM aufgefallen: 2 Stück SAP in DIAG, 2 in UNASSIGNED, Broker
// führt 4 - alles richtig. Zeilenweise verglichen meldete die Ansicht zwei Fehlbestände.
// Der Broker kennt unsere Bücher nicht; vergleichbar ist nur die Summe.
var sut = CreateSut(out _);
_portfolio.GetAllPositionsAsync(Arg.Any<CancellationToken>())
.Returns(Task.FromResult<IReadOnlyList<Position>>(
[
new Position("DIAG", Sap, 2, 185m),
new Position(BookId.Unassigned.Value, Sap, 2, 187m)
]));
_broker.GetPositionsAsync(Arg.Any<CancellationToken>())
.Returns(Task.FromResult<IReadOnlyList<BrokerPosition>>(
[new BrokerPosition { Symbol = "SAP", SecType = "STK", Currency = "EUR",
ConId = 1, Quantity = 4, AvgCost = 186m, Instrument = Sap }]));
var overview = await sut.GetOverviewAsync();
overview.Positions.Should().HaveCount(2);
overview.Positions.Should().OnlyContain(p => !p.Diverges, "2 + 2 sind die 4 des Brokers");
overview.Positions.Should().OnlyContain(p => p.OwnTotalQuantity == 4);
overview.Positions.Should().OnlyContain(p => p.IsSharedAcrossBooks);
}
[Fact]
public async Task Overview_StillFindsARealDivergenceAcrossBooks()
{
// Die Gegenprobe: Summiert wird, aber nicht weggerechnet. Fehlt beim Broker ein Stück,
// muss es auffallen - sonst hätte die Zusammenfassung die Prüfung abgeschafft.
var sut = CreateSut(out _);
_portfolio.GetAllPositionsAsync(Arg.Any<CancellationToken>())
.Returns(Task.FromResult<IReadOnlyList<Position>>(
[
new Position("DIAG", Sap, 2, 185m),
new Position(BookId.Unassigned.Value, Sap, 2, 187m)
]));
_broker.GetPositionsAsync(Arg.Any<CancellationToken>())
.Returns(Task.FromResult<IReadOnlyList<BrokerPosition>>(
[new BrokerPosition { Symbol = "SAP", SecType = "STK", Currency = "EUR",
ConId = 1, Quantity = 3, AvgCost = 186m, Instrument = Sap }]));
var overview = await sut.GetOverviewAsync();
overview.Positions.Should().OnlyContain(p => p.Diverges, "wir führen 4, der Broker 3");
}
}