unit Myc.Trade.DataServer; interface uses System.SysUtils, System.Generics.Collections, Myc.Lazy, Myc.Trade.DataPoint, Myc.Trade.DataSeries, Myc.Signals, // Added for TLatch Myc.Futures; // Added for TFuture type IDataProvider = interface ['{B2A9996C-724B-4416-A882-595B7E58066D}'] procedure Fetch; end; IDataServer = interface(IDataProvider) ['{A6E246A2-E84E-49AB-A63E-333E561E488C}'] function GetData: TMutable>; function GetIsLiveData: TMutable; property Data: TMutable> read GetData; property IsLiveData: TMutable read GetIsLiveData; end; TDataServer = class(TInterfacedObject, IDataServer, IDataProvider) protected function GetData: TMutable>; virtual; abstract; function GetIsLiveData: TMutable; virtual; abstract; procedure Fetch; virtual; abstract; end; IDataServerNode = interface ['{DA3531F1-158E-4A63-8E5A-34089C537B1B}'] function CreateDataServer: IDataServer; end; TDataServerNode = class(TInterfacedObject, IDataServerNode) public // Factory method to create the actual data server instance. function CreateDataServer: IDataServer; virtual; abstract; end; TTABFileServer = class(TDataServer) private FFilename: String; // The maximum number of data points to be produced per Fetch call. FMaxFetch: Integer; FHasNewData: TEvent; FNeedNewChunk: Boolean; FIsLiveData: TMutable.IWriteable; // Internal state FCurrentFileName: string; FCurrentData: TFuture>; FNextFileName: string; FNextData: TFuture>; FData: TMutable>; FCurrPosInFile: Int64; FCurrChunk: TArray; FLoadGate: TLatch; FCurrentIdx: Int64; FLastTimeStamp: TDateTime; function ConvertTicks(var Idx: Int64; const Ticks: TArray): TArray; function UpdateChunk: TArray; protected procedure Fetch; override; function GetData: TMutable>; override; function GetIsLiveData: TMutable; override; public constructor Create(const AFilename: String; AMaxFetch: Integer); end; implementation uses System.IOUtils; { TTABFileServer } constructor TTABFileServer.Create(const AFilename: String; AMaxFetch: Integer); begin inherited Create; FFilename := AFilename; FMaxFetch := AMaxFetch; FNextData := TFuture>.Null; FNeedNewChunk := true; FIsLiveData := TMutable.CreateWriteable(false); FHasNewData := TEvent.CreateEvent; FData := TMutable>.Construct(FHasNewData.Signal, function: TArray begin Result := UpdateChunk; end); end; function TTABFileServer.ConvertTicks(var Idx: Int64; const Ticks: TArray): TArray; begin SetLength(Result, Length(Ticks)); for var i := 0 to High(Ticks) do begin Result[i] := TDataPoint.Create(Idx, Ticks[i].OADateTime, 1, TTick.Create(Ticks[i].Data.Ask, Ticks[i].Data.Bid)); inc(Idx); end; end; procedure TTABFileServer.Fetch; begin // Check if the source filename has changed. if (FFilename <> FCurrentFileName) and (FFilename <> '') then begin // Filename has changed, initialize by loading the new file. FCurrentFileName := FFilename; FCurrentData := TAskBid.LoadDataFile(TLatch.Enqueue(FLoadGate), FCurrentFileName); FCurrPosInFile := 0; FCurrentIdx := 0; FIsLiveData.SetValue(false); FLastTimeStamp := 0; FFilename := ''; FNextFileName := FindNextDataFile(FCurrentFileName); if FNextFileName <> '' then FNextData := TAskBid.LoadDataFile(TLatch.Enqueue(FLoadGate), FNextFileName); end; FHasNewData.Notify; FNeedNewChunk := true; FCurrChunk := nil; end; function TTABFileServer.GetData: TMutable>; begin Result := FData; end; function TTABFileServer.GetIsLiveData: TMutable; begin Result := FIsLiveData; end; function TTABFileServer.UpdateChunk: TArray; begin if not FNeedNewChunk then exit(FCurrChunk); FNeedNewChunk := false; if FCurrentData.Done.IsSet then begin SetLength(FCurrChunk, FMaxFetch); var n := 0; while n < FMaxFetch do begin if FCurrPosInFile >= Length(FCurrentData.Value) then begin // Next File! FCurrentData := FNextData; FCurrentFileName := FNextFileName; FCurrPosInFile := 0; FNextFileName := ''; FNextData := TFuture>.Null; if FCurrentFileName <> '' then begin FNextFileName := FindNextDataFile(FCurrentFileName); if FNextFileName <> '' then FNextData := TAskBid.LoadDataFile(TLatch.Enqueue(FLoadGate), FNextFileName); end else begin // We're done with this series FIsLiveData.SetValue(true); end; SetLength(FCurrChunk, n); break; end; var currData := FCurrentData.Value[FCurrPosInFile]; if FLastTimeStamp < currData.OADateTime then begin FLastTimeStamp := currData.OADateTime; FCurrChunk[n] := TDataPoint.Create(FCurrentIdx, FLastTimeStamp, 1, TTick.Create(currData.Data.Ask, currData.Data.Bid)); inc(FCurrentIdx); inc(n); end; inc(FCurrPosInFile); end; end; Result := FCurrChunk; end; end.