Work in Progress
This commit is contained in:
@@ -19,7 +19,7 @@ type
|
||||
end;
|
||||
|
||||
// Concrete data provider that manages a list of processors (listeners).
|
||||
TMycDataProvider<T> = class abstract(TContainedObject, TDataProvider<T>.IDataProvider)
|
||||
TMycContainedDataProvider<T> = class abstract(TContainedObject, TDataProvider<T>.IDataProvider)
|
||||
private
|
||||
FListeners: TMycNotifyList<IMycProcessor<T>>;
|
||||
public
|
||||
@@ -33,6 +33,19 @@ type
|
||||
procedure Unlink(Tag: TDataProvider<T>.TTag);
|
||||
end;
|
||||
|
||||
TMycSequence<T> = class(TMycProcessor<T>, IMycDataSequence<T>)
|
||||
private
|
||||
FDataProviders: TArray<TMycContainedDataProvider<T>>;
|
||||
function GetCount: Integer;
|
||||
function GetDataProvider(Idx: Integer): TDataProvider<T>;
|
||||
protected
|
||||
function ProcessData(const Value: T): TState; override;
|
||||
function ProcessDataProvider(Idx: Integer; const Value: T): TState;
|
||||
public
|
||||
constructor Create(ACount: Integer);
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
// Null object implementation for IDataProvider.
|
||||
TNullDataProvider<T> = class(TInterfacedObject, TDataProvider<T>.IDataProvider)
|
||||
public
|
||||
@@ -43,7 +56,7 @@ type
|
||||
// Abstract base class for components that process data of type S and provide data of type T.
|
||||
TMycConverter<S, T> = class abstract(TMycProcessor<S>, TConverter<S, T>.IConverter)
|
||||
private
|
||||
FSender: TMycDataProvider<T>;
|
||||
FSender: TMycContainedDataProvider<T>;
|
||||
function GetSender: TDataProvider<T>.IDataProvider;
|
||||
protected
|
||||
function ProcessData(const Value: S): TState; override; abstract;
|
||||
@@ -73,6 +86,12 @@ type
|
||||
constructor Create(const AFunc: TConstFunc<S, T>);
|
||||
end;
|
||||
|
||||
// A converter specialized for calculating indicators.
|
||||
TMycIdentityConverter<T> = class(TMycConverter<T, T>)
|
||||
protected
|
||||
function ProcessData(const Value: T): TState; override; final;
|
||||
end;
|
||||
|
||||
// A converter specialized for calculating indicators.
|
||||
TMycIndicator<S, T> = class(TMycConverter<S, T>)
|
||||
protected
|
||||
@@ -145,26 +164,43 @@ type
|
||||
destructor Destroy; override;
|
||||
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;
|
||||
public
|
||||
constructor Create(const ATimeframe: TTimeframe);
|
||||
function ProcessData(const Value: TDataPoint<Double>): TState; override;
|
||||
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
|
||||
property Timeframe: TTimeframe read GetTimeframe;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.TypInfo,
|
||||
System.RTTI;
|
||||
System.RTTI,
|
||||
System.DateUtils,
|
||||
System.Math,
|
||||
Myc.TaskManager;
|
||||
|
||||
{ TMycDataProvider<T> }
|
||||
{ TMycContainedDataProvider<T> }
|
||||
|
||||
constructor TMycDataProvider<T>.Create(const Controller: IInterface);
|
||||
constructor TMycContainedDataProvider<T>.Create(const Controller: IInterface);
|
||||
begin
|
||||
inherited Create(Controller);
|
||||
end;
|
||||
|
||||
destructor TMycDataProvider<T>.Destroy;
|
||||
destructor TMycContainedDataProvider<T>.Destroy;
|
||||
begin
|
||||
FListeners.Finalize;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TMycDataProvider<T>.Broadcast(const Value: T): TState;
|
||||
function TMycContainedDataProvider<T>.Broadcast(const Value: T): TState;
|
||||
begin
|
||||
FListeners.Lock;
|
||||
try
|
||||
@@ -192,7 +228,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMycDataProvider<T>.Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
|
||||
function TMycContainedDataProvider<T>.Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
|
||||
begin
|
||||
// Add the Processor to the notification list
|
||||
FListeners.Lock;
|
||||
@@ -203,7 +239,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMycDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
|
||||
procedure TMycContainedDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
|
||||
begin
|
||||
FListeners.Lock;
|
||||
try
|
||||
@@ -230,7 +266,7 @@ end;
|
||||
constructor TMycConverter<S, T>.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
FSender := TMycDataProvider<T>.Create(Self);
|
||||
FSender := TMycContainedDataProvider<T>.Create(Self);
|
||||
end;
|
||||
|
||||
destructor TMycConverter<S, T>.Destroy;
|
||||
@@ -411,4 +447,156 @@ begin
|
||||
FChanged.Notify;
|
||||
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
|
||||
// Align the time grid to UTC 0:00 using functions from System.DateUtils
|
||||
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, D3 are uncommon; this is a simple modulo-based approach relative to TDateTime's epoch.
|
||||
D2: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 2);
|
||||
D3: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 3);
|
||||
|
||||
W: Result := TimeStamp.StartOfTheWeek;
|
||||
|
||||
MN: Result := TimeStamp.StartOfTheMonth;
|
||||
// Quarter alignment
|
||||
MN3: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 3 * 3 + 1);
|
||||
// Half-year alignment
|
||||
MN6: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 6 * 6 + 1);
|
||||
|
||||
Y: Result := TimeStamp.StartOfTheYear;
|
||||
else
|
||||
// Fallback for any undefined timeframe
|
||||
Result := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TTickAggregation.GetCurrentBar: TDataPoint<TOhlcItem>;
|
||||
begin
|
||||
Result := FCurrentBar;
|
||||
end;
|
||||
|
||||
function TTickAggregation.GetTimeframe: TTimeframe;
|
||||
begin
|
||||
Result := FTimeframe;
|
||||
end;
|
||||
|
||||
function TTickAggregation.ProcessData(const Value: TDataPoint<Double>): TState;
|
||||
var
|
||||
barStartTime: TDateTime;
|
||||
lastBarTime: TDateTime;
|
||||
begin
|
||||
// Update bar for the strategy's timeframe
|
||||
barStartTime := GetBarStartTime(Value.Time, FTimeframe);
|
||||
lastBarTime := FCurrentBar.Time;
|
||||
|
||||
if (barStartTime > lastBarTime) then
|
||||
begin
|
||||
// A new bar starts, so the previous one is now complete.
|
||||
if (lastBarTime > 0) then
|
||||
begin
|
||||
Result := Broadcast(FCurrentBar);
|
||||
end;
|
||||
|
||||
// Start a new bar, Volume is 1 because this is the first tick.
|
||||
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
|
||||
// Update the currently aggregating bar
|
||||
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;
|
||||
// Volume is the number of ticks needed to build the complete bar.
|
||||
FCurrentBar.Data.Volume := FCurrentBar.Data.Volume + 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TMycSequence<T> }
|
||||
|
||||
constructor TMycSequence<T>.Create(ACount: Integer);
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
SetLength(FDataProviders, ACount);
|
||||
for var i := 0 to High(FDataProviders) do
|
||||
FDataProviders[i] := TMycContainedDataProvider<T>.Create(Self);
|
||||
end;
|
||||
|
||||
destructor TMycSequence<T>.Destroy;
|
||||
begin
|
||||
for var i := High(FDataProviders) downto 0 do
|
||||
FDataProviders[i].Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TMycSequence<T>.GetCount: Integer;
|
||||
begin
|
||||
Result := Length(FDataProviders);
|
||||
end;
|
||||
|
||||
function TMycSequence<T>.GetDataProvider(Idx: Integer): TDataProvider<T>;
|
||||
begin
|
||||
Result := FDataProviders[Idx];
|
||||
end;
|
||||
|
||||
function TMycSequence<T>.ProcessData(const Value: T): TState;
|
||||
begin
|
||||
Result := ProcessDataProvider(0, Value);
|
||||
end;
|
||||
|
||||
function TMycSequence<T>.ProcessDataProvider(Idx: Integer; const Value: T): TState;
|
||||
begin
|
||||
if Idx >= Length(FDataProviders) then
|
||||
exit;
|
||||
|
||||
Result :=
|
||||
TaskManager
|
||||
.RunTask(FDataProviders[idx].Broadcast(Value), function: TState begin Result := ProcessDataProvider(1 + idx, Value); end);
|
||||
end;
|
||||
|
||||
function TMycIdentityConverter<T>.ProcessData(const Value: T): TState;
|
||||
begin
|
||||
Result := Broadcast(Value);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user