Unit refactoring

Fixed massive heap corruption bug in TDataRecord
This commit is contained in:
Michael Schimmel
2025-07-25 11:54:53 +02:00
parent 6b18d95570
commit aa53a88953
13 changed files with 461 additions and 359 deletions
+31 -236
View File
@@ -9,9 +9,8 @@ uses
Myc.Signals,
Myc.Mutable,
Myc.Core.Notifier,
Myc.Data.Pipeline,
Myc.Trade.Types,
Myc.Data.Series;
Myc.Data.Series,
Myc.Data.Pipeline;
type
// Abstract base class for data consumers.
@@ -31,16 +30,6 @@ type
class property Null: IConsumer<T> read FNull;
end;
// A consumer implementation that is owned by a controller.
TMycGenericConsumer<T> = class(TMycConsumer<T>)
private
FProc: TConstFunc<T, TState>;
protected
function Consume(const Value: T): TState; override; final;
public
constructor Create(const Controller: IInterface; const AProc: TConstFunc<T, TState>);
end;
TMycProducer<T> = class(TInterfacedObject, IProducer<T>)
strict private
type
@@ -63,16 +52,14 @@ type
class property Null: IProducer<T> read FNull;
end;
// Concrete producer that manages a list of consumers (listeners).
TMycContainedProducer<T> = class(TContainedObject, IProducer<T>)
// A consumer implementation that is owned by a controller.
TMycGenericConsumer<T> = class(TMycConsumer<T>)
private
FListeners: TMycNotifyList<IConsumer<T>>;
FProc: TConvertFunc<T, TState>;
protected
function Consume(const Value: T): TState; override; final;
public
constructor Create(const Controller: IInterface);
destructor Destroy; override;
function Broadcast(const Value: T): TState;
function Link(const Consumer: IConsumer<T>): TTag;
procedure Unlink(Tag: TTag);
constructor Create(const Controller: IInterface; const AProc: TConvertFunc<T, TState>);
end;
// Abstract base class for components that now act as a producer and contain a consumer.
@@ -101,33 +88,45 @@ type
class property Null: IConverter<S, T> read FNull;
end;
// Concrete producer that manages a list of consumers (listeners).
TMycContainedProducer<T> = class(TContainedObject, IProducer<T>)
private
FListeners: TMycNotifyList<IConsumer<T>>;
public
constructor Create(const Controller: IInterface);
destructor Destroy; override;
function Broadcast(const Value: T): TState;
function Link(const Consumer: IConsumer<T>): TTag;
procedure Unlink(Tag: TTag);
end;
// A generic converter that uses a function reference for the conversion logic.
TMycGenericConverter<S, T> = class(TMycConverter<S, T>)
private
FFunc: TConstFunc<S, T>;
FFunc: TConvertFunc<S, T>;
protected
function Consume(const Value: S): TState; override;
public
constructor Create(const AFunc: TConstFunc<S, T>);
constructor Create(const AFunc: TConvertFunc<S, T>);
end;
TMycGenericAggregator<S, T> = class(TMycConverter<S, T>)
private
FFunc: TConstFunc<S, TConverter<S, T>.TBroadcastProc, TState>;
FFunc: TAggregateFunc<S, T>;
protected
function Consume(const Value: S): TState; override;
public
constructor Create(const AFunc: TConstFunc<S, TConverter<S, T>.TBroadcastProc, TState>);
constructor Create(const AFunc: TAggregateFunc<S, T>);
end;
TMycGenericParallelConverter<S, T> = class(TMycConverter<S, T>)
private
FFunc: TConstFunc<S, T>;
FFunc: TConvertFunc<S, T>;
FQueue: TState;
protected
function Consume(const Value: S): TState; override;
public
constructor Create(const AFunc: TConstFunc<S, T>);
constructor Create(const AFunc: TConvertFunc<S, T>);
end;
TMycIdentityConverter<T> = class(TMycConverter<T, T>)
@@ -187,21 +186,6 @@ type
class function CreateDataEndpoint(Lookback: Integer; out Series: TLazy<TSeries<T>>): IConsumer<T>;
end;
TTickAggregation = class(TMycConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>)
private
FTimeframe: TTimeframe;
FCurrentBar: TDataPoint<TOhlcItem>;
function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
function GetCurrentBar: TDataPoint<TOhlcItem>;
function GetTimeframe: TTimeframe;
protected
function Consume(const Value: TDataPoint<Double>): TState; override;
public
constructor Create(const ATimeframe: TTimeframe);
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
property Timeframe: TTimeframe read GetTimeframe;
end;
TMycParallelConverter<T> = class(TMycConverter<T, T>)
private
FQueue: TState;
@@ -209,21 +193,6 @@ type
function Consume(const Value: T): TState; override; final;
end;
TOhlcAggregation = class(TMycConverter<TDataPoint<TOhlcItem>, TDataPoint<TOhlcItem>>)
private
FTimeframe: TTimeframe;
FCurrentBar: TDataPoint<TOhlcItem>;
function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
function GetCurrentBar: TDataPoint<TOhlcItem>;
function GetTimeframe: TTimeframe;
protected
function Consume(const Value: TDataPoint<TOhlcItem>): TState; override;
public
constructor Create(const ATimeframe: TTimeframe);
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
property Timeframe: TTimeframe read GetTimeframe;
end;
// Endpoint that collects data into a series.
TMycDataJoin<T> = class(TInterfacedObject, IProducer<TArray<T>>)
private
@@ -273,11 +242,7 @@ type
implementation
uses
System.TypInfo,
System.RTTI,
System.DateUtils,
System.Math,
Winapi.Windows,
Myc.TaskManager;
class constructor TMycConsumer<T>.CreateClass;
@@ -392,7 +357,7 @@ end;
{ TMycGenericConverter<S, T> }
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConstFunc<S, T>);
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc<S, T>);
begin
inherited Create;
FFunc := AFunc;
@@ -462,7 +427,7 @@ end;
{ TMycGenericConsumer<T> }
constructor TMycGenericConsumer<T>.Create(const Controller: IInterface; const AProc: TConstFunc<T, TState>);
constructor TMycGenericConsumer<T>.Create(const Controller: IInterface; const AProc: TConvertFunc<T, TState>);
begin
inherited Create(Controller);
FProc := AProc;
@@ -550,94 +515,6 @@ begin
end;
end;
{ TTickAggregation }
constructor TTickAggregation.Create(const ATimeframe: TTimeframe);
begin
inherited Create;
FTimeframe := ATimeframe;
end;
function TTickAggregation.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
var
baseTime: TDateTime;
begin
// Implementation is unchanged
baseTime := RecodeMilliSecond(TimeStamp, 0);
case Timeframe of
S: Result := baseTime;
S5: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 5));
S15: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 15));
S30: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 30));
M: Result := RecodeSecond(baseTime, 0);
M2: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 2));
M3: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 3));
M5: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 5));
M10: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 10));
M15: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 15));
M30: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 30));
H: Result := RecodeMinute(RecodeSecond(baseTime, 0), 0);
H2: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 2));
H3: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 3));
H4: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 4));
H8: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 8));
H12: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 12));
D: Result := StartOfTheDay(TimeStamp);
D2: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 2);
D3: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 3);
W: Result := TimeStamp.StartOfTheWeek;
MN: Result := TimeStamp.StartOfTheMonth;
MN3: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 3 * 3 + 1);
MN6: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 6 * 6 + 1);
Y: Result := TimeStamp.StartOfTheYear;
else
Result := 0;
end;
end;
function TTickAggregation.GetCurrentBar: TDataPoint<TOhlcItem>;
begin
Result := FCurrentBar;
end;
function TTickAggregation.GetTimeframe: TTimeframe;
begin
Result := FTimeframe;
end;
function TTickAggregation.Consume(const Value: TDataPoint<Double>): TState;
var
barStartTime: TDateTime;
lastBarTime: TDateTime;
begin
barStartTime := GetBarStartTime(Value.Time, FTimeframe);
lastBarTime := FCurrentBar.Time;
if (barStartTime > lastBarTime) then
begin
if (lastBarTime > 0) then
begin
Result := Broadcast(FCurrentBar);
end;
FCurrentBar.Data.Open := Value.Data;
FCurrentBar.Data.High := Value.Data;
FCurrentBar.Data.Low := Value.Data;
FCurrentBar.Data.Close := Value.Data;
FCurrentBar.Data.Volume := 1;
FCurrentBar.Time := barStartTime;
end
else
begin
if Value.Data > FCurrentBar.Data.High then
FCurrentBar.Data.High := Value.Data;
if Value.Data < FCurrentBar.Data.Low then
FCurrentBar.Data.Low := Value.Data;
FCurrentBar.Data.Close := Value.Data;
FCurrentBar.Data.Volume := FCurrentBar.Data.Volume + 1;
end;
end;
{ TMycSequence<T> }
constructor TMycSequence<T>.Create(ACount: Integer);
@@ -688,7 +565,7 @@ end;
{ TMycGenericParallelConverter<S, T> }
constructor TMycGenericParallelConverter<S, T>.Create(const AFunc: TConstFunc<S, T>);
constructor TMycGenericParallelConverter<S, T>.Create(const AFunc: TConvertFunc<S, T>);
begin
inherited Create;
FFunc := AFunc;
@@ -711,88 +588,6 @@ begin
FQueue := Result;
end;
{ TOhlcAggregation }
constructor TOhlcAggregation.Create(const ATimeframe: TTimeframe);
begin
inherited Create;
FTimeframe := ATimeframe;
end;
function TOhlcAggregation.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
begin
// Same implementation as TTickAggregation
var baseTime := RecodeMilliSecond(TimeStamp, 0);
case Timeframe of
S: Result := baseTime;
S5: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 5));
S15: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 15));
S30: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 30));
M: Result := RecodeSecond(baseTime, 0);
M2: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 2));
M3: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 3));
M5: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 5));
M10: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 10));
M15: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 15));
M30: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 30));
H: Result := RecodeMinute(RecodeSecond(baseTime, 0), 0);
H2: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 2));
H3: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 3));
H4: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 4));
H8: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 8));
H12: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 12));
D: Result := StartOfTheDay(TimeStamp);
D2: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 2);
D3: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 3);
W: Result := TimeStamp.StartOfTheWeek;
MN: Result := TimeStamp.StartOfTheMonth;
MN3: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 3 * 3 + 1);
MN6: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 6 * 6 + 1);
Y: Result := TimeStamp.StartOfTheYear;
else
Result := 0;
end;
end;
function TOhlcAggregation.GetCurrentBar: TDataPoint<TOhlcItem>;
begin
Result := FCurrentBar;
end;
function TOhlcAggregation.GetTimeframe: TTimeframe;
begin
Result := FTimeframe;
end;
function TOhlcAggregation.Consume(const Value: TDataPoint<TOhlcItem>): TState;
var
barStartTime: TDateTime;
lastBarTime: TDateTime;
begin
barStartTime := GetBarStartTime(Value.Time, FTimeframe);
lastBarTime := FCurrentBar.Time;
if (barStartTime > lastBarTime) then
begin
if (lastBarTime > 0) then
begin
Result := Broadcast(FCurrentBar);
end;
FCurrentBar.Data := Value.Data;
FCurrentBar.Time := barStartTime;
end
else
begin
if Value.Data.High > FCurrentBar.Data.High then
FCurrentBar.Data.High := Value.Data.High;
if Value.Data.Low < FCurrentBar.Data.Low then
FCurrentBar.Data.Low := Value.Data.Low;
FCurrentBar.Data.Close := Value.Data.Close;
FCurrentBar.Data.Volume := FCurrentBar.Data.Volume + Value.Data.Volume;
end;
end;
{ TMycDataJoin<T> }
constructor TMycDataJoin<T>.Create(ACount: Integer);
@@ -807,7 +602,7 @@ begin
FContainedProvider := TMycContainedProducer<TArray<T>>.Create(Self);
var cFunc :=
function(Idx: Integer): TConstFunc<T, TState>
function(Idx: Integer): TConvertFunc<T, TState>
begin
Result := function(const Value: T): TState begin Result := Consume(Idx, Value); end
end;
@@ -882,7 +677,7 @@ end;
{ TMycGenericAggregator<S, T> }
constructor TMycGenericAggregator<S, T>.Create(const AFunc: TConstFunc<S, TConverter<S, T>.TBroadcastProc, TState>);
constructor TMycGenericAggregator<S, T>.Create(const AFunc: TAggregateFunc<S, T>);
begin
inherited Create;
FFunc := AFunc;