Unit refactoring
Fixed massive heap corruption bug in TDataRecord
This commit is contained in:
@@ -6,7 +6,8 @@ uses
|
|||||||
FMX.Forms,
|
FMX.Forms,
|
||||||
MainForm in 'MainForm.pas' {Form1},
|
MainForm in 'MainForm.pas' {Form1},
|
||||||
TestModule in 'TestModule.pas',
|
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}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,7 @@
|
|||||||
</DCCReference>
|
</DCCReference>
|
||||||
<DCCReference Include="TestModule.pas"/>
|
<DCCReference Include="TestModule.pas"/>
|
||||||
<DCCReference Include="DynamicFMXControl.pas"/>
|
<DCCReference Include="DynamicFMXControl.pas"/>
|
||||||
|
<DCCReference Include="..\Src\Myc.Trade.Pipeline.Impl.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ uses
|
|||||||
Myc.Futures,
|
Myc.Futures,
|
||||||
Myc.Trade.Types,
|
Myc.Trade.Types,
|
||||||
Myc.Trade.DataStream,
|
Myc.Trade.DataStream,
|
||||||
|
Myc.Trade.Pipeline,
|
||||||
Myc.Data.Series,
|
Myc.Data.Series,
|
||||||
Myc.Data.Pipeline,
|
Myc.Data.Pipeline,
|
||||||
Myc.Signals,
|
Myc.Signals,
|
||||||
@@ -298,7 +299,7 @@ begin
|
|||||||
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
|
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
|
||||||
Result := ticker.Consumer;
|
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');
|
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
|
||||||
|
|
||||||
@@ -423,7 +424,7 @@ begin
|
|||||||
|
|
||||||
var equity :=
|
var equity :=
|
||||||
TConverter<Double, Double>.CreateAggregation(
|
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
|
begin
|
||||||
if not FInit then
|
if not FInit then
|
||||||
begin
|
begin
|
||||||
@@ -599,7 +600,7 @@ begin
|
|||||||
|
|
||||||
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
|
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 OhlcTicker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
|
||||||
// var OhlcPoint := OhlcTicker.Sender;
|
// var OhlcPoint := OhlcTicker.Sender;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ uses
|
|||||||
Myc.Signals,
|
Myc.Signals,
|
||||||
Myc.Data.Pipeline,
|
Myc.Data.Pipeline,
|
||||||
Myc.Trade.Types,
|
Myc.Trade.Types,
|
||||||
|
Myc.Trade.Pipeline,
|
||||||
Myc.Trade.Indicators;
|
Myc.Trade.Indicators;
|
||||||
|
|
||||||
function CreateStrategy1(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, Double>; overload;
|
function CreateStrategy1(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, Double>; overload;
|
||||||
@@ -28,7 +29,7 @@ type
|
|||||||
begin
|
begin
|
||||||
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
|
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');
|
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ begin
|
|||||||
var positionManager :=
|
var positionManager :=
|
||||||
signalGenerator.Chain<Double>(
|
signalGenerator.Chain<Double>(
|
||||||
TConverter<TSignalEvent, Double>.CreateAggregation(
|
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
|
var
|
||||||
pnl: Double;
|
pnl: Double;
|
||||||
begin
|
begin
|
||||||
@@ -164,7 +165,7 @@ begin
|
|||||||
var equity :=
|
var equity :=
|
||||||
positionManager.Chain<Double>(
|
positionManager.Chain<Double>(
|
||||||
TConverter<Double, Double>.CreateAggregation(
|
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
|
begin
|
||||||
if not FInit then
|
if not FInit then
|
||||||
begin
|
begin
|
||||||
|
|||||||
+31
-236
@@ -9,9 +9,8 @@ uses
|
|||||||
Myc.Signals,
|
Myc.Signals,
|
||||||
Myc.Mutable,
|
Myc.Mutable,
|
||||||
Myc.Core.Notifier,
|
Myc.Core.Notifier,
|
||||||
Myc.Data.Pipeline,
|
Myc.Data.Series,
|
||||||
Myc.Trade.Types,
|
Myc.Data.Pipeline;
|
||||||
Myc.Data.Series;
|
|
||||||
|
|
||||||
type
|
type
|
||||||
// Abstract base class for data consumers.
|
// Abstract base class for data consumers.
|
||||||
@@ -31,16 +30,6 @@ type
|
|||||||
class property Null: IConsumer<T> read FNull;
|
class property Null: IConsumer<T> read FNull;
|
||||||
end;
|
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>)
|
TMycProducer<T> = class(TInterfacedObject, IProducer<T>)
|
||||||
strict private
|
strict private
|
||||||
type
|
type
|
||||||
@@ -63,16 +52,14 @@ type
|
|||||||
class property Null: IProducer<T> read FNull;
|
class property Null: IProducer<T> read FNull;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Concrete producer that manages a list of consumers (listeners).
|
// A consumer implementation that is owned by a controller.
|
||||||
TMycContainedProducer<T> = class(TContainedObject, IProducer<T>)
|
TMycGenericConsumer<T> = class(TMycConsumer<T>)
|
||||||
private
|
private
|
||||||
FListeners: TMycNotifyList<IConsumer<T>>;
|
FProc: TConvertFunc<T, TState>;
|
||||||
|
protected
|
||||||
|
function Consume(const Value: T): TState; override; final;
|
||||||
public
|
public
|
||||||
constructor Create(const Controller: IInterface);
|
constructor Create(const Controller: IInterface; const AProc: TConvertFunc<T, TState>);
|
||||||
destructor Destroy; override;
|
|
||||||
function Broadcast(const Value: T): TState;
|
|
||||||
function Link(const Consumer: IConsumer<T>): TTag;
|
|
||||||
procedure Unlink(Tag: TTag);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Abstract base class for components that now act as a producer and contain a consumer.
|
// 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;
|
class property Null: IConverter<S, T> read FNull;
|
||||||
end;
|
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.
|
// A generic converter that uses a function reference for the conversion logic.
|
||||||
TMycGenericConverter<S, T> = class(TMycConverter<S, T>)
|
TMycGenericConverter<S, T> = class(TMycConverter<S, T>)
|
||||||
private
|
private
|
||||||
FFunc: TConstFunc<S, T>;
|
FFunc: TConvertFunc<S, T>;
|
||||||
protected
|
protected
|
||||||
function Consume(const Value: S): TState; override;
|
function Consume(const Value: S): TState; override;
|
||||||
public
|
public
|
||||||
constructor Create(const AFunc: TConstFunc<S, T>);
|
constructor Create(const AFunc: TConvertFunc<S, T>);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TMycGenericAggregator<S, T> = class(TMycConverter<S, T>)
|
TMycGenericAggregator<S, T> = class(TMycConverter<S, T>)
|
||||||
private
|
private
|
||||||
FFunc: TConstFunc<S, TConverter<S, T>.TBroadcastProc, TState>;
|
FFunc: TAggregateFunc<S, T>;
|
||||||
protected
|
protected
|
||||||
function Consume(const Value: S): TState; override;
|
function Consume(const Value: S): TState; override;
|
||||||
public
|
public
|
||||||
constructor Create(const AFunc: TConstFunc<S, TConverter<S, T>.TBroadcastProc, TState>);
|
constructor Create(const AFunc: TAggregateFunc<S, T>);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TMycGenericParallelConverter<S, T> = class(TMycConverter<S, T>)
|
TMycGenericParallelConverter<S, T> = class(TMycConverter<S, T>)
|
||||||
private
|
private
|
||||||
FFunc: TConstFunc<S, T>;
|
FFunc: TConvertFunc<S, T>;
|
||||||
FQueue: TState;
|
FQueue: TState;
|
||||||
protected
|
protected
|
||||||
function Consume(const Value: S): TState; override;
|
function Consume(const Value: S): TState; override;
|
||||||
public
|
public
|
||||||
constructor Create(const AFunc: TConstFunc<S, T>);
|
constructor Create(const AFunc: TConvertFunc<S, T>);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TMycIdentityConverter<T> = class(TMycConverter<T, T>)
|
TMycIdentityConverter<T> = class(TMycConverter<T, T>)
|
||||||
@@ -187,21 +186,6 @@ type
|
|||||||
class function CreateDataEndpoint(Lookback: Integer; out Series: TLazy<TSeries<T>>): IConsumer<T>;
|
class function CreateDataEndpoint(Lookback: Integer; out Series: TLazy<TSeries<T>>): IConsumer<T>;
|
||||||
end;
|
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>)
|
TMycParallelConverter<T> = class(TMycConverter<T, T>)
|
||||||
private
|
private
|
||||||
FQueue: TState;
|
FQueue: TState;
|
||||||
@@ -209,21 +193,6 @@ type
|
|||||||
function Consume(const Value: T): TState; override; final;
|
function Consume(const Value: T): TState; override; final;
|
||||||
end;
|
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.
|
// Endpoint that collects data into a series.
|
||||||
TMycDataJoin<T> = class(TInterfacedObject, IProducer<TArray<T>>)
|
TMycDataJoin<T> = class(TInterfacedObject, IProducer<TArray<T>>)
|
||||||
private
|
private
|
||||||
@@ -273,11 +242,7 @@ type
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
System.TypInfo,
|
|
||||||
System.RTTI,
|
System.RTTI,
|
||||||
System.DateUtils,
|
|
||||||
System.Math,
|
|
||||||
Winapi.Windows,
|
|
||||||
Myc.TaskManager;
|
Myc.TaskManager;
|
||||||
|
|
||||||
class constructor TMycConsumer<T>.CreateClass;
|
class constructor TMycConsumer<T>.CreateClass;
|
||||||
@@ -392,7 +357,7 @@ end;
|
|||||||
|
|
||||||
{ TMycGenericConverter<S, T> }
|
{ TMycGenericConverter<S, T> }
|
||||||
|
|
||||||
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConstFunc<S, T>);
|
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc<S, T>);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FFunc := AFunc;
|
FFunc := AFunc;
|
||||||
@@ -462,7 +427,7 @@ end;
|
|||||||
|
|
||||||
{ TMycGenericConsumer<T> }
|
{ 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
|
begin
|
||||||
inherited Create(Controller);
|
inherited Create(Controller);
|
||||||
FProc := AProc;
|
FProc := AProc;
|
||||||
@@ -550,94 +515,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
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> }
|
{ TMycSequence<T> }
|
||||||
|
|
||||||
constructor TMycSequence<T>.Create(ACount: Integer);
|
constructor TMycSequence<T>.Create(ACount: Integer);
|
||||||
@@ -688,7 +565,7 @@ end;
|
|||||||
|
|
||||||
{ TMycGenericParallelConverter<S, T> }
|
{ TMycGenericParallelConverter<S, T> }
|
||||||
|
|
||||||
constructor TMycGenericParallelConverter<S, T>.Create(const AFunc: TConstFunc<S, T>);
|
constructor TMycGenericParallelConverter<S, T>.Create(const AFunc: TConvertFunc<S, T>);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FFunc := AFunc;
|
FFunc := AFunc;
|
||||||
@@ -711,88 +588,6 @@ begin
|
|||||||
FQueue := Result;
|
FQueue := Result;
|
||||||
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;
|
|
||||||
|
|
||||||
{ TMycDataJoin<T> }
|
{ TMycDataJoin<T> }
|
||||||
|
|
||||||
constructor TMycDataJoin<T>.Create(ACount: Integer);
|
constructor TMycDataJoin<T>.Create(ACount: Integer);
|
||||||
@@ -807,7 +602,7 @@ begin
|
|||||||
FContainedProvider := TMycContainedProducer<TArray<T>>.Create(Self);
|
FContainedProvider := TMycContainedProducer<TArray<T>>.Create(Self);
|
||||||
|
|
||||||
var cFunc :=
|
var cFunc :=
|
||||||
function(Idx: Integer): TConstFunc<T, TState>
|
function(Idx: Integer): TConvertFunc<T, TState>
|
||||||
begin
|
begin
|
||||||
Result := function(const Value: T): TState begin Result := Consume(Idx, Value); end
|
Result := function(const Value: T): TState begin Result := Consume(Idx, Value); end
|
||||||
end;
|
end;
|
||||||
@@ -882,7 +677,7 @@ end;
|
|||||||
|
|
||||||
{ TMycGenericAggregator<S, T> }
|
{ 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
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FFunc := AFunc;
|
FFunc := AFunc;
|
||||||
|
|||||||
+11
-24
@@ -5,7 +5,6 @@ interface
|
|||||||
uses
|
uses
|
||||||
Myc.Signals,
|
Myc.Signals,
|
||||||
Myc.Mutable,
|
Myc.Mutable,
|
||||||
Myc.Trade.Types,
|
|
||||||
Myc.Data.Series,
|
Myc.Data.Series,
|
||||||
Myc.Data.Records;
|
Myc.Data.Records;
|
||||||
|
|
||||||
@@ -31,6 +30,10 @@ type
|
|||||||
property Consumer: IConsumer<S> read GetConsumer;
|
property Consumer: IConsumer<S> read GetConsumer;
|
||||||
end;
|
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.
|
// Interface helper for IProducer providing the null object pattern nad subscriptions for linked consumers.
|
||||||
TProducer<T> = record
|
TProducer<T> = record
|
||||||
public
|
public
|
||||||
@@ -64,7 +67,7 @@ type
|
|||||||
// Chain consumers
|
// Chain consumers
|
||||||
function Chain(const Next: IConsumer<T>): IConsumer<T>; overload; inline;
|
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 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).
|
// Extracts the field of a record by it's name (using RTTI).
|
||||||
function Field<R>(const FieldName: String): TProducer<R>; inline;
|
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.
|
// Interface helper for IConverter<S,T> providing the null object pattern.
|
||||||
TConverter<S, T> = record
|
TConverter<S, T> = record
|
||||||
public
|
|
||||||
type
|
|
||||||
TBroadcastProc = reference to function(const Value: T): TState;
|
|
||||||
private
|
private
|
||||||
FConverter: IConverter<S, T>;
|
FConverter: IConverter<S, T>;
|
||||||
function GetConsumer: IConsumer<S>; inline;
|
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 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 CreateConverter(const Func: TConvertFunc<S, T>): TConverter<S, T>; static;
|
||||||
class function CreateAggregation(const Func: TConstFunc<S, TBroadcastProc, TState>): TConverter<S, T>; static;
|
class function CreateAggregation(const Func: TAggregateFunc<S, T>): TConverter<S, T>; static;
|
||||||
|
|
||||||
property Consumer: IConsumer<S> read GetConsumer;
|
property Consumer: IConsumer<S> read GetConsumer;
|
||||||
property Producer: TProducer<T> read GetProducer;
|
property Producer: TProducer<T> read GetProducer;
|
||||||
@@ -114,9 +114,6 @@ type
|
|||||||
class function CreateTicker<T>: TConverter<TArray<T>, T>; static;
|
class function CreateTicker<T>: TConverter<TArray<T>, T>; static;
|
||||||
class function CreateIdentity<T>: TConverter<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 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;
|
class function Join<T>(const Producers: TArray<TProducer<T>>): TProducer<TArray<T>>; static;
|
||||||
@@ -188,7 +185,7 @@ begin
|
|||||||
Result := Next;
|
Result := Next;
|
||||||
end;
|
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
|
begin
|
||||||
Result := Chain<R>(TMycGenericConverter<T, R>.Create(Func));
|
Result := Chain<R>(TMycGenericConverter<T, R>.Create(Func));
|
||||||
end;
|
end;
|
||||||
@@ -251,12 +248,12 @@ begin
|
|||||||
Result := TMycComposedConverter<S, T>.Create(Consumer, Producer);
|
Result := TMycComposedConverter<S, T>.Create(Consumer, Producer);
|
||||||
end;
|
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
|
begin
|
||||||
Result := TMycGenericAggregator<S, T>.Create(Func);
|
Result := TMycGenericAggregator<S, T>.Create(Func);
|
||||||
end;
|
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
|
begin
|
||||||
Result := TMycGenericConverter<S, T>.Create(Func);
|
Result := TMycGenericConverter<S, T>.Create(Func);
|
||||||
end;
|
end;
|
||||||
@@ -291,11 +288,6 @@ begin
|
|||||||
Result := A.FConverter;
|
Result := A.FConverter;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TConverter.CreateTickAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>;
|
|
||||||
begin
|
|
||||||
Result := TTickAggregation.Create(Timeframe);
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TConverter }
|
{ TConverter }
|
||||||
|
|
||||||
class function TConverter.CreateCounter<T>: TConverter<T, Int64>;
|
class function TConverter.CreateCounter<T>: TConverter<T, Int64>;
|
||||||
@@ -318,11 +310,6 @@ begin
|
|||||||
Result := TMycTicker<T>.Create;
|
Result := TMycTicker<T>.Create;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TConverter.CreateOhlcAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, TDataPoint<TOhlcItem>>;
|
|
||||||
begin
|
|
||||||
Result := TOhlcAggregation.Create(Timeframe);
|
|
||||||
end;
|
|
||||||
|
|
||||||
class function TConverter.DataMapping(
|
class function TConverter.DataMapping(
|
||||||
const Inputs: TArray<TRecordMapping>;
|
const Inputs: TArray<TRecordMapping>;
|
||||||
const Output: TDataRecord.TLayout
|
const Output: TDataRecord.TLayout
|
||||||
@@ -350,7 +337,7 @@ begin
|
|||||||
Result := TDataRecord.Create(Output);
|
Result := TDataRecord.Create(Output);
|
||||||
for var i := 0 to High(Idxs) do
|
for var i := 0 to High(Idxs) do
|
||||||
for var j := 0 to High(Idxs[i]) 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
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|||||||
+75
-40
@@ -4,8 +4,6 @@ interface
|
|||||||
|
|
||||||
uses
|
uses
|
||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
System.Generics.Collections,
|
|
||||||
System.Rtti,
|
|
||||||
System.TypInfo;
|
System.TypInfo;
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -23,8 +21,11 @@ type
|
|||||||
procedure FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src);
|
procedure FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src);
|
||||||
procedure ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst);
|
procedure ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst);
|
||||||
|
|
||||||
procedure Assign(const [ref] Buffer: TBytes; const Src); overload;
|
procedure InitField(const [ref] Buffer: TBytes);
|
||||||
procedure Finalize(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;
|
property Offset: Integer read FOffset;
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ type
|
|||||||
FFields: TArray<TField>;
|
FFields: TArray<TField>;
|
||||||
|
|
||||||
constructor Create(const AFields: TArray<TField>);
|
constructor Create(const AFields: TArray<TField>);
|
||||||
|
|
||||||
public
|
public
|
||||||
class function FromRecord<T>: TLayout; static;
|
class function FromRecord<T>: TLayout; static;
|
||||||
class function Construct(const Def: TArray<TFieldDef>): TLayout; static;
|
class function Construct(const Def: TArray<TFieldDef>): TLayout; static;
|
||||||
@@ -60,15 +62,17 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
const
|
const
|
||||||
Align = 8;
|
Align = sizeof(Pointer);
|
||||||
|
|
||||||
private
|
private
|
||||||
FLayout: TLayout;
|
FLayout: TLayout;
|
||||||
FBuffer: TBytes;
|
FBuffer: TBytes;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(const ALayout: TLayout; const ABuffer: TBytes = nil);
|
constructor Create(const ALayout: TLayout);
|
||||||
|
|
||||||
class operator Finalize(var Dest: TDataRecord);
|
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>: TDataRecord; overload; static;
|
||||||
class function FromRecord<T>(const Src: 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;
|
function GetValue<T>(const Name: String): T; overload;
|
||||||
procedure GetValue(Idx: Integer; out Value); 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;
|
property Layout: TLayout read FLayout;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.Rtti;
|
||||||
|
|
||||||
const
|
const
|
||||||
DataSize: array[TDataRecord.TFieldType] of Integer =
|
DataSize: array[TDataRecord.TFieldType] of Integer =
|
||||||
(sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime), sizeof(TDataRecord));
|
(sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime), sizeof(TDataRecord));
|
||||||
|
|
||||||
implementation
|
{ TDataRecord }
|
||||||
|
|
||||||
uses
|
constructor TDataRecord.Create(const ALayout: TLayout);
|
||||||
System.Classes;
|
|
||||||
|
|
||||||
constructor TDataRecord.Create(const ALayout: TLayout; const ABuffer: TBytes = nil);
|
|
||||||
begin
|
begin
|
||||||
FLayout := ALayout;
|
FLayout := ALayout;
|
||||||
FBuffer := ABuffer;
|
|
||||||
|
|
||||||
var bufSize := 0;
|
var bufSize := 0;
|
||||||
if Length(FLayout.Fields) > 0 then
|
if Length(FLayout.Fields) > 0 then
|
||||||
with FLayout.Fields[High(FLayout.Fields)] do
|
with FLayout.Fields[High(FLayout.Fields)] do
|
||||||
bufSize := Offset + AlignedSize;
|
bufSize := Offset + AlignedSize;
|
||||||
|
|
||||||
if FBuffer = nil then
|
SetLength(FBuffer, bufSize);
|
||||||
SetLength(FBuffer, bufSize)
|
for var i := 0 to High(FLayout.Fields) do
|
||||||
else
|
FLayout.Fields[i].InitField(FBuffer);
|
||||||
Assert(Length(FBuffer) >= bufSize);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TDataRecord.CopyField(Idx: Integer; Dst: TDataRecord; DstIdx: Integer);
|
procedure TDataRecord.CopyValue(const SrcRec: TDataRecord; SrcIdx, DstIdx: Integer);
|
||||||
begin
|
begin
|
||||||
Assert(Dst.Layout.Fields[DstIdx].FieldType = FLayout.Fields[Idx].FieldType);
|
Assert(SrcRec.Layout.Fields[SrcIdx].FieldType = FLayout.Fields[SrcIdx].FieldType);
|
||||||
Dst.Layout.Fields[DstIdx].Assign(Dst.FBuffer, FBuffer[FLayout.Fields[DstIdx].Offset])
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ 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;
|
class function TDataRecord.FromRecord<T>: TDataRecord;
|
||||||
begin
|
begin
|
||||||
Result := TDataRecord.Create(TLayout.FromRecord<T>);
|
Result.Create(TLayout.FromRecord<T>);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TDataRecord.FromRecord<T>(const Src: T): TDataRecord;
|
class function TDataRecord.FromRecord<T>(const Src: T): TDataRecord;
|
||||||
begin
|
begin
|
||||||
Result := FromRecord<T>;
|
Result.Create(TLayout.FromRecord<T>);
|
||||||
|
|
||||||
var ctx := TRttiContext.Create;
|
var ctx := TRttiContext.Create;
|
||||||
var rttiType := ctx.GetType(TypeInfo(T));
|
var rttiType := ctx.GetType(TypeInfo(T));
|
||||||
@@ -187,10 +193,22 @@ begin
|
|||||||
FLayout.Fields[idx].FromType(FBuffer, TypeInfo(T), Value);
|
FLayout.Fields[idx].FromType(FBuffer, TypeInfo(T), Value);
|
||||||
end;
|
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);
|
class operator TDataRecord.Finalize(var Dest: TDataRecord);
|
||||||
begin
|
begin
|
||||||
for var i := 0 to High(Dest.FLayout.Fields) do
|
for var i := High(Dest.FLayout.Fields) downto 0 do
|
||||||
Dest.FLayout.Fields[i].Finalize(Dest.FBuffer);
|
Dest.Layout.Fields[i].FinalizeField(Dest.FBuffer);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; AOffset: Integer);
|
constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; AOffset: Integer);
|
||||||
@@ -200,7 +218,7 @@ begin
|
|||||||
FOffset := AOffset;
|
FOffset := AOffset;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TDataRecord.TField.Finalize(const [ref] Buffer: TBytes);
|
procedure TDataRecord.TField.InitField(const [ref] Buffer: TBytes);
|
||||||
begin
|
begin
|
||||||
if not (FFieldType in [dfString, dfRecord]) then
|
if not (FFieldType in [dfString, dfRecord]) then
|
||||||
exit;
|
exit;
|
||||||
@@ -209,8 +227,22 @@ begin
|
|||||||
|
|
||||||
var P := @Buffer[FOffset];
|
var P := @Buffer[FOffset];
|
||||||
case FFieldType of
|
case FFieldType of
|
||||||
dfString: PString(P)^ := '';
|
dfString: Initialize(PString(P)^);
|
||||||
dfRecord: TDataRecord(P^) := Default(TDataRecord);
|
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;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -218,8 +250,6 @@ procedure TDataRecord.TField.FromType(const [ref] Buffer: TBytes; SrcType: PType
|
|||||||
begin
|
begin
|
||||||
Assert(FOffset + Size <= Length(Buffer));
|
Assert(FOffset + Size <= Length(Buffer));
|
||||||
|
|
||||||
Finalize(Buffer);
|
|
||||||
|
|
||||||
var Dst := @Buffer[FOffset];
|
var Dst := @Buffer[FOffset];
|
||||||
case FFieldType of
|
case FFieldType of
|
||||||
dfFloat:
|
dfFloat:
|
||||||
@@ -317,19 +347,24 @@ begin
|
|||||||
end;
|
end;
|
||||||
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
|
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
|
case FFieldType of
|
||||||
dfFloat: PDouble(Dst)^ := PDouble(@Src)^;
|
dfFloat: PDouble(Dst)^ := PDouble(Src)^;
|
||||||
dfInteger: PInt64(Dst)^ := PInt64(@Src)^;
|
dfInteger: PInt64(Dst)^ := PInt64(Src)^;
|
||||||
dfString: PString(Dst)^ := PString(@Src)^;
|
dfString: PString(Dst)^ := PString(Src)^;
|
||||||
dfTimestamp: PDateTime(Dst)^ := PDateTime(@Src)^;
|
dfTimestamp: PDateTime(Dst)^ := PDateTime(Src)^;
|
||||||
dfRecord: TDataRecord(Dst^) := TDataRecord(Src);
|
dfRecord: TDataRecord(Dst^) := TDataRecord(Src^);
|
||||||
else
|
else
|
||||||
Assert(false);
|
Assert(false);
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ uses
|
|||||||
System.Generics.Collections,
|
System.Generics.Collections,
|
||||||
System.Rtti,
|
System.Rtti,
|
||||||
Myc.Data.Records,
|
Myc.Data.Records,
|
||||||
Myc.Trade.Types,
|
Myc.Data.Pipeline,
|
||||||
Myc.Data.Series;
|
Myc.Data.Series,
|
||||||
|
Myc.Trade.Types;
|
||||||
|
|
||||||
type
|
type
|
||||||
// Result for the Moving Average Convergence Divergence (MACD) indicator.
|
// 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;
|
class function CalculateWMA(const Series: TSeries<Double>; const Period: Integer): Double; static;
|
||||||
public
|
public
|
||||||
// Simple Moving Average
|
// Simple Moving Average
|
||||||
class function CreateSMA(Period: Integer): TConstFunc<Double, Double>; static;
|
class function CreateSMA(Period: Integer): TConvertFunc<Double, Double>; static;
|
||||||
// Exponential Moving Average
|
// Exponential Moving Average
|
||||||
class function CreateEMA(Period: Integer): TConstFunc<Double, Double>; static;
|
class function CreateEMA(Period: Integer): TConvertFunc<Double, Double>; static;
|
||||||
// Hull Moving Average
|
// Hull Moving Average
|
||||||
class function CreateHMA(Period: Integer): TConstFunc<Double, Double>; static;
|
class function CreateHMA(Period: Integer): TConvertFunc<Double, Double>; static;
|
||||||
// Relative Strength Index
|
// 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
|
// 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(
|
class function CreateMACD(
|
||||||
const EmaFast,
|
const EmaFast,
|
||||||
EmaSlow,
|
EmaSlow,
|
||||||
EmaSignal: TConstFunc<Double, Double>
|
EmaSignal: TConvertFunc<Double, Double>
|
||||||
): TConstFunc<Double, TMacdResult>; overload; static;
|
): TConvertFunc<Double, TMacdResult>; overload; static;
|
||||||
// Stochastic Oscillator
|
// 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(
|
class function CreateStochastic(
|
||||||
KPeriod: Integer;
|
KPeriod: Integer;
|
||||||
const SmaD: TConstFunc<Double, Double>
|
const SmaD: TConvertFunc<Double, Double>
|
||||||
): TConstFunc<TOhlcItem, TStochasticResult>; overload; static;
|
): TConvertFunc<TOhlcItem, TStochasticResult>; overload; static;
|
||||||
// Bollinger Bands
|
// 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
|
// Average True Range
|
||||||
class function CreateATR(Period: Integer): TConstFunc<TOhlcItem, Double>; overload; static;
|
class function CreateATR(Period: Integer): TConvertFunc<TOhlcItem, Double>; overload; static;
|
||||||
class function CreateATR(const MovAvgTR: TConstFunc<Double, Double>): TConstFunc<TOhlcItem, Double>; overload; static;
|
class function CreateATR(const MovAvgTR: TConvertFunc<Double, Double>): TConvertFunc<TOhlcItem, Double>; overload; static;
|
||||||
// Keltner Channels
|
// Keltner Channels
|
||||||
class function CreateKeltnerChannels(
|
class function CreateKeltnerChannels(
|
||||||
Period: Integer;
|
Period: Integer;
|
||||||
Multiplier: Double
|
Multiplier: Double
|
||||||
): TConstFunc<TOhlcItem, TKeltnerChannelsResult>; overload; static;
|
): TConvertFunc<TOhlcItem, TKeltnerChannelsResult>; overload; static;
|
||||||
class function CreateKeltnerChannels(
|
class function CreateKeltnerChannels(
|
||||||
const MovAvgMiddle: TConstFunc<Double, Double>;
|
const MovAvgMiddle: TConvertFunc<Double, Double>;
|
||||||
const AtrFunc: TConstFunc<TOhlcItem, Double>;
|
const AtrFunc: TConvertFunc<TOhlcItem, Double>;
|
||||||
Multiplier: 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;
|
end;
|
||||||
|
|
||||||
TIndicatorFactory = class
|
TIndicatorFactory = class
|
||||||
type
|
type
|
||||||
TFunc = TConstFunc<TDataRecord, TDataRecord>;
|
TFunc = TConvertFunc<TDataRecord, TDataRecord>;
|
||||||
private
|
private
|
||||||
FParams: TDataRecord.TLayout;
|
FParams: TDataRecord.TLayout;
|
||||||
FInput: TDataRecord.TLayout;
|
FInput: TDataRecord.TLayout;
|
||||||
@@ -125,9 +126,9 @@ type
|
|||||||
TMACD = class
|
TMACD = class
|
||||||
type
|
type
|
||||||
TParam = record
|
TParam = record
|
||||||
Fast: TConstFunc<Double, Double>;
|
Fast: TConvertFunc<Double, Double>;
|
||||||
Slow: TConstFunc<Double, Double>;
|
Slow: TConvertFunc<Double, Double>;
|
||||||
Signal: TConstFunc<Double, Double>;
|
Signal: TConvertFunc<Double, Double>;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TInput = record
|
TInput = record
|
||||||
@@ -141,7 +142,7 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
public
|
public
|
||||||
class function CreateMACD(const Param: TParam): TConstFunc<TInput, TResult>; static;
|
class function CreateMACD(const Param: TParam): TConvertFunc<TInput, TResult>; static;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
var
|
var
|
||||||
@@ -208,7 +209,7 @@ begin
|
|||||||
Result := numerator / denominator;
|
Result := numerator / denominator;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TConstFunc<Double, TBollingerBandsResult>;
|
class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TConvertFunc<Double, TBollingerBandsResult>;
|
||||||
begin
|
begin
|
||||||
var sourceData: TSeries<Double>;
|
var sourceData: TSeries<Double>;
|
||||||
Result :=
|
Result :=
|
||||||
@@ -231,7 +232,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TIndicators.CreateEMA(Period: Integer): TConstFunc<Double, Double>;
|
class function TIndicators.CreateEMA(Period: Integer): TConvertFunc<Double, Double>;
|
||||||
begin
|
begin
|
||||||
var lastEma: Double := Double.NaN;
|
var lastEma: Double := Double.NaN;
|
||||||
var sourceData: TSeries<Double>;
|
var sourceData: TSeries<Double>;
|
||||||
@@ -262,7 +263,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TIndicators.CreateHMA(Period: Integer): TConstFunc<Double, Double>;
|
class function TIndicators.CreateHMA(Period: Integer): TConvertFunc<Double, Double>;
|
||||||
begin
|
begin
|
||||||
var periodHalf := Period div 2;
|
var periodHalf := Period div 2;
|
||||||
var periodSqrt := Round(Sqrt(Period));
|
var periodSqrt := Round(Sqrt(Period));
|
||||||
@@ -305,13 +306,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Standard MACD using EMAs.
|
// 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
|
begin
|
||||||
Result := CreateMACD(CreateEMA(FastPeriod), CreateEMA(SlowPeriod), CreateEMA(SignalPeriod));
|
Result := CreateMACD(CreateEMA(FastPeriod), CreateEMA(SlowPeriod), CreateEMA(SignalPeriod));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Creates a MACD indicator from three provided moving average functions.
|
// 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
|
begin
|
||||||
Result :=
|
Result :=
|
||||||
function(const Value: Double): TMacdResult
|
function(const Value: Double): TMacdResult
|
||||||
@@ -339,7 +340,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TIndicators.CreateRSI(Period: Integer): TConstFunc<Double, Double>;
|
class function TIndicators.CreateRSI(Period: Integer): TConvertFunc<Double, Double>;
|
||||||
begin
|
begin
|
||||||
var avgGain: Double := Double.NaN;
|
var avgGain: Double := Double.NaN;
|
||||||
var avgLoss: Double := Double.NaN;
|
var avgLoss: Double := Double.NaN;
|
||||||
@@ -398,7 +399,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TIndicators.CreateSMA(Period: Integer): TConstFunc<Double, Double>;
|
class function TIndicators.CreateSMA(Period: Integer): TConvertFunc<Double, Double>;
|
||||||
begin
|
begin
|
||||||
var sourceData: TSeries<Double>;
|
var sourceData: TSeries<Double>;
|
||||||
Result :=
|
Result :=
|
||||||
@@ -413,7 +414,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Standard Stochastic Oscillator using an SMA for the %D line.
|
// 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
|
begin
|
||||||
Result := CreateStochastic(KPeriod, CreateSMA(DPeriod));
|
Result := CreateStochastic(KPeriod, CreateSMA(DPeriod));
|
||||||
end;
|
end;
|
||||||
@@ -421,8 +422,8 @@ end;
|
|||||||
// Creates a Stochastic Oscillator using an injectable moving average for the %D line.
|
// Creates a Stochastic Oscillator using an injectable moving average for the %D line.
|
||||||
class function TIndicators.CreateStochastic(
|
class function TIndicators.CreateStochastic(
|
||||||
KPeriod: Integer;
|
KPeriod: Integer;
|
||||||
const SmaD: TConstFunc<Double, Double>
|
const SmaD: TConvertFunc<Double, Double>
|
||||||
): TConstFunc<TOhlcItem, TStochasticResult>;
|
): TConvertFunc<TOhlcItem, TStochasticResult>;
|
||||||
begin
|
begin
|
||||||
var sourceData: TSeries<TOhlcItem>;
|
var sourceData: TSeries<TOhlcItem>;
|
||||||
|
|
||||||
@@ -461,13 +462,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Standard ATR using an EMA for smoothing.
|
// 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
|
begin
|
||||||
Result := CreateATR(CreateEMA(Period));
|
Result := CreateATR(CreateEMA(Period));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Calculates the Average True Range (ATR) using an injectable moving average.
|
// 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
|
begin
|
||||||
var sourceData: TSeries<TOhlcItem>;
|
var sourceData: TSeries<TOhlcItem>;
|
||||||
|
|
||||||
@@ -495,17 +496,17 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Standard Keltner Channels using an EMA for the middle line and an EMA-based ATR.
|
// 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
|
begin
|
||||||
Result := CreateKeltnerChannels(CreateEMA(Period), CreateATR(Period), Multiplier);
|
Result := CreateKeltnerChannels(CreateEMA(Period), CreateATR(Period), Multiplier);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Calculates Keltner Channels using an injectable ATR and middle band moving average.
|
// Calculates Keltner Channels using an injectable ATR and middle band moving average.
|
||||||
class function TIndicators.CreateKeltnerChannels(
|
class function TIndicators.CreateKeltnerChannels(
|
||||||
const MovAvgMiddle: TConstFunc<Double, Double>;
|
const MovAvgMiddle: TConvertFunc<Double, Double>;
|
||||||
const AtrFunc: TConstFunc<TOhlcItem, Double>;
|
const AtrFunc: TConvertFunc<TOhlcItem, Double>;
|
||||||
Multiplier: Double
|
Multiplier: Double
|
||||||
): TConstFunc<TOhlcItem, TKeltnerChannelsResult>;
|
): TConvertFunc<TOhlcItem, TKeltnerChannelsResult>;
|
||||||
begin
|
begin
|
||||||
Result :=
|
Result :=
|
||||||
function(const Value: TOhlcItem): TKeltnerChannelsResult
|
function(const Value: TOhlcItem): TKeltnerChannelsResult
|
||||||
@@ -533,7 +534,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TIndicators.CreateMean: TConstFunc<TArray<Double>, Double>;
|
class function TIndicators.CreateMean: TConvertFunc<TArray<Double>, Double>;
|
||||||
begin
|
begin
|
||||||
Result :=
|
Result :=
|
||||||
function(const Value: TArray<Double>): Double
|
function(const Value: TArray<Double>): Double
|
||||||
@@ -548,7 +549,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Creates a MACD indicator from three provided moving average functions.
|
// 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
|
begin
|
||||||
Result :=
|
Result :=
|
||||||
function(const Input: TInput): TResult
|
function(const Input: TInput): TResult
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -2,6 +2,9 @@ unit Myc.Trade.Types;
|
|||||||
|
|
||||||
interface
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
Myc.Core.Future;
|
||||||
|
|
||||||
type
|
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);
|
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);
|
constructor Create(ATime: TDateTime; const AData: T);
|
||||||
end;
|
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
|
implementation
|
||||||
|
|
||||||
{ TAskBidItem }
|
{ TAskBidItem }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ interface
|
|||||||
uses
|
uses
|
||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
DUnitX.TestFramework,
|
DUnitX.TestFramework,
|
||||||
Myc.Trade.DataArray;
|
Myc.Data.Series;
|
||||||
|
|
||||||
type
|
type
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
|||||||
+37
-3
@@ -7,7 +7,7 @@ uses
|
|||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
System.DateUtils,
|
System.DateUtils,
|
||||||
System.Rtti,
|
System.Rtti,
|
||||||
Myc.DataRecord;
|
Myc.Data.Records;
|
||||||
|
|
||||||
type
|
type
|
||||||
// A record that contains all supported data types for testing
|
// A record that contains all supported data types for testing
|
||||||
@@ -52,6 +52,10 @@ type
|
|||||||
// [IgnoreMemoryLeaks]
|
// [IgnoreMemoryLeaks]
|
||||||
procedure TestNestedDataRecord;
|
procedure TestNestedDataRecord;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
// [IgnoreMemoryLeaks]
|
||||||
|
procedure TestInternalRecordAssignment;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
// [IgnoreMemoryLeaks]
|
// [IgnoreMemoryLeaks]
|
||||||
procedure TestNestedSetValueAndGetValue;
|
procedure TestNestedSetValueAndGetValue;
|
||||||
@@ -174,7 +178,8 @@ var
|
|||||||
begin
|
begin
|
||||||
// Setup: Create the nested record and convert it to a TDataRecord
|
// Setup: Create the nested record and convert it to a TDataRecord
|
||||||
nestedSrc.NestedValue := 999;
|
nestedSrc.NestedValue := 999;
|
||||||
nestedSrc.NestedString := 'Nested Record Test';
|
const str = 'Nested Record Test';
|
||||||
|
nestedSrc.NestedString := str;
|
||||||
nestedDataRecord := TDataRecord.FromRecord<TNestedTestRecord>(nestedSrc);
|
nestedDataRecord := TDataRecord.FromRecord<TNestedTestRecord>(nestedSrc);
|
||||||
|
|
||||||
// Setup: Create the outer record containing the nested TDataRecord
|
// Setup: Create the outer record containing the nested TDataRecord
|
||||||
@@ -192,12 +197,41 @@ begin
|
|||||||
|
|
||||||
// Assert values within the nested record
|
// Assert values within the nested record
|
||||||
Assert.AreEqual(999, retrievedNestedRecord.GetValue<Integer>('NestedValue'), 'Nested integer value mismatch');
|
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 value in the outer record
|
||||||
Assert.AreEqual(111, mainDataRecord.GetValue<Integer>('OuterValue'), 'Outer integer value mismatch');
|
Assert.AreEqual(111, mainDataRecord.GetValue<Integer>('OuterValue'), 'Outer integer value mismatch');
|
||||||
end;
|
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;
|
procedure TTestDataRecord.TestNestedSetValueAndGetValue;
|
||||||
var
|
var
|
||||||
outerRecord: TDataRecord;
|
outerRecord: TDataRecord;
|
||||||
|
|||||||
Reference in New Issue
Block a user