Chart
This commit is contained in:
+157
-32
@@ -2,6 +2,9 @@ unit Myc.Trade.DataPoint;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.TimeSpan;
|
||||
|
||||
type
|
||||
// A data record for an Ask/Bid price pair.
|
||||
TAskBidItem = packed record
|
||||
@@ -10,6 +13,15 @@ type
|
||||
constructor Create(AAsk, ABid: Single);
|
||||
end;
|
||||
|
||||
TOhlcItem = record
|
||||
Open: Single;
|
||||
High: Single;
|
||||
Low: Single;
|
||||
Close: Single;
|
||||
Volume: Single;
|
||||
constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Single);
|
||||
end;
|
||||
|
||||
// Represents a time-stamped data point in a series.
|
||||
TDataPoint<T> = record
|
||||
Time: TDateTime;
|
||||
@@ -17,41 +29,38 @@ type
|
||||
constructor Create(ATime: TDateTime; const AData: T);
|
||||
end;
|
||||
|
||||
IDataSeriesWriter<T> = interface
|
||||
procedure Add(const Data: TDataPoint<T>); overload;
|
||||
procedure Add(const Data: array of TDataPoint<T>; NumToAdd: Integer = -1); overload;
|
||||
procedure Clear;
|
||||
end;
|
||||
|
||||
// A time-ordered series of data points, optimized for chronological additions.
|
||||
// The most recently added element has the logical index 0.
|
||||
IDataSeries<T> = interface
|
||||
function GetCount: Int64;
|
||||
function GetItems(Idx: Int64): TDataPoint<T>;
|
||||
function GetTotalCount: Int64;
|
||||
function Immutable: IDataSeries<T>;
|
||||
function GetWriter: IDataSeriesWriter<T>;
|
||||
function GetLookback: Int64;
|
||||
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||
property Count: Int64 read GetCount;
|
||||
// Accesses data points by their logical index.
|
||||
// Index 0 is the newest element, Index (Count - 1) is the oldest.
|
||||
property Items[Idx: Int64]: TDataPoint<T> read GetItems; default;
|
||||
// The maximum number of adressable items. Count will never be bigger than the Lookback.
|
||||
property Lookback: Int64 read GetLookback;
|
||||
// The total number of items ever added to the series.
|
||||
property TotalCount: Int64 read GetTotalCount;
|
||||
property Writer: IDataSeriesWriter<T> read GetWriter;
|
||||
end;
|
||||
|
||||
// Interface Helper for IDataSeries<T>.
|
||||
// Provides a safe, value-type-like wrapper around the interface.
|
||||
TDataSeries<T> = record
|
||||
type
|
||||
TConvertFunc<S> = reference to function(const Val: TDataPoint<T>): S;
|
||||
private
|
||||
FDataSeries: IDataSeries<T>;
|
||||
function GetCount: Int64;
|
||||
function GetItems(Idx: Int64): TDataPoint<T>;
|
||||
function GetData(Idx: Int64): T;
|
||||
function GetIsWriteable: Boolean;
|
||||
function GetTime(Idx: Int64): TDateTime;
|
||||
class function GetNull: IDataSeries<T>; static;
|
||||
function GetTotalCount: Int64;
|
||||
function GetLookback: Int64;
|
||||
class function GetNull: IDataSeries<T>; static;
|
||||
public
|
||||
constructor Create(ADataSeries: IDataSeries<T>);
|
||||
|
||||
@@ -61,14 +70,12 @@ type
|
||||
class operator Implicit(const A: TDataSeries<T>): IDataSeries<T>;
|
||||
class operator Implicit(const A: IDataSeries<T>): TDataSeries<T>;
|
||||
|
||||
class function CreateWriteable(MaxLookback: Int64): TDataSeries<T>; static;
|
||||
class function CreateDataSeries(Lookback: Int64; const Data: TArray<TDataPoint<T>> = nil): TDataSeries<T>; static;
|
||||
|
||||
// Create an immutable version of the given series.
|
||||
function Immutable: TDataSeries<T>;
|
||||
function Add(const Data: TArray<TDataPoint<T>>): TDataSeries<T>; overload;
|
||||
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): TDataSeries<T>; overload;
|
||||
|
||||
// Writing (only if IsWriteable=true)
|
||||
procedure Add(const Data: array of TDataPoint<T>; NumToAdd: Integer = -1);
|
||||
procedure Clear;
|
||||
function Convert<S>(const Func: TConvertFunc<S>): TDataSeries<S>;
|
||||
|
||||
// Searches for a data point by its timestamp.
|
||||
// Returns the logical index of the matching item.
|
||||
@@ -76,20 +83,111 @@ type
|
||||
// Returns -1 if the timestamp is before the oldest item in the series.
|
||||
function IndexOf(TimeStamp: TDateTime): Int64;
|
||||
|
||||
function ToArray: TArray<TDataPoint<T>>;
|
||||
function ToDataArray: TArray<T>;
|
||||
|
||||
class property Null: IDataSeries<T> read GetNull;
|
||||
property Count: Int64 read GetCount;
|
||||
property TotalCount: Int64 read GetTotalCount;
|
||||
property Lookback: Int64 read GetLookback;
|
||||
property Items[Idx: Int64]: TDataPoint<T> read GetItems; default;
|
||||
property Data[Idx: Int64]: T read GetData;
|
||||
property IsWriteable: Boolean read GetIsWriteable;
|
||||
property Time[Idx: Int64]: TDateTime read GetTime;
|
||||
property TotalCount: Int64 read GetTotalCount;
|
||||
end;
|
||||
|
||||
TDataSeriesDoubleHelper = record helper for TDataSeries<Double>
|
||||
function ToOhlc(TimeFrame: TTimeSpan): TDataSeries<TOhlcItem>;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Math,
|
||||
System.Generics.Collections,
|
||||
Myc.Trade.Core.DataPoint;
|
||||
|
||||
// Optimized helper function using direct TTimeSpan features and integer arithmetic.
|
||||
function CeilToTimeSpan(const ATime: TDateTime; const ATimeSpan: TTimeSpan): TDateTime;
|
||||
var
|
||||
timeSinceMidnight: TTimeSpan;
|
||||
timeSpanTicks: Int64;
|
||||
numIntervals, ceiledTicks: Int64;
|
||||
begin
|
||||
timeSpanTicks := ATimeSpan.Ticks;
|
||||
Assert(timeSpanTicks > 0, 'TimeSpan must be positive.');
|
||||
|
||||
// Get the time portion of ATime directly as a TTimeSpan.
|
||||
timeSinceMidnight := TTimeSpan.Subtract(ATime, Trunc(ATime));
|
||||
|
||||
// Using integer arithmetic to find the ceiling is robust.
|
||||
// This is a standard formula for integer ceiling division: (numerator + denominator - 1) / denominator
|
||||
numIntervals := (timeSinceMidnight.Ticks + timeSpanTicks - 1) div timeSpanTicks;
|
||||
|
||||
ceiledTicks := numIntervals * timeSpanTicks;
|
||||
|
||||
// Construct the final DateTime from the date part and the new, aligned time part.
|
||||
Result := Trunc(ATime) + TTimeSpan.FromTicks(ceiledTicks);
|
||||
end;
|
||||
|
||||
function TDataSeriesDoubleHelper.ToOhlc(TimeFrame: TTimeSpan): TDataSeries<TOhlcItem>;
|
||||
var
|
||||
ohlcPoints: TList<TDataPoint<TOhlcItem>>;
|
||||
currentBar: TOhlcItem;
|
||||
windowEndTime: TDateTime;
|
||||
sourceIdx: Int64;
|
||||
firstPointInBar: Boolean;
|
||||
begin
|
||||
if Self.Count = 0 then
|
||||
exit(TDataSeries<TOhlcItem>.Create(TNullDataSeries<TOhlcItem>.Null));
|
||||
|
||||
ohlcPoints := TList<TDataPoint<TOhlcItem>>.Create;
|
||||
try
|
||||
if TimeFrame.Ticks <= 0 then
|
||||
raise EArgumentException.Create('Invalid TimeFrame for OHLC aggregation.');
|
||||
|
||||
sourceIdx := Self.Count - 1; // Start with the oldest data point
|
||||
firstPointInBar := True;
|
||||
windowEndTime := 0;
|
||||
|
||||
// Iterate through all source points chronologically (oldest to newest)
|
||||
while sourceIdx >= 0 do
|
||||
begin
|
||||
var P := Self.Items[sourceIdx];
|
||||
|
||||
if firstPointInBar then
|
||||
begin
|
||||
windowEndTime := CeilToTimeSpan(P.Time, TimeFrame);
|
||||
currentBar.Create(P.Data, P.Data, P.Data, P.Data, 0);
|
||||
firstPointInBar := False;
|
||||
end;
|
||||
|
||||
if P.Time >= windowEndTime then
|
||||
begin
|
||||
ohlcPoints.Add(TDataPoint<TOhlcItem>.Create(windowEndTime, currentBar));
|
||||
firstPointInBar := True;
|
||||
Continue; // Re-evaluate the same point for the next bar
|
||||
end;
|
||||
|
||||
currentBar.High := Max(currentBar.High, P.Data);
|
||||
currentBar.Low := Min(currentBar.Low, P.Data);
|
||||
currentBar.Close := P.Data;
|
||||
currentBar.Volume := currentBar.Volume + 1;
|
||||
|
||||
Dec(sourceIdx);
|
||||
end;
|
||||
|
||||
if not firstPointInBar then
|
||||
ohlcPoints.Add(TDataPoint<TOhlcItem>.Create(windowEndTime, currentBar));
|
||||
|
||||
var dataArray := TMycDataArray<TOhlcItem>.CreateFromArray(ohlcPoints.ToArray, 0, ohlcPoints.Count);
|
||||
var seriesImpl := TMycDataSeries<TOhlcItem>.Create(ohlcPoints.Count, dataArray, ohlcPoints.Count);
|
||||
Result := TDataSeries<TOhlcItem>.Create(seriesImpl);
|
||||
finally
|
||||
ohlcPoints.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TAskBidItem }
|
||||
|
||||
constructor TAskBidItem.Create(AAsk, ABid: Single);
|
||||
@@ -98,6 +196,17 @@ begin
|
||||
Bid := ABid;
|
||||
end;
|
||||
|
||||
{ TOhlcItem }
|
||||
|
||||
constructor TOhlcItem.Create(AOpen, AHigh, ALow, AClose, AVolume: Single);
|
||||
begin
|
||||
Open := AOpen;
|
||||
High := AHigh;
|
||||
Low := ALow;
|
||||
Close := AClose;
|
||||
Volume := AVolume;
|
||||
end;
|
||||
|
||||
{ TDataPoint<T> }
|
||||
|
||||
constructor TDataPoint<T>.Create(ATime: TDateTime; const AData: T);
|
||||
@@ -114,26 +223,24 @@ begin
|
||||
FDataSeries := Null;
|
||||
end;
|
||||
|
||||
procedure TDataSeries<T>.Add(const Data: array of TDataPoint<T>; NumToAdd: Integer = -1);
|
||||
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): TDataSeries<T>;
|
||||
begin
|
||||
Assert(IsWriteable);
|
||||
FDataSeries.Writer.Add(Data, NumToAdd);
|
||||
Result := FDataSeries.Add(Data, First, Count);
|
||||
end;
|
||||
|
||||
procedure TDataSeries<T>.Clear;
|
||||
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>): TDataSeries<T>;
|
||||
begin
|
||||
Assert(IsWriteable);
|
||||
FDataSeries.Writer.Clear;
|
||||
Result := FDataSeries.Add(Data, 0, Length(Data));
|
||||
end;
|
||||
|
||||
function TDataSeries<T>.Immutable: TDataSeries<T>;
|
||||
function TDataSeries<T>.Convert<S>(const Func: TConvertFunc<S>): TDataSeries<S>;
|
||||
begin
|
||||
Result := FDataSeries.Immutable;
|
||||
Result := TConvertSeries<T, S>.Create(FDataSeries, Func);
|
||||
end;
|
||||
|
||||
class function TDataSeries<T>.CreateWriteable(MaxLookback: Int64): TDataSeries<T>;
|
||||
class function TDataSeries<T>.CreateDataSeries(Lookback: Int64; const Data: TArray<TDataPoint<T>> = nil): TDataSeries<T>;
|
||||
begin
|
||||
Result := TDataArray<T>.Create(MaxLookback);
|
||||
Result := TMycDataSeries<T>.CreateDataSeries(Lookback, TMycDataArray<T>.CreateFromArray(Data, 0, Length(Data)), Length(Data));
|
||||
end;
|
||||
|
||||
class operator TDataSeries<T>.Finalize(var Dest: TDataSeries<T>);
|
||||
@@ -171,9 +278,9 @@ begin
|
||||
Result := FDataSeries[Idx].Data;
|
||||
end;
|
||||
|
||||
function TDataSeries<T>.GetIsWriteable: Boolean;
|
||||
function TDataSeries<T>.GetLookback: Int64;
|
||||
begin
|
||||
Result := Assigned(FDataSeries.Writer);
|
||||
Result := FDataSeries.Lookback;
|
||||
end;
|
||||
|
||||
function TDataSeries<T>.GetTime(Idx: Int64): TDateTime;
|
||||
@@ -206,7 +313,7 @@ begin
|
||||
while (low <= high) do
|
||||
begin
|
||||
mid := low + (high - low) div 2;
|
||||
dataPointTime := GetItems(mid).Time; // Use GetItems to stay within the class
|
||||
dataPointTime := FDataSeries[mid].Time;
|
||||
|
||||
if (dataPointTime = TimeStamp) then
|
||||
begin
|
||||
@@ -225,4 +332,22 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TDataSeries<T>.ToArray: TArray<TDataPoint<T>>;
|
||||
begin
|
||||
var n := FDataSeries.Count;
|
||||
SetLength(Result, n);
|
||||
dec(n);
|
||||
for var i := 0 to n do
|
||||
Result[i] := FDataSeries[n - i];
|
||||
end;
|
||||
|
||||
function TDataSeries<T>.ToDataArray: TArray<T>;
|
||||
begin
|
||||
var n := FDataSeries.Count;
|
||||
SetLength(Result, n);
|
||||
dec(n);
|
||||
for var i := 0 to n do
|
||||
Result[i] := FDataSeries[n - i].Data;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user