Polished DataProvider & Converter, RTTI field access

This commit is contained in:
Michael Schimmel
2025-07-15 01:56:44 +02:00
parent 4247fde7cd
commit 1a2b6cf8a0
15 changed files with 687 additions and 1044 deletions
+3 -2
View File
@@ -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}
+3 -2
View File
@@ -4,7 +4,7 @@
<ProjectVersion>20.3</ProjectVersion>
<FrameworkType>FMX</FrameworkType>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<Config Condition="'$(Config)'==''">Release</Config>
<Platform Condition="'$(Platform)'==''">Win64</Platform>
<ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName>
<TargetedPlatforms>3</TargetedPlatforms>
@@ -133,7 +133,6 @@
<DCCReference Include="MainForm.pas">
<Form>Form1</Form>
</DCCReference>
<DCCReference Include="..\Src\Myc.Trade.Core.DataPoint.pas"/>
<DCCReference Include="..\Src\Myc.Aura.Module.pas"/>
<DCCReference Include="..\Src\Myc.Aura.Parameter.pas"/>
<DCCReference Include="TestModule.pas"/>
@@ -143,6 +142,8 @@
<DCCReference Include="..\Src\Myc.FMX.Chart.Series.pas"/>
<DCCReference Include="..\Src\Myc.Trade.Indicators.pas"/>
<DCCReference Include="..\Src\Myc.Trade.Types.pas"/>
<DCCReference Include="..\Src\Myc.Trade.DataConverter.pas"/>
<DCCReference Include="..\Src\Myc.Trade.Core.DataConverter.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
+22 -96
View File
@@ -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<S, T> = class(TMycConverter<S, T>)
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<S, T> = class(TMycConverter<S, T>)
protected
function ProcessData(const Value: S): TState; override; final;
function Calculate(const Value: S): T; virtual; abstract;
end;
TGenericIndicator<S, T> = class(TIndicator<S, T>)
private
FFunc: TIndicatorFunc<S, T>;
protected
function Calculate(const Value: S): T; override; final;
public
constructor Create(const AFunc: TIndicatorFunc<S, T>);
end;
TTicksToBars = class(TMycConverter<TDataPoint<TAskBidItem>, TDataPoint<TOhlcItem>>)
TTickAggregation = class(TMycConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>)
private
FTimeframe: TTimeframe;
FCurrentBar: TDataPoint<TOhlcItem>;
@@ -48,64 +24,26 @@ type
function GetTimeframe: TTimeframe;
public
constructor Create(const ATimeframe: TTimeframe);
function ProcessData(const Value: TDataPoint<TAskBidItem>): TState; override;
function ProcessData(const Value: TDataPoint<Double>): TState; override;
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
property Timeframe: TTimeframe read GetTimeframe;
end;
TTicker<T> = class(TMycConverter<TArray<T>, T>)
public
function ProcessData(const Values: TArray<T>): TState; override;
end;
implementation
uses
System.DateUtils,
System.Math;
{ TMycGenericConverter<S, T> }
{ TTickAggregation }
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc);
begin
inherited Create;
FFunc := AFunc;
end;
function TMycGenericConverter<S, T>.ProcessData(const Value: S): TState;
begin
Result := Broadcast(FFunc(Value));
end;
{ TIndicator<S,T> }
function TIndicator<S, T>.ProcessData(const Value: S): TState;
begin
Result := Broadcast(Calculate(Value));
end;
{ TGenericIndicator<S,T> }
constructor TGenericIndicator<S, T>.Create(const AFunc: TIndicatorFunc<S, T>);
begin
inherited Create;
FFunc := AFunc;
end;
function TGenericIndicator<S, T>.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<TOhlcItem>;
function TTickAggregation.GetCurrentBar: TDataPoint<TOhlcItem>;
begin
Result := FCurrentBar;
end;
function TTicksToBars.GetTimeframe: TTimeframe;
function TTickAggregation.GetTimeframe: TTimeframe;
begin
Result := FTimeframe;
end;
function TTicksToBars.ProcessData(const Value: TDataPoint<TAskBidItem>): TState;
function TTickAggregation.ProcessData(const Value: TDataPoint<Double>): 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<T>.ProcessData(const Values: TArray<T>): 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.
+10
View File
@@ -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
+128 -15
View File
@@ -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<TArray<TDataPoint<TAskBidItem>>>): TState;
var
dataProvider: IMycConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>;
dataProvider: TConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>;
begin
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
dataProvider :=
TMycGenericConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>.Create(
TConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>.CreateGeneric(
function(const Values: TArray<TDataPoint<TAuraAskBidFileItem>>): TArray<TDataPoint<TAskBidItem>>
begin
SetLength(Result, Length(Values));
@@ -328,32 +331,141 @@ begin
/////
var timeframe := TTimeframe.S15;
var timeframe := TTimeframe.H4;
var ticker := TTicker<TDataPoint<TAskBidItem>>.Create;
var ticker := TConverter.CreateTicker<TDataPoint<TAskBidItem>>;
var OhlcPoint := TTicksToBars.Create(timeframe);
ticker.Sender.Link(OhlcPoint);
var lastPrice :=
ticker.Chain<TDataPoint<Double>>(
function(const Tick: TDataPoint<TAskBidItem>): TDataPoint<Double>
begin
Result.Time := Tick.Time;
Result.Data := 0.5 * (Tick.Data.Ask + Tick.Data.Bid);
end
);
var Timestamps: IMycConverter<TDataPoint<TOhlcItem>, TDateTime> :=
TMycGenericConverter<TDataPoint<TOhlcItem>, TDateTime>
.Create(function(const Ohlc: TDataPoint<TOhlcItem>): TDateTime begin Result := Ohlc.Time; end);
var OhlcPoint := lastPrice.Chain<TDataPoint<TOhlcItem>>(TTickAggregation.Create(timeframe));
var Timestamps := OhlcPoint.Chain<TDateTime>(function(const Ohlc: TDataPoint<TOhlcItem>): TDateTime begin Result := Ohlc.Time; end);
var Ohlc := OhlcPoint.Chain<TOhlcItem>(function(const Ohlc: TDataPoint<TOhlcItem>): TOhlcItem begin Result := Ohlc.Data; end);
var Closes := Ohlc.Chain<Double>(function(const Ohlc: TOhlcItem): Double begin Result := Ohlc.Close; end);
var Hull := Closes.Chain<Double>(TIndicators.CreateHMA(150));
var Sma := Closes.Chain<Double>(TIndicators.CreateSMA(50));
var Ema := Closes.Chain<Double>(TIndicators.CreateEMA(21));
var Boli := Closes.Chain<TBollingerBandsResult>(TIndicators.CreateBollingerBands(20, 2.0));
var Rsi := Closes.Chain<Double>(TIndicators.CreateRSI(14));
var Macd := Closes.Chain<TMacdResult>(TIndicators.CreateMACD(12, 26, 9));
var Stoch := Ohlc.Chain<TStochasticResult>(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<Double>('UpperBand').Sender, TAlphaColors.Gray);
Panel.AddDoubleSeries(Boli.Field<Double>('MiddleBand').Sender, TAlphaColors.Darkgray, 1.0);
Panel.AddDoubleSeries(Boli.Field<Double>('LowerBand').Sender, TAlphaColors.Gray);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Macd.Field<Double>('MacdLine').Sender, TAlphaColors.Orange);
Panel.AddDoubleSeries(Macd.Field<Double>('SignalLine').Sender, TAlphaColors.Dodgerblue);
Panel.AddDoubleSeries(Macd.Field<Double>('Histogram').Sender, TAlphaColors.Lightgreen);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Stoch.Field<Double>('K').Sender, TAlphaColors.Green);
Panel.AddDoubleSeries(Stoch.Field<Double>('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<TDateTime>('Time');
var TickData := ticker.Field<TAskBidItem>('Data');
var TickAsk := TickData.Field<Double>('Ask');
var TickBid := TickData.Field<Double>('Bid');
var TickSpread := TickData.Chain<Double>(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<TDataPoint<TAskBidItem>>;
var lastPrice :=
ticker.Chain<TDataPoint<Double>>(
function(const Tick: TDataPoint<TAskBidItem>): TDataPoint<Double>
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<TDataPoint<TOhlcItem>, Double>
.CreateGeneric(function(const Ohlc: TDataPoint<TOhlcItem>): Double begin Result := Ohlc.Data.Close; end);
var Hull := TConverter<Double, Double>.CreateGeneric(TIndicators.CreateHMA(150));
var Timestamps :=
TConverter<TDataPoint<TOhlcItem>, TDateTime>
.CreateGeneric(function(const Ohlc: TDataPoint<TOhlcItem>): TDateTime begin Result := Ohlc.Time; end);
OhlcPoint.Sender.Link(TimeStamps);
var Layout := CurrLayout<TVertScrollBox>;
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<TDataPoint<TOhlcItem>, TOhlcItem> :=
TMycGenericConverter<TDataPoint<TOhlcItem>, TOhlcItem>
.Create(function(const Ohlc: TDataPoint<TOhlcItem>): TOhlcItem begin Result := Ohlc.Data; end);
OhlcPoint.Sender.Link(Ohlc);
Panel.AddOhlcSeries(Ohlc.Sender);
var Closes: IMycConverter<TOhlcItem, Double> :=
TMycGenericConverter<TOhlcItem, Double>.Create(function(const Ohlc: TOhlcItem): Double begin Result := Ohlc.Close; end);
Ohlc.Sender.Link(Closes);
var Hull: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateHMA(150));
@@ -477,6 +589,7 @@ begin
var done := ExecuteStrategy(Symbol, ticker);
FProcessDone := TState.All([FProcessDone, done]);
}
end;
end.
-6
View File
@@ -236,9 +236,6 @@ type
// A bot executes a strategy
IAuraBot = interface(IAuraLiveObject)
function GetPnL: TMutable<TDataSeries<Double>>;
// Zero-based PnL series
property PnL: TMutable<TDataSeries<Double>> 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<Double>;
// Perform Monte Carlo Simulation and generate performance distribution
function CalcMonteCarloSimulation(Steps: Integer): TFuture<TAuraMonteCarloResult>;
// PnLs of each trade = Equity curve
property Trades: TDataSeries<Double> read GetTrades;
// The performance of this equity curve
property Performance: TAuraTradePerformance read GetPerformance;
end;
+47 -46
View File
@@ -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<T> = class(TMycConverter<T, Int64>)
private
FCount: Int64;
protected
function ProcessData(const Value: T): TState; override;
public
constructor Create;
end;
TChartSeriesReceiver<T> = class(TMycProcessor<T>)
strict private
FCurrData: TMycDataArray<T>;
@@ -40,19 +32,18 @@ type
TChartSeriesProcessor<T> = class(TMycChart.TSeries)
strict private
FDataSeries: TMycDataArray<T>;
FLock: TSpinLock;
private
FData: TMycDataArray<T>;
FDataProvider: IMycDataProvider<T>;
FDataProvider: TDataProvider<T>;
FReceiver: TChartSeriesReceiver<T>;
FReceiverTag: TTag;
FReceiverTag: TDataProvider<T>.TTag;
protected
function GetCount: Int64; override;
function GetTotalCount: Int64; override;
procedure Update;
public
constructor Create(const ADataProvider: IMycDataProvider<T>; const ALookback: TMutable<Int64>);
constructor Create(const ADataProvider: TDataProvider<T>; const ALookback: TMutable<Int64>);
destructor Destroy; override;
property Data: TMycDataArray<T> 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<Int64, Single>; const YForm: TFunc<Double, Single>);
override; abstract;
procedure Paint(
const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
); override; abstract;
public
constructor Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider<T>);
constructor Create(AParent: TMycChart.TPanel; const ADataProvider: TDataProvider<T>);
destructor Destroy; override;
end;
@@ -79,11 +74,16 @@ type
FDownColor: TMutable<TAlphaColor>;
protected
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc<Int64, Single>; const YForm: TFunc<Double, Single>); override;
procedure Paint(
const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
); override;
public
constructor Create(
AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider<TOhlcItem>;
const ADataProvider: TDataProvider<TOhlcItem>;
const AUpColor, ADownColor: TMutable<TAlphaColor>
);
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<Int64, Single>; const YForm: TFunc<Double, Single>); override;
procedure Paint(
const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
); override;
public
constructor Create(
AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider<Double>;
const ADataProvider: TDataProvider<Double>;
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<T>);
constructor Create(AOwner: TMycChart; const ADataProvider: TDataProvider<T>);
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<TDateTime>);
constructor Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: TDataProvider<TDateTime>);
end;
implementation
@@ -136,18 +141,6 @@ uses
System.Math,
FMX.Types;
constructor TChartSeriesCounter<T>.Create;
begin
inherited Create;
FCount := 0;
end;
function TChartSeriesCounter<T>.ProcessData(const Value: T): TState;
begin
Result := Broadcast(FCount);
inc(FCount);
end;
{ TChartSeriesReceiver<T> }
constructor TChartSeriesReceiver<T>.Create(const ALookback: TMutable<Int64>);
@@ -167,13 +160,13 @@ end;
{ TChartSeriesProcessor<T> }
constructor TChartSeriesProcessor<T>.Create(const ADataProvider: IMycDataProvider<T>; const ALookback: TMutable<Int64>);
constructor TChartSeriesProcessor<T>.Create(const ADataProvider: TDataProvider<T>; const ALookback: TMutable<Int64>);
begin
inherited Create;
FDataProvider := ADataProvider;
FLock := TSpinLock.Create(false);
FDataSeries := TMycDataArray<T>.CreateEmpty;
FData := FDataSeries;
FReceiver := TChartSeriesReceiver<T>.Create(ALookback);
FReceiverTag := FDataProvider.Link(FReceiver);
end;
@@ -203,7 +196,7 @@ end;
constructor TChartLineLayer.Create(
AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider<Double>;
const ADataProvider: TDataProvider<Double>;
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<Int64, Single>; const YForm: TFunc<Double,
Single>);
procedure TChartLineLayer.Paint(
const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
);
var
points: TPathData;
n: Int64;
@@ -273,7 +270,7 @@ end;
constructor TChartOhlcLayer.Create(
AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider<TOhlcItem>;
const ADataProvider: TDataProvider<TOhlcItem>;
const AUpColor, ADownColor: TMutable<TAlphaColor>
);
begin
@@ -301,8 +298,12 @@ begin
Result := (MinValue <> MaxDouble);
end;
procedure TChartOhlcLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc<Int64, Single>; const YForm: TFunc<Double,
Single>);
procedure TChartOhlcLayer.Paint(
const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
);
var
i: Int64;
x, candleWidth: Single;
@@ -340,7 +341,7 @@ end;
{ TChartCustomLayer }
constructor TChartCustomLayer<T>.Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider<T>);
constructor TChartCustomLayer<T>.Create(AParent: TMycChart.TPanel; const ADataProvider: TDataProvider<T>);
begin
inherited Create(AParent);
FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AParent.Owner.Lookback.AsMutable);
@@ -364,7 +365,7 @@ end;
{ TChartXAxisLayer }
constructor TChartXAxisLayer<T>.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>);
constructor TChartXAxisLayer<T>.Create(AOwner: TMycChart; const ADataProvider: TDataProvider<T>);
begin
inherited Create(AOwner);
FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AOwner.Lookback.AsMutable);
@@ -386,7 +387,7 @@ begin
FSeries.Update;
end;
constructor TChartXAxisTimestampLayer.Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: IMycDataProvider<TDateTime>);
constructor TChartXAxisTimestampLayer.Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: TDataProvider<TDateTime>);
begin
inherited Create(AOwner, ADataProvider);
FTimeframe := ATimeframe;
+13 -13
View File
@@ -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<Int64, Single>; const YForm: TFunc<Double, Single>); virtual;
abstract;
procedure Paint(
const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
); virtual; abstract;
public
constructor Create(AParent: TPanel);
end;
@@ -105,13 +109,13 @@ type
destructor Destroy; override;
function AddOhlcSeries(
const DataProvider: IMycDataProvider<TOhlcItem>;
const DataProvider: TDataProvider<TOhlcItem>;
const AUpColor: TAlphaColor = TAlphaColors.Green;
const ADownColor: TAlphaColor = TAlphaColors.Red
): TMycChart.TDataLayer;
function AddDoubleSeries(
const DataProvider: IMycDataProvider<Double>;
const DataProvider: TDataProvider<Double>;
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<TDateTime>): TMycChart.TXAxisLayer;
function SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: TDataProvider<TDateTime>): TMycChart.TXAxisLayer;
property Lookback: TWriteable<Int64> read FLookback write FLookback;
property NeedRepaint: TFlag read FNeedRepaint;
@@ -635,7 +639,7 @@ begin
Repaint;
end;
function TMycChart.SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: IMycDataProvider<TDateTime>): TMycChart.TXAxisLayer;
function TMycChart.SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: TDataProvider<TDateTime>): TMycChart.TXAxisLayer;
begin
FXAxisSeries.Free;
@@ -665,7 +669,7 @@ begin
end;
function TMycChart.TPanel.AddDoubleSeries(
const DataProvider: IMycDataProvider<Double>;
const DataProvider: TDataProvider<Double>;
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<TOhlcItem>;
const DataProvider: TDataProvider<TOhlcItem>;
const AUpColor: TAlphaColor = TAlphaColors.Green;
const ADownColor: TAlphaColor = TAlphaColors.Red
): TMycChart.TDataLayer;
@@ -744,11 +748,7 @@ begin
var top: Double := Viewport.Top + 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
+197
View File
@@ -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<S, T> = class(TInterfacedObject, TConverter<S, T>.IConverter)
private
function GetSender: TDataProvider<T>.IDataProvider;
public
function ProcessData(const Value: S): TState;
end;
TMycConverter<S, T> = class abstract(TMycProcessor<S>, TConverter<S, T>.IConverter)
private
FSender: TMycDataProvider<T>;
function GetSender: TDataProvider<T>.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<T>.IDataProvider read GetSender;
end;
TMycGenericConverter<S, T> = class(TMycConverter<S, T>)
private
FFunc: TConstFunc<S, T>;
protected
function ProcessData(const Value: S): TState; override;
public
constructor Create(const AFunc: TConstFunc<S, T>);
end;
TMycIndicator<S, T> = class(TMycConverter<S, T>)
protected
function ProcessData(const Value: S): TState; override; final;
function Calculate(const Value: S): T; virtual; abstract;
end;
TMycDataCounter<T> = class(TMycConverter<T, Int64>)
private
FCount: Int64;
protected
function ProcessData(const Value: T): TState; override;
public
constructor Create;
end;
TMycTicker<T> = class(TMycConverter<TArray<T>, T>)
public
function ProcessData(const Values: TArray<T>): TState; override;
end;
TMycRecordFieldReader<S, T> = class(TMycConverter<S, T>)
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<S, T> }
constructor TMycConverter<S, T>.Create;
begin
inherited Create;
FSender := TMycDataProvider<T>.Create(Self);
end;
destructor TMycConverter<S, T>.Destroy;
begin
FSender.Free;
inherited Destroy;
end;
function TMycConverter<S, T>.Broadcast(const Value: T): TState;
begin
Result := FSender.Broadcast(Value);
end;
function TMycConverter<S, T>.GetSender: TDataProvider<T>.IDataProvider;
begin
Result := FSender;
end;
{ TNullConverter }
function TNullConverter<S, T>.GetSender: TDataProvider<T>.IDataProvider;
begin
Result := TDataProvider<T>.Null;
end;
function TNullConverter<S, T>.ProcessData(const Value: S): TState;
begin
Result := TState.Null;
end;
{ TMycGenericConverter<S, T> }
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConstFunc<S, T>);
begin
inherited Create;
FFunc := AFunc;
end;
function TMycGenericConverter<S, T>.ProcessData(const Value: S): TState;
begin
Result := Broadcast(FFunc(Value));
end;
{ TMycIndicator<S,T> }
function TMycIndicator<S, T>.ProcessData(const Value: S): TState;
begin
Result := Broadcast(Calculate(Value));
end;
constructor TMycDataCounter<T>.Create;
begin
inherited Create;
FCount := 0;
end;
function TMycDataCounter<T>.ProcessData(const Value: T): TState;
begin
Result := Broadcast(FCount);
inc(FCount);
end;
function TMycTicker<T>.ProcessData(const Values: TArray<T>): 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<S, T>.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<S, T>.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.
-460
View File
@@ -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<T>.
TMycDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
private
FData: TMycDataArray<TDataPoint<T>>;
FLookback: Int64;
FTotalCount: Int64;
function GetCount: Int64;
function GetItems(Idx: Int64): TDataPoint<T>;
function GetLookback: Int64;
function GetTotalCount: Int64;
public
constructor Create(ALookback: Int64; const AData: TMycDataArray<TDataPoint<T>>; ATotalCount: Int64);
destructor Destroy; override;
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
class function CreateDataSeries(
Lookback: Int64;
const AData: TMycDataArray<TDataPoint<T>>;
ATotalCount: Int64
): IDataSeries<T>; static;
end;
// Null object implementation for IDataSeries<T>
TNullDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
strict private
class var
FNull: IDataSeries<T>;
private
FTotalCount: Int64;
function GetCount: Int64;
function GetItems(Idx: Int64): TDataPoint<T>;
function GetTotalCount: Int64;
function GetLookback: Int64;
class constructor CreateClass;
public
constructor Create(ATotalCount: Int64);
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
class property Null: IDataSeries<T> read FNull;
end;
// A virtual series that combines a base series and an array of new data without copying.
TCompositeDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
private
FBaseSeries: IDataSeries<T>;
FAddedData: TMycDataArray<TDataPoint<T>>;
FLookback: Int64;
FCount: Int64;
function GetCount: Int64;
function GetItems(Idx: Int64): TDataPoint<T>;
function GetLookback: Int64;
function GetTotalCount: Int64;
public
constructor Create(const ABaseSeries: IDataSeries<T>; const AAddedData: TMycDataArray<TDataPoint<T>>);
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
class function CreateComposite(
const BaseSeries: IDataSeries<T>;
const Data: TArray<TDataPoint<T>>;
First, Count: Integer
): IDataSeries<T>; static;
end;
TConvertSeries<T, S> = class(TInterfacedObject, IDataSeries<S>)
private
FSource: IDataSeries<T>;
FConvertFunc: TDataSeries<T>.TConvertFunc<S>;
function GetCount: Int64;
function GetItems(Idx: Int64): TDataPoint<S>;
function GetLookback: Int64;
function GetTotalCount: Int64;
public
constructor Create(const ASource: IDataSeries<T>; const AConvertFunc: TDataSeries<T>.TConvertFunc<S>);
function Add(const Data: TArray<TDataPoint<S>>; First, Count: Integer): IDataSeries<S>;
end;
TAggregateDataSeries<T, S> = class(TInterfacedObject, IDataSeries<S>)
public
// Defines the function signature for aggregating a set of source data points into a single target value.
type
TAggregateFunc = reference to function(const ASourcePoints: TArray<TDataPoint<T>>): S;
private
FSource: IDataSeries<T>;
FTimeFrame: TDateTime;
FAggregateFunc: TAggregateFunc;
FCachedItems: TDictionary<Int64, TDataPoint<S>>;
FBaseTime: TDateTime;
FAggregatedCount: Int64;
function GetCount: Int64;
function GetItems(Idx: Int64): TDataPoint<S>;
function GetLookback: Int64;
function GetTotalCount: Int64;
procedure CalculateAggregatedCount;
public
constructor Create(const ASource: IDataSeries<T>; ATimeFrame: TDateTime; const AAggregateFunc: TAggregateFunc);
destructor Destroy; override;
function Add(const Data: TArray<TDataPoint<S>>; First, Count: Integer): IDataSeries<S>;
end;
implementation
uses
System.SysUtils,
System.Math;
{ TMycDataSeries<T> }
constructor TMycDataSeries<T>.Create(ALookback: Int64; const AData: TMycDataArray<TDataPoint<T>>; ATotalCount: Int64);
begin
inherited Create;
FLookback := ALookback;
FData := AData;
FTotalCount := ATotalCount;
end;
destructor TMycDataSeries<T>.Destroy;
begin
inherited;
end;
class function TMycDataSeries<T>.CreateDataSeries(
Lookback: Int64;
const AData: TMycDataArray<TDataPoint<T>>;
ATotalCount: Int64
): IDataSeries<T>;
begin
if Lookback > 0 then
Result := TMycDataSeries<T>.Create(Lookback, AData, ATotalCount)
else
Result := TNullDataSeries<T>.Null;
end;
function TMycDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
var
newData: TMycDataArray<TDataPoint<T>>;
newTotalCount: Int64;
begin
if Count < 0 then
Count := Length(Data) - First;
newData := FData.Add(Data, First, Count, FLookback);
newTotalCount := FTotalCount + Count;
Result := TMycDataSeries<T>.Create(FLookback, newData, newTotalCount);
end;
function TMycDataSeries<T>.GetCount: Int64;
begin
Result := FData.Count;
end;
function TMycDataSeries<T>.GetItems(Idx: Int64): TDataPoint<T>;
begin
Assert((Idx >= 0) and (Idx < FData.Count), 'Index is out of bounds.');
Result := FData.Items[Idx];
end;
function TMycDataSeries<T>.GetLookback: Int64;
begin
Result := FLookback;
end;
function TMycDataSeries<T>.GetTotalCount: Int64;
begin
Result := FTotalCount;
end;
{ TNullDataSeries<T> }
class constructor TNullDataSeries<T>.CreateClass;
begin
FNull := TNullDataSeries<T>.Create(0);
end;
constructor TNullDataSeries<T>.Create(ATotalCount: Int64);
begin
inherited Create;
FTotalCount := ATotalCount;
end;
function TNullDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
begin
if Count < 0 then
Count := Length(Data) - First;
if Count > 0 then
Result := TNullDataSeries<T>.Create(FTotalCount + Count)
else
Result := Self;
end;
function TNullDataSeries<T>.GetCount: Int64;
begin
Result := 0;
end;
function TNullDataSeries<T>.GetItems(Idx: Int64): TDataPoint<T>;
begin
Assert(false, 'Data series is empty.');
Result := Default(TDataPoint<T>);
end;
function TNullDataSeries<T>.GetLookback: Int64;
begin
Result := 0;
end;
function TNullDataSeries<T>.GetTotalCount: Int64;
begin
Result := FTotalCount;
end;
{ TCompositeDataSeries<T> }
constructor TCompositeDataSeries<T>.Create(const ABaseSeries: IDataSeries<T>; const AAddedData: TMycDataArray<TDataPoint<T>>);
begin
inherited Create;
FBaseSeries := ABaseSeries;
FAddedData := AAddedData;
FLookback := FBaseSeries.Lookback;
Assert(FLookback > 0);
FCount := FBaseSeries.Count + FAddedData.Count;
if FCount > FLookback then
FCount := FLookback;
end;
function TCompositeDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
var
newAddedData: TMycDataArray<TDataPoint<T>>;
itemsInBase: Int64;
lookbackForAdd: Int64;
begin
if Count < 0 then
Count := Length(Data) - First;
if Count = 0 then
exit(Self);
itemsInBase := FBaseSeries.Count;
lookbackForAdd := FLookback - itemsInBase;
if lookbackForAdd < 0 then
lookbackForAdd := 0;
newAddedData := FAddedData.Add(Data, First, Count, lookbackForAdd);
// Optimization: If the added data fills the entire lookback window,
// the base series is no longer relevant. We can return a simpler TMycDataSeries.
if newAddedData.Count >= FLookback then
begin
Result := TMycDataSeries<T>.Create(FLookback, newAddedData, GetTotalCount + Count);
end
else
begin
Result := TCompositeDataSeries<T>.Create(FBaseSeries, newAddedData);
end;
end;
class function TCompositeDataSeries<T>.CreateComposite(
const BaseSeries: IDataSeries<T>;
const Data: TArray<TDataPoint<T>>;
First, Count: Integer
): IDataSeries<T>;
begin
if Count < 0 then
Count := Length(Data) - First;
if Count = 0 then
exit(BaseSeries);
Result := TCompositeDataSeries<T>.Create(BaseSeries, TMycDataArray<TDataPoint<T>>.CreateFromArray(Data, First, Count));
end;
function TCompositeDataSeries<T>.GetCount: Int64;
begin
Result := FCount;
end;
function TCompositeDataSeries<T>.GetItems(Idx: Int64): TDataPoint<T>;
var
addedCount: Int64;
begin
Assert((Idx >= 0) and (Idx < FCount), 'Logical index is out of bounds.');
addedCount := FAddedData.Count;
if Idx < addedCount then
begin
Result := FAddedData.Items[Idx];
end
else
begin
Result := FBaseSeries.Items[Idx - addedCount];
end;
end;
function TCompositeDataSeries<T>.GetLookback: Int64;
begin
Result := FLookback;
end;
function TCompositeDataSeries<T>.GetTotalCount: Int64;
begin
Result := FBaseSeries.TotalCount + FAddedData.Count;
end;
{ TConvertSeries<T, S> }
constructor TConvertSeries<T, S>.Create(const ASource: IDataSeries<T>; const AConvertFunc: TDataSeries<T>.TConvertFunc<S>);
begin
inherited Create;
FSource := ASource;
FConvertFunc := AConvertFunc;
end;
function TConvertSeries<T, S>.Add(const Data: TArray<TDataPoint<S>>; First, Count: Integer): IDataSeries<S>;
begin
Result := TCompositeDataSeries<S>.CreateComposite(Self, Data, First, Count);
end;
function TConvertSeries<T, S>.GetCount: Int64;
begin
Result := FSource.Count;
end;
function TConvertSeries<T, S>.GetItems(Idx: Int64): TDataPoint<S>;
var
P: TDataPoint<T>;
begin
P := FSource[Idx];
Result.Create(P.Time, FConvertFunc(P));
end;
function TConvertSeries<T, S>.GetLookback: Int64;
begin
Result := FSource.Lookback;
end;
function TConvertSeries<T, S>.GetTotalCount: Int64;
begin
Result := FSource.TotalCount;
end;
{ TAggregateDataSeries<T, S> }
constructor TAggregateDataSeries<T, S>.Create(const ASource: IDataSeries<T>; ATimeFrame: TDateTime; const AAggregateFunc: TAggregateFunc);
begin
inherited Create;
FSource := ASource;
FTimeFrame := ATimeFrame;
FAggregateFunc := AAggregateFunc;
FCachedItems := TDictionary<Int64, TDataPoint<S>>.Create;
FAggregatedCount := -1; // -1 indicates that it has not been calculated yet
end;
destructor TAggregateDataSeries<T, S>.Destroy;
begin
FCachedItems.Free;
inherited;
end;
procedure TAggregateDataSeries<T, S>.CalculateAggregatedCount;
var
totalTimeSpan: Double;
begin
if FSource.Count = 0 then
begin
FBaseTime := 0;
FAggregatedCount := 0;
end
else
begin
// The timestamp of the oldest element serves as the anchor for our time grid.
FBaseTime := FSource.Items[FSource.Count - 1].Time;
// Total duration covered by the source series.
totalTimeSpan := FSource.Items[0].Time - FBaseTime;
if totalTimeSpan >= 0 then
FAggregatedCount := Trunc(totalTimeSpan / FTimeFrame) + 1
else
FAggregatedCount := 0;
end;
end;
function TAggregateDataSeries<T, S>.Add(const Data: TArray<TDataPoint<S>>; First, Count: Integer): IDataSeries<S>;
begin
// Adding to an aggregated series is complex.
// The most straightforward approach is to create a composite series.
Result := TCompositeDataSeries<S>.CreateComposite(Self, Data, First, Count);
end;
function TAggregateDataSeries<T, S>.GetCount: Int64;
begin
if FAggregatedCount = -1 then
CalculateAggregatedCount;
Result := FAggregatedCount;
end;
function TAggregateDataSeries<T, S>.GetItems(Idx: Int64): TDataPoint<S>;
var
startTime, endTime: TDateTime;
sourcePoints: TList<TDataPoint<T>>;
sourceIdx: Int64;
begin
Assert((Idx >= 0) and (Idx < GetCount), 'Index is out of bounds.');
if FCachedItems.TryGetValue(Idx, Result) then
exit;
// Calculate the time window for the requested aggregated data point.
// Index 0 is the newest, so we calculate backwards from the total count.
endTime := FBaseTime + (FAggregatedCount - Idx) * FTimeFrame;
startTime := endTime - FTimeFrame;
sourcePoints := TList<TDataPoint<T>>.Create;
try
// Find all source data points that fall into this time window.
// We can optimize the start of the search using the IndexOf function.
sourceIdx := TDataSeries<T>(FSource).IndexOf(endTime);
if sourceIdx = -1 then
sourceIdx := 0; // If endTime is after the last element, start at the newest.
while (sourceIdx < FSource.Count) do
begin
var P := FSource.Items[sourceIdx];
if P.Time < startTime then
break; // We have moved past our time window
if P.Time < endTime then // Time is within [startTime, endTime)
begin
sourcePoints.Add(P);
end;
Inc(sourceIdx);
end;
// The aggregation function expects the data in chronological order (oldest first),
// but we collected it in reverse. So we reverse the list.
sourcePoints.Reverse;
Result.Create(endTime, FAggregateFunc(sourcePoints.ToArray));
finally
sourcePoints.Free;
end;
// Cache the result for future calls
FCachedItems.Add(Idx, Result);
end;
function TAggregateDataSeries<T, S>.GetLookback: Int64;
begin
Result := FSource.Lookback; // The lookback is defined by the source series.
end;
function TAggregateDataSeries<T, S>.GetTotalCount: Int64;
begin
// The total count of aggregated items is simply its current count, as it's a view.
Result := GetCount;
end;
end.
+145
View File
@@ -0,0 +1,145 @@
unit Myc.Trade.DataConverter;
interface
uses
Myc.Signals,
Myc.Trade.Types,
Myc.Trade.DataPoint;
type
// Interface helper for IMycConverter<S,T> providing the null object pattern.
TConverter<S, T> = record
type
IConverter = interface(IMycProcessor<S>)
function GetSender: TDataProvider<T>.IDataProvider;
property Sender: TDataProvider<T>.IDataProvider read GetSender;
end;
{$REGION 'private'}
strict private
class var
FNull: IConverter;
class constructor CreateClass;
private
FConverter: IConverter;
function GetSender: TDataProvider<T>; inline;
{$ENDREGION}
public
constructor Create(const AConverter: IConverter);
// Managed record operators
class operator Initialize(out Dest: TConverter<S, T>);
class operator Implicit(const A: IConverter): TConverter<S, T>; overload;
class operator Implicit(const A: TConverter<S, T>): IConverter; overload;
class function CreateGeneric(const Func: TConstFunc<S, T>): TConverter<S, T>; static;
// Wrapper for IMycProcessor.ProcessData
function ProcessData(const Value: S): TState; inline;
function Chain<R>(const Next: TConverter<T, R>): TConverter<T, R>; overload; inline;
function Chain<R>(const Func: TConstFunc<T, R>): TConverter<T, R>; overload; inline;
function Field<R>(const FieldName: String): TConverter<T, R>; overload; inline;
// Provides access to the null object instance.
class property Null: IConverter read FNull;
// Wrapper for IMycConverter.Sender
property Sender: TDataProvider<T> read GetSender;
end;
TConverter = record
class function CreateCounter<T>: TConverter<T, Int64>; static;
class function CreateTicker<T>: TConverter<TArray<T>, T>; static;
class function CreateRecordField<S, T>(const FieldName: String): TConverter<S, T>; static;
end;
implementation
uses
Myc.Trade.Core.DataConverter;
{ TConverter<S, T> }
class constructor TConverter<S, T>.CreateClass;
begin
// Create the singleton null object instance.
FNull := TNullConverter<S, T>.Create;
end;
constructor TConverter<S, T>.Create(const AConverter: IConverter);
begin
FConverter := AConverter;
// Ensure that the internal interface is never nil.
if not Assigned(FConverter) then
FConverter := FNull;
end;
function TConverter<S, T>.Chain<R>(const Next: TConverter<T, R>): TConverter<T, R>;
begin
FConverter.Sender.Link(Next);
Result := Next;
end;
function TConverter<S, T>.Chain<R>(const Func: TConstFunc<T, R>): TConverter<T, R>;
begin
Result := Chain<R>(TMycGenericConverter<T, R>.Create(Func));
end;
class function TConverter<S, T>.CreateGeneric(const Func: TConstFunc<S, T>): TConverter<S, T>;
begin
Result := TMycGenericConverter<S, T>.Create(Func);
end;
function TConverter<S, T>.Field<R>(const FieldName: String): TConverter<T, R>;
begin
Result := Chain<R>(TMycRecordFieldReader<T, R>.Create(FieldName));
end;
function TConverter<S, T>.GetSender: TDataProvider<T>;
begin
// Forward the call to the wrapped interface.
Result := FConverter.Sender;
end;
class operator TConverter<S, T>.Initialize(out Dest: TConverter<S, T>);
begin
// Initialize new record instances with the null object.
Dest.FConverter := FNull;
end;
class operator TConverter<S, T>.Implicit(const A: IConverter): TConverter<S, T>;
begin
// Allow implicit conversion from the interface to the helper.
Result.Create(A);
end;
class operator TConverter<S, T>.Implicit(const A: TConverter<S, T>): IConverter;
begin
// Allow implicit conversion from the helper to the interface.
Result := A.FConverter;
end;
function TConverter<S, T>.ProcessData(const Value: S): TState;
begin
// Forward the call to the wrapped interface.
Result := FConverter.ProcessData(Value);
end;
class function TConverter.CreateCounter<T>: TConverter<T, Int64>;
begin
Result := TMycDataCounter<T>.Create;
end;
class function TConverter.CreateRecordField<S, T>(const FieldName: String): TConverter<S, T>;
begin
Result := TMycRecordFieldReader<S, T>.Create(FieldName);
end;
class function TConverter.CreateTicker<T>: TConverter<TArray<T>, T>;
begin
Result := TMycTicker<T>.Create;
end;
end.
+69 -355
View File
@@ -3,10 +3,8 @@ unit Myc.Trade.DataPoint;
interface
uses
System.TimeSpan,
Myc.Signals,
Myc.Core.Notifier,
Myc.Trade.Types;
Myc.Core.Notifier;
type
// Represents a time-stamped data point in a series.
@@ -25,14 +23,39 @@ type
function ProcessData(const Value: T): TState; virtual; abstract;
end;
TDataProvider<T> = record
type
TTag = Pointer;
IMycDataProvider<T> = interface
IDataProvider = interface
function Link(const Receiver: IMycProcessor<T>): TTag;
procedure Unlink(Tag: TTag);
end;
TMycDataProvider<T> = class abstract(TContainedObject, IMycDataProvider<T>)
{$REGION 'private'}
strict private
class var
FNull: IDataProvider;
class constructor CreateClass;
private
FDataProvider: IDataProvider;
{$ENDREGION}
public
constructor Create(const ADataProvider: IDataProvider);
// Managed record operators
class operator Initialize(out Dest: TDataProvider<T>);
class operator Implicit(const A: IDataProvider): TDataProvider<T>; overload;
class operator Implicit(const A: TDataProvider<T>): IDataProvider; overload;
// Wrapper for IMycDataProvider methods
function Link(const Receiver: IMycProcessor<T>): TTag; inline;
procedure Unlink(Tag: TTag); inline;
// Provides access to the null object instance.
class property Null: IDataProvider read FNull;
end;
TMycDataProvider<T> = class abstract(TContainedObject, TDataProvider<T>.IDataProvider)
private
FListeners: TMycNotifyList<IMycProcessor<T>>;
public
@@ -42,9 +65,9 @@ type
function Broadcast(const Value: T): TState;
procedure Notify(const Func: TMycNotifyList<IMycProcessor<T>>.TNotifyProc);
// Link a Processor
function Link(const Processor: IMycProcessor<T>): TTag;
function Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
// Unlink a linked strategy
procedure Unlink(Tag: TTag);
procedure Unlink(Tag: TDataProvider<T>.TTag);
end;
TMycGenericProcessor<T> = class(TMycProcessor<T>)
@@ -68,216 +91,24 @@ type
constructor Create(const Controller: IInterface; const AProc: TProc);
end;
IMycConverter<S, T> = interface(IMycProcessor<S>)
function GetSender: IMycDataProvider<T>;
property Sender: IMycDataProvider<T> read GetSender;
end;
TMycConverter<S, T> = class abstract(TMycProcessor<S>, IMycConverter<S, T>)
private
FSender: TMycDataProvider<T>;
function GetSender: IMycDataProvider<T>;
protected
function ProcessData(const Value: S): TState; override; abstract;
// Broadcasts the given data to all linked processors.
function Broadcast(const Value: T): TState;
TNullDataProvider<T> = class(TInterfacedObject, TDataProvider<T>.IDataProvider)
public
constructor Create;
destructor Destroy; override;
property Sender: IMycDataProvider<T> read GetSender;
end;
// An immutable time-ordered series of data points.
// The most recent element has the logical index 0.
IDataSeries<T> = interface
function GetCount: Int64;
function GetItems(Idx: Int64): TDataPoint<T>;
function GetTotalCount: Int64;
function GetLookback: Int64;
// Add data and result the new series.
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
property Count: Int64 read GetCount;
// Accesses data points by their logical index.
// Index 0 is the newest element, Index (Count - 1) is the oldest.
property Items[Idx: Int64]: TDataPoint<T> read GetItems; default;
// The maximum number of adressable items. Count will never be bigger than the Lookback.
property Lookback: Int64 read GetLookback;
// The total number of items ever added to the series.
property TotalCount: Int64 read GetTotalCount;
end;
// Interface Helper for IDataSeries<T>.
// Provides a safe, value-type-like wrapper around the interface.
TDataSeries<T> = record
type
TConvertFunc<S> = reference to function(const Val: TDataPoint<T>): S;
private
FDataSeries: IDataSeries<T>;
function GetCount: Int64;
function GetItems(Idx: Int64): TDataPoint<T>;
function GetData(Idx: Int64): T;
function GetTime(Idx: Int64): TDateTime;
function GetTotalCount: Int64;
function GetLookback: Int64;
class function GetNull: IDataSeries<T>; static;
public
constructor Create(ADataSeries: IDataSeries<T>);
class operator Initialize(out Dest: TDataSeries<T>);
class operator Finalize(var Dest: TDataSeries<T>);
class operator Implicit(const A: TDataSeries<T>): IDataSeries<T>;
class operator Implicit(const A: IDataSeries<T>): TDataSeries<T>;
class function CreateDataSeries(Lookback: Int64; const Data: TArray<TDataPoint<T>> = nil): TDataSeries<T>; static;
function Add(const Data: TArray<TDataPoint<T>>): TDataSeries<T>; overload;
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): TDataSeries<T>; overload;
function Convert<S>(const Func: TConvertFunc<S>): TDataSeries<S>;
// Searches for a data point by its timestamp.
// Returns the logical index of the matching item.
// If no exact match, returns the index of the item immediately preceding the timestamp.
// Returns -1 if the timestamp is before the oldest item in the series.
function IndexOf(TimeStamp: TDateTime): Int64;
function ToArray: TArray<TDataPoint<T>>;
function ToDataArray: TArray<T>;
class property Null: IDataSeries<T> read GetNull;
property Count: Int64 read GetCount;
property TotalCount: Int64 read GetTotalCount;
property Lookback: Int64 read GetLookback;
property Items[Idx: Int64]: TDataPoint<T> read GetItems; default;
property Data[Idx: Int64]: T read GetData;
property Time[Idx: Int64]: TDateTime read GetTime;
end;
TDataSeriesDoubleHelper = record helper for TDataSeries<Double>
function ToOhlc(TimeFrame: TTimeSpan): TDataSeries<TOhlcItem>;
end;
TDataSeriesOhlcHelper = record helper for TDataSeries<TOhlcItem>
function ToOpen: TDataSeries<Single>;
function ToClose: TDataSeries<Single>;
function ToHigh: TDataSeries<Single>;
function ToLow: TDataSeries<Single>;
function ToVolume: TDataSeries<Single>;
function Link(const Receiver: IMycProcessor<T>): TDataProvider<T>.TTag;
procedure Unlink(Tag: TDataProvider<T>.TTag);
end;
implementation
uses
System.SysUtils,
System.Math,
System.Generics.Collections,
Myc.Trade.DataArray,
Myc.Trade.Core.DataPoint;
{ TNullDataProvider }
// Optimized helper function using direct TTimeSpan features and integer arithmetic.
function CeilToTimeSpan(const ATime: TDateTime; const ATimeSpan: TTimeSpan): TDateTime;
var
timeSinceMidnight: TTimeSpan;
timeSpanTicks: Int64;
numIntervals, ceiledTicks: Int64;
function TNullDataProvider<T>.Link(const Receiver: IMycProcessor<T>): TDataProvider<T>.TTag;
begin
timeSpanTicks := ATimeSpan.Ticks;
Assert(timeSpanTicks > 0, 'TimeSpan must be positive.');
// Get the time portion of ATime directly as a TTimeSpan.
timeSinceMidnight := TTimeSpan.Subtract(ATime, Trunc(ATime));
// Using integer arithmetic to find the ceiling is robust.
// This is a standard formula for integer ceiling division: (numerator + denominator - 1) / denominator
numIntervals := (timeSinceMidnight.Ticks + timeSpanTicks - 1) div timeSpanTicks;
ceiledTicks := numIntervals * timeSpanTicks;
// Construct the final DateTime from the date part and the new, aligned time part.
Result := Trunc(ATime) + TTimeSpan.FromTicks(ceiledTicks);
Result := nil;
end;
function TDataSeriesDoubleHelper.ToOhlc(TimeFrame: TTimeSpan): TDataSeries<TOhlcItem>;
var
ohlcPoints: TList<TDataPoint<TOhlcItem>>;
currentBar: TOhlcItem;
windowEndTime: TDateTime;
sourceIdx: Int64;
firstPointInBar: Boolean;
procedure TNullDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
begin
if Self.Count = 0 then
exit(TDataSeries<TOhlcItem>.Create(TNullDataSeries<TOhlcItem>.Null));
ohlcPoints := TList<TDataPoint<TOhlcItem>>.Create;
try
if TimeFrame.Ticks <= 0 then
raise EArgumentException.Create('Invalid TimeFrame for OHLC aggregation.');
sourceIdx := Self.Count - 1; // Start with the oldest data point
firstPointInBar := True;
windowEndTime := 0;
// Iterate through all source points chronologically (oldest to newest)
while sourceIdx >= 0 do
begin
var P := Self.Items[sourceIdx];
if firstPointInBar then
begin
windowEndTime := CeilToTimeSpan(P.Time, TimeFrame);
currentBar.Create(P.Data, P.Data, P.Data, P.Data, 0);
firstPointInBar := False;
end;
if P.Time >= windowEndTime then
begin
ohlcPoints.Add(TDataPoint<TOhlcItem>.Create(windowEndTime, currentBar));
firstPointInBar := True;
Continue; // Re-evaluate the same point for the next bar
end;
currentBar.High := Max(currentBar.High, P.Data);
currentBar.Low := Min(currentBar.Low, P.Data);
currentBar.Close := P.Data;
currentBar.Volume := currentBar.Volume + 1;
Dec(sourceIdx);
end;
if not firstPointInBar then
ohlcPoints.Add(TDataPoint<TOhlcItem>.Create(windowEndTime, currentBar));
Result := TDataSeries<TOhlcItem>.CreateDataSeries(ohlcPoints.Count, ohlcPoints.ToArray);
finally
ohlcPoints.Free;
end;
end;
function TDataSeriesOhlcHelper.ToClose: TDataSeries<Single>;
begin
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Close; end);
end;
function TDataSeriesOhlcHelper.ToHigh: TDataSeries<Single>;
begin
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.High; end);
end;
function TDataSeriesOhlcHelper.ToLow: TDataSeries<Single>;
begin
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Low; end);
end;
function TDataSeriesOhlcHelper.ToOpen: TDataSeries<Single>;
begin
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Open; end);
end;
function TDataSeriesOhlcHelper.ToVolume: TDataSeries<Single>;
begin
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Volume; end);
// Do nothing in the null implementation.
end;
{ TDataPoint<T> }
@@ -288,148 +119,53 @@ begin
Data := AData;
end;
constructor TDataSeries<T>.Create(ADataSeries: IDataSeries<T>);
{ TDataProvider<T> }
class constructor TDataProvider<T>.CreateClass;
begin
if Assigned(ADataSeries) then
FDataSeries := ADataSeries
else
FDataSeries := Null;
// Create the singleton null object instance.
FNull := TNullDataProvider<T>.Create;
end;
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): TDataSeries<T>;
constructor TDataProvider<T>.Create(const ADataProvider: IDataProvider);
begin
{$ifdef DEBUG}
for var i := First + 1 to First + Count - 1 do
Assert(Data[i].Time >= Data[i - 1].Time, 'Input array for Add is not chronologically sorted');
if FDataSeries.Count > 0 then
Assert(Data[First].Time >= FDataSeries[0].Time, 'First new item is older than last existing item');
{$endif}
Result := FDataSeries.Add(Data, First, Count);
FDataProvider := ADataProvider;
// Ensure that the internal interface is never nil.
if not Assigned(FDataProvider) then
FDataProvider := FNull;
end;
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>): TDataSeries<T>;
class operator TDataProvider<T>.Initialize(out Dest: TDataProvider<T>);
begin
Result := Add(Data, 0, Length(Data));
// Initialize new record instances with the null object.
Dest.FDataProvider := FNull;
end;
function TDataSeries<T>.Convert<S>(const Func: TConvertFunc<S>): TDataSeries<S>;
begin
Result := TConvertSeries<T, S>.Create(FDataSeries, Func);
end;
class function TDataSeries<T>.CreateDataSeries(Lookback: Int64; const Data: TArray<TDataPoint<T>> = nil): TDataSeries<T>;
begin
Result :=
TMycDataSeries<T>.CreateDataSeries(Lookback, TMycDataArray<TDataPoint<T>>.CreateFromArray(Data, 0, Length(Data)), Length(Data));
end;
class operator TDataSeries<T>.Finalize(var Dest: TDataSeries<T>);
begin
Dest.FDataSeries := nil;
end;
class operator TDataSeries<T>.Initialize(out Dest: TDataSeries<T>);
begin
Dest.FDataSeries := Null;
end;
class operator TDataSeries<T>.Implicit(const A: TDataSeries<T>): IDataSeries<T>;
begin
Result := A.FDataSeries;
end;
class operator TDataSeries<T>.Implicit(const A: IDataSeries<T>): TDataSeries<T>;
class operator TDataProvider<T>.Implicit(const A: IDataProvider): TDataProvider<T>;
begin
// Allow implicit conversion from the interface to the helper.
Result.Create(A);
end;
function TDataSeries<T>.GetCount: Int64;
class operator TDataProvider<T>.Implicit(const A: TDataProvider<T>): IDataProvider;
begin
Result := FDataSeries.GetCount;
// Allow implicit conversion from the helper to the interface.
Result := A.FDataProvider;
end;
function TDataSeries<T>.GetItems(Idx: Int64): TDataPoint<T>;
function TDataProvider<T>.Link(const Receiver: IMycProcessor<T>): TTag;
begin
Result := FDataSeries[Idx];
// Forward the call to the wrapped interface.
Result := FDataProvider.Link(Receiver);
end;
function TDataSeries<T>.GetData(Idx: Int64): T;
procedure TDataProvider<T>.Unlink(Tag: TTag);
begin
Result := FDataSeries[Idx].Data;
// Forward the call to the wrapped interface.
FDataProvider.Unlink(Tag);
end;
function TDataSeries<T>.GetLookback: Int64;
begin
Result := FDataSeries.Lookback;
end;
function TDataSeries<T>.GetTime(Idx: Int64): TDateTime;
begin
Result := FDataSeries[Idx].Time;
end;
class function TDataSeries<T>.GetNull: IDataSeries<T>;
begin
Result := TNullDataSeries<T>.Null;
end;
function TDataSeries<T>.GetTotalCount: Int64;
begin
Result := FDataSeries.GetTotalCount;
end;
function TDataSeries<T>.IndexOf(TimeStamp: TDateTime): Int64;
var
low, high, mid: Int64;
dataPointTime: TDateTime;
begin
Result := -1;
if Count = 0 then
Exit;
low := 0;
high := Count - 1;
while (low <= high) do
begin
mid := low + (high - low) div 2;
dataPointTime := FDataSeries[mid].Time;
if (dataPointTime = TimeStamp) then
begin
Result := mid;
break;
end
else if (dataPointTime < TimeStamp) then
begin
Result := mid;
high := mid - 1;
end
else
begin
low := mid + 1;
end;
end;
end;
function TDataSeries<T>.ToArray: TArray<TDataPoint<T>>;
begin
var n := FDataSeries.Count;
SetLength(Result, n);
dec(n);
for var i := 0 to n do
Result[i] := FDataSeries[n - i];
end;
function TDataSeries<T>.ToDataArray: TArray<T>;
begin
var n := FDataSeries.Count;
SetLength(Result, n);
dec(n);
for var i := 0 to n do
Result[i] := FDataSeries[n - i].Data;
end;
{ TMycGenericProcessor<T> }
constructor TMycGenericProcessor<T>.Create(const AProc: TProc);
begin
@@ -442,6 +178,8 @@ begin
Result := FProc(Value);
end;
{ TMycContainedProcessor<T> }
constructor TMycContainedProcessor<T>.Create(const Controller: IInterface; const AProc: TProc);
begin
inherited Create(Controller);
@@ -453,7 +191,7 @@ begin
Result := FProc(Value);
end;
{ TMycDataProvider<S, T> }
{ TMycDataProvider<T> }
constructor TMycDataProvider<T>.Create(const Controller: IInterface);
begin
@@ -504,7 +242,7 @@ begin
end;
end;
function TMycDataProvider<T>.Link(const Processor: IMycProcessor<T>): TTag;
function TMycDataProvider<T>.Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
begin
// Add the Processor to the notification list
FListeners.Lock;
@@ -515,7 +253,7 @@ begin
end;
end;
procedure TMycDataProvider<T>.Unlink(Tag: TTag);
procedure TMycDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
begin
FListeners.Lock;
try
@@ -525,28 +263,4 @@ begin
end;
end;
{ TMycConverter<S, T> }
constructor TMycConverter<S, T>.Create;
begin
inherited Create;
FSender := TMycDataProvider<T>.Create(Self);
end;
destructor TMycConverter<S, T>.Destroy;
begin
FSender.Free;
inherited Destroy;
end;
function TMycConverter<S, T>.Broadcast(const Value: T): TState;
begin
Result := FSender.Broadcast(Value);
end;
function TMycConverter<S, T>.GetSender: IMycDataProvider<T>;
begin
Result := FSender;
end;
end.
+16 -15
View File
@@ -6,7 +6,8 @@ uses
System.SysUtils,
System.Math,
Myc.Trade.Types,
Myc.Trade.DataArray;
Myc.Trade.DataArray,
Myc.Trade.DataConverter;
type
// Result for the Moving Average Convergence Divergence (MACD) indicator.
@@ -36,19 +37,19 @@ type
class function CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double; static;
public
// Simple Moving Average
class function CreateSMA(Period: Integer): TIndicatorFunc<Double, Double>; static;
class function CreateSMA(Period: Integer): TConstFunc<Double, Double>; static;
// Exponential Moving Average
class function CreateEMA(Period: Integer): TIndicatorFunc<Double, Double>; static;
class function CreateEMA(Period: Integer): TConstFunc<Double, Double>; static;
// Hull Moving Average
class function CreateHMA(Period: Integer): TIndicatorFunc<Double, Double>; static;
class function CreateHMA(Period: Integer): TConstFunc<Double, Double>; static;
// Relative Strength Index
class function CreateRSI(Period: Integer): TIndicatorFunc<Double, Double>; static;
class function CreateRSI(Period: Integer): TConstFunc<Double, Double>; static;
// Moving Average Convergence Divergence
class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TIndicatorFunc<Double, TMacdResult>; static;
class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConstFunc<Double, TMacdResult>; static;
// Stochastic Oscillator
class function CreateStochastic(KPeriod, DPeriod: Integer): TIndicatorFunc<TOhlcItem, TStochasticResult>; static;
class function CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>; static;
// Bollinger Bands
class function CreateBollingerBands(Period: Integer; Multiplier: Double): TIndicatorFunc<Double, TBollingerBandsResult>; static;
class function CreateBollingerBands(Period: Integer; Multiplier: Double): TConstFunc<Double, TBollingerBandsResult>; static;
end;
implementation
@@ -112,7 +113,7 @@ begin
Result := numerator / denominator;
end;
class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TIndicatorFunc<Double, TBollingerBandsResult>;
class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TConstFunc<Double, TBollingerBandsResult>;
begin
var sourceData := TMycDataArray<Double>.CreateEmpty;
Result :=
@@ -135,7 +136,7 @@ begin
end;
end;
class function TIndicators.CreateEMA(Period: Integer): TIndicatorFunc<Double, Double>;
class function TIndicators.CreateEMA(Period: Integer): TConstFunc<Double, Double>;
begin
var lastEma: Double := Double.NaN;
var sourceData := TMycDataArray<Double>.CreateEmpty;
@@ -166,7 +167,7 @@ begin
end;
end;
class function TIndicators.CreateHMA(Period: Integer): TIndicatorFunc<Double, Double>;
class function TIndicators.CreateHMA(Period: Integer): TConstFunc<Double, Double>;
begin
var periodHalf := Period div 2;
var periodSqrt := Round(Sqrt(Period));
@@ -208,7 +209,7 @@ begin
end;
end;
class function TIndicators.CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TIndicatorFunc<Double, TMacdResult>;
class function TIndicators.CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConstFunc<Double, TMacdResult>;
begin
var emaFast := CreateEMA(FastPeriod);
var emaSlow := CreateEMA(SlowPeriod);
@@ -240,7 +241,7 @@ begin
end;
end;
class function TIndicators.CreateRSI(Period: Integer): TIndicatorFunc<Double, Double>;
class function TIndicators.CreateRSI(Period: Integer): TConstFunc<Double, Double>;
begin
var avgGain: Double := Double.NaN;
var avgLoss: Double := Double.NaN;
@@ -299,7 +300,7 @@ begin
end;
end;
class function TIndicators.CreateSMA(Period: Integer): TIndicatorFunc<Double, Double>;
class function TIndicators.CreateSMA(Period: Integer): TConstFunc<Double, Double>;
begin
var sourceData := TMycDataArray<Double>.CreateEmpty;
Result :=
@@ -313,7 +314,7 @@ begin
end;
end;
class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TIndicatorFunc<TOhlcItem, TStochasticResult>;
class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>;
begin
var sourceData := TMycDataArray<TOhlcItem>.CreateEmpty;
var smaD := CreateSMA(DPeriod);
+2 -2
View File
@@ -5,8 +5,6 @@ interface
type
TTimeframe = (S, S5, S15, S30, M, M2, M3, M5, M10, M15, M30, H, H2, H3, H4, H8, H12, D, D2, D3, W, MN, MN3, MN6, Y);
TIndicatorFunc<S, T> = reference to function(const Value: S): T;
// A data record for an Ask/Bid price pair.
TAskBidItem = packed record
Ask: Double;
@@ -23,6 +21,8 @@ type
constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
end;
TConstFunc<S, T> = reference to function(const Value: S): T;
implementation
{ TAskBidItem }