diff --git a/AuraTrader/AuraTrader.dpr b/AuraTrader/AuraTrader.dpr
index 018becb..3a2f499 100644
--- a/AuraTrader/AuraTrader.dpr
+++ b/AuraTrader/AuraTrader.dpr
@@ -5,7 +5,6 @@ uses
System.StartUpCopy,
FMX.Forms,
MainForm in 'MainForm.pas' {Form1},
- Myc.Trade.Core.DataPoint in '..\Src\Myc.Trade.Core.DataPoint.pas',
Myc.Aura.Module in '..\Src\Myc.Aura.Module.pas',
Myc.Aura.Parameter in '..\Src\Myc.Aura.Parameter.pas',
TestModule in 'TestModule.pas',
@@ -14,7 +13,9 @@ uses
Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas',
Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas',
Myc.Trade.Indicators in '..\Src\Myc.Trade.Indicators.pas',
- Myc.Trade.Types in '..\Src\Myc.Trade.Types.pas';
+ Myc.Trade.Types in '..\Src\Myc.Trade.Types.pas',
+ Myc.Trade.DataConverter in '..\Src\Myc.Trade.DataConverter.pas',
+ Myc.Trade.Core.DataConverter in '..\Src\Myc.Trade.Core.DataConverter.pas';
{$R *.res}
diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj
index b01b2ad..8c03150 100644
--- a/AuraTrader/AuraTrader.dproj
+++ b/AuraTrader/AuraTrader.dproj
@@ -4,7 +4,7 @@
20.3
FMX
True
- Debug
+ Release
Win64
AuraTrader
3
@@ -133,7 +133,6 @@
-
@@ -143,6 +142,8 @@
+
+
Base
diff --git a/AuraTrader/FirstStrategy.pas b/AuraTrader/FirstStrategy.pas
index 3f6ab48..8f14917 100644
--- a/AuraTrader/FirstStrategy.pas
+++ b/AuraTrader/FirstStrategy.pas
@@ -10,36 +10,12 @@ uses
Myc.TaskManager,
Myc.Trade.Types,
Myc.Trade.DataPoint,
- Myc.Trade.DataArray;
+ Myc.Trade.DataArray,
+ Myc.Trade.DataConverter,
+ Myc.Trade.Core.DataConverter;
type
- TMycGenericConverter = class(TMycConverter)
- type
- TConvertFunc = reference to function(const Value: S): T;
- private
- FFunc: TConvertFunc;
- protected
- function ProcessData(const Value: S): TState; override;
- public
- constructor Create(const AFunc: TConvertFunc);
- end;
-
- TIndicator = class(TMycConverter)
- protected
- function ProcessData(const Value: S): TState; override; final;
- function Calculate(const Value: S): T; virtual; abstract;
- end;
-
- TGenericIndicator = class(TIndicator)
- private
- FFunc: TIndicatorFunc;
- protected
- function Calculate(const Value: S): T; override; final;
- public
- constructor Create(const AFunc: TIndicatorFunc);
- end;
-
- TTicksToBars = class(TMycConverter, TDataPoint>)
+ TTickAggregation = class(TMycConverter, TDataPoint>)
private
FTimeframe: TTimeframe;
FCurrentBar: TDataPoint;
@@ -48,64 +24,26 @@ type
function GetTimeframe: TTimeframe;
public
constructor Create(const ATimeframe: TTimeframe);
- function ProcessData(const Value: TDataPoint): TState; override;
+ function ProcessData(const Value: TDataPoint): TState; override;
property CurrentBar: TDataPoint read GetCurrentBar;
property Timeframe: TTimeframe read GetTimeframe;
end;
- TTicker = class(TMycConverter, T>)
- public
- function ProcessData(const Values: TArray): TState; override;
- end;
-
implementation
uses
System.DateUtils,
System.Math;
-{ TMycGenericConverter }
+{ TTickAggregation }
-constructor TMycGenericConverter.Create(const AFunc: TConvertFunc);
-begin
- inherited Create;
- FFunc := AFunc;
-end;
-
-function TMycGenericConverter.ProcessData(const Value: S): TState;
-begin
- Result := Broadcast(FFunc(Value));
-end;
-
-{ TIndicator }
-
-function TIndicator.ProcessData(const Value: S): TState;
-begin
- Result := Broadcast(Calculate(Value));
-end;
-
-{ TGenericIndicator }
-
-constructor TGenericIndicator.Create(const AFunc: TIndicatorFunc);
-begin
- inherited Create;
- FFunc := AFunc;
-end;
-
-function TGenericIndicator.Calculate(const Value: S): T;
-begin
- Result := FFunc(Value);
-end;
-
-{ TTicksToBars }
-
-constructor TTicksToBars.Create(const ATimeframe: TTimeframe);
+constructor TTickAggregation.Create(const ATimeframe: TTimeframe);
begin
inherited Create;
FTimeframe := ATimeframe;
end;
-function TTicksToBars.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
+function TTickAggregation.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
var
baseTime: TDateTime;
begin
@@ -153,25 +91,21 @@ begin
end;
end;
-function TTicksToBars.GetCurrentBar: TDataPoint;
+function TTickAggregation.GetCurrentBar: TDataPoint;
begin
Result := FCurrentBar;
end;
-function TTicksToBars.GetTimeframe: TTimeframe;
+function TTickAggregation.GetTimeframe: TTimeframe;
begin
Result := FTimeframe;
end;
-function TTicksToBars.ProcessData(const Value: TDataPoint): TState;
+function TTickAggregation.ProcessData(const Value: TDataPoint): TState;
var
- midPrice: Single;
barStartTime: TDateTime;
lastBarTime: TDateTime;
- currentBar: TOhlcItem;
begin
- midPrice := (Value.Data.Ask + Value.Data.Bid) / 2;
-
// Update bar for the strategy's timeframe
barStartTime := GetBarStartTime(Value.Time, FTimeframe);
lastBarTime := FCurrentBar.Time;
@@ -185,32 +119,24 @@ begin
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.Data.Open := Value.Data;
+ FCurrentBar.Data.High := Value.Data;
+ FCurrentBar.Data.Low := Value.Data;
+ FCurrentBar.Data.Close := Value.Data;
+ FCurrentBar.Data.Volume := 1;
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;
+ if Value.Data > FCurrentBar.Data.High then
+ FCurrentBar.Data.High := Value.Data;
+ if Value.Data < FCurrentBar.Data.Low then
+ FCurrentBar.Data.Low := Value.Data;
+ FCurrentBar.Data.Close := Value.Data;
// Volume is the number of ticks needed to build the complete bar.
- currentBar.Volume := currentBar.Volume + 1;
- FCurrentBar.Data := currentBar;
+ FCurrentBar.Data.Volume := FCurrentBar.Data.Volume + 1;
end;
end;
-function TTicker.ProcessData(const Values: TArray): TState;
-begin
- var done := TLatch.CreateLatch( Length(Values) );
-
- // Process each incoming data point
- for var i:=0 to High(Values) do
- Broadcast(Values[i]).Signal.Subscribe(done);
-
- Result := done.State;
-end;
-
end.
diff --git a/AuraTrader/MainForm.fmx b/AuraTrader/MainForm.fmx
index 8b924e8..9d6f252 100644
--- a/AuraTrader/MainForm.fmx
+++ b/AuraTrader/MainForm.fmx
@@ -146,6 +146,16 @@ object Form1: TForm1
TextSettings.Trimming = None
OnClick = StopButtonClick
end
+ object Strat2Button: TSpeedButton
+ Align = FitLeft
+ Position.X = 639.119262695312500000
+ Size.Width = 123.636352539062500000
+ Size.Height = 34.000000000000000000
+ Size.PlatformDefault = False
+ Text = 'Strat 2'
+ TextSettings.Trimming = None
+ OnClick = Strat2ButtonClick
+ end
end
end
object ObjectsPanel: TPanel
diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas
index 4b92402..316dc64 100644
--- a/AuraTrader/MainForm.pas
+++ b/AuraTrader/MainForm.pas
@@ -30,6 +30,7 @@ uses
Myc.Trade.Types,
Myc.Trade.DataStream,
Myc.Trade.DataPoint,
+ Myc.Trade.DataConverter,
Myc.Signals,
Myc.Mutable,
Myc.Signals.FMX,
@@ -74,11 +75,13 @@ type
TestPopup: TPopup;
FlowLayout: TFlowLayout;
StrategyButton: TSpeedButton;
+ Strat2Button: TSpeedButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure StopButtonClick(Sender: TObject);
procedure TreeViewDblClick(Sender: TObject);
procedure AddWorkspaceActionExecute(Sender: TObject);
+ procedure Strat2ButtonClick(Sender: TObject);
procedure TestActionExecute(Sender: TObject);
procedure StrategyButtonClick(Sender: TObject);
private
@@ -279,12 +282,12 @@ end;
function TForm1.ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor>>): TState;
var
- dataProvider: IMycConverter>, TArray>>;
+ dataProvider: TConverter>, TArray>>;
begin
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
dataProvider :=
- TMycGenericConverter>, TArray>>.Create(
+ TConverter>, TArray>>.CreateGeneric(
function(const Values: TArray>): TArray>
begin
SetLength(Result, Length(Values));
@@ -328,32 +331,141 @@ begin
/////
- var timeframe := TTimeframe.S15;
+ var timeframe := TTimeframe.H4;
- var ticker := TTicker>.Create;
+ var ticker := TConverter.CreateTicker>;
- var OhlcPoint := TTicksToBars.Create(timeframe);
- ticker.Sender.Link(OhlcPoint);
+ var lastPrice :=
+ ticker.Chain>(
+ function(const Tick: TDataPoint): TDataPoint
+ begin
+ Result.Time := Tick.Time;
+ Result.Data := 0.5 * (Tick.Data.Ask + Tick.Data.Bid);
+ end
+ );
- var Timestamps: IMycConverter, TDateTime> :=
- TMycGenericConverter, TDateTime>
- .Create(function(const Ohlc: TDataPoint): TDateTime begin Result := Ohlc.Time; end);
+ var OhlcPoint := lastPrice.Chain>(TTickAggregation.Create(timeframe));
+ var Timestamps := OhlcPoint.Chain(function(const Ohlc: TDataPoint): TDateTime begin Result := Ohlc.Time; end);
+
+ var Ohlc := OhlcPoint.Chain(function(const Ohlc: TDataPoint): TOhlcItem begin Result := Ohlc.Data; end);
+
+ var Closes := Ohlc.Chain(function(const Ohlc: TOhlcItem): Double begin Result := Ohlc.Close; end);
+
+ var Hull := Closes.Chain(TIndicators.CreateHMA(150));
+ var Sma := Closes.Chain(TIndicators.CreateSMA(50));
+ var Ema := Closes.Chain(TIndicators.CreateEMA(21));
+ var Boli := Closes.Chain(TIndicators.CreateBollingerBands(20, 2.0));
+ var Rsi := Closes.Chain(TIndicators.CreateRSI(14));
+ var Macd := Closes.Chain(TIndicators.CreateMACD(12, 26, 9));
+ var Stoch := Ohlc.Chain(TIndicators.CreateStochastic(14, 3));
+
+ chart.SetXAxisSeries(timeframe, Timestamps.Sender);
+
+ var Panel := chart.AddPanel;
+ Panel.AddOhlcSeries(Ohlc.Sender);
+ Panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
+ Panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
+ Panel.AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
+ Panel.AddDoubleSeries(Boli.Field('UpperBand').Sender, TAlphaColors.Gray);
+ Panel.AddDoubleSeries(Boli.Field('MiddleBand').Sender, TAlphaColors.Darkgray, 1.0);
+ Panel.AddDoubleSeries(Boli.Field('LowerBand').Sender, TAlphaColors.Gray);
+
+ Panel := chart.AddPanel;
+ Panel.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
+
+ Panel := chart.AddPanel;
+ Panel.AddDoubleSeries(Macd.Field('MacdLine').Sender, TAlphaColors.Orange);
+ Panel.AddDoubleSeries(Macd.Field('SignalLine').Sender, TAlphaColors.Dodgerblue);
+ Panel.AddDoubleSeries(Macd.Field('Histogram').Sender, TAlphaColors.Lightgreen);
+
+ Panel := chart.AddPanel;
+ Panel.AddDoubleSeries(Stoch.Field('K').Sender, TAlphaColors.Green);
+ Panel.AddDoubleSeries(Stoch.Field('D').Sender, TAlphaColors.Red);
+
+ /////
+
+ var tickChart := TMycChart.Create(Self);
+ tickChart.Height := Layout.ChildrenRect.Width * 9 / 16;
+ AlignControl(tickChart);
+ tickChart.Lookback.Value := 1000000;
+
+ var TickTime := ticker.Field('Time');
+
+ var TickData := ticker.Field('Data');
+ var TickAsk := TickData.Field('Ask');
+ var TickBid := TickData.Field('Bid');
+
+ var TickSpread := TickData.Chain(function(const Tick: TAskBidItem): Double begin Result := Tick.Bid - Tick.Ask; end);
+
+ { tickChart.SetXAxisSeries(TTimeframe.S, TickTime.Sender);
+ panel := tickChart.AddPanel;
+ panel.AddDoubleSeries(TickAsk.Sender, TAlphaColors.Blue);
+ panel.AddDoubleSeries(TickBid.Sender, TAlphaColors.Red);
+
+ panel := tickChart.AddPanel;
+ panel.AddDoubleSeries(TickSpread.Sender);
+}
+ /////
+
+ var done := ExecuteStrategy(Symbol, ticker);
+ FProcessDone := TState.All([FProcessDone, done]);
+end;
+
+procedure TForm1.Strat2ButtonClick(Sender: TObject);
+begin
+ var timeframe := TTimeframe.M15;
+
+ var ticker := TConverter.CreateTicker>;
+
+ var lastPrice :=
+ ticker.Chain>(
+ function(const Tick: TDataPoint): TDataPoint
+ begin
+ Result.Time := Tick.Time;
+ Result.Data := 0.5 * (Tick.Data.Ask + Tick.Data.Bid);
+ end
+ );
+
+ var OhlcPoint := TTickAggregation.Create(timeframe);
+ lastPrice.Sender.Link(OhlcPoint);
+
+ var Closes :=
+ TConverter, Double>
+ .CreateGeneric(function(const Ohlc: TDataPoint): Double begin Result := Ohlc.Data.Close; end);
+
+ var Hull := TConverter.CreateGeneric(TIndicators.CreateHMA(150));
+
+ var Timestamps :=
+ TConverter, TDateTime>
+ .CreateGeneric(function(const Ohlc: TDataPoint): TDateTime begin Result := Ohlc.Time; end);
OhlcPoint.Sender.Link(TimeStamps);
+
+ var Layout := CurrLayout;
+ if Layout = nil then
+ exit;
+
+ var Symbol := SelectedSymbol;
+ if Symbol = '' then
+ exit;
+
+ var chart := TMycChart.Create(Self);
+ AlignControl(chart);
+ chart.Height := Layout.ChildrenRect.Width * 9 / 16;
+ chart.Lookback.Value := 50000;
+
+ /////
+
+ {
+
chart.SetXAxisSeries(timeframe, Timestamps.Sender);
var Panel := chart.AddPanel;
- var Ohlc: IMycConverter, TOhlcItem> :=
- TMycGenericConverter, TOhlcItem>
- .Create(function(const Ohlc: TDataPoint): TOhlcItem begin Result := Ohlc.Data; end);
OhlcPoint.Sender.Link(Ohlc);
Panel.AddOhlcSeries(Ohlc.Sender);
- var Closes: IMycConverter :=
- TMycGenericConverter.Create(function(const Ohlc: TOhlcItem): Double begin Result := Ohlc.Close; end);
-
Ohlc.Sender.Link(Closes);
var Hull: IMycConverter := TGenericIndicator.Create(TIndicators.CreateHMA(150));
@@ -477,6 +589,7 @@ begin
var done := ExecuteStrategy(Symbol, ticker);
FProcessDone := TState.All([FProcessDone, done]);
+ }
end;
end.
diff --git a/Src/Myc.Aura.Module.pas b/Src/Myc.Aura.Module.pas
index 0acef8b..2f4cfc6 100644
--- a/Src/Myc.Aura.Module.pas
+++ b/Src/Myc.Aura.Module.pas
@@ -236,9 +236,6 @@ type
// A bot executes a strategy
IAuraBot = interface(IAuraLiveObject)
- function GetPnL: TMutable>;
- // Zero-based PnL series
- property PnL: TMutable> read GetPnL;
end;
// A strategy creates a bot that executes that strategy with given parameters and within a given time slot.
@@ -262,11 +259,8 @@ type
IAuraTradeResult = interface
function GetPerformance: TAuraTradePerformance;
- function GetTrades: TDataSeries;
// Perform Monte Carlo Simulation and generate performance distribution
function CalcMonteCarloSimulation(Steps: Integer): TFuture;
- // PnLs of each trade = Equity curve
- property Trades: TDataSeries read GetTrades;
// The performance of this equity curve
property Performance: TAuraTradePerformance read GetPerformance;
end;
diff --git a/Src/Myc.Fmx.Chart.Series.pas b/Src/Myc.Fmx.Chart.Series.pas
index 69274e2..653176c 100644
--- a/Src/Myc.Fmx.Chart.Series.pas
+++ b/Src/Myc.Fmx.Chart.Series.pas
@@ -4,7 +4,6 @@ interface
uses
System.SysUtils,
- System.SyncObjs,
System.UITypes,
FMX.Graphics,
Myc.Signals,
@@ -12,18 +11,11 @@ uses
Myc.Trade.Types,
Myc.Trade.DataArray,
Myc.Trade.DataPoint,
+ Myc.Trade.DataConverter,
+ Myc.Trade.Core.DataConverter,
Myc.Fmx.Chart;
type
- TChartSeriesCounter = class(TMycConverter)
- private
- FCount: Int64;
- protected
- function ProcessData(const Value: T): TState; override;
- public
- constructor Create;
- end;
-
TChartSeriesReceiver = class(TMycProcessor)
strict private
FCurrData: TMycDataArray;
@@ -40,19 +32,18 @@ type
TChartSeriesProcessor = class(TMycChart.TSeries)
strict private
FDataSeries: TMycDataArray;
- FLock: TSpinLock;
private
FData: TMycDataArray;
- FDataProvider: IMycDataProvider;
+ FDataProvider: TDataProvider;
FReceiver: TChartSeriesReceiver;
- FReceiverTag: TTag;
+ FReceiverTag: TDataProvider.TTag;
protected
function GetCount: Int64; override;
function GetTotalCount: Int64; override;
procedure Update;
public
- constructor Create(const ADataProvider: IMycDataProvider; const ALookback: TMutable);
+ constructor Create(const ADataProvider: TDataProvider; const ALookback: TMutable);
destructor Destroy; override;
property Data: TMycDataArray read FData;
end;
@@ -65,10 +56,14 @@ type
function GetSeries: TMycChart.TSeries; override;
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override; abstract;
procedure Update; override;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc);
- override; abstract;
+ procedure Paint(
+ const Canvas: TCanvas;
+ First, Last: Int64;
+ const XForm: TFunc;
+ const YForm: TFunc
+ ); override; abstract;
public
- constructor Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider);
+ constructor Create(AParent: TMycChart.TPanel; const ADataProvider: TDataProvider);
destructor Destroy; override;
end;
@@ -79,11 +74,16 @@ type
FDownColor: TMutable;
protected
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc); override;
+ procedure Paint(
+ const Canvas: TCanvas;
+ First, Last: Int64;
+ const XForm: TFunc;
+ const YForm: TFunc
+ ); override;
public
constructor Create(
AParent: TMycChart.TPanel;
- const ADataProvider: IMycDataProvider;
+ const ADataProvider: TDataProvider;
const AUpColor, ADownColor: TMutable
);
end;
@@ -95,11 +95,16 @@ type
FLineWidth: Single;
protected
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc); override;
+ procedure Paint(
+ const Canvas: TCanvas;
+ First, Last: Int64;
+ const XForm: TFunc;
+ const YForm: TFunc
+ ); override;
public
constructor Create(
AParent: TMycChart.TPanel;
- const ADataProvider: IMycDataProvider;
+ const ADataProvider: TDataProvider;
const ALineColor: TAlphaColor;
ALineWidth: Single
);
@@ -114,7 +119,7 @@ type
procedure Update; override;
function GetCaption(Idx: Int64): String; override; abstract;
public
- constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
+ constructor Create(AOwner: TMycChart; const ADataProvider: TDataProvider);
destructor Destroy; override;
end;
@@ -126,7 +131,7 @@ type
// Provide a formatted timestamp string for a given data index.
function GetCaption(Idx: Int64): String; override; final;
public
- constructor Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: IMycDataProvider);
+ constructor Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: TDataProvider);
end;
implementation
@@ -136,18 +141,6 @@ uses
System.Math,
FMX.Types;
-constructor TChartSeriesCounter.Create;
-begin
- inherited Create;
- FCount := 0;
-end;
-
-function TChartSeriesCounter.ProcessData(const Value: T): TState;
-begin
- Result := Broadcast(FCount);
- inc(FCount);
-end;
-
{ TChartSeriesReceiver }
constructor TChartSeriesReceiver.Create(const ALookback: TMutable);
@@ -167,13 +160,13 @@ end;
{ TChartSeriesProcessor }
-constructor TChartSeriesProcessor.Create(const ADataProvider: IMycDataProvider; const ALookback: TMutable);
+constructor TChartSeriesProcessor.Create(const ADataProvider: TDataProvider; const ALookback: TMutable);
begin
inherited Create;
FDataProvider := ADataProvider;
- FLock := TSpinLock.Create(false);
FDataSeries := TMycDataArray.CreateEmpty;
FData := FDataSeries;
+
FReceiver := TChartSeriesReceiver.Create(ALookback);
FReceiverTag := FDataProvider.Link(FReceiver);
end;
@@ -203,7 +196,7 @@ end;
constructor TChartLineLayer.Create(
AParent: TMycChart.TPanel;
- const ADataProvider: IMycDataProvider;
+ const ADataProvider: TDataProvider;
const ALineColor: TAlphaColor;
ALineWidth: Single
);
@@ -231,8 +224,12 @@ begin
Result := (MinValue < MaxValue);
end;
-procedure TChartLineLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc);
+procedure TChartLineLayer.Paint(
+ const Canvas: TCanvas;
+ First, Last: Int64;
+ const XForm: TFunc;
+ const YForm: TFunc
+);
var
points: TPathData;
n: Int64;
@@ -249,13 +246,13 @@ begin
if n < Last then
begin
points.MoveTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
- inc( n );
+ inc(n);
while n <= Last do
begin
if IsNaN(FSeries.Data[n]) then
break;
points.LineTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
- inc( n );
+ inc(n);
end;
end;
end;
@@ -273,7 +270,7 @@ end;
constructor TChartOhlcLayer.Create(
AParent: TMycChart.TPanel;
- const ADataProvider: IMycDataProvider;
+ const ADataProvider: TDataProvider;
const AUpColor, ADownColor: TMutable
);
begin
@@ -301,8 +298,12 @@ begin
Result := (MinValue <> MaxDouble);
end;
-procedure TChartOhlcLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc);
+procedure TChartOhlcLayer.Paint(
+ const Canvas: TCanvas;
+ First, Last: Int64;
+ const XForm: TFunc;
+ const YForm: TFunc
+);
var
i: Int64;
x, candleWidth: Single;
@@ -340,7 +341,7 @@ end;
{ TChartCustomLayer }
-constructor TChartCustomLayer.Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider);
+constructor TChartCustomLayer.Create(AParent: TMycChart.TPanel; const ADataProvider: TDataProvider);
begin
inherited Create(AParent);
FSeries := TChartSeriesProcessor.Create(ADataProvider, AParent.Owner.Lookback.AsMutable);
@@ -364,7 +365,7 @@ end;
{ TChartXAxisLayer }
-constructor TChartXAxisLayer.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
+constructor TChartXAxisLayer.Create(AOwner: TMycChart; const ADataProvider: TDataProvider);
begin
inherited Create(AOwner);
FSeries := TChartSeriesProcessor.Create(ADataProvider, AOwner.Lookback.AsMutable);
@@ -386,7 +387,7 @@ begin
FSeries.Update;
end;
-constructor TChartXAxisTimestampLayer.Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: IMycDataProvider);
+constructor TChartXAxisTimestampLayer.Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: TDataProvider);
begin
inherited Create(AOwner, ADataProvider);
FTimeframe := ATimeframe;
diff --git a/Src/Myc.Fmx.Chart.pas b/Src/Myc.Fmx.Chart.pas
index 21f92b4..10efe72 100644
--- a/Src/Myc.Fmx.Chart.pas
+++ b/Src/Myc.Fmx.Chart.pas
@@ -54,8 +54,12 @@ type
protected
function GetOwner: TMycChart; override; final;
function GetValueRange(First, Last: Int64; out Min, Max: Double): Boolean; virtual; abstract;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc); virtual;
- abstract;
+ procedure Paint(
+ const Canvas: TCanvas;
+ First, Last: Int64;
+ const XForm: TFunc;
+ const YForm: TFunc
+ ); virtual; abstract;
public
constructor Create(AParent: TPanel);
end;
@@ -105,13 +109,13 @@ type
destructor Destroy; override;
function AddOhlcSeries(
- const DataProvider: IMycDataProvider;
+ const DataProvider: TDataProvider;
const AUpColor: TAlphaColor = TAlphaColors.Green;
const ADownColor: TAlphaColor = TAlphaColors.Red
): TMycChart.TDataLayer;
function AddDoubleSeries(
- const DataProvider: IMycDataProvider;
+ const DataProvider: TDataProvider;
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
const ALineWidth: Single = 1.5
): TMycChart.TDataLayer;
@@ -162,7 +166,7 @@ type
function AddPanel: TPanel;
// Sets the master series that defines the time scale (X-axis).
- function SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: IMycDataProvider): TMycChart.TXAxisLayer;
+ function SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: TDataProvider): TMycChart.TXAxisLayer;
property Lookback: TWriteable read FLookback write FLookback;
property NeedRepaint: TFlag read FNeedRepaint;
@@ -220,7 +224,7 @@ end;
function TMycChart.AddPanel: TPanel;
begin
- Result := TPanel.Create(Self, IfThen(FPanelList.Count=0, 1, 0.2));
+ Result := TPanel.Create(Self, IfThen(FPanelList.Count = 0, 1, 0.2));
FPanelList.Add(Result);
Repaint;
end;
@@ -635,7 +639,7 @@ begin
Repaint;
end;
-function TMycChart.SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: IMycDataProvider): TMycChart.TXAxisLayer;
+function TMycChart.SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: TDataProvider): TMycChart.TXAxisLayer;
begin
FXAxisSeries.Free;
@@ -665,7 +669,7 @@ begin
end;
function TMycChart.TPanel.AddDoubleSeries(
- const DataProvider: IMycDataProvider;
+ const DataProvider: TDataProvider;
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
const ALineWidth: Single = 1.5
): TMycChart.TDataLayer;
@@ -675,7 +679,7 @@ begin
end;
function TMycChart.TPanel.AddOhlcSeries(
- const DataProvider: IMycDataProvider;
+ const DataProvider: TDataProvider;
const AUpColor: TAlphaColor = TAlphaColors.Green;
const ADownColor: TAlphaColor = TAlphaColors.Red
): TMycChart.TDataLayer;
@@ -742,13 +746,9 @@ begin
// 2. Create transforms for this panel
const padding = 2;
var top: Double := Viewport.Top + padding;
- var height: Double := Viewport.Height-(2*padding);
+ var height: Double := Viewport.Height - (2 * padding);
var range: Double := localMax - localMin;
- yTransform :=
- function(value: Double): Single
- begin
- Result := top + (1 - (value - localMin) / range) * height;
- end;
+ yTransform := function(value: Double): Single begin Result := top + (1 - (value - localMin) / range) * height; end;
// 3. Paint all layers in this panel
for var series in FSeriesList do
diff --git a/Src/Myc.Trade.Core.DataConverter.pas b/Src/Myc.Trade.Core.DataConverter.pas
new file mode 100644
index 0000000..f84d46d
--- /dev/null
+++ b/Src/Myc.Trade.Core.DataConverter.pas
@@ -0,0 +1,197 @@
+unit Myc.Trade.Core.DataConverter;
+
+interface
+
+uses
+ Myc.Signals,
+ Myc.Trade.Types,
+ Myc.Trade.DataPoint,
+ Myc.Trade.DataConverter;
+
+type
+ // Null object implementation for IMycConverter
+ TNullConverter = class(TInterfacedObject, TConverter.IConverter)
+ private
+ function GetSender: TDataProvider.IDataProvider;
+ public
+ function ProcessData(const Value: S): TState;
+ end;
+
+ TMycConverter = class abstract(TMycProcessor, TConverter.IConverter)
+ private
+ FSender: TMycDataProvider;
+ function GetSender: TDataProvider.IDataProvider;
+ protected
+ function ProcessData(const Value: S): TState; override; abstract;
+
+ // Broadcasts the given data to all linked processors.
+ function Broadcast(const Value: T): TState;
+
+ public
+ constructor Create;
+ destructor Destroy; override;
+ property Sender: TDataProvider.IDataProvider read GetSender;
+ end;
+
+ TMycGenericConverter = class(TMycConverter)
+ private
+ FFunc: TConstFunc;
+ protected
+ function ProcessData(const Value: S): TState; override;
+ public
+ constructor Create(const AFunc: TConstFunc);
+ end;
+
+ TMycIndicator = class(TMycConverter)
+ protected
+ function ProcessData(const Value: S): TState; override; final;
+ function Calculate(const Value: S): T; virtual; abstract;
+ end;
+
+ TMycDataCounter = class(TMycConverter)
+ private
+ FCount: Int64;
+ protected
+ function ProcessData(const Value: T): TState; override;
+ public
+ constructor Create;
+ end;
+
+ TMycTicker = class(TMycConverter, T>)
+ public
+ function ProcessData(const Values: TArray): TState; override;
+ end;
+
+ TMycRecordFieldReader = class(TMycConverter)
+ private
+ FOffset: Integer;
+
+ public
+ constructor Create(const AFieldName: String);
+ function ProcessData(const Values: S): TState; override;
+ end;
+
+implementation
+
+uses
+ System.TypInfo,
+ System.SysUtils,
+ System.RTTI;
+
+{ TMycConverter }
+
+constructor TMycConverter.Create;
+begin
+ inherited Create;
+ FSender := TMycDataProvider.Create(Self);
+end;
+
+destructor TMycConverter.Destroy;
+begin
+ FSender.Free;
+ inherited Destroy;
+end;
+
+function TMycConverter.Broadcast(const Value: T): TState;
+begin
+ Result := FSender.Broadcast(Value);
+end;
+
+function TMycConverter.GetSender: TDataProvider.IDataProvider;
+begin
+ Result := FSender;
+end;
+
+{ TNullConverter }
+
+function TNullConverter.GetSender: TDataProvider.IDataProvider;
+begin
+ Result := TDataProvider.Null;
+end;
+
+function TNullConverter.ProcessData(const Value: S): TState;
+begin
+ Result := TState.Null;
+end;
+
+{ TMycGenericConverter }
+
+constructor TMycGenericConverter.Create(const AFunc: TConstFunc);
+begin
+ inherited Create;
+ FFunc := AFunc;
+end;
+
+function TMycGenericConverter.ProcessData(const Value: S): TState;
+begin
+ Result := Broadcast(FFunc(Value));
+end;
+
+{ TMycIndicator }
+
+function TMycIndicator.ProcessData(const Value: S): TState;
+begin
+ Result := Broadcast(Calculate(Value));
+end;
+
+constructor TMycDataCounter.Create;
+begin
+ inherited Create;
+ FCount := 0;
+end;
+
+function TMycDataCounter.ProcessData(const Value: T): TState;
+begin
+ Result := Broadcast(FCount);
+ inc(FCount);
+end;
+
+function TMycTicker.ProcessData(const Values: TArray): TState;
+begin
+ var done := TLatch.CreateLatch(Length(Values));
+
+ // Process each incoming data point
+ for var i := 0 to High(Values) do
+ Broadcast(Values[i]).Signal.Subscribe(done);
+
+ Result := done.State;
+end;
+
+constructor TMycRecordFieldReader.Create(const AFieldName: String);
+begin
+ inherited Create;
+
+ var Context := TRttiContext.Create;
+ var Field := Context.GetType(TypeInfo(S)).GetField(AFieldName);
+ var TypeT := Context.GetType(TypeInfo(T));
+
+ var Fields := Context.GetType(TypeInfo(S)).GetFields;
+ var name := '';
+ if AFieldName = 'Time' then
+ for var i := 0 to High(Fields) do
+ begin
+ name := name + ' ' + Fields[i].Name;
+ end;
+
+ Assert(Assigned(Field), 'Field ' + AFieldName + ' not found');
+ Assert(Field.FieldType.TypeKind = TypeT.TypeKind, 'Incorrect type');
+
+ if Assigned(Field) and (Field.FieldType.TypeKind = TypeT.TypeKind) then
+ FOffset := Field.Offset
+ else
+ FOffset := -1;
+end;
+
+function TMycRecordFieldReader.ProcessData(const Values: S): TState;
+type
+ PT = ^T;
+begin
+ if FOffset < 0 then
+ exit(TState.Null);
+
+ var fieldPtr := PByte(@Values);
+ inc(fieldPtr, FOffset);
+ Result := Broadcast(PT(fieldPtr)^);
+end;
+
+end.
diff --git a/Src/Myc.Trade.Core.DataPoint.pas b/Src/Myc.Trade.Core.DataPoint.pas
deleted file mode 100644
index 35bdf51..0000000
--- a/Src/Myc.Trade.Core.DataPoint.pas
+++ /dev/null
@@ -1,460 +0,0 @@
-unit Myc.Trade.Core.DataPoint;
-
-interface
-
-uses
- System.Generics.Collections,
- System.TimeSpan,
- Myc.Trade.DataPoint,
- Myc.Trade.DataArray;
-
-type
- // The implementation class for IDataSeries.
- TMycDataSeries = class(TInterfacedObject, IDataSeries)
- private
- FData: TMycDataArray>;
- FLookback: Int64;
- FTotalCount: Int64;
- function GetCount: Int64;
- function GetItems(Idx: Int64): TDataPoint;
- function GetLookback: Int64;
- function GetTotalCount: Int64;
- public
- constructor Create(ALookback: Int64; const AData: TMycDataArray>; ATotalCount: Int64);
- destructor Destroy; override;
- function Add(const Data: TArray>; First, Count: Integer): IDataSeries;
- class function CreateDataSeries(
- Lookback: Int64;
- const AData: TMycDataArray>;
- ATotalCount: Int64
- ): IDataSeries; static;
- end;
-
- // Null object implementation for IDataSeries
- TNullDataSeries