unit FirstStrategy; interface uses System.SysUtils, System.Generics.Collections, Myc.Signals, Myc.Mutable, Myc.TaskManager, Myc.Trade.Types, Myc.Trade.DataPoint, Myc.Trade.DataArray; type 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) protected function ProcessData(const Value: S): TState; override; final; function Calculate(const Value: S): T; virtual; abstract; end; TGenericIndicator = class(TIndicator) private FFunc: TIndicatorFunc; protected function Calculate(const Value: S): T; override; final; public constructor Create(const AFunc: TIndicatorFunc); 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; 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 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 } 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; { TIndicator } function TIndicator.ProcessData(const Value: S): TState; begin Result := Broadcast(Calculate(Value)); end; { TGenericIndicator } constructor TGenericIndicator.Create(const AFunc: TIndicatorFunc); begin inherited Create; FFunc := AFunc; end; function TGenericIndicator.Calculate(const Value: S): T; begin Result := FFunc(Value); end; end.