unit Myc.Test.Trade.DataPoint; interface uses System.SysUtils, System.Classes, DUnitX.TestFramework, Myc.Trade.Types, Myc.Trade.DataPoint; type [TestFixture] TTestDataSeries = class(TObject) private FSeries: TDataSeries; procedure SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 10); public [Setup] procedure Setup; [Teardown] procedure Teardown; // Tests for Setup and basic Add/Count [Test] procedure TestSetupSeriesWithDataVerification; [Test] procedure TestAddAndCount; [Test] [IgnoreMemoryLeaks] procedure TestAddOrderAssertion; [Test] procedure TestGetItemsIndexing; // Tests for bulk Add [Test] procedure TestBulkAddIncreasesCount; [Test] [IgnoreMemoryLeaks] procedure TestBulkAddChronologicalAssertion_Internal; [Test] [IgnoreMemoryLeaks] procedure TestBulkAddChronologicalAssertion_External; [Test] procedure TestBulkAddSpanningMultipleChunks; [Test] procedure TestBulkAddWithMaxLookback; // Tests for TotalCount [Test] procedure TestTotalCountIncrementsCorrectly; [Test] procedure TestTotalCountIgnoresTrimming; [Test] procedure TestTotalCountResetsOnRecreate; // Tests for IndexOf [Test] procedure TestIndexOfExistingTimeStamp; [Test] procedure TestIndexOfNonExistingBetween; [Test] procedure TestIndexOfBeforeFirstItem; [Test] procedure TestIndexOfAfterLastItem; [Test] procedure TestIndexOfExactOldestItem; [Test] procedure TestIndexOfExactNewestItem; [Test] procedure TestIndexOfEmptySeries; [Test] procedure TestIndexOfSingleItemSeries; [Test] procedure TestIndexOfRespectsMaxLookback; [Test] procedure TestIndexOfWithTrimmedData; // Tests for Copy (Immutability) [Test] procedure TestCopyHasSameContent; [Test] procedure TestCopyIsImmutableAfterOriginalChanges; [Test] procedure TestCopyRespectsMaxLookback; // Tests for Lookback [Test] procedure TestCountIsCappedByMaxLookback_NoTrim; [Test] procedure TestCountIsCappedByMaxLookback_WithTrim; [Test] [IgnoreMemoryLeaks] procedure TestLoopIsSafeWithMaxLookback; [Test] procedure TestDataAndTimePropertiesRespectLookback; [Test] procedure TestLookbackZero; [Test] procedure TestLookbackOne; end; implementation { TTestDataSeries } procedure TTestDataSeries.Setup; begin FSeries := TDataSeries.CreateDataSeries(1); end; procedure TTestDataSeries.Teardown; begin FSeries := Default(TDataSeries); end; procedure TTestDataSeries.SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 10); var i: Integer; DataPoint: TDataPoint; baseTime: TDateTime; begin FSeries := TDataSeries.CreateDataSeries(MaxLookBack); baseTime := EncodeDate(2020, 7, 7); // Add data points with increasing timestamps. for i := 0 to ItemCount - 1 do begin DataPoint := TDataPoint.Create(baseTime + i, TAskBidItem.Create(i * 1.0, i * 1.0 + 0.1)); FSeries := FSeries.Add([DataPoint], 0, 1); end; end; procedure TTestDataSeries.TestSetupSeriesWithDataVerification; var i: Integer; baseTime, expectedTime: TDateTime; expectedAsk: Double; begin SetupSeriesWithData; baseTime := EncodeDate(2020, 7, 7); Assert.AreEqual(Int64(10), FSeries.Count, 'Setup should create exactly 10 items'); // Check all items to ensure correct reverse chronological order. for i := 0 to FSeries.Count - 1 do begin expectedTime := baseTime + (9 - i); expectedAsk := Single(9 - i); Assert.AreEqual(expectedTime, FSeries[i].Time, 'Item at logical index should have correct reversed timestamp'); Assert.AreEqual(expectedAsk, FSeries[i].Data.Ask, 'Item at logical index should have correct reversed data'); end; end; procedure TTestDataSeries.TestAddAndCount; var DataPoint: TDataPoint; newTime: TDateTime; begin SetupSeriesWithData(10, 11); newTime := EncodeDate(2020, 7, 17); Assert.AreEqual(Int64(10), FSeries.Count, 'Initial count should be 10'); DataPoint := TDataPoint.Create(newTime, TAskBidItem.Create(100.0, 100.1)); FSeries := FSeries.Add([DataPoint], 0, 1); Assert.AreEqual(Int64(11), FSeries.Count, 'Count should be 11 after adding one more'); Assert.AreEqual(newTime, FSeries.Items[0].Time, 'Newest item should be at index 0'); end; procedure TTestDataSeries.TestAddOrderAssertion; var olderTime: TDateTime; DataPoint: TDataPoint; begin SetupSeriesWithData; // Newest item is at 2020-07-16 olderTime := EncodeDate(2020, 7, 15); Assert.WillRaise( procedure begin DataPoint := TDataPoint.Create(olderTime, TAskBidItem.Create(0.0, 0.0)); FSeries.Add([DataPoint], 0, 1); end, EAssertionFailed, 'Adding item with older timestamp should raise an assert error' ); end; procedure TTestDataSeries.TestGetItemsIndexing; var i: Integer; expectedTime: TDateTime; baseTime: TDateTime; begin SetupSeriesWithData; baseTime := EncodeDate(2020, 7, 7); for i := 0 to 9 do begin expectedTime := baseTime + (9 - i); Assert.AreEqual(expectedTime, FSeries.Items[i].Time, 'Item at logical index should have reversed chronological time'); Assert.AreEqual(Double(9 - i), FSeries.Items[i].Data.Ask, 'Item data at logical index should match reversed insertion order'); end; end; //-------------------------------------------------------------------------------------------------- // Tests for bulk Add //-------------------------------------------------------------------------------------------------- procedure TTestDataSeries.TestBulkAddIncreasesCount; var newData: TArray>; baseTime, newTime: TDateTime; i: Integer; begin SetupSeriesWithData(10, 15); baseTime := EncodeDate(2020, 7, 7); SetLength(newData, 5); for i := 0 to High(newData) do begin newTime := baseTime + 10 + i; newData[i] := TDataPoint.Create(newTime, TAskBidItem.Create(i, i)); end; FSeries := FSeries.Add(newData, 0, Length(newData)); Assert.AreEqual(Int64(15), FSeries.Count, 'Count should be increased by the number of items in the bulk add'); Assert.AreEqual(baseTime + 10 + High(newData), FSeries[0].Time, 'Newest item should be the last item from the added array'); end; procedure TTestDataSeries.TestBulkAddChronologicalAssertion_Internal; var newData: TArray>; begin SetupSeriesWithData(10); SetLength(newData, 2); newData[0] := TDataPoint.Create(Now + 1, TAskBidItem.Create(1, 1)); newData[1] := TDataPoint.Create(Now, TAskBidItem.Create(2, 2)); // Not sorted Assert.WillRaise( procedure begin FSeries.Add(newData, 0, Length(newData)); end, EAssertionFailed, 'Bulk Add should fail if the input array is not chronologically sorted' ); end; procedure TTestDataSeries.TestBulkAddChronologicalAssertion_External; var newData: TArray>; olderTime: TDateTime; begin SetupSeriesWithData(10); // Newest is baseTime + 9 olderTime := EncodeDate(2020, 7, 7) + 8; SetLength(newData, 1); newData[0] := TDataPoint.Create(olderTime, TAskBidItem.Create(1, 1)); Assert.WillRaise( procedure begin FSeries.Add(newData, 0, Length(newData)); end, EAssertionFailed, 'Bulk Add should fail if its first item is older than the series last item' ); end; procedure TTestDataSeries.TestBulkAddSpanningMultipleChunks; const CHUNK_SIZE = 1024; var newData: TArray>; lastTimeBeforeAdd, firstNewTime, lastNewTime: TDateTime; i: Integer; begin SetupSeriesWithData(CHUNK_SIZE - 4, CHUNK_SIZE + 8); // Almost fill the first chunk lastTimeBeforeAdd := FSeries[0].Time; SetLength(newData, 8); // Add 8 items, which will span the chunk boundary for i := 0 to High(newData) do begin newData[i] := TDataPoint.Create(lastTimeBeforeAdd + 1 + i, TAskBidItem.Create(i, i)); end; firstNewTime := newData[0].Time; lastNewTime := newData[High(newData)].Time; FSeries := FSeries.Add(newData, 0, Length(newData)); Assert.AreEqual(Int64(CHUNK_SIZE + 4), FSeries.Count, 'Count should be correct after spanning a chunk'); Assert.AreEqual(lastNewTime, FSeries[0].Time, 'Newest item should be correct'); Assert.AreEqual(firstNewTime, FSeries[7].Time, 'Oldest of the new items should be at the correct logical index'); end; procedure TTestDataSeries.TestBulkAddWithMaxLookback; var newData: TArray>; i: Integer; baseTime: TDateTime; begin SetupSeriesWithData(400, 500); baseTime := FSeries[0].Time; SetLength(newData, 200); for i := 0 to High(newData) do begin newData[i] := TDataPoint.Create(baseTime + 1 + i, TAskBidItem.Create(i, i)); end; FSeries := FSeries.Add(newData, 0, Length(newData)); // Total items would be 600, but MaxLookback is 500. // The public Count must be capped at 500. Assert.AreEqual(Int64(500), FSeries.Count, 'Count should be capped by MaxLookback after bulk add'); end; //-------------------------------------------------------------------------------------------------- // Tests for TotalCount //-------------------------------------------------------------------------------------------------- procedure TTestDataSeries.TestTotalCountIncrementsCorrectly; var newData: TArray>; i: Integer; DataPoint: TDataPoint; begin Assert.AreEqual(Int64(0), FSeries.TotalCount, 'TotalCount should be 0 on creation'); // Test single add DataPoint := TDataPoint.Create(Now, TAskBidItem.Create(1, 1)); FSeries := FSeries.Add([DataPoint], 0, 1); Assert.AreEqual(Int64(1), FSeries.TotalCount, 'TotalCount should be 1 after single add'); // Test bulk add SetLength(newData, 10); for i := 0 to High(newData) do newData[i] := TDataPoint.Create(Now + 1 + i, TAskBidItem.Create(i, i)); FSeries := FSeries.Add(newData, 0, Length(newData)); Assert.AreEqual(Int64(11), FSeries.TotalCount, 'TotalCount should be 11 after bulk add'); end; procedure TTestDataSeries.TestTotalCountIgnoresTrimming; begin // Create series with a lookback that will cause trimming SetupSeriesWithData(20, 10); // The visible count is capped by MaxLookback Assert.AreEqual(Int64(10), FSeries.Count, 'Count should be capped by MaxLookback'); // The total count should reflect all items that were ever added Assert.AreEqual(Int64(20), FSeries.TotalCount, 'TotalCount must not be affected by trimming'); end; procedure TTestDataSeries.TestTotalCountResetsOnRecreate; begin SetupSeriesWithData(15); Assert.IsTrue(FSeries.TotalCount > 0, 'Pre-condition: TotalCount should be greater than 0'); // Re-create the series to clear it FSeries := TDataSeries.CreateDataSeries(0); Assert.AreEqual(Int64(0), FSeries.TotalCount, 'TotalCount should be 0 after re-creation'); Assert.AreEqual(Int64(0), FSeries.Count, 'Count should be 0 after re-creation'); end; //-------------------------------------------------------------------------------------------------- // Tests for IndexOf //-------------------------------------------------------------------------------------------------- procedure TTestDataSeries.TestIndexOfExistingTimeStamp; var ATimeStamp: TDateTime; expectedIndex: Int64; begin SetupSeriesWithData; ATimeStamp := EncodeDate(2020, 7, 7) + 9; // Newest item expectedIndex := 0; Assert.AreEqual(expectedIndex, FSeries.IndexOf(ATimeStamp), 'IndexOf for the newest existing item timestamp should return 0'); end; procedure TTestDataSeries.TestIndexOfNonExistingBetween; var ATimeStamp: TDateTime; expectedIndex: Int64; begin SetupSeriesWithData; // Time is between item 8 (2020-07-15) and 9 (2020-07-16) ATimeStamp := EncodeDate(2020, 7, 15) + 0.5; // Should return the index of the preceding item, which is the one at 2020-07-15. // Its logical index is 1 (0 is newest at 2020-07-16) expectedIndex := Int64(1); Assert.AreEqual( expectedIndex, FSeries.IndexOf(ATimeStamp), 'IndexOf for a non-existing timestamp should return the index of the immediately preceding item' ); end; procedure TTestDataSeries.TestIndexOfBeforeFirstItem; var ATimeStamp: TDateTime; baseTime: TDateTime; begin SetupSeriesWithData; baseTime := EncodeDate(2020, 7, 7); ATimeStamp := baseTime - 1; // A day before the oldest item Assert.AreEqual(Int64(-1), FSeries.IndexOf(ATimeStamp), 'IndexOf for a timestamp before the oldest item should return -1'); end; procedure TTestDataSeries.TestIndexOfAfterLastItem; var ATimeStamp: TDateTime; baseTime: TDateTime; begin SetupSeriesWithData; baseTime := EncodeDate(2020, 7, 7); ATimeStamp := baseTime + 10; // A day after the newest item Assert.AreEqual( Int64(0), FSeries.IndexOf(ATimeStamp), 'IndexOf for a timestamp after the newest item should return the index of the newest item' ); end; procedure TTestDataSeries.TestIndexOfExactOldestItem; var ATimeStamp: TDateTime; begin SetupSeriesWithData; ATimeStamp := EncodeDate(2020, 7, 7); Assert.AreEqual(Int64(9), FSeries.IndexOf(ATimeStamp), 'IndexOf for the exact oldest timestamp should return the last index'); end; procedure TTestDataSeries.TestIndexOfExactNewestItem; var ATimeStamp: TDateTime; begin SetupSeriesWithData; ATimeStamp := EncodeDate(2020, 7, 16); Assert.AreEqual(Int64(0), FSeries.IndexOf(ATimeStamp), 'IndexOf for the exact newest timestamp should return index 0'); end; procedure TTestDataSeries.TestIndexOfEmptySeries; begin // Use default setup with no data Assert.AreEqual(Int64(0), FSeries.Count); Assert.AreEqual(Int64(-1), FSeries.IndexOf(Now), 'IndexOf on empty series should return -1'); end; procedure TTestDataSeries.TestIndexOfSingleItemSeries; var DataPoint: TDataPoint; testTime: TDateTime; begin // Use default setup, but add one item testTime := EncodeDate(2025, 1, 1); DataPoint := TDataPoint.Create(testTime, TAskBidItem.Create(1.0, 2.0)); FSeries := FSeries.Add([DataPoint], 0, 1); Assert.AreEqual(Int64(1), FSeries.Count); Assert.AreEqual(Int64(0), FSeries.IndexOf(testTime), 'IndexOf for exact single item should be 0'); Assert.AreEqual(Int64(0), FSeries.IndexOf(testTime + 0.5), 'IndexOf for time after single item should be 0'); Assert.AreEqual(Int64(-1), FSeries.IndexOf(testTime - 1), 'IndexOf for time before single item should be -1'); end; procedure TTestDataSeries.TestIndexOfRespectsMaxLookback; var baseTime, searchTime: TDateTime; begin SetupSeriesWithData(500, 400); // Items from baseTime+0 to baseTime+499 baseTime := EncodeDate(2020, 7, 7); Assert.AreEqual(Int64(400), FSeries.Count, 'Pre-condition: Count must be 400'); // The oldest *physically present* item has a timestamp of baseTime+0. // This item is outside the MaxLookback range. // IndexOf must not find it. searchTime := baseTime; // Time of oldest physical item. Assert.AreEqual(Int64(-1), FSeries.IndexOf(searchTime), 'IndexOf should not find items outside the MaxLookback range'); // The oldest *logically valid* item is at index 399. // This item corresponds to the 100th item added (i=100), since 500-400=100 items are trimmed from the start. // Its timestamp is baseTime + 100. searchTime := baseTime + 100; Assert.AreEqual(Int64(399), FSeries.IndexOf(searchTime), 'IndexOf should find the oldest valid item at the edge of MaxLookback'); end; procedure TTestDataSeries.TestIndexOfWithTrimmedData; var baseTime, searchTime: TDateTime; begin SetupSeriesWithData(500, 400); // Oldest visible item has time baseTime+100 baseTime := EncodeDate(2020, 7, 7); // Search for an exact timestamp that was trimmed searchTime := baseTime + 50; // Added, but now outside the lookback window Assert.AreEqual(Int64(-1), FSeries.IndexOf(searchTime), 'IndexOf should not find an exact timestamp that has been trimmed'); // Search for a non-exact timestamp in the trimmed range searchTime := baseTime + 99.5; // This would resolve to item 99, which is trimmed. Assert.AreEqual(Int64(-1), FSeries.IndexOf(searchTime), 'IndexOf should return -1 when the preceding item is trimmed'); // Search for a non-exact timestamp that should resolve to the oldest visible item searchTime := baseTime + 100.5; // This should resolve to item 100 (logical index 399) Assert.AreEqual( Int64(399), FSeries.IndexOf(searchTime), 'IndexOf should find the oldest visible item when searching just after its timestamp' ); end; //-------------------------------------------------------------------------------------------------- // Tests for Copy (Immutability) //-------------------------------------------------------------------------------------------------- procedure TTestDataSeries.TestCopyHasSameContent; var copy: TDataSeries; i: Integer; begin SetupSeriesWithData(15); copy := FSeries; Assert.AreEqual(FSeries.Count, copy.Count, 'Copy must have the same count as the original'); Assert.AreEqual(FSeries.TotalCount, copy.TotalCount, 'Copy must have the same total count as the original'); for i := 0 to FSeries.Count - 1 do begin Assert.AreEqual(FSeries[i].Time, copy[i].Time, 'Copied item timestamp must match original'); Assert.AreEqual(FSeries[i].Data.Ask, copy[i].Data.Ask, 'Copied item data must match original'); end; end; procedure TTestDataSeries.TestCopyIsImmutableAfterOriginalChanges; var copy: TDataSeries; originalCount, originalTotalCount: Int64; newPoint: TDataPoint; lastTime: TDateTime; begin SetupSeriesWithData(10, 11); // Create the copy copy := FSeries; originalCount := copy.Count; originalTotalCount := copy.TotalCount; // Modify the original series by creating a new one lastTime := FSeries[0].Time; newPoint := TDataPoint.Create(lastTime + 1, TAskBidItem.Create(99, 99.1)); FSeries := FSeries.Add([newPoint], 0, 1); Assert.AreEqual(Int64(11), FSeries.Count, 'Original series count should have increased'); // Verify the copy remains unchanged Assert.AreEqual(originalCount, copy.Count, 'Copy count must not change after original is modified'); Assert.AreEqual(originalTotalCount, copy.TotalCount, 'Copy total count must not change after original is modified'); Assert.AreNotEqual(FSeries[0].Time, copy[0].Time, 'Newest item in copy should not be the new item from original'); end; procedure TTestDataSeries.TestCopyRespectsMaxLookback; var copy: TDataSeries; newPoint: TDataPoint; lastTime: TDateTime; begin SetupSeriesWithData(20, 15); // 20 items total, but series count is capped at 15 Assert.AreEqual(Int64(15), FSeries.Count, 'Pre-condition: Series count should be capped by MaxLookback'); Assert.AreEqual(Int64(20), FSeries.TotalCount, 'Pre-condition: Series total count should be 20'); copy := FSeries; // Verify the copy reflects the MaxLookback state Assert.AreEqual(Int64(15), copy.Count, 'Copy count should be the MaxLookback value of the original'); Assert.AreEqual(Int64(20), copy.TotalCount, 'Copy total count should be the total items added to the original'); // Modify original lastTime := FSeries[0].Time; newPoint := TDataPoint.Create(lastTime + 1, TAskBidItem.Create(99, 99.1)); FSeries := FSeries.Add([newPoint], 0, 1); // Verify the copy is still unchanged Assert.AreEqual(Int64(15), copy.Count, 'Copy count must remain unchanged after original is modified'); Assert.AreEqual(Int64(20), copy.TotalCount, 'Copy total count must remain unchanged after original is modified'); end; //-------------------------------------------------------------------------------------------------- // Tests for Lookback //-------------------------------------------------------------------------------------------------- procedure TTestDataSeries.TestCountIsCappedByMaxLookback_NoTrim; begin SetupSeriesWithData(500, 400); // Less than one chunk // The public Count must now be the MaxLookback value, even though // no chunk was freed and FCount is still 500 internally. Assert.AreEqual(Int64(400), FSeries.Count, 'Public count should be capped by MaxLookback even if no chunk is freed'); end; procedure TTestDataSeries.TestCountIsCappedByMaxLookback_WithTrim; const ITEMS_TO_ADD = 2000; LOOKBACK_LIMIT = 900; begin SetupSeriesWithData(ITEMS_TO_ADD, LOOKBACK_LIMIT); // Internally, the item count will be trimmed. // The public Count should be capped at the lookback limit. Assert.AreEqual(Int64(LOOKBACK_LIMIT), FSeries.Count, 'Public count should be capped by MaxLookback after trimming'); end; procedure TTestDataSeries.TestLoopIsSafeWithMaxLookback; var dummy: TDataPoint; begin SetupSeriesWithData(500, 400); Assert.AreEqual(Int64(400), FSeries.Count, 'Pre-condition: Count must be capped at 400'); // This test proves that a standard loop based on the public Count is safe // and will not access an invalid index. Assert.WillNotRaise( procedure begin for var i := 0 to FSeries.Count - 1 do begin dummy := FSeries[i]; end; end, EAssertionFailed, // Using EAssertionFailed here to catch potential internal range checks 'Looping up to the public Count should not raise an exception' ); // Also test the assert when going one item beyond the public count Assert.WillRaise( procedure begin dummy := FSeries[400]; end, EAssertionFailed, 'Accessing index at the limit of MaxLookback should raise an exception' ); end; procedure TTestDataSeries.TestDataAndTimePropertiesRespectLookback; var baseTime: TDateTime; expectedOldestTime: TDateTime; expectedOldestAsk: Double; expectedNewestTime: TDateTime; expectedNewestAsk: Double; begin SetupSeriesWithData(20, 15); // Add 20 items, lookback 15. // Visible items are those originally at index 5 through 19. baseTime := EncodeDate(2020, 7, 7); // Newest item in series is from i=19. Time = baseTime + 19. Logical index = 0. expectedNewestTime := baseTime + 19; expectedNewestAsk := 19.0; // Oldest visible item is from i=5. Time = baseTime + 5. Logical index = 14. expectedOldestTime := baseTime + 5; expectedOldestAsk := 5.0; Assert.AreEqual(Int64(15), FSeries.Count, 'Pre-condition: Count must be 15'); // Check Data and Time properties at the boundaries Assert.AreEqual(expectedNewestTime, FSeries.Time[0], 'Time[0] should be the newest visible item''s time'); Assert.AreEqual(expectedNewestAsk, FSeries.Data[0].Ask, 'Data[0] should be the newest visible item''s data'); Assert.AreEqual(expectedOldestTime, FSeries.Time[14], 'Time[Count-1] should be the oldest visible item''s time'); Assert.AreEqual(expectedOldestAsk, FSeries.Data[14].Ask, 'Data[Count-1] should be the oldest visible item''s data'); // Check access will fail beyond the lookback-limited count Assert.WillRaise( procedure begin var dummy := FSeries.Data[15]; end, EAssertionFailed, 'Accessing Data property beyond public count should fail' ); Assert.WillRaise( procedure begin var dummy := FSeries.Time[15]; end, EAssertionFailed, 'Accessing Time property beyond public count should fail' ); end; procedure TTestDataSeries.TestLookbackZero; var DataPoint: TDataPoint; begin FSeries := TDataSeries.CreateDataSeries(0); Assert.AreEqual(Int64(0), FSeries.Lookback, 'Lookback should be 0'); Assert.AreEqual(Int64(0), FSeries.Count, 'Count on new series should be 0'); Assert.AreEqual(Int64(0), FSeries.TotalCount, 'TotalCount on new series should be 0'); // Add an item DataPoint := TDataPoint.Create(Now, TAskBidItem.Create(1, 1.1)); FSeries := FSeries.Add([DataPoint], 0, 1); // Count should still be 0, but TotalCount should be 1 Assert.AreEqual(Int64(0), FSeries.Count, 'Count must remain 0 when Lookback is 0'); Assert.AreEqual(Int64(1), FSeries.TotalCount, 'TotalCount should increment even with Lookback 0'); // Accessing any item should fail Assert.WillRaise( procedure begin var dummy := FSeries[0]; end, EAssertionFailed, 'Accessing item[0] on a series with Lookback=0 should raise an error' ); // IndexOf should also find nothing Assert.AreEqual(Int64(-1), FSeries.IndexOf(Now), 'IndexOf on a series with Lookback=0 should return -1'); end; procedure TTestDataSeries.TestLookbackOne; var DataPoint1, DataPoint2, DataPoint3: TDataPoint; time1, time2, time3: TDateTime; begin FSeries := TDataSeries.CreateDataSeries(1); Assert.AreEqual(Int64(1), FSeries.Lookback, 'Lookback should be 1'); // Add first item time1 := Now; DataPoint1 := TDataPoint.Create(time1, TAskBidItem.Create(1, 1.1)); FSeries := FSeries.Add([DataPoint1], 0, 1); Assert.AreEqual(Int64(1), FSeries.Count, 'Count should be 1 after one add'); Assert.AreEqual(Int64(1), FSeries.TotalCount, 'TotalCount should be 1'); Assert.AreEqual(time1, FSeries[0].Time, 'The first item should be at index 0'); // Add second item time2 := time1 + EncodeTime(0, 0, 1, 0); DataPoint2 := TDataPoint.Create(time2, TAskBidItem.Create(2, 2.1)); FSeries := FSeries.Add([DataPoint2], 0, 1); Assert.AreEqual(Int64(1), FSeries.Count, 'Count must be capped at 1'); Assert.AreEqual(Int64(2), FSeries.TotalCount, 'TotalCount should be 2'); Assert.AreEqual(time2, FSeries[0].Time, 'The newest item should now be at index 0'); // Add third item time3 := time2 + EncodeTime(0, 0, 1, 0); DataPoint3 := TDataPoint.Create(time3, TAskBidItem.Create(3, 3.1)); FSeries := FSeries.Add([DataPoint3], 0, 1); Assert.AreEqual(Int64(1), FSeries.Count, 'Count must still be capped at 1'); Assert.AreEqual(Int64(3), FSeries.TotalCount, 'TotalCount should be 3'); Assert.AreEqual(time3, FSeries[0].Time, 'The third item should now be at index 0'); // Accessing index 1 should fail Assert.WillRaise( procedure begin var dummy := FSeries[1]; end, EAssertionFailed, 'Accessing item[1] on a series with Lookback=1 should raise an error' ); end; initialization TDUnitX.RegisterTestFixture(TTestDataSeries); end.