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
+2 -1
View File
@@ -6,7 +6,8 @@ uses
FMX.Forms,
MainForm in 'MainForm.pas' {Form1},
TestModule in 'TestModule.pas',
DynamicFMXControl in 'DynamicFMXControl.pas';
DynamicFMXControl in 'DynamicFMXControl.pas',
Myc.Trade.Pipeline.Impl in '..\Src\Myc.Trade.Pipeline.Impl.pas';
{$R *.res}
+1
View File
@@ -137,6 +137,7 @@
</DCCReference>
<DCCReference Include="TestModule.pas"/>
<DCCReference Include="DynamicFMXControl.pas"/>
<DCCReference Include="..\Src\Myc.Trade.Pipeline.Impl.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
+4 -3
View File
@@ -32,6 +32,7 @@ uses
Myc.Futures,
Myc.Trade.Types,
Myc.Trade.DataStream,
Myc.Trade.Pipeline,
Myc.Data.Series,
Myc.Data.Pipeline,
Myc.Signals,
@@ -298,7 +299,7 @@ begin
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
Result := ticker.Consumer;
var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateOhlcAggregation(Timeframe));
var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TTradeConverter.CreateOhlcAggregation(Timeframe));
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
@@ -423,7 +424,7 @@ begin
var equity :=
TConverter<Double, Double>.CreateAggregation(
function(const Value: Double; const Broadcast: TConverter<Double, Double>.TBroadcastProc): TState
function(const Value: Double; const Broadcast: TBroadcastFunc<Double>): TState
begin
if not FInit then
begin
@@ -599,7 +600,7 @@ begin
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateOhlcAggregation(timeframe));
var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TTradeConverter.CreateOhlcAggregation(timeframe));
// var OhlcTicker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
// var OhlcPoint := OhlcTicker.Sender;
+4 -3
View File
@@ -6,6 +6,7 @@ uses
Myc.Signals,
Myc.Data.Pipeline,
Myc.Trade.Types,
Myc.Trade.Pipeline,
Myc.Trade.Indicators;
function CreateStrategy1(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, Double>; overload;
@@ -28,7 +29,7 @@ type
begin
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateOhlcAggregation(Timeframe));
var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TTradeConverter.CreateOhlcAggregation(Timeframe));
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
@@ -97,7 +98,7 @@ begin
var positionManager :=
signalGenerator.Chain<Double>(
TConverter<TSignalEvent, Double>.CreateAggregation(
function(const Value: TSignalEvent; const Broadcast: TConverter<TSignalEvent, Double>.TBroadcastProc): TState
function(const Value: TSignalEvent; const Broadcast: TBroadcastFunc<Double>): TState
var
pnl: Double;
begin
@@ -164,7 +165,7 @@ begin
var equity :=
positionManager.Chain<Double>(
TConverter<Double, Double>.CreateAggregation(
function(const Value: Double; const Broadcast: TConverter<Double, Double>.TBroadcastProc): TState
function(const Value: Double; const Broadcast: TBroadcastFunc<Double>): TState
begin
if not FInit then
begin
+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;
+11 -24
View File
@@ -5,7 +5,6 @@ interface
uses
Myc.Signals,
Myc.Mutable,
Myc.Trade.Types,
Myc.Data.Series,
Myc.Data.Records;
@@ -31,6 +30,10 @@ type
property Consumer: IConsumer<S> read GetConsumer;
end;
TConvertFunc<S, T> = reference to function(const Value: S): T;
TBroadcastFunc<T> = reference to function(const Value: T): TState;
TAggregateFunc<S, T> = reference to function(const Value: S; const Broadcast: TBroadcastFunc<T>): TState;
// Interface helper for IProducer providing the null object pattern nad subscriptions for linked consumers.
TProducer<T> = record
public
@@ -64,7 +67,7 @@ type
// Chain consumers
function Chain(const Next: IConsumer<T>): IConsumer<T>; overload; inline;
function Chain<R>(const Next: IConverter<T, R>): TProducer<R>; overload; inline;
function Chain<R>(const Func: TConstFunc<T, R>): TProducer<R>; overload; inline;
function Chain<R>(const Func: TConvertFunc<T, R>): TProducer<R>; overload; inline;
// Extracts the field of a record by it's name (using RTTI).
function Field<R>(const FieldName: String): TProducer<R>; inline;
@@ -76,9 +79,6 @@ type
// Interface helper for IConverter<S,T> providing the null object pattern.
TConverter<S, T> = record
public
type
TBroadcastProc = reference to function(const Value: T): TState;
private
FConverter: IConverter<S, T>;
function GetConsumer: IConsumer<S>; inline;
@@ -94,8 +94,8 @@ type
class function Construct(const Consumer: IConsumer<S>; const Producer: TProducer<T>): TConverter<S, T>; static;
class function CreateConverter(const Func: TConstFunc<S, T>): TConverter<S, T>; static;
class function CreateAggregation(const Func: TConstFunc<S, TBroadcastProc, TState>): TConverter<S, T>; static;
class function CreateConverter(const Func: TConvertFunc<S, T>): TConverter<S, T>; static;
class function CreateAggregation(const Func: TAggregateFunc<S, T>): TConverter<S, T>; static;
property Consumer: IConsumer<S> read GetConsumer;
property Producer: TProducer<T> read GetProducer;
@@ -114,9 +114,6 @@ type
class function CreateTicker<T>: TConverter<TArray<T>, T>; static;
class function CreateIdentity<T>: TConverter<T, T>; static;
class function CreateTickAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>; static;
class function CreateOhlcAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, TDataPoint<TOhlcItem>>; static;
class function CreateEndpoint<T>(Lookback: Int64; out Series: TLazy<TSeries<T>>): IConsumer<T>; static;
class function Join<T>(const Producers: TArray<TProducer<T>>): TProducer<TArray<T>>; static;
@@ -188,7 +185,7 @@ begin
Result := Next;
end;
function TProducer<T>.Chain<R>(const Func: TConstFunc<T, R>): TProducer<R>;
function TProducer<T>.Chain<R>(const Func: TConvertFunc<T, R>): TProducer<R>;
begin
Result := Chain<R>(TMycGenericConverter<T, R>.Create(Func));
end;
@@ -251,12 +248,12 @@ begin
Result := TMycComposedConverter<S, T>.Create(Consumer, Producer);
end;
class function TConverter<S, T>.CreateAggregation(const Func: TConstFunc<S, TBroadcastProc, TState>): TConverter<S, T>;
class function TConverter<S, T>.CreateAggregation(const Func: TAggregateFunc<S, T>): TConverter<S, T>;
begin
Result := TMycGenericAggregator<S, T>.Create(Func);
end;
class function TConverter<S, T>.CreateConverter(const Func: TConstFunc<S, T>): TConverter<S, T>;
class function TConverter<S, T>.CreateConverter(const Func: TConvertFunc<S, T>): TConverter<S, T>;
begin
Result := TMycGenericConverter<S, T>.Create(Func);
end;
@@ -291,11 +288,6 @@ begin
Result := A.FConverter;
end;
class function TConverter.CreateTickAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>;
begin
Result := TTickAggregation.Create(Timeframe);
end;
{ TConverter }
class function TConverter.CreateCounter<T>: TConverter<T, Int64>;
@@ -318,11 +310,6 @@ begin
Result := TMycTicker<T>.Create;
end;
class function TConverter.CreateOhlcAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, TDataPoint<TOhlcItem>>;
begin
Result := TOhlcAggregation.Create(Timeframe);
end;
class function TConverter.DataMapping(
const Inputs: TArray<TRecordMapping>;
const Output: TDataRecord.TLayout
@@ -350,7 +337,7 @@ begin
Result := TDataRecord.Create(Output);
for var i := 0 to High(Idxs) do
for var j := 0 to High(Idxs[i]) do
Inputs[i].CopyField(Idxs[i][j].FromIdx, Result, Idxs[i][j].ToIdx);
Result.CopyValue(Inputs[i], Idxs[i][j].FromIdx, Idxs[i][j].ToIdx);
end
);
end;
+75 -40
View File
@@ -4,8 +4,6 @@ interface
uses
System.SysUtils,
System.Generics.Collections,
System.Rtti,
System.TypInfo;
type
@@ -23,8 +21,11 @@ type
procedure FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src);
procedure ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst);
procedure Assign(const [ref] Buffer: TBytes; const Src); overload;
procedure Finalize(const [ref] Buffer: TBytes);
procedure InitField(const [ref] Buffer: TBytes);
procedure AssignField(const [ref] Dest: TBytes; const [ref] Source: TBytes);
procedure FinalizeField(const [ref] Buffer: TBytes);
procedure CopyField(Src, Dst: Pointer);
property Offset: Integer read FOffset;
@@ -51,6 +52,7 @@ type
FFields: TArray<TField>;
constructor Create(const AFields: TArray<TField>);
public
class function FromRecord<T>: TLayout; static;
class function Construct(const Def: TArray<TFieldDef>): TLayout; static;
@@ -60,15 +62,17 @@ type
end;
const
Align = 8;
Align = sizeof(Pointer);
private
FLayout: TLayout;
FBuffer: TBytes;
public
constructor Create(const ALayout: TLayout; const ABuffer: TBytes = nil);
constructor Create(const ALayout: TLayout);
class operator Finalize(var Dest: TDataRecord);
class operator Assign(var Dest: TDataRecord; const [ref] Src: TDataRecord);
class function FromRecord<T>: TDataRecord; overload; static;
class function FromRecord<T>(const Src: T): TDataRecord; overload; static;
@@ -79,52 +83,54 @@ type
function GetValue<T>(const Name: String): T; overload;
procedure GetValue(Idx: Integer; out Value); overload;
procedure CopyField(Idx: Integer; Dst: TDataRecord; DstIdx: Integer);
procedure CopyValue(const SrcRec: TDataRecord; SrcIdx, DstIdx: Integer);
property Layout: TLayout read FLayout;
end;
implementation
uses
System.Rtti;
const
DataSize: array[TDataRecord.TFieldType] of Integer =
(sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime), sizeof(TDataRecord));
implementation
{ TDataRecord }
uses
System.Classes;
constructor TDataRecord.Create(const ALayout: TLayout; const ABuffer: TBytes = nil);
constructor TDataRecord.Create(const ALayout: TLayout);
begin
FLayout := ALayout;
FBuffer := ABuffer;
var bufSize := 0;
if Length(FLayout.Fields) > 0 then
with FLayout.Fields[High(FLayout.Fields)] do
bufSize := Offset + AlignedSize;
if FBuffer = nil then
SetLength(FBuffer, bufSize)
else
Assert(Length(FBuffer) >= bufSize);
SetLength(FBuffer, bufSize);
for var i := 0 to High(FLayout.Fields) do
FLayout.Fields[i].InitField(FBuffer);
end;
procedure TDataRecord.CopyField(Idx: Integer; Dst: TDataRecord; DstIdx: Integer);
procedure TDataRecord.CopyValue(const SrcRec: TDataRecord; SrcIdx, DstIdx: Integer);
begin
Assert(Dst.Layout.Fields[DstIdx].FieldType = FLayout.Fields[Idx].FieldType);
Dst.Layout.Fields[DstIdx].Assign(Dst.FBuffer, FBuffer[FLayout.Fields[DstIdx].Offset])
end;
Assert(SrcRec.Layout.Fields[SrcIdx].FieldType = FLayout.Fields[SrcIdx].FieldType);
{ TDataRecord }
var Src := @SrcRec.FBuffer[SrcRec.Layout.Fields[SrcIdx].Offset];
var Dst := @FBuffer[FLayout.Fields[DstIdx].Offset];
FLayout.Fields[SrcIdx].CopyField(Src, Dst);
end;
class function TDataRecord.FromRecord<T>: TDataRecord;
begin
Result := TDataRecord.Create(TLayout.FromRecord<T>);
Result.Create(TLayout.FromRecord<T>);
end;
class function TDataRecord.FromRecord<T>(const Src: T): TDataRecord;
begin
Result := FromRecord<T>;
Result.Create(TLayout.FromRecord<T>);
var ctx := TRttiContext.Create;
var rttiType := ctx.GetType(TypeInfo(T));
@@ -187,10 +193,22 @@ begin
FLayout.Fields[idx].FromType(FBuffer, TypeInfo(T), Value);
end;
class operator TDataRecord.Assign(var Dest: TDataRecord; const [ref] Src: TDataRecord);
begin
if Dest.FLayout.FFields <> Src.FLayout.FFields then
begin
Finalize(Dest);
Dest.Create(Src.Layout);
end;
for var i := 0 to High(Dest.Layout.Fields) do
Dest.Layout.Fields[i].AssignField(Dest.FBuffer, Src.FBuffer);
end;
class operator TDataRecord.Finalize(var Dest: TDataRecord);
begin
for var i := 0 to High(Dest.FLayout.Fields) do
Dest.FLayout.Fields[i].Finalize(Dest.FBuffer);
for var i := High(Dest.FLayout.Fields) downto 0 do
Dest.Layout.Fields[i].FinalizeField(Dest.FBuffer);
end;
constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; AOffset: Integer);
@@ -200,7 +218,7 @@ begin
FOffset := AOffset;
end;
procedure TDataRecord.TField.Finalize(const [ref] Buffer: TBytes);
procedure TDataRecord.TField.InitField(const [ref] Buffer: TBytes);
begin
if not (FFieldType in [dfString, dfRecord]) then
exit;
@@ -209,8 +227,22 @@ begin
var P := @Buffer[FOffset];
case FFieldType of
dfString: PString(P)^ := '';
dfRecord: TDataRecord(P^) := Default(TDataRecord);
dfString: Initialize(PString(P)^);
dfRecord: Initialize(TDataRecord(P^));
end;
end;
procedure TDataRecord.TField.FinalizeField(const [ref] Buffer: TBytes);
begin
if not (FFieldType in [dfString, dfRecord]) then
exit;
Assert(FOffset + Size <= Length(Buffer));
var P := @Buffer[FOffset];
case FFieldType of
dfString: Finalize(PString(P)^);
dfRecord: Finalize(TDataRecord(P^));
end;
end;
@@ -218,8 +250,6 @@ procedure TDataRecord.TField.FromType(const [ref] Buffer: TBytes; SrcType: PType
begin
Assert(FOffset + Size <= Length(Buffer));
Finalize(Buffer);
var Dst := @Buffer[FOffset];
case FFieldType of
dfFloat:
@@ -317,19 +347,24 @@ begin
end;
end;
procedure TDataRecord.TField.Assign(const [ref] Buffer: TBytes; const Src);
procedure TDataRecord.TField.AssignField(const [ref] Dest: TBytes; const [ref] Source: TBytes);
begin
Assert(FOffset + Size <= Length(Buffer));
Assert(FOffset + Size <= Length(Dest));
Finalize(Buffer);
var Dst := @Dest[FOffset];
var Src := @Source[FOffset];
var Dst := @Buffer[FOffset];
CopyField(Src, Dst);
end;
procedure TDataRecord.TField.CopyField(Src, Dst: Pointer);
begin
case FFieldType of
dfFloat: PDouble(Dst)^ := PDouble(@Src)^;
dfInteger: PInt64(Dst)^ := PInt64(@Src)^;
dfString: PString(Dst)^ := PString(@Src)^;
dfTimestamp: PDateTime(Dst)^ := PDateTime(@Src)^;
dfRecord: TDataRecord(Dst^) := TDataRecord(Src);
dfFloat: PDouble(Dst)^ := PDouble(Src)^;
dfInteger: PInt64(Dst)^ := PInt64(Src)^;
dfString: PString(Dst)^ := PString(Src)^;
dfTimestamp: PDateTime(Dst)^ := PDateTime(Src)^;
dfRecord: TDataRecord(Dst^) := TDataRecord(Src^);
else
Assert(false);
end;
+44 -43
View File
@@ -8,8 +8,9 @@ uses
System.Generics.Collections,
System.Rtti,
Myc.Data.Records,
Myc.Trade.Types,
Myc.Data.Series;
Myc.Data.Pipeline,
Myc.Data.Series,
Myc.Trade.Types;
type
// Result for the Moving Average Convergence Divergence (MACD) indicator.
@@ -46,48 +47,48 @@ type
class function CalculateWMA(const Series: TSeries<Double>; const Period: Integer): Double; static;
public
// Simple Moving Average
class function CreateSMA(Period: Integer): TConstFunc<Double, Double>; static;
class function CreateSMA(Period: Integer): TConvertFunc<Double, Double>; static;
// Exponential Moving Average
class function CreateEMA(Period: Integer): TConstFunc<Double, Double>; static;
class function CreateEMA(Period: Integer): TConvertFunc<Double, Double>; static;
// Hull Moving Average
class function CreateHMA(Period: Integer): TConstFunc<Double, Double>; static;
class function CreateHMA(Period: Integer): TConvertFunc<Double, Double>; static;
// Relative Strength Index
class function CreateRSI(Period: Integer): TConstFunc<Double, Double>; static;
class function CreateRSI(Period: Integer): TConvertFunc<Double, Double>; static;
// Moving Average Convergence Divergence
class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConstFunc<Double, TMacdResult>; overload; static;
class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConvertFunc<Double, TMacdResult>; overload; static;
class function CreateMACD(
const EmaFast,
EmaSlow,
EmaSignal: TConstFunc<Double, Double>
): TConstFunc<Double, TMacdResult>; overload; static;
EmaSignal: TConvertFunc<Double, Double>
): TConvertFunc<Double, TMacdResult>; overload; static;
// Stochastic Oscillator
class function CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>; overload; static;
class function CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc<TOhlcItem, TStochasticResult>; overload; static;
class function CreateStochastic(
KPeriod: Integer;
const SmaD: TConstFunc<Double, Double>
): TConstFunc<TOhlcItem, TStochasticResult>; overload; static;
const SmaD: TConvertFunc<Double, Double>
): TConvertFunc<TOhlcItem, TStochasticResult>; overload; static;
// Bollinger Bands
class function CreateBollingerBands(Period: Integer; Multiplier: Double): TConstFunc<Double, TBollingerBandsResult>; static;
class function CreateBollingerBands(Period: Integer; Multiplier: Double): TConvertFunc<Double, TBollingerBandsResult>; static;
// Average True Range
class function CreateATR(Period: Integer): TConstFunc<TOhlcItem, Double>; overload; static;
class function CreateATR(const MovAvgTR: TConstFunc<Double, Double>): TConstFunc<TOhlcItem, Double>; overload; static;
class function CreateATR(Period: Integer): TConvertFunc<TOhlcItem, Double>; overload; static;
class function CreateATR(const MovAvgTR: TConvertFunc<Double, Double>): TConvertFunc<TOhlcItem, Double>; overload; static;
// Keltner Channels
class function CreateKeltnerChannels(
Period: Integer;
Multiplier: Double
): TConstFunc<TOhlcItem, TKeltnerChannelsResult>; overload; static;
): TConvertFunc<TOhlcItem, TKeltnerChannelsResult>; overload; static;
class function CreateKeltnerChannels(
const MovAvgMiddle: TConstFunc<Double, Double>;
const AtrFunc: TConstFunc<TOhlcItem, Double>;
const MovAvgMiddle: TConvertFunc<Double, Double>;
const AtrFunc: TConvertFunc<TOhlcItem, Double>;
Multiplier: Double
): TConstFunc<TOhlcItem, TKeltnerChannelsResult>; overload; static;
): TConvertFunc<TOhlcItem, TKeltnerChannelsResult>; overload; static;
class function CreateMean: TConstFunc<TArray<Double>, Double>; static;
class function CreateMean: TConvertFunc<TArray<Double>, Double>; static;
end;
TIndicatorFactory = class
type
TFunc = TConstFunc<TDataRecord, TDataRecord>;
TFunc = TConvertFunc<TDataRecord, TDataRecord>;
private
FParams: TDataRecord.TLayout;
FInput: TDataRecord.TLayout;
@@ -125,9 +126,9 @@ type
TMACD = class
type
TParam = record
Fast: TConstFunc<Double, Double>;
Slow: TConstFunc<Double, Double>;
Signal: TConstFunc<Double, Double>;
Fast: TConvertFunc<Double, Double>;
Slow: TConvertFunc<Double, Double>;
Signal: TConvertFunc<Double, Double>;
end;
TInput = record
@@ -141,7 +142,7 @@ type
end;
public
class function CreateMACD(const Param: TParam): TConstFunc<TInput, TResult>; static;
class function CreateMACD(const Param: TParam): TConvertFunc<TInput, TResult>; static;
end;
var
@@ -208,7 +209,7 @@ begin
Result := numerator / denominator;
end;
class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TConstFunc<Double, TBollingerBandsResult>;
class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TConvertFunc<Double, TBollingerBandsResult>;
begin
var sourceData: TSeries<Double>;
Result :=
@@ -231,7 +232,7 @@ begin
end;
end;
class function TIndicators.CreateEMA(Period: Integer): TConstFunc<Double, Double>;
class function TIndicators.CreateEMA(Period: Integer): TConvertFunc<Double, Double>;
begin
var lastEma: Double := Double.NaN;
var sourceData: TSeries<Double>;
@@ -262,7 +263,7 @@ begin
end;
end;
class function TIndicators.CreateHMA(Period: Integer): TConstFunc<Double, Double>;
class function TIndicators.CreateHMA(Period: Integer): TConvertFunc<Double, Double>;
begin
var periodHalf := Period div 2;
var periodSqrt := Round(Sqrt(Period));
@@ -305,13 +306,13 @@ begin
end;
// Standard MACD using EMAs.
class function TIndicators.CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConstFunc<Double, TMacdResult>;
class function TIndicators.CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConvertFunc<Double, TMacdResult>;
begin
Result := CreateMACD(CreateEMA(FastPeriod), CreateEMA(SlowPeriod), CreateEMA(SignalPeriod));
end;
// Creates a MACD indicator from three provided moving average functions.
class function TIndicators.CreateMACD(const EmaFast, EmaSlow, EmaSignal: TConstFunc<Double, Double>): TConstFunc<Double, TMacdResult>;
class function TIndicators.CreateMACD(const EmaFast, EmaSlow, EmaSignal: TConvertFunc<Double, Double>): TConvertFunc<Double, TMacdResult>;
begin
Result :=
function(const Value: Double): TMacdResult
@@ -339,7 +340,7 @@ begin
end;
end;
class function TIndicators.CreateRSI(Period: Integer): TConstFunc<Double, Double>;
class function TIndicators.CreateRSI(Period: Integer): TConvertFunc<Double, Double>;
begin
var avgGain: Double := Double.NaN;
var avgLoss: Double := Double.NaN;
@@ -398,7 +399,7 @@ begin
end;
end;
class function TIndicators.CreateSMA(Period: Integer): TConstFunc<Double, Double>;
class function TIndicators.CreateSMA(Period: Integer): TConvertFunc<Double, Double>;
begin
var sourceData: TSeries<Double>;
Result :=
@@ -413,7 +414,7 @@ begin
end;
// Standard Stochastic Oscillator using an SMA for the %D line.
class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>;
class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc<TOhlcItem, TStochasticResult>;
begin
Result := CreateStochastic(KPeriod, CreateSMA(DPeriod));
end;
@@ -421,8 +422,8 @@ end;
// Creates a Stochastic Oscillator using an injectable moving average for the %D line.
class function TIndicators.CreateStochastic(
KPeriod: Integer;
const SmaD: TConstFunc<Double, Double>
): TConstFunc<TOhlcItem, TStochasticResult>;
const SmaD: TConvertFunc<Double, Double>
): TConvertFunc<TOhlcItem, TStochasticResult>;
begin
var sourceData: TSeries<TOhlcItem>;
@@ -461,13 +462,13 @@ begin
end;
// Standard ATR using an EMA for smoothing.
class function TIndicators.CreateATR(Period: Integer): TConstFunc<TOhlcItem, Double>;
class function TIndicators.CreateATR(Period: Integer): TConvertFunc<TOhlcItem, Double>;
begin
Result := CreateATR(CreateEMA(Period));
end;
// Calculates the Average True Range (ATR) using an injectable moving average.
class function TIndicators.CreateATR(const MovAvgTR: TConstFunc<Double, Double>): TConstFunc<TOhlcItem, Double>;
class function TIndicators.CreateATR(const MovAvgTR: TConvertFunc<Double, Double>): TConvertFunc<TOhlcItem, Double>;
begin
var sourceData: TSeries<TOhlcItem>;
@@ -495,17 +496,17 @@ begin
end;
// Standard Keltner Channels using an EMA for the middle line and an EMA-based ATR.
class function TIndicators.CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConstFunc<TOhlcItem, TKeltnerChannelsResult>;
class function TIndicators.CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc<TOhlcItem, TKeltnerChannelsResult>;
begin
Result := CreateKeltnerChannels(CreateEMA(Period), CreateATR(Period), Multiplier);
end;
// Calculates Keltner Channels using an injectable ATR and middle band moving average.
class function TIndicators.CreateKeltnerChannels(
const MovAvgMiddle: TConstFunc<Double, Double>;
const AtrFunc: TConstFunc<TOhlcItem, Double>;
const MovAvgMiddle: TConvertFunc<Double, Double>;
const AtrFunc: TConvertFunc<TOhlcItem, Double>;
Multiplier: Double
): TConstFunc<TOhlcItem, TKeltnerChannelsResult>;
): TConvertFunc<TOhlcItem, TKeltnerChannelsResult>;
begin
Result :=
function(const Value: TOhlcItem): TKeltnerChannelsResult
@@ -533,7 +534,7 @@ begin
end;
end;
class function TIndicators.CreateMean: TConstFunc<TArray<Double>, Double>;
class function TIndicators.CreateMean: TConvertFunc<TArray<Double>, Double>;
begin
Result :=
function(const Value: TArray<Double>): Double
@@ -548,7 +549,7 @@ begin
end;
// Creates a MACD indicator from three provided moving average functions.
class function TMACD.CreateMACD(const Param: TParam): TConstFunc<TInput, TResult>;
class function TMACD.CreateMACD(const Param: TParam): TConvertFunc<TInput, TResult>;
begin
Result :=
function(const Input: TInput): TResult
+218
View File
@@ -0,0 +1,218 @@
unit Myc.Trade.Pipeline.Impl;
interface
uses
Myc.Signals,
Myc.Data.Pipeline,
Myc.Data.Pipeline.Impl,
Myc.Trade.Types;
type
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;
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;
implementation
uses
System.Math,
System.DateUtils;
{ 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;
{ 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;
end.
+30
View File
@@ -0,0 +1,30 @@
unit Myc.Trade.Pipeline;
interface
uses
Myc.Data.Pipeline,
Myc.Trade.Types;
type
TTradeConverter = record
class function CreateTickAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>; static;
class function CreateOhlcAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, TDataPoint<TOhlcItem>>; static;
end;
implementation
uses
Myc.Trade.Pipeline.Impl;
class function TTradeConverter.CreateTickAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>;
begin
Result := TTickAggregation.Create(Timeframe);
end;
class function TTradeConverter.CreateOhlcAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, TDataPoint<TOhlcItem>>;
begin
Result := TOhlcAggregation.Create(Timeframe);
end;
end.
+3 -5
View File
@@ -2,6 +2,9 @@ unit Myc.Trade.Types;
interface
uses
Myc.Core.Future;
type
TTimeframe = (S, S5, S15, S30, M, M2, M3, M5, M10, M15, M30, H, H2, H3, H4, H8, H12, D, D2, D3, W, MN, MN3, MN6, Y);
@@ -28,11 +31,6 @@ type
constructor Create(ATime: TDateTime; const AData: T);
end;
TConstFunc<S, T> = reference to function(const Value: S): T;
TConstFunc<S, T, U> = reference to function(const Value1: S; const Value2: T): U;
TConstProc<T> = reference to procedure(const Value: T);
TConstFuncPredicate<S, T> = reference to function(const Value: S; out Res: T): Boolean;
implementation
{ TAskBidItem }
+1 -1
View File
@@ -5,7 +5,7 @@ interface
uses
System.SysUtils,
DUnitX.TestFramework,
Myc.Trade.DataArray;
Myc.Data.Series;
type
[TestFixture]
+37 -3
View File
@@ -7,7 +7,7 @@ uses
System.SysUtils,
System.DateUtils,
System.Rtti,
Myc.DataRecord;
Myc.Data.Records;
type
// A record that contains all supported data types for testing
@@ -52,6 +52,10 @@ type
// [IgnoreMemoryLeaks]
procedure TestNestedDataRecord;
[Test]
// [IgnoreMemoryLeaks]
procedure TestInternalRecordAssignment;
[Test]
// [IgnoreMemoryLeaks]
procedure TestNestedSetValueAndGetValue;
@@ -174,7 +178,8 @@ var
begin
// Setup: Create the nested record and convert it to a TDataRecord
nestedSrc.NestedValue := 999;
nestedSrc.NestedString := 'Nested Record Test';
const str = 'Nested Record Test';
nestedSrc.NestedString := str;
nestedDataRecord := TDataRecord.FromRecord<TNestedTestRecord>(nestedSrc);
// Setup: Create the outer record containing the nested TDataRecord
@@ -192,12 +197,41 @@ begin
// Assert values within the nested record
Assert.AreEqual(999, retrievedNestedRecord.GetValue<Integer>('NestedValue'), 'Nested integer value mismatch');
Assert.AreEqual('Nested Record Test', retrievedNestedRecord.GetValue<string>('NestedString'), 'Nested string value mismatch');
Assert.AreEqual(str, retrievedNestedRecord.GetValue<string>('NestedString'), 'Nested string value mismatch');
// Assert value in the outer record
Assert.AreEqual(111, mainDataRecord.GetValue<Integer>('OuterValue'), 'Outer integer value mismatch');
end;
procedure TTestDataRecord.TestInternalRecordAssignment;
var
nestedSrc: TNestedTestRecord;
nestedDataRecord: TDataRecord;
outerSrc: TOuterTestRecord;
mainDataRecord: TDataRecord;
retrievedNestedRecord: TDataRecord;
begin
// Setup: Create the nested record and convert it to a TDataRecord
const str = 'Nested Record Test';
nestedSrc.NestedString := str;
nestedDataRecord := TDataRecord.FromRecord<TNestedTestRecord>(nestedSrc);
// Setup: Create the outer record containing the nested TDataRecord
outerSrc.MyNestedRecord := nestedDataRecord;
// Place a record on the heap. FAILS: This erases the nested record data!!
TDataRecord.FromRecord<TOuterTestRecord>(outerSrc);
// Test: Create the main TDataRecord from the outer record instance
mainDataRecord := TDataRecord.FromRecord<TOuterTestRecord>(outerSrc);
// Assert: Retrieve the nested record and check its contents
retrievedNestedRecord := mainDataRecord.GetValue<TDataRecord>('MyNestedRecord');
// Assert values within the nested record
Assert.AreEqual(str, retrievedNestedRecord.GetValue<string>('NestedString'), 'Nested string value mismatch');
end;
procedure TTestDataRecord.TestNestedSetValueAndGetValue;
var
outerRecord: TDataRecord;