218 lines
7.7 KiB
ObjectPascal
218 lines
7.7 KiB
ObjectPascal
unit FirstStrategy;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Generics.Collections,
|
|
Myc.Signals,
|
|
Myc.Lazy,
|
|
Myc.TaskManager,
|
|
Myc.Trade.Types,
|
|
Myc.Trade.DataPoint,
|
|
Myc.Trade.DataArray;
|
|
|
|
type
|
|
TMycGenericConverter<S, T> = class(TMycConverter<S, T>)
|
|
type
|
|
TConvertFunc = reference to function(const Value: S): T;
|
|
private
|
|
FFunc: TConvertFunc;
|
|
protected
|
|
function ProcessData(const Value: S): TState; override;
|
|
public
|
|
constructor Create(const AFunc: TConvertFunc);
|
|
end;
|
|
|
|
TTicksToTimeframe = class(TMycConverter<TArray<TDataPoint<TAskBidItem>>, TDataPoint<TOhlcItem>>)
|
|
private
|
|
FTimeframe: TTimeframe;
|
|
// Stores the currently aggregating OHLC data.
|
|
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);
|
|
|
|
// Process new data. This is called concurrently and must not have side effects out of the scope of this class!
|
|
function ProcessData(const Values: TArray<TDataPoint<TAskBidItem>>): TState; override;
|
|
|
|
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
|
|
property Timeframe: TTimeframe read GetTimeframe;
|
|
end;
|
|
|
|
TIndicator<S, T> = class(TMycConverter<S, T>)
|
|
protected
|
|
function ProcessData(const Value: S): TState; override; final;
|
|
function Calculate(const Value: S): T; virtual; abstract;
|
|
end;
|
|
|
|
TGenericIndicator<S, T> = class(TIndicator<S, T>)
|
|
private
|
|
FFunc: TIndicatorFunc<S, T>;
|
|
protected
|
|
function Calculate(const Value: S): T; override; final;
|
|
public
|
|
constructor Create(const AFunc: TIndicatorFunc<S, T>);
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.DateUtils,
|
|
System.Math;
|
|
|
|
{ TTicksToTimeframe }
|
|
|
|
constructor TTicksToTimeframe.Create(const ATimeframe: TTimeframe);
|
|
begin
|
|
inherited Create;
|
|
FTimeframe := ATimeframe;
|
|
end;
|
|
|
|
function TTicksToTimeframe.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 TTicksToTimeframe.GetCurrentBar: TDataPoint<TOhlcItem>;
|
|
begin
|
|
Result := FCurrentBar;
|
|
end;
|
|
|
|
function TTicksToTimeframe.GetTimeframe: TTimeframe;
|
|
begin
|
|
Result := FTimeframe;
|
|
end;
|
|
|
|
function TTicksToTimeframe.ProcessData(const Values: TArray<TDataPoint<TAskBidItem>>): TState;
|
|
var
|
|
point: TDataPoint<TAskBidItem>;
|
|
midPrice: Single;
|
|
barStartTime: TDateTime;
|
|
lastBarTime: TDateTime;
|
|
currentBar: TOhlcItem;
|
|
states: TList<TState>;
|
|
begin
|
|
states := TList<TState>.Create;
|
|
try
|
|
// Process each incoming data point
|
|
for point in Values do
|
|
begin
|
|
midPrice := (point.Data.Ask + point.Data.Bid) / 2;
|
|
|
|
// Update bar for the strategy's timeframe
|
|
barStartTime := GetBarStartTime(point.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
|
|
states.Add(Broadcast(FCurrentBar));
|
|
end;
|
|
|
|
// Start a new bar, Volume is 1 because this is the first tick.
|
|
currentBar := TOhlcItem.Create(midPrice, midPrice, midPrice, midPrice, 1);
|
|
FCurrentBar.Data := currentBar;
|
|
FCurrentBar.Time := barStartTime;
|
|
end
|
|
else
|
|
begin
|
|
// Update the currently aggregating bar
|
|
currentBar := FCurrentBar.Data;
|
|
currentBar.High := Max(currentBar.High, midPrice);
|
|
currentBar.Low := Min(currentBar.Low, midPrice);
|
|
currentBar.Close := midPrice;
|
|
// Volume is the number of ticks needed to build the complete bar.
|
|
currentBar.Volume := currentBar.Volume + 1;
|
|
FCurrentBar.Data := currentBar;
|
|
end;
|
|
end;
|
|
|
|
Result := TState.All(states.ToArray);
|
|
finally
|
|
states.Free;
|
|
end;
|
|
end;
|
|
|
|
{ TMycGenericConverter<S, T> }
|
|
|
|
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc);
|
|
begin
|
|
inherited Create;
|
|
FFunc := AFunc;
|
|
end;
|
|
|
|
function TMycGenericConverter<S, T>.ProcessData(const Value: S): TState;
|
|
begin
|
|
Result := Broadcast(FFunc(Value));
|
|
end;
|
|
|
|
{ TIndicator<S,T> }
|
|
|
|
function TIndicator<S, T>.ProcessData(const Value: S): TState;
|
|
begin
|
|
Result := Broadcast(Calculate(Value));
|
|
end;
|
|
|
|
{ TGenericIndicator<S,T> }
|
|
|
|
constructor TGenericIndicator<S, T>.Create(const AFunc: TIndicatorFunc<S, T>);
|
|
begin
|
|
inherited Create;
|
|
FFunc := AFunc;
|
|
end;
|
|
|
|
function TGenericIndicator<S, T>.Calculate(const Value: S): T;
|
|
begin
|
|
Result := FFunc(Value);
|
|
end;
|
|
|
|
end.
|