Skip to content

Commit f76688f

Browse files
committed
Add PositionSnapshotHolder.GetAllSnapshots() with tests
Add method to enumerate all position snapshots as cloned copies. Used by server modules to replay cached positions on client PortfolioLookup.
1 parent 80bc769 commit f76688f

2 files changed

Lines changed: 211 additions & 0 deletions

File tree

Messages/PositionSnapshotHolder.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,15 @@ private static void ApplyChanges(PositionChangeMessage snapshot, PositionChangeM
112112
snapshot.BoardCode = posMsg.BoardCode;
113113
}
114114

115+
/// <summary>
116+
/// Get cloned copies of all current position snapshots.
117+
/// </summary>
118+
public IEnumerable<PositionChangeMessage> GetAllSnapshots()
119+
{
120+
using (_snapshots.EnterScope())
121+
return _snapshots.Values.Select(s => s.TypedClone()).ToList();
122+
}
123+
115124
/// <summary>
116125
/// Reset snapshot for the specified position key.
117126
/// </summary>

Tests/SnapshotHolderTests.cs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2497,6 +2497,208 @@ public void Position_KeyIsCaseInsensitive()
24972497
retrieved.Changes[PositionChangeTypes.CurrentValue].AssertEqual(200m);
24982498
}
24992499

2500+
[TestMethod]
2501+
public void Position_GetAllSnapshots_Empty_ReturnsEmpty()
2502+
{
2503+
var holder = new PositionSnapshotHolder();
2504+
2505+
var snapshots = holder.GetAllSnapshots().ToList();
2506+
2507+
snapshots.Count.AssertEqual(0);
2508+
}
2509+
2510+
[TestMethod]
2511+
public void Position_GetAllSnapshots_ReturnsAllPositions()
2512+
{
2513+
var holder = new PositionSnapshotHolder();
2514+
2515+
holder.Process(new PositionChangeMessage
2516+
{
2517+
PortfolioName = "Portfolio1",
2518+
SecurityId = _secId1,
2519+
ServerTime = _now,
2520+
}.TryAdd(PositionChangeTypes.CurrentValue, 100m));
2521+
2522+
holder.Process(new PositionChangeMessage
2523+
{
2524+
PortfolioName = "Portfolio2",
2525+
SecurityId = _secId2,
2526+
ServerTime = _now,
2527+
}.TryAdd(PositionChangeTypes.CurrentValue, 200m));
2528+
2529+
var snapshots = holder.GetAllSnapshots().ToList();
2530+
2531+
snapshots.Count.AssertEqual(2);
2532+
2533+
var snap1 = snapshots.First(s => s.PortfolioName == "Portfolio1");
2534+
var snap2 = snapshots.First(s => s.PortfolioName == "Portfolio2");
2535+
2536+
snap1.Changes[PositionChangeTypes.CurrentValue].AssertEqual(100m);
2537+
snap2.Changes[PositionChangeTypes.CurrentValue].AssertEqual(200m);
2538+
}
2539+
2540+
[TestMethod]
2541+
public void Position_GetAllSnapshots_ReturnsCopies()
2542+
{
2543+
var holder = new PositionSnapshotHolder();
2544+
2545+
holder.Process(new PositionChangeMessage
2546+
{
2547+
PortfolioName = "Portfolio1",
2548+
SecurityId = _secId1,
2549+
ServerTime = _now,
2550+
}.TryAdd(PositionChangeTypes.CurrentValue, 100m));
2551+
2552+
var snapshots = holder.GetAllSnapshots().ToList();
2553+
snapshots[0].Changes[PositionChangeTypes.CurrentValue] = 999m;
2554+
2555+
// Internal state should not be affected
2556+
holder.TryGetSnapshot("Portfolio1", _secId1, null, null, null, null, null, out var internal1).AssertTrue();
2557+
internal1.Changes[PositionChangeTypes.CurrentValue].AssertEqual(100m);
2558+
}
2559+
2560+
[TestMethod]
2561+
public void Position_GetAllSnapshots_ReflectsUpdates()
2562+
{
2563+
var holder = new PositionSnapshotHolder();
2564+
2565+
holder.Process(new PositionChangeMessage
2566+
{
2567+
PortfolioName = "Portfolio1",
2568+
SecurityId = _secId1,
2569+
ServerTime = _now,
2570+
}.TryAdd(PositionChangeTypes.CurrentValue, 100m));
2571+
2572+
// Update same position
2573+
holder.Process(new PositionChangeMessage
2574+
{
2575+
PortfolioName = "Portfolio1",
2576+
SecurityId = _secId1,
2577+
ServerTime = _now.AddSeconds(1),
2578+
}.TryAdd(PositionChangeTypes.CurrentValue, 150m)
2579+
.TryAdd(PositionChangeTypes.BlockedValue, 10m));
2580+
2581+
var snapshots = holder.GetAllSnapshots().ToList();
2582+
2583+
snapshots.Count.AssertEqual(1);
2584+
snapshots[0].Changes[PositionChangeTypes.CurrentValue].AssertEqual(150m);
2585+
snapshots[0].Changes[PositionChangeTypes.BlockedValue].AssertEqual(10m);
2586+
}
2587+
2588+
[TestMethod]
2589+
public void Position_GetAllSnapshots_AfterReset_ReturnsEmpty()
2590+
{
2591+
var holder = new PositionSnapshotHolder();
2592+
2593+
holder.Process(new PositionChangeMessage
2594+
{
2595+
PortfolioName = "Portfolio1",
2596+
SecurityId = _secId1,
2597+
ServerTime = _now,
2598+
}.TryAdd(PositionChangeTypes.CurrentValue, 100m));
2599+
2600+
holder.Process(new PositionChangeMessage
2601+
{
2602+
PortfolioName = "Portfolio2",
2603+
SecurityId = _secId2,
2604+
ServerTime = _now,
2605+
}.TryAdd(PositionChangeTypes.CurrentValue, 200m));
2606+
2607+
holder.ResetSnapshot(null);
2608+
2609+
holder.GetAllSnapshots().ToList().Count.AssertEqual(0);
2610+
}
2611+
2612+
[TestMethod]
2613+
public void Position_GetAllSnapshots_AfterPartialReset()
2614+
{
2615+
var holder = new PositionSnapshotHolder();
2616+
2617+
holder.Process(new PositionChangeMessage
2618+
{
2619+
PortfolioName = "Portfolio1",
2620+
SecurityId = _secId1,
2621+
ServerTime = _now,
2622+
}.TryAdd(PositionChangeTypes.CurrentValue, 100m));
2623+
2624+
holder.Process(new PositionChangeMessage
2625+
{
2626+
PortfolioName = "Portfolio2",
2627+
SecurityId = _secId2,
2628+
ServerTime = _now,
2629+
}.TryAdd(PositionChangeTypes.CurrentValue, 200m));
2630+
2631+
holder.ResetSnapshot("Portfolio1", _secId1);
2632+
2633+
var snapshots = holder.GetAllSnapshots().ToList();
2634+
snapshots.Count.AssertEqual(1);
2635+
snapshots[0].PortfolioName.AssertEqual("Portfolio2");
2636+
}
2637+
2638+
[TestMethod]
2639+
public void Position_GetAllSnapshots_MultipleSecuritiesPerPortfolio()
2640+
{
2641+
var holder = new PositionSnapshotHolder();
2642+
2643+
holder.Process(new PositionChangeMessage
2644+
{
2645+
PortfolioName = "Portfolio1",
2646+
SecurityId = _secId1,
2647+
ServerTime = _now,
2648+
}.TryAdd(PositionChangeTypes.CurrentValue, 100m));
2649+
2650+
holder.Process(new PositionChangeMessage
2651+
{
2652+
PortfolioName = "Portfolio1",
2653+
SecurityId = _secId2,
2654+
ServerTime = _now,
2655+
}.TryAdd(PositionChangeTypes.CurrentValue, 200m));
2656+
2657+
var snapshots = holder.GetAllSnapshots().ToList();
2658+
snapshots.Count.AssertEqual(2);
2659+
2660+
var byAapl = snapshots.First(s => s.SecurityId == _secId1);
2661+
var byMsft = snapshots.First(s => s.SecurityId == _secId2);
2662+
2663+
byAapl.Changes[PositionChangeTypes.CurrentValue].AssertEqual(100m);
2664+
byMsft.Changes[PositionChangeTypes.CurrentValue].AssertEqual(200m);
2665+
}
2666+
2667+
[TestMethod]
2668+
public Task Position_GetAllSnapshots_ConcurrentAccess_ThreadSafe()
2669+
{
2670+
var holder = new PositionSnapshotHolder();
2671+
var token = CancellationToken;
2672+
2673+
// Pre-populate
2674+
for (var i = 0; i < 10; i++)
2675+
{
2676+
holder.Process(new PositionChangeMessage
2677+
{
2678+
PortfolioName = $"Portfolio{i}",
2679+
SecurityId = _secId1,
2680+
ServerTime = _now,
2681+
}.TryAdd(PositionChangeTypes.CurrentValue, i));
2682+
}
2683+
2684+
var tasks = Enumerable.Range(0, 100).Select(i => Task.Run(() =>
2685+
{
2686+
// Concurrent reads via GetAllSnapshots
2687+
var all = holder.GetAllSnapshots().ToList();
2688+
(all.Count >= 10).AssertTrue();
2689+
2690+
// Concurrent writes
2691+
holder.Process(new PositionChangeMessage
2692+
{
2693+
PortfolioName = $"Portfolio{i % 10}",
2694+
SecurityId = _secId1,
2695+
ServerTime = _now.AddMilliseconds(i),
2696+
}.TryAdd(PositionChangeTypes.CurrentValue, i * 10m));
2697+
}, token)).ToArray();
2698+
2699+
return Task.WhenAll(tasks);
2700+
}
2701+
25002702
#endregion
25012703

25022704
#region Order Immutability and Behavior Tests

0 commit comments

Comments
 (0)