Process.Update for synchronization

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