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 @@
-
@@ -185,12 +184,6 @@
true
-
-
- AuraTrader.rsm
- true
-
-
AuraTrader.exe
diff --git a/AuraTrader/AuraTrader.res b/AuraTrader/AuraTrader.res
index 5c25dbd..b58c115 100644
Binary files a/AuraTrader/AuraTrader.res and b/AuraTrader/AuraTrader.res differ
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.