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 TTimeframe = (M1, M5, H1, D); TMycGenericConverter = class(TMycConverter) 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>, TDataPoint>) private FTimeframe: TTimeframe; // Stores the currently aggregating OHLC data. FCurrentBar: TDataPoint; function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime; function GetCurrentBar: TDataPoint; 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>): TState; override; property CurrentBar: TDataPoint read GetCurrentBar; property Timeframe: TTimeframe read GetTimeframe; end; TIndicator = class(TMycConverter) strict private FQueue: TQueue; protected function ProcessData(const Value: S): TState; override; final; function Calculate(const Value: S): T; virtual; abstract; end; TGenericIndicator = class(TIndicator) private FFunc: TFunc; protected function Calculate(const Value: S): T; override; final; public constructor Create(const AFunc: TFunc); 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; begin // Align the time grid to UTC 0:00 using functions from System.DateUtils case Timeframe of M1: Result := RecodeSecond(RecodeMilliSecond(TimeStamp, 0), 0); M5: Result := RecodeMinute(RecodeSecond(RecodeMilliSecond(TimeStamp, 0), 0), MinuteOf(TimeStamp) - MinuteOf(TimeStamp) mod 5); H1: Result := RecodeMinute(RecodeSecond(RecodeMilliSecond(TimeStamp, 0), 0), 0); D: Result := StartOfTheDay(TimeStamp); else Result := 0; end; end; function TTicksToTimeframe.GetCurrentBar: TDataPoint; begin Result := FCurrentBar; end; function TTicksToTimeframe.GetTimeframe: TTimeframe; begin Result := FTimeframe; end; function TTicksToTimeframe.ProcessData(const Values: TArray>): TState; var point: TDataPoint; midPrice: Single; barStartTime: TDateTime; lastBarTime: TDateTime; currentBar: TOhlcItem; states: TList; begin states := TList.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 states.Add(Broadcast(FCurrentBar)); // 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 } constructor TMycGenericConverter.Create(const AFunc: TConvertFunc); begin inherited Create; FFunc := AFunc; end; function TMycGenericConverter.ProcessData(const Value: S): TState; begin Result := Broadcast(FFunc(Value)); end; function TIndicator.ProcessData(const Value: S): TState; begin Result := FQueue.Enqueue(function: TState begin Result := Broadcast(Calculate(Value)); end); end; constructor TGenericIndicator.Create(const AFunc: TFunc); begin inherited Create; FFunc := AFunc; end; function TGenericIndicator.Calculate(const Value: S): T; begin Result := FFunc(Value); end; end.