Unit refactoring
Fixed massive heap corruption bug in TDataRecord
This commit is contained in:
@@ -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}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
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 }
|
||||
|
||||
@@ -5,7 +5,7 @@ interface
|
||||
uses
|
||||
System.SysUtils,
|
||||
DUnitX.TestFramework,
|
||||
Myc.Trade.DataArray;
|
||||
Myc.Data.Series;
|
||||
|
||||
type
|
||||
[TestFixture]
|
||||
|
||||
+37
-3
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user