From cf17e06e32f2eb6c08b87c4bcbc765f8f94e65a6 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sat, 12 Jul 2025 11:38:23 +0200 Subject: [PATCH] Process.Update for synchronization --- AuraTrader/AuraTrader.dpr | 1 - AuraTrader/AuraTrader.dproj | 7 - AuraTrader/AuraTrader.res | Bin 154804 -> 154804 bytes AuraTrader/FirstStrategy.pas | 313 ++++++++++------------------------- AuraTrader/MainForm.pas | 23 +-- AuraTrader/Myc.Fmx.Chart.pas | 204 +++++++++++++++-------- Src/Myc.Trade.DataArray.pas | 9 +- Src/Myc.Trade.DataPoint.pas | 82 ++++++++- Src/Myc.Trade.DataStream.pas | 3 +- Src/Myc.Trade.Ticker.pas | 56 ------- 10 files changed, 318 insertions(+), 380 deletions(-) delete mode 100644 Src/Myc.Trade.Ticker.pas diff --git a/AuraTrader/AuraTrader.dpr b/AuraTrader/AuraTrader.dpr index 1b8b2c9..7b8be3a 100644 --- a/AuraTrader/AuraTrader.dpr +++ b/AuraTrader/AuraTrader.dpr @@ -6,7 +6,6 @@ uses FMX.Forms, MainForm in 'MainForm.pas' {Form1}, Myc.Trade.Core.DataPoint in '..\Src\Myc.Trade.Core.DataPoint.pas', - Myc.Trade.Ticker in '..\Src\Myc.Trade.Ticker.pas', Myc.Aura.Module in '..\Src\Myc.Aura.Module.pas', Myc.Aura.Parameter in '..\Src\Myc.Aura.Parameter.pas', TestModule in 'TestModule.pas', diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj index a29543d..7305040 100644 --- a/AuraTrader/AuraTrader.dproj +++ b/AuraTrader/AuraTrader.dproj @@ -134,7 +134,6 @@
Form1
- @@ -185,12 +184,6 @@ true - - - AuraTrader.rsm - true - - AuraTrader.exe diff --git a/AuraTrader/AuraTrader.res b/AuraTrader/AuraTrader.res index 5c25dbd94b8baa36dc7add956848281becc41748..b58c115d2e7e7d19cba9a59dab44e38119ddddf0 100644 GIT binary patch delta 18 Zcmdn8lXJ^X&W0AoEle9?SeO|Y7yw7)1-k$M delta 18 Zcmdn8lXJ^X&W0AoEle9?SeO_X7yw7$1-bwL diff --git a/AuraTrader/FirstStrategy.pas b/AuraTrader/FirstStrategy.pas index 72093e5..079fcbd 100644 --- a/AuraTrader/FirstStrategy.pas +++ b/AuraTrader/FirstStrategy.pas @@ -7,49 +7,28 @@ uses Myc.Signals, Myc.Lazy, Myc.Trade.DataPoint, - Myc.Trade.DataArray, - Myc.Core.Notifier; + Myc.Trade.DataArray; type TTimeframe = (M1, M5, H1, D); - TTag = Pointer; - - IMycBroadcast = interface - function Link(const Strategy: IMycProcessor): TTag; - procedure Unlink(Tag: TTag); - end; - - // A contained object that broadcasts Values to linked strategies - TMycBroadcast = class(TContainedObject, IMycBroadcast) - private - FLinkedStrategies: TMycNotifyList>; - protected - // Link a strategy - function Link(const Strategy: IMycProcessor): TTag; - // Unlink a linked strategy - procedure Unlink(Tag: TTag); - // Broadcasts the given data points to all linked strategies. - procedure Broadcast(const Value: T); - public - constructor Create(const Controller: IInterface); - destructor Destroy; override; - end; - IMycConverter = interface(IMycProcessor) - function GetObservers: IMycBroadcast; - property Observers: IMycBroadcast read GetObservers; + function GetSender: IMycDataProvider; + property Sender: IMycDataProvider read GetSender; end; TMycConverter = class abstract(TMycProcessor, IMycConverter) private - FObservers: TMycBroadcast; + FSender: TMycDataProvider; + function GetSender: IMycDataProvider; protected + // Broadcasts the given data to all linked processors. procedure Broadcast(const Value: T); - function GetObservers: IMycBroadcast; + procedure Update; override; public constructor Create; destructor Destroy; override; + property Sender: IMycDataProvider read GetSender; end; TMycGenericConverter = class(TMycConverter) @@ -63,40 +42,7 @@ type constructor Create(const AFunc: TConvertFunc); end; - // Series - - IMycSeriesConverter = interface(IMycConverter, T>) - function GetLookback: Integer; - property Lookback: Integer read GetLookback; - end; - - // Indicator - - IMycIndicator = interface(IMycSeriesConverter>) - end; - - TMycIndicator = class abstract(TMycConverter, TArray>, IMycIndicator) - protected - function GetLookback: Integer; virtual; abstract; - function ProcessData(const Value: TArray): Boolean; override; abstract; - public - property Lookback: Integer read GetLookback; - end; - - TMycGenericIndicator = class(TMycIndicator) - type - TConvertFunc = reference to function(const Value: S): T; - private - FLookback: Integer; - FFunc: TConvertFunc; - protected - function GetLookback: Integer; override; - function ProcessData(const Value: TArray): Boolean; override; - public - constructor Create(ALookback: Integer; const AFunc: TConvertFunc); - end; - - ITicksToTimeframe = interface(IMycConverter>, TArray>>) + ITicksToTimeframe = interface(IMycConverter>, TDataPoint>) function GetCurrentBar: TDataPoint; function GetStateText: TWriteable; function GetTimeframe: TTimeframe; @@ -105,7 +51,7 @@ type property Timeframe: TTimeframe read GetTimeframe; end; - TTicksToTimeframe = class(TMycConverter>, TArray>>, ITicksToTimeframe) + TTicksToTimeframe = class(TMycConverter>, TDataPoint>, ITicksToTimeframe) private FTimeframe: TTimeframe; FStateText: TWriteable; @@ -127,7 +73,7 @@ type end; // Implements the Hull Moving Average indicator. - THullMovingAverage = class(TMycIndicator) + THullMovingAverage = class(TMycConverter) private FPeriod: Integer; FPeriodHalf: Integer; @@ -139,8 +85,7 @@ type // Calculates the Weighted Moving Average for the most recent data. function CalculateWMA(const Series: TMycDataArray; const Period: Integer): Double; protected - function ProcessData(const Values: TArray): Boolean; override; - function GetLookback: Integer; override; + function ProcessData(const Value: Double): Boolean; override; public constructor Create(const APeriod: Integer); end; @@ -152,57 +97,6 @@ uses System.DateUtils, System.Math; -{ TMycBroadcast } - -constructor TMycBroadcast.Create(const Controller: IInterface); -begin - inherited Create(Controller); -end; - -destructor TMycBroadcast.Destroy; -begin - FLinkedStrategies.Finalize; - inherited Destroy; -end; - -procedure TMycBroadcast.Broadcast(const Value: T); -begin - var cValue := Value; - - FLinkedStrategies.Lock; - try - FLinkedStrategies.Notify( - function(const Processor: IMycProcessor): Boolean - begin - Result := Processor.ProcessData(cValue); - end - ); - finally - FLinkedStrategies.Release; - end; -end; - -function TMycBroadcast.Link(const Strategy: IMycProcessor): TTag; -begin - // Add the strategy to the notification list - FLinkedStrategies.Lock; - try - Result := FLinkedStrategies.Advise(Strategy); - finally - FLinkedStrategies.Release; - end; -end; - -procedure TMycBroadcast.Unlink(Tag: TTag); -begin - FLinkedStrategies.Lock; - try - FLinkedStrategies.Unadvise(Tag); - finally - FLinkedStrategies.Release; - end; -end; - { TTicksToTimeframe } constructor TTicksToTimeframe.Create(const ATimeframe: TTimeframe; const AStateText: TWriteable); @@ -247,54 +141,47 @@ var barStartTime: TDateTime; lastBarTime: TDateTime; currentBar: TOhlcItem; - producedBars: TList>; begin Result := true; - producedBars := TList>.Create; - try - // Process each incoming data point - for point in Values do + + // 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 - 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 + // A new bar starts, so the previous one is now complete. + if (lastBarTime > 0) then begin - // A new bar starts, so the previous one is now complete. - if (lastBarTime > 0) then - begin - producedBars.Add(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; + 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; - - with FCurrentBar do - FStateText.Value := - Format('Cuur Bar: O:%.5f H:%.5f L:%.5f C:%.5f V:%.0f', [Data.Open, Data.High, Data.Low, Data.Close, Data.Volume]); - - Broadcast(producedBars.ToArray); - finally - producedBars.Free; end; + + with FCurrentBar do + FStateText.Value := + Format('Cuur Bar: O:%.5f H:%.5f L:%.5f C:%.5f V:%.0f', [Data.Open, Data.High, Data.Low, Data.Close, Data.Volume]); end; { THullMovingAverage } @@ -337,62 +224,43 @@ begin Result := numerator / denominator; end; -function THullMovingAverage.GetLookback: Integer; -begin - Result := FPeriod + FPeriodSqrt - 1; -end; - -function THullMovingAverage.ProcessData(const Values: TArray): Boolean; +function THullMovingAverage.ProcessData(const Value: Double): Boolean; var - i: Integer; price: Double; wmaHalf, wmaFull, diff: Double; hma: Double; - resultArray: TArray; begin Result := true; - // Pre-allocate the result array since its size is known in advance. - SetLength(resultArray, Length(Values)); + price := Value; - for i := 0 to High(Values) do + // Default HMA to NaN for the warm-up period. + hma := Double.NaN; + + // Add new price to the source data array, respecting the lookback period. + FSourceData := FSourceData.Add(price, FPeriod); + + // Check if there is enough data to start the first stage of calculation. + if (FSourceData.Count >= FPeriod) then begin - price := Values[i]; + // Calculate the two WMAs for the first step. + wmaHalf := CalculateWMA(FSourceData, FPeriodHalf); + wmaFull := CalculateWMA(FSourceData, FPeriod); - // Default HMA to NaN for the warm-up period. - hma := Double.NaN; + // Calculate the difference and add to the intermediate series. + diff := 2 * wmaHalf - wmaFull; + FDiffSeries := FDiffSeries.Add(diff, FPeriodSqrt); - // Add new price to the source data array, respecting the lookback period. - FSourceData := FSourceData.Add(price, FPeriod); - - // Check if there is enough data to start the first stage of calculation. - if (FSourceData.Count >= FPeriod) then + // Check if there is enough intermediate data for the final calculation. + if (FDiffSeries.Count >= FPeriodSqrt) then begin - // Calculate the two WMAs for the first step. - wmaHalf := CalculateWMA(FSourceData, FPeriodHalf); - wmaFull := CalculateWMA(FSourceData, FPeriod); - - // Calculate the difference and add to the intermediate series. - diff := 2 * wmaHalf - wmaFull; - FDiffSeries := FDiffSeries.Add(diff, FPeriodSqrt); - - // Check if there is enough intermediate data for the final calculation. - if (FDiffSeries.Count >= FPeriodSqrt) then - begin - // Calculate the final HMA value, overwriting the default 0.0. - hma := CalculateWMA(FDiffSeries, FPeriodSqrt); - end; + // Calculate the final HMA value, overwriting the default 0.0. + hma := CalculateWMA(FDiffSeries, FPeriodSqrt); end; - - // Assign the result (either the calculated HMA or 0.0) directly into the array. - resultArray[i] := hma; end; - // Broadcast the result array if the input was not empty. - if (Length(resultArray) > 0) then - begin - Broadcast(resultArray); - end; + // Broadcast the result + Broadcast(hma); end; { TMycGenericConverter } @@ -409,51 +277,40 @@ begin Broadcast(FFunc(Value)); end; -constructor TMycGenericIndicator.Create(ALookback: Integer; const AFunc: TConvertFunc); -begin - inherited Create; - FFunc := AFunc; - FLookback := ALookback; -end; - -function TMycGenericIndicator.GetLookback: Integer; -begin - Result := FLookback; -end; - -function TMycGenericIndicator.ProcessData(const Value: TArray): Boolean; -var - Arr: TArray; -begin - Result := true; - SetLength(Arr, Length(Value)); - for var i := 0 to High(Arr) do - Arr[i] := FFunc(Value[i]); - Broadcast(Arr); -end; - { TMycConverter } constructor TMycConverter.Create; begin inherited Create; - FObservers := TMycBroadcast.Create(Self); + FSender := TMycDataProvider.Create(Self); end; destructor TMycConverter.Destroy; begin - FObservers.Free; + FSender.Free; inherited Destroy; end; procedure TMycConverter.Broadcast(const Value: T); begin - FObservers.Broadcast(Value); + var cValue := Value; + FSender.Notify(function(const Processor: IMycProcessor): Boolean begin Result := Processor.ProcessData(cValue) end); end; -function TMycConverter.GetObservers: IMycBroadcast; +function TMycConverter.GetSender: IMycDataProvider; begin - Result := FObservers; + Result := FSender; +end; + +procedure TMycConverter.Update; +begin + FSender.Notify( + function(const Processor: IMycProcessor): Boolean + begin + Processor.Update; + Result := true + end + ); end; end. diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas index 6197fc7..2b60681 100644 --- a/AuraTrader/MainForm.pas +++ b/AuraTrader/MainForm.pas @@ -165,34 +165,35 @@ begin var chart := TMycChart.Create(Self); AlignControl( chart ); chart.Height := Layout.ChildrenRect.Width*9/16; + chart.Lookback := 1000; ///// - var strategy: ITicksToTimeframe := TTicksToTimeframe.Create( D, stateText ); + var OhlcPoint: ITicksToTimeframe := TTicksToTimeframe.Create( H1, stateText ); - var Ohlc: IMycIndicator, TOhlcItem> := TMycGenericIndicator, TOhlcItem>.Create( 0, + var Ohlc: IMycConverter, TOhlcItem> := TMycGenericConverter, TOhlcItem>.Create( function( const Ohlc: TDataPoint ): TOhlcItem begin Result := Ohlc.Data; end ); - strategy.Observers.Link(Ohlc); + OhlcPoint.Sender.Link(Ohlc); - var Closes: IMycIndicator := TMycGenericIndicator.Create( 0, + var Closes: IMycConverter := TMycGenericConverter.Create( function( const Ohlc: TOhlcItem ): Double begin Result := Ohlc.Close; end ); - Ohlc.Observers.Link( Closes ); + Ohlc.Sender.Link( Closes ); - var Hull: IMycIndicator := THullMovingAverage.Create( 50 ); + var Hull: IMycConverter := THullMovingAverage.Create( 250 ); - Closes.Observers.Link( Hull ); + Closes.Sender.Link( Hull ); - Hull.Observers.Link(chart.CreateDoubleListener); + chart.AddDoubleSeries( Hull.Sender ); - var done := ExecuteStrategy( Symbol, strategy ); + var done := ExecuteStrategy( Symbol, OhlcPoint ); ///// @@ -205,7 +206,7 @@ begin FProcessDone := TState.All([FProcessDone, done]); - strategy.Observers.Link(chart.CreateOhlcListener); + chart.AddOhlcSeries( OhlcPoint.Sender ); end; procedure TForm1.TreeViewDblClick(Sender: TObject); @@ -531,7 +532,7 @@ begin end; end ); - dataProvider.Observers.Link( Processor ); + dataProvider.Sender.Link( Processor ); Result := FServer.ProcessData( Symbol, terminated, dataProvider ); end; diff --git a/AuraTrader/Myc.Fmx.Chart.pas b/AuraTrader/Myc.Fmx.Chart.pas index 90f687d..d1af204 100644 --- a/AuraTrader/Myc.Fmx.Chart.pas +++ b/AuraTrader/Myc.Fmx.Chart.pas @@ -15,17 +15,18 @@ uses FMX.Controls, FMX.Graphics, Myc.Trade.DataPoint, - Myc.Signals; + Myc.Signals, + Myc.Lazy; type TCandleStyle = (csCandleStick, csHiLoBar); TMycChart = class(TStyledControl) type - TSeries = class abstract(TContainedObject) + TSeries = class abstract(TObject) private + FOwner: TMycChart; function GetMainSeries: TSeries; - function GetOwner: TMycChart; protected function GetCount: Int64; virtual; abstract; function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual; abstract; @@ -36,7 +37,7 @@ type constructor Create(AOwner: TMycChart); property Count: Int64 read GetCount; property MainSeries: TSeries read GetMainSeries; - property Owner: TMycChart read GetOwner; + property Owner: TMycChart read FOwner; end; private @@ -51,15 +52,19 @@ type destructor Destroy; override; // Creates an OHLC candlestick/bar series - function CreateOhlcListener( + procedure AddOhlcSeries( + const DataProvider: IMycDataProvider>; const AUpColor: TAlphaColor = TAlphaColors.Green; const ADownColor: TAlphaColor = TAlphaColors.Red; const AStyle: TCandleStyle = csCandleStick - ): IMycProcessor>>; + ); // Creates a simple line series for double values - function CreateDoubleListener(const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue; const ALineWidth: Single = 1.5): - IMycProcessor>; + procedure AddDoubleSeries( + const DataProvider: IMycDataProvider; + const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue; + const ALineWidth: Single = 1.5 + ); // The maximum number of data points to display from the main series. property Lookback: Integer read FLookback write FLookback; @@ -69,24 +74,42 @@ implementation uses System.Math, - System.SyncObjs, WinApi.Windows, + System.SyncObjs, + WinApi.Windows, Myc.Trade.DataArray; type - TChartSeriesProcessor = class(TMycChart.TSeries, IMycProcessor>) + TChartSeriesReceiver = class(TMycProcessor) + strict private + FCurrData: TMycDataArray; + FLookback: Int64; + private + FData: TWriteable>; + protected + function ProcessData(const Value: T): Boolean; override; + procedure Update; override; + public + constructor Create(ALookback: Int64); + function GetData(var Data: TMycDataArray): Boolean; + property Data: TWriteable> read FData; + end; + + TChartSeriesProcessor = class(TMycChart.TSeries) strict private FDataSeries: TMycDataArray; - FChanged: Boolean; FLock: TSpinLock; - function ProcessData(const Values: TArray): Boolean; private FData: TMycDataArray; + FDataProvider: IMycDataProvider; + FReceiver: TChartSeriesReceiver; + FReceiverTag: TTag; protected function GetCount: Int64; override; function Update: Boolean; override; public - constructor Create(AOwner: TMycChart); + constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider); + destructor Destroy; override; property Data: TMycDataArray read FData; end; @@ -100,7 +123,12 @@ type function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; override; procedure Paint(const ACanvas: TCanvas; const AXForm, AYForm: TFunc); override; public - constructor Create(AOwner: TMycChart; const AUpColor, ADownColor: TAlphaColor; AStyle: TCandleStyle); + constructor Create( + AOwner: TMycChart; + const ADataProvider: IMycDataProvider>; + const AUpColor, ADownColor: TAlphaColor; + AStyle: TCandleStyle + ); end; { TChartLineSeries } @@ -112,14 +140,20 @@ type function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; override; procedure Paint(const ACanvas: TCanvas; const AXForm, AYForm: TFunc); override; public - constructor Create(AOwner: TMycChart; const ALineColor: TAlphaColor; ALineWidth: Single); + constructor Create( + AOwner: TMycChart; + const ADataProvider: IMycDataProvider; + const ALineColor: TAlphaColor; + ALineWidth: Single + ); end; { TMycChart.TSeries } constructor TMycChart.TSeries.Create(AOwner: TMycChart); begin - inherited Create(AOwner); + inherited Create; + FOwner := AOwner; end; function TMycChart.TSeries.GetMainSeries: TSeries; @@ -129,18 +163,13 @@ begin Result := Owner.FSeriesList[0]; end; -function TMycChart.TSeries.GetOwner: TMycChart; -begin - Result := Controller as TMycChart; -end; - { TMycChart } constructor TMycChart.Create(AOwner: TComponent); begin inherited Create(AOwner); FSeriesList := TObjectList.Create(true); - FLookback := 100; // Default lookback + FLookback := 10000; FIdleSubscrId := TMessageManager @@ -156,27 +185,29 @@ begin inherited; end; -function TMycChart.CreateDoubleListener(const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue; const ALineWidth: Single = 1.5): - IMycProcessor>; +procedure TMycChart.AddDoubleSeries( + const DataProvider: IMycDataProvider; + const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue; + const ALineWidth: Single = 1.5 +); var series: TChartLineSeries; begin - series := TChartLineSeries.Create(Self, ALineColor, ALineWidth); + series := TChartLineSeries.Create(Self, DataProvider, ALineColor, ALineWidth); FSeriesList.Add(series); - Result := series; end; -function TMycChart.CreateOhlcListener( +procedure TMycChart.AddOhlcSeries( + const DataProvider: IMycDataProvider>; const AUpColor: TAlphaColor = TAlphaColors.Green; const ADownColor: TAlphaColor = TAlphaColors.Red; const AStyle: TCandleStyle = csCandleStick -): IMycProcessor>>; +); var series: TChartOhlcSeries; begin - series := TChartOhlcSeries.Create(Self, AUpColor, ADownColor, AStyle); + series := TChartOhlcSeries.Create(Self, DataProvider, AUpColor, ADownColor, AStyle); FSeriesList.Add(series); - Result := series; end; procedure TMycChart.DoIdle; @@ -241,14 +272,11 @@ begin xTransform := function(index: Double): Single begin Result := rect.Right - (index / (FLookback - 1)) * rect.Width; end; yTransform := - function(value: Double): Single - begin - Result := rect.Top + (1 - (value - globalMin) / (globalMax - globalMin)) * rect.Height; - end; + function(value: Double): Single begin Result := rect.Top + (1 - (value - globalMin) / (globalMax - globalMin)) * rect.Height; end; -// var T := -// TMatrix.CreateTranslation(rect.Left, rect.Top + rect.Height*globalMax / (globalMax - globalMin)) * -// TMatrix.CreateScaling(rect.Width / (FLookback - 1), -rect.Height / (globalMax - globalMin)); + // var T := + // TMatrix.CreateTranslation(rect.Left, rect.Top + rect.Height*globalMax / (globalMax - globalMin)) * + // TMatrix.CreateScaling(rect.Width / (FLookback - 1), -rect.Height / (globalMax - globalMin)); for series in FSeriesList do begin @@ -258,12 +286,21 @@ end; { TChartSeriesProcessor } -constructor TChartSeriesProcessor.Create(AOwner: TMycChart); +constructor TChartSeriesProcessor.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider); begin inherited Create(AOwner); FLock := TSpinLock.Create(false); FDataSeries := TMycDataArray.CreateEmpty; FData := FDataSeries; + FDataProvider := ADataProvider; + FReceiver := TChartSeriesReceiver.Create(AOwner.Lookback); + FReceiverTag := FDataProvider.Link(FReceiver); +end; + +destructor TChartSeriesProcessor.Destroy; +begin + FDataProvider.Unlink(FReceiverTag); + inherited; end; function TChartSeriesProcessor.GetCount: Int64; @@ -271,36 +308,21 @@ begin Result := FData.Count; end; -function TChartSeriesProcessor.ProcessData(const Values: TArray): Boolean; -begin - Result := true; - FLock.Enter; - try - FDataSeries := FDataSeries.Add(Values, 0, Length(Values), Owner.Lookback); - FChanged := true; - finally - FLock.Exit; - end; -end; - function TChartSeriesProcessor.Update: Boolean; begin - FLock.Enter; - try - Result := FChanged; - FChanged := false; - if Result then - FData := FDataSeries; - finally - FLock.Exit; - end; + Result := FReceiver.GetData(FData); end; { TChartOhlcSeries } -constructor TChartOhlcSeries.Create(AOwner: TMycChart; const AUpColor, ADownColor: TAlphaColor; AStyle: TCandleStyle); +constructor TChartOhlcSeries.Create( + AOwner: TMycChart; + const ADataProvider: IMycDataProvider>; + const AUpColor, ADownColor: TAlphaColor; + AStyle: TCandleStyle +); begin - inherited Create(AOwner); + inherited Create(AOwner, ADataProvider); FUpColor := AUpColor; FDownColor := ADownColor; FStyle := AStyle; @@ -318,8 +340,9 @@ begin Max := -MaxDouble; for i := StartIndex to System.Math.Min(GetCount - 1, StartIndex + Count - 1) do begin - Min := System.Math.Min(Min, Data.Items[i].Data.Low); - Max := System.Math.Max(Max, Data.Items[i].Data.High); + var dp := Data.Items[i]; + Min := System.Math.Min(Min, dp.Data.Low); + Max := System.Math.Max(Max, dp.Data.High); end; Result := (Min <> MaxDouble); end; @@ -372,9 +395,14 @@ end; { TChartLineSeries } -constructor TChartLineSeries.Create(AOwner: TMycChart; const ALineColor: TAlphaColor; ALineWidth: Single); +constructor TChartLineSeries.Create( + AOwner: TMycChart; + const ADataProvider: IMycDataProvider; + const ALineColor: TAlphaColor; + ALineWidth: Single +); begin - inherited Create(AOwner); + inherited Create(AOwner, ADataProvider); FLineColor := ALineColor; FLineWidth := ALineWidth; end; @@ -389,10 +417,14 @@ begin Min := MaxDouble; Max := -MaxDouble; - for i := StartIndex to System.Math.Min(GetCount - 1, StartIndex + Count - 1) do + for i := StartIndex to System.Math.Min(Data.Count - 1, StartIndex + Count - 1) do begin - Min := System.Math.Min(Min, Data.Items[i]); - Max := System.Math.Max(Max, Data.Items[i]); + var v := Data.Items[i]; + if not IsNaN(v) then + begin + Min := System.Math.Min(Min, v); + Max := System.Math.Max(Max, v); + end; end; Result := (Min <> MaxDouble); end; @@ -412,18 +444,46 @@ begin var n := 0; while IsNaN(Data[n]) do begin - inc( n ); - if n >= displaycount-2 then + inc(n); + if n >= displaycount - 2 then exit; end; - points.MoveTo( TPointF.Create(AXForm(n), AYForm(Data[n])) ); + points.MoveTo(TPointF.Create(AXForm(n), AYForm(Data[n]))); for var i := n to displayCount - 1 do - points.LineTo( TPointF.Create(AXForm(i), AYForm(Data[i])) ); + points.LineTo(TPointF.Create(AXForm(i), AYForm(Data[i]))); ACanvas.Stroke.Color := FLineColor; ACanvas.Stroke.Thickness := FLineWidth; ACanvas.DrawPath(points, 1); end; +{ TChartSeriesReceiver } + +constructor TChartSeriesReceiver.Create(ALookback: Int64); +begin + inherited Create; + FLookback := ALookback; + FCurrData := TMycDataArray.CreateEmpty; + FData := TWriteable>.CreateWriteable( FCurrData ).Protect; +end; + +function TChartSeriesReceiver.GetData(var Data: TMycDataArray): Boolean; +begin + var prevTotalCount := Data.TotalCount; + Data := FData.Value; + Result := prevTotalCount <> Data.TotalCount; +end; + +function TChartSeriesReceiver.ProcessData(const Value: T): Boolean; +begin + Result := true; + FCurrData := FCurrData.Add(Value, FLookback); +end; + +procedure TChartSeriesReceiver.Update; +begin + FData.Value := FCurrData; +end; + end. diff --git a/Src/Myc.Trade.DataArray.pas b/Src/Myc.Trade.DataArray.pas index 393fd09..b617cc9 100644 --- a/Src/Myc.Trade.DataArray.pas +++ b/Src/Myc.Trade.DataArray.pas @@ -15,17 +15,19 @@ type private FChunks: TArray; FCount: Int64; + FTotalCount: Int64; function LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; inline; function GetItems(Idx: Int64): T; inline; public - constructor Create(const AChunks: TArray; ACount: Int64); + constructor Create(const AChunks: TArray; ACount, ATotalCount: Int64); function Add(const Data: T; Lookback: Int64): TMycDataArray; overload; function Add(const Data: array of T; First, Count, Lookback: Int64): TMycDataArray; overload; class function CreateEmpty: TMycDataArray; static; // Helper to create a data array from a raw TArray. class function CreateFromArray(const AData: TArray; First, Count: Integer): TMycDataArray; static; property Count: Int64 read FCount; + property TotalCount: Int64 read FTotalCount; property Items[Idx: Int64]: T read GetItems; default; end; @@ -33,10 +35,11 @@ implementation { TMycDataArray } -constructor TMycDataArray.Create(const AChunks: TArray; ACount: Int64); +constructor TMycDataArray.Create(const AChunks: TArray; ACount, ATotalCount: Int64); begin FChunks := AChunks; FCount := ACount; + FTotalCount := ATotalCount; end; class function TMycDataArray.CreateEmpty: TMycDataArray; @@ -98,7 +101,7 @@ begin end; end; - Result := TMycDataArray.Create(newChunks, newCount); + Result := TMycDataArray.Create(newChunks, newCount, FTotalCount + Count); end; function TMycDataArray.Add(const Data: T; Lookback: Int64): TMycDataArray; diff --git a/Src/Myc.Trade.DataPoint.pas b/Src/Myc.Trade.DataPoint.pas index 7feb315..f7464db 100644 --- a/Src/Myc.Trade.DataPoint.pas +++ b/Src/Myc.Trade.DataPoint.pas @@ -3,7 +3,8 @@ unit Myc.Trade.DataPoint; interface uses - System.TimeSpan; + System.TimeSpan, + Myc.Core.Notifier; type // A data record for an Ask/Bid price pair. @@ -31,11 +32,34 @@ type IMycProcessor = interface function ProcessData(const Value: T): Boolean; + procedure Update; end; TMycProcessor = class abstract(TInterfacedObject, IMycProcessor) protected function ProcessData(const Value: T): Boolean; virtual; abstract; + procedure Update; virtual; + end; + + TTag = Pointer; + + IMycDataProvider = interface + function Link(const Receiver: IMycProcessor): TTag; + procedure Unlink(Tag: TTag); + end; + + TMycDataProvider = class abstract(TContainedObject, IMycDataProvider) + private + FListeners: TMycNotifyList>; + public + constructor Create(const Controller: IInterface); + destructor Destroy; override; + // Notifies all linked processors. + procedure Notify(const Func: TMycNotifyList>.TNotifyProc); + // Link a Processor + function Link(const Processor: IMycProcessor): TTag; + // Unlink a linked strategy + procedure Unlink(Tag: TTag); end; TMycGenericProcessor = class(TMycProcessor) @@ -43,6 +67,7 @@ type TProc = reference to function(const Value: T): Boolean; private FProc: TProc; + protected function ProcessData(const Value: T): Boolean; override; public constructor Create(const AProc: TProc); @@ -54,6 +79,7 @@ type private FProc: TProc; function ProcessData(const Value: T): Boolean; + procedure Update; public constructor Create(const Controller: IInterface; const AProc: TProc); end; @@ -441,4 +467,58 @@ begin Result := FProc(Value); end; +procedure TMycContainedProcessor.Update; +begin + +end; + +{ TMycDataProvider } + +constructor TMycDataProvider.Create(const Controller: IInterface); +begin + inherited Create(Controller); +end; + +destructor TMycDataProvider.Destroy; +begin + FListeners.Finalize; + inherited Destroy; +end; + +procedure TMycDataProvider.Notify(const Func: TMycNotifyList>.TNotifyProc); +begin + FListeners.Lock; + try + FListeners.Notify(Func); + finally + FListeners.Release; + end; +end; + +function TMycDataProvider.Link(const Processor: IMycProcessor): TTag; +begin + // Add the Processor to the notification list + FListeners.Lock; + try + Result := FListeners.Advise(Processor); + finally + FListeners.Release; + end; +end; + +procedure TMycDataProvider.Unlink(Tag: TTag); +begin + FListeners.Lock; + try + FListeners.Unadvise(Tag); + finally + FListeners.Release; + end; +end; + +procedure TMycProcessor.Update; +begin + +end; + end. diff --git a/Src/Myc.Trade.DataStream.pas b/Src/Myc.Trade.DataStream.pas index 9308afe..f9cbff2 100644 --- a/Src/Myc.Trade.DataStream.pas +++ b/Src/Myc.Trade.DataStream.pas @@ -437,7 +437,8 @@ begin function(const Data: TArray>): TState begin if not Processor.ProcessData(Data) then - exit( TState.Null ); + exit(TState.Null); + Processor.Update; Result := ProcessFile(nextFileInfo, nextFile, cTerminated, Processor); end diff --git a/Src/Myc.Trade.Ticker.pas b/Src/Myc.Trade.Ticker.pas deleted file mode 100644 index 524c445..0000000 --- a/Src/Myc.Trade.Ticker.pas +++ /dev/null @@ -1,56 +0,0 @@ -unit Myc.Trade.Ticker; - -interface - -uses - System.SysUtils, - Myc.Signals, - Myc.Lazy, - Myc.Trade.DataPoint, - Myc.Trade.DataStream, - Myc.Trade.DataProvider; - -type - TTickProc = reference to procedure(const Tick: TDataPoint); - - TTicker = class(TInterfacedObject, TSignal.ISubscriber) - type - TConsumer = record - Proc: TTickProc; - Lookback: Int64; - end; - private - FStream: IDataStream; - FConsumers: TArray; - FProvider: TMutable>; - - function Notify: Boolean; - public - constructor Create(const AStream: IDataStream; const AConsumers: TArray); - - end; - -implementation - -constructor TTicker.Create(const AStream: IDataStream; const AConsumers: TArray); -begin - inherited Create; - FStream := AStream; - FConsumers := AConsumers; - - var ml: Int64 := 0; - for var consumer in FConsumers do - if consumer.Lookback > ml then - ml := consumer.Lookback; - - FProvider := TDataStreamProvider.Create(ml, 100, AStream); -end; - -{ TTicker } - -function TTicker.Notify: Boolean; -begin - -end; - -end.