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, System.StartUpCopy,
FMX.Forms, FMX.Forms,
MainForm in 'MainForm.pas' {Form1}, MainForm in 'MainForm.pas' {Form1},
Myc.Trade.Core.DataPoint in '..\Src\Myc.Trade.Core.DataPoint.pas',
Myc.Aura.Module in '..\Src\Myc.Aura.Module.pas', Myc.Aura.Module in '..\Src\Myc.Aura.Module.pas',
Myc.Aura.Parameter in '..\Src\Myc.Aura.Parameter.pas', Myc.Aura.Parameter in '..\Src\Myc.Aura.Parameter.pas',
TestModule in 'TestModule.pas', TestModule in 'TestModule.pas',
@@ -14,7 +13,9 @@ uses
Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas', Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas',
Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas', Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas',
Myc.Trade.Indicators in '..\Src\Myc.Trade.Indicators.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} {$R *.res}
+3 -2
View File
@@ -4,7 +4,7 @@
<ProjectVersion>20.3</ProjectVersion> <ProjectVersion>20.3</ProjectVersion>
<FrameworkType>FMX</FrameworkType> <FrameworkType>FMX</FrameworkType>
<Base>True</Base> <Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config> <Config Condition="'$(Config)'==''">Release</Config>
<Platform Condition="'$(Platform)'==''">Win64</Platform> <Platform Condition="'$(Platform)'==''">Win64</Platform>
<ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName> <ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName>
<TargetedPlatforms>3</TargetedPlatforms> <TargetedPlatforms>3</TargetedPlatforms>
@@ -133,7 +133,6 @@
<DCCReference Include="MainForm.pas"> <DCCReference Include="MainForm.pas">
<Form>Form1</Form> <Form>Form1</Form>
</DCCReference> </DCCReference>
<DCCReference Include="..\Src\Myc.Trade.Core.DataPoint.pas"/>
<DCCReference Include="..\Src\Myc.Aura.Module.pas"/> <DCCReference Include="..\Src\Myc.Aura.Module.pas"/>
<DCCReference Include="..\Src\Myc.Aura.Parameter.pas"/> <DCCReference Include="..\Src\Myc.Aura.Parameter.pas"/>
<DCCReference Include="TestModule.pas"/> <DCCReference Include="TestModule.pas"/>
@@ -143,6 +142,8 @@
<DCCReference Include="..\Src\Myc.FMX.Chart.Series.pas"/> <DCCReference Include="..\Src\Myc.FMX.Chart.Series.pas"/>
<DCCReference Include="..\Src\Myc.Trade.Indicators.pas"/> <DCCReference Include="..\Src\Myc.Trade.Indicators.pas"/>
<DCCReference Include="..\Src\Myc.Trade.Types.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"> <BuildConfiguration Include="Base">
<Key>Base</Key> <Key>Base</Key>
</BuildConfiguration> </BuildConfiguration>
+22 -96
View File
@@ -10,36 +10,12 @@ uses
Myc.TaskManager, Myc.TaskManager,
Myc.Trade.Types, Myc.Trade.Types,
Myc.Trade.DataPoint, Myc.Trade.DataPoint,
Myc.Trade.DataArray; Myc.Trade.DataArray,
Myc.Trade.DataConverter,
Myc.Trade.Core.DataConverter;
type type
TMycGenericConverter<S, T> = class(TMycConverter<S, T>) TTickAggregation = class(TMycConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>)
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>>)
private private
FTimeframe: TTimeframe; FTimeframe: TTimeframe;
FCurrentBar: TDataPoint<TOhlcItem>; FCurrentBar: TDataPoint<TOhlcItem>;
@@ -48,64 +24,26 @@ type
function GetTimeframe: TTimeframe; function GetTimeframe: TTimeframe;
public public
constructor Create(const ATimeframe: TTimeframe); 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 CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
property Timeframe: TTimeframe read GetTimeframe; property Timeframe: TTimeframe read GetTimeframe;
end; end;
TTicker<T> = class(TMycConverter<TArray<T>, T>)
public
function ProcessData(const Values: TArray<T>): TState; override;
end;
implementation implementation
uses uses
System.DateUtils, System.DateUtils,
System.Math; System.Math;
{ TMycGenericConverter<S, T> } { TTickAggregation }
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc); constructor TTickAggregation.Create(const ATimeframe: TTimeframe);
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);
begin begin
inherited Create; inherited Create;
FTimeframe := ATimeframe; FTimeframe := ATimeframe;
end; end;
function TTicksToBars.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime; function TTickAggregation.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
var var
baseTime: TDateTime; baseTime: TDateTime;
begin begin
@@ -153,25 +91,21 @@ begin
end; end;
end; end;
function TTicksToBars.GetCurrentBar: TDataPoint<TOhlcItem>; function TTickAggregation.GetCurrentBar: TDataPoint<TOhlcItem>;
begin begin
Result := FCurrentBar; Result := FCurrentBar;
end; end;
function TTicksToBars.GetTimeframe: TTimeframe; function TTickAggregation.GetTimeframe: TTimeframe;
begin begin
Result := FTimeframe; Result := FTimeframe;
end; end;
function TTicksToBars.ProcessData(const Value: TDataPoint<TAskBidItem>): TState; function TTickAggregation.ProcessData(const Value: TDataPoint<Double>): TState;
var var
midPrice: Single;
barStartTime: TDateTime; barStartTime: TDateTime;
lastBarTime: TDateTime; lastBarTime: TDateTime;
currentBar: TOhlcItem;
begin begin
midPrice := (Value.Data.Ask + Value.Data.Bid) / 2;
// Update bar for the strategy's timeframe // Update bar for the strategy's timeframe
barStartTime := GetBarStartTime(Value.Time, FTimeframe); barStartTime := GetBarStartTime(Value.Time, FTimeframe);
lastBarTime := FCurrentBar.Time; lastBarTime := FCurrentBar.Time;
@@ -185,32 +119,24 @@ begin
end; end;
// Start a new bar, Volume is 1 because this is the first tick. // Start a new bar, Volume is 1 because this is the first tick.
currentBar := TOhlcItem.Create(midPrice, midPrice, midPrice, midPrice, 1); FCurrentBar.Data.Open := Value.Data;
FCurrentBar.Data := currentBar; FCurrentBar.Data.High := Value.Data;
FCurrentBar.Data.Low := Value.Data;
FCurrentBar.Data.Close := Value.Data;
FCurrentBar.Data.Volume := 1;
FCurrentBar.Time := barStartTime; FCurrentBar.Time := barStartTime;
end end
else else
begin begin
// Update the currently aggregating bar // Update the currently aggregating bar
currentBar := FCurrentBar.Data; if Value.Data > FCurrentBar.Data.High then
currentBar.High := Max(currentBar.High, midPrice); FCurrentBar.Data.High := Value.Data;
currentBar.Low := Min(currentBar.Low, midPrice); if Value.Data < FCurrentBar.Data.Low then
currentBar.Close := midPrice; FCurrentBar.Data.Low := Value.Data;
FCurrentBar.Data.Close := Value.Data;
// Volume is the number of ticks needed to build the complete bar. // Volume is the number of ticks needed to build the complete bar.
currentBar.Volume := currentBar.Volume + 1; FCurrentBar.Data.Volume := FCurrentBar.Data.Volume + 1;
FCurrentBar.Data := currentBar;
end; end;
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. end.
+10
View File
@@ -146,6 +146,16 @@ object Form1: TForm1
TextSettings.Trimming = None TextSettings.Trimming = None
OnClick = StopButtonClick OnClick = StopButtonClick
end 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
end end
object ObjectsPanel: TPanel object ObjectsPanel: TPanel
+128 -15
View File
@@ -30,6 +30,7 @@ uses
Myc.Trade.Types, Myc.Trade.Types,
Myc.Trade.DataStream, Myc.Trade.DataStream,
Myc.Trade.DataPoint, Myc.Trade.DataPoint,
Myc.Trade.DataConverter,
Myc.Signals, Myc.Signals,
Myc.Mutable, Myc.Mutable,
Myc.Signals.FMX, Myc.Signals.FMX,
@@ -74,11 +75,13 @@ type
TestPopup: TPopup; TestPopup: TPopup;
FlowLayout: TFlowLayout; FlowLayout: TFlowLayout;
StrategyButton: TSpeedButton; StrategyButton: TSpeedButton;
Strat2Button: TSpeedButton;
procedure FormCreate(Sender: TObject); procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject); procedure FormDestroy(Sender: TObject);
procedure StopButtonClick(Sender: TObject); procedure StopButtonClick(Sender: TObject);
procedure TreeViewDblClick(Sender: TObject); procedure TreeViewDblClick(Sender: TObject);
procedure AddWorkspaceActionExecute(Sender: TObject); procedure AddWorkspaceActionExecute(Sender: TObject);
procedure Strat2ButtonClick(Sender: TObject);
procedure TestActionExecute(Sender: TObject); procedure TestActionExecute(Sender: TObject);
procedure StrategyButtonClick(Sender: TObject); procedure StrategyButtonClick(Sender: TObject);
private private
@@ -279,12 +282,12 @@ end;
function TForm1.ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor<TArray<TDataPoint<TAskBidItem>>>): TState; function TForm1.ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor<TArray<TDataPoint<TAskBidItem>>>): TState;
var var
dataProvider: IMycConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>; dataProvider: TConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>;
begin begin
var terminated := TFlag.CreateObserver(FTerminate.Signal).State; var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
dataProvider := 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>> function(const Values: TArray<TDataPoint<TAuraAskBidFileItem>>): TArray<TDataPoint<TAskBidItem>>
begin begin
SetLength(Result, Length(Values)); 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); var lastPrice :=
ticker.Sender.Link(OhlcPoint); 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> := var OhlcPoint := lastPrice.Chain<TDataPoint<TOhlcItem>>(TTickAggregation.Create(timeframe));
TMycGenericConverter<TDataPoint<TOhlcItem>, TDateTime>
.Create(function(const Ohlc: TDataPoint<TOhlcItem>): TDateTime begin Result := Ohlc.Time; end);
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); 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); chart.SetXAxisSeries(timeframe, Timestamps.Sender);
var Panel := chart.AddPanel; 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); OhlcPoint.Sender.Link(Ohlc);
Panel.AddOhlcSeries(Ohlc.Sender); 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); Ohlc.Sender.Link(Closes);
var Hull: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateHMA(150)); var Hull: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateHMA(150));
@@ -477,6 +589,7 @@ begin
var done := ExecuteStrategy(Symbol, ticker); var done := ExecuteStrategy(Symbol, ticker);
FProcessDone := TState.All([FProcessDone, done]); FProcessDone := TState.All([FProcessDone, done]);
}
end; end;
end. end.
-6
View File
@@ -236,9 +236,6 @@ type
// A bot executes a strategy // A bot executes a strategy
IAuraBot = interface(IAuraLiveObject) IAuraBot = interface(IAuraLiveObject)
function GetPnL: TMutable<TDataSeries<Double>>;
// Zero-based PnL series
property PnL: TMutable<TDataSeries<Double>> read GetPnL;
end; end;
// A strategy creates a bot that executes that strategy with given parameters and within a given time slot. // 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 IAuraTradeResult = interface
function GetPerformance: TAuraTradePerformance; function GetPerformance: TAuraTradePerformance;
function GetTrades: TDataSeries<Double>;
// Perform Monte Carlo Simulation and generate performance distribution // Perform Monte Carlo Simulation and generate performance distribution
function CalcMonteCarloSimulation(Steps: Integer): TFuture<TAuraMonteCarloResult>; function CalcMonteCarloSimulation(Steps: Integer): TFuture<TAuraMonteCarloResult>;
// PnLs of each trade = Equity curve
property Trades: TDataSeries<Double> read GetTrades;
// The performance of this equity curve // The performance of this equity curve
property Performance: TAuraTradePerformance read GetPerformance; property Performance: TAuraTradePerformance read GetPerformance;
end; end;
+49 -48
View File
@@ -4,7 +4,6 @@ interface
uses uses
System.SysUtils, System.SysUtils,
System.SyncObjs,
System.UITypes, System.UITypes,
FMX.Graphics, FMX.Graphics,
Myc.Signals, Myc.Signals,
@@ -12,18 +11,11 @@ uses
Myc.Trade.Types, Myc.Trade.Types,
Myc.Trade.DataArray, Myc.Trade.DataArray,
Myc.Trade.DataPoint, Myc.Trade.DataPoint,
Myc.Trade.DataConverter,
Myc.Trade.Core.DataConverter,
Myc.Fmx.Chart; Myc.Fmx.Chart;
type 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>) TChartSeriesReceiver<T> = class(TMycProcessor<T>)
strict private strict private
FCurrData: TMycDataArray<T>; FCurrData: TMycDataArray<T>;
@@ -40,19 +32,18 @@ type
TChartSeriesProcessor<T> = class(TMycChart.TSeries) TChartSeriesProcessor<T> = class(TMycChart.TSeries)
strict private strict private
FDataSeries: TMycDataArray<T>; FDataSeries: TMycDataArray<T>;
FLock: TSpinLock;
private private
FData: TMycDataArray<T>; FData: TMycDataArray<T>;
FDataProvider: IMycDataProvider<T>; FDataProvider: TDataProvider<T>;
FReceiver: TChartSeriesReceiver<T>; FReceiver: TChartSeriesReceiver<T>;
FReceiverTag: TTag; FReceiverTag: TDataProvider<T>.TTag;
protected protected
function GetCount: Int64; override; function GetCount: Int64; override;
function GetTotalCount: Int64; override; function GetTotalCount: Int64; override;
procedure Update; procedure Update;
public public
constructor Create(const ADataProvider: IMycDataProvider<T>; const ALookback: TMutable<Int64>); constructor Create(const ADataProvider: TDataProvider<T>; const ALookback: TMutable<Int64>);
destructor Destroy; override; destructor Destroy; override;
property Data: TMycDataArray<T> read FData; property Data: TMycDataArray<T> read FData;
end; end;
@@ -65,10 +56,14 @@ type
function GetSeries: TMycChart.TSeries; override; function GetSeries: TMycChart.TSeries; override;
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override; abstract; function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override; abstract;
procedure Update; override; procedure Update; override;
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc<Int64, Single>; const YForm: TFunc<Double, Single>); procedure Paint(
override; abstract; const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
); override; abstract;
public public
constructor Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider<T>); constructor Create(AParent: TMycChart.TPanel; const ADataProvider: TDataProvider<T>);
destructor Destroy; override; destructor Destroy; override;
end; end;
@@ -79,11 +74,16 @@ type
FDownColor: TMutable<TAlphaColor>; FDownColor: TMutable<TAlphaColor>;
protected protected
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override; 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 public
constructor Create( constructor Create(
AParent: TMycChart.TPanel; AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider<TOhlcItem>; const ADataProvider: TDataProvider<TOhlcItem>;
const AUpColor, ADownColor: TMutable<TAlphaColor> const AUpColor, ADownColor: TMutable<TAlphaColor>
); );
end; end;
@@ -95,11 +95,16 @@ type
FLineWidth: Single; FLineWidth: Single;
protected protected
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override; 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 public
constructor Create( constructor Create(
AParent: TMycChart.TPanel; AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider<Double>; const ADataProvider: TDataProvider<Double>;
const ALineColor: TAlphaColor; const ALineColor: TAlphaColor;
ALineWidth: Single ALineWidth: Single
); );
@@ -114,7 +119,7 @@ type
procedure Update; override; procedure Update; override;
function GetCaption(Idx: Int64): String; override; abstract; function GetCaption(Idx: Int64): String; override; abstract;
public public
constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>); constructor Create(AOwner: TMycChart; const ADataProvider: TDataProvider<T>);
destructor Destroy; override; destructor Destroy; override;
end; end;
@@ -126,7 +131,7 @@ type
// Provide a formatted timestamp string for a given data index. // Provide a formatted timestamp string for a given data index.
function GetCaption(Idx: Int64): String; override; final; function GetCaption(Idx: Int64): String; override; final;
public public
constructor Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: IMycDataProvider<TDateTime>); constructor Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: TDataProvider<TDateTime>);
end; end;
implementation implementation
@@ -136,18 +141,6 @@ uses
System.Math, System.Math,
FMX.Types; 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> } { TChartSeriesReceiver<T> }
constructor TChartSeriesReceiver<T>.Create(const ALookback: TMutable<Int64>); constructor TChartSeriesReceiver<T>.Create(const ALookback: TMutable<Int64>);
@@ -167,13 +160,13 @@ end;
{ TChartSeriesProcessor<T> } { 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 begin
inherited Create; inherited Create;
FDataProvider := ADataProvider; FDataProvider := ADataProvider;
FLock := TSpinLock.Create(false);
FDataSeries := TMycDataArray<T>.CreateEmpty; FDataSeries := TMycDataArray<T>.CreateEmpty;
FData := FDataSeries; FData := FDataSeries;
FReceiver := TChartSeriesReceiver<T>.Create(ALookback); FReceiver := TChartSeriesReceiver<T>.Create(ALookback);
FReceiverTag := FDataProvider.Link(FReceiver); FReceiverTag := FDataProvider.Link(FReceiver);
end; end;
@@ -203,7 +196,7 @@ end;
constructor TChartLineLayer.Create( constructor TChartLineLayer.Create(
AParent: TMycChart.TPanel; AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider<Double>; const ADataProvider: TDataProvider<Double>;
const ALineColor: TAlphaColor; const ALineColor: TAlphaColor;
ALineWidth: Single ALineWidth: Single
); );
@@ -231,8 +224,12 @@ begin
Result := (MinValue < MaxValue); Result := (MinValue < MaxValue);
end; end;
procedure TChartLineLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc<Int64, Single>; const YForm: TFunc<Double, procedure TChartLineLayer.Paint(
Single>); const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
);
var var
points: TPathData; points: TPathData;
n: Int64; n: Int64;
@@ -249,13 +246,13 @@ begin
if n < Last then if n < Last then
begin begin
points.MoveTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n]))); points.MoveTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
inc( n ); inc(n);
while n <= Last do while n <= Last do
begin begin
if IsNaN(FSeries.Data[n]) then if IsNaN(FSeries.Data[n]) then
break; break;
points.LineTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n]))); points.LineTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
inc( n ); inc(n);
end; end;
end; end;
end; end;
@@ -273,7 +270,7 @@ end;
constructor TChartOhlcLayer.Create( constructor TChartOhlcLayer.Create(
AParent: TMycChart.TPanel; AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider<TOhlcItem>; const ADataProvider: TDataProvider<TOhlcItem>;
const AUpColor, ADownColor: TMutable<TAlphaColor> const AUpColor, ADownColor: TMutable<TAlphaColor>
); );
begin begin
@@ -301,8 +298,12 @@ begin
Result := (MinValue <> MaxDouble); Result := (MinValue <> MaxDouble);
end; end;
procedure TChartOhlcLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc<Int64, Single>; const YForm: TFunc<Double, procedure TChartOhlcLayer.Paint(
Single>); const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
);
var var
i: Int64; i: Int64;
x, candleWidth: Single; x, candleWidth: Single;
@@ -340,7 +341,7 @@ end;
{ TChartCustomLayer } { TChartCustomLayer }
constructor TChartCustomLayer<T>.Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider<T>); constructor TChartCustomLayer<T>.Create(AParent: TMycChart.TPanel; const ADataProvider: TDataProvider<T>);
begin begin
inherited Create(AParent); inherited Create(AParent);
FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AParent.Owner.Lookback.AsMutable); FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AParent.Owner.Lookback.AsMutable);
@@ -364,7 +365,7 @@ end;
{ TChartXAxisLayer } { TChartXAxisLayer }
constructor TChartXAxisLayer<T>.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider<T>); constructor TChartXAxisLayer<T>.Create(AOwner: TMycChart; const ADataProvider: TDataProvider<T>);
begin begin
inherited Create(AOwner); inherited Create(AOwner);
FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AOwner.Lookback.AsMutable); FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AOwner.Lookback.AsMutable);
@@ -386,7 +387,7 @@ begin
FSeries.Update; FSeries.Update;
end; end;
constructor TChartXAxisTimestampLayer.Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: IMycDataProvider<TDateTime>); constructor TChartXAxisTimestampLayer.Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: TDataProvider<TDateTime>);
begin begin
inherited Create(AOwner, ADataProvider); inherited Create(AOwner, ADataProvider);
FTimeframe := ATimeframe; FTimeframe := ATimeframe;
+15 -15
View File
@@ -54,8 +54,12 @@ type
protected protected
function GetOwner: TMycChart; override; final; function GetOwner: TMycChart; override; final;
function GetValueRange(First, Last: Int64; out Min, Max: Double): Boolean; virtual; abstract; 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; procedure Paint(
abstract; const Canvas: TCanvas;
First, Last: Int64;
const XForm: TFunc<Int64, Single>;
const YForm: TFunc<Double, Single>
); virtual; abstract;
public public
constructor Create(AParent: TPanel); constructor Create(AParent: TPanel);
end; end;
@@ -105,13 +109,13 @@ type
destructor Destroy; override; destructor Destroy; override;
function AddOhlcSeries( function AddOhlcSeries(
const DataProvider: IMycDataProvider<TOhlcItem>; const DataProvider: TDataProvider<TOhlcItem>;
const AUpColor: TAlphaColor = TAlphaColors.Green; const AUpColor: TAlphaColor = TAlphaColors.Green;
const ADownColor: TAlphaColor = TAlphaColors.Red const ADownColor: TAlphaColor = TAlphaColors.Red
): TMycChart.TDataLayer; ): TMycChart.TDataLayer;
function AddDoubleSeries( function AddDoubleSeries(
const DataProvider: IMycDataProvider<Double>; const DataProvider: TDataProvider<Double>;
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue; const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
const ALineWidth: Single = 1.5 const ALineWidth: Single = 1.5
): TMycChart.TDataLayer; ): TMycChart.TDataLayer;
@@ -162,7 +166,7 @@ type
function AddPanel: TPanel; function AddPanel: TPanel;
// Sets the master series that defines the time scale (X-axis). // 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 Lookback: TWriteable<Int64> read FLookback write FLookback;
property NeedRepaint: TFlag read FNeedRepaint; property NeedRepaint: TFlag read FNeedRepaint;
@@ -220,7 +224,7 @@ end;
function TMycChart.AddPanel: TPanel; function TMycChart.AddPanel: TPanel;
begin 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); FPanelList.Add(Result);
Repaint; Repaint;
end; end;
@@ -635,7 +639,7 @@ begin
Repaint; Repaint;
end; end;
function TMycChart.SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: IMycDataProvider<TDateTime>): TMycChart.TXAxisLayer; function TMycChart.SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: TDataProvider<TDateTime>): TMycChart.TXAxisLayer;
begin begin
FXAxisSeries.Free; FXAxisSeries.Free;
@@ -665,7 +669,7 @@ begin
end; end;
function TMycChart.TPanel.AddDoubleSeries( function TMycChart.TPanel.AddDoubleSeries(
const DataProvider: IMycDataProvider<Double>; const DataProvider: TDataProvider<Double>;
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue; const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
const ALineWidth: Single = 1.5 const ALineWidth: Single = 1.5
): TMycChart.TDataLayer; ): TMycChart.TDataLayer;
@@ -675,7 +679,7 @@ begin
end; end;
function TMycChart.TPanel.AddOhlcSeries( function TMycChart.TPanel.AddOhlcSeries(
const DataProvider: IMycDataProvider<TOhlcItem>; const DataProvider: TDataProvider<TOhlcItem>;
const AUpColor: TAlphaColor = TAlphaColors.Green; const AUpColor: TAlphaColor = TAlphaColors.Green;
const ADownColor: TAlphaColor = TAlphaColors.Red const ADownColor: TAlphaColor = TAlphaColors.Red
): TMycChart.TDataLayer; ): TMycChart.TDataLayer;
@@ -742,13 +746,9 @@ begin
// 2. Create transforms for this panel // 2. Create transforms for this panel
const padding = 2; const padding = 2;
var top: Double := Viewport.Top + padding; 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; var range: Double := localMax - localMin;
yTransform := yTransform := function(value: Double): Single begin Result := top + (1 - (value - localMin) / range) * height; end;
function(value: Double): Single
begin
Result := top + (1 - (value - localMin) / range) * height;
end;
// 3. Paint all layers in this panel // 3. Paint all layers in this panel
for var series in FSeriesList do 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.
+71 -357
View File
@@ -3,10 +3,8 @@ unit Myc.Trade.DataPoint;
interface interface
uses uses
System.TimeSpan,
Myc.Signals, Myc.Signals,
Myc.Core.Notifier, Myc.Core.Notifier;
Myc.Trade.Types;
type type
// Represents a time-stamped data point in a series. // Represents a time-stamped data point in a series.
@@ -25,14 +23,39 @@ type
function ProcessData(const Value: T): TState; virtual; abstract; function ProcessData(const Value: T): TState; virtual; abstract;
end; end;
TTag = Pointer; TDataProvider<T> = record
type
TTag = Pointer;
IDataProvider = interface
function Link(const Receiver: IMycProcessor<T>): TTag;
procedure Unlink(Tag: TTag);
end;
IMycDataProvider<T> = interface {$REGION 'private'}
function Link(const Receiver: IMycProcessor<T>): TTag; strict private
procedure Unlink(Tag: TTag); 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; end;
TMycDataProvider<T> = class abstract(TContainedObject, IMycDataProvider<T>) TMycDataProvider<T> = class abstract(TContainedObject, TDataProvider<T>.IDataProvider)
private private
FListeners: TMycNotifyList<IMycProcessor<T>>; FListeners: TMycNotifyList<IMycProcessor<T>>;
public public
@@ -42,9 +65,9 @@ type
function Broadcast(const Value: T): TState; function Broadcast(const Value: T): TState;
procedure Notify(const Func: TMycNotifyList<IMycProcessor<T>>.TNotifyProc); procedure Notify(const Func: TMycNotifyList<IMycProcessor<T>>.TNotifyProc);
// Link a Processor // Link a Processor
function Link(const Processor: IMycProcessor<T>): TTag; function Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
// Unlink a linked strategy // Unlink a linked strategy
procedure Unlink(Tag: TTag); procedure Unlink(Tag: TDataProvider<T>.TTag);
end; end;
TMycGenericProcessor<T> = class(TMycProcessor<T>) TMycGenericProcessor<T> = class(TMycProcessor<T>)
@@ -68,216 +91,24 @@ type
constructor Create(const Controller: IInterface; const AProc: TProc); constructor Create(const Controller: IInterface; const AProc: TProc);
end; end;
IMycConverter<S, T> = interface(IMycProcessor<S>) TNullDataProvider<T> = class(TInterfacedObject, TDataProvider<T>.IDataProvider)
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;
public public
constructor Create; function Link(const Receiver: IMycProcessor<T>): TDataProvider<T>.TTag;
destructor Destroy; override; procedure Unlink(Tag: TDataProvider<T>.TTag);
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>;
end; end;
implementation implementation
uses { TNullDataProvider }
System.SysUtils,
System.Math,
System.Generics.Collections,
Myc.Trade.DataArray,
Myc.Trade.Core.DataPoint;
// Optimized helper function using direct TTimeSpan features and integer arithmetic. function TNullDataProvider<T>.Link(const Receiver: IMycProcessor<T>): TDataProvider<T>.TTag;
function CeilToTimeSpan(const ATime: TDateTime; const ATimeSpan: TTimeSpan): TDateTime;
var
timeSinceMidnight: TTimeSpan;
timeSpanTicks: Int64;
numIntervals, ceiledTicks: Int64;
begin begin
timeSpanTicks := ATimeSpan.Ticks; Result := nil;
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);
end; end;
function TDataSeriesDoubleHelper.ToOhlc(TimeFrame: TTimeSpan): TDataSeries<TOhlcItem>; procedure TNullDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
var
ohlcPoints: TList<TDataPoint<TOhlcItem>>;
currentBar: TOhlcItem;
windowEndTime: TDateTime;
sourceIdx: Int64;
firstPointInBar: Boolean;
begin begin
if Self.Count = 0 then // Do nothing in the null implementation.
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);
end; end;
{ TDataPoint<T> } { TDataPoint<T> }
@@ -288,148 +119,53 @@ begin
Data := AData; Data := AData;
end; end;
constructor TDataSeries<T>.Create(ADataSeries: IDataSeries<T>); { TDataProvider<T> }
class constructor TDataProvider<T>.CreateClass;
begin begin
if Assigned(ADataSeries) then // Create the singleton null object instance.
FDataSeries := ADataSeries FNull := TNullDataProvider<T>.Create;
else
FDataSeries := Null;
end; end;
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): TDataSeries<T>; constructor TDataProvider<T>.Create(const ADataProvider: IDataProvider);
begin begin
{$ifdef DEBUG} FDataProvider := ADataProvider;
for var i := First + 1 to First + Count - 1 do // Ensure that the internal interface is never nil.
Assert(Data[i].Time >= Data[i - 1].Time, 'Input array for Add is not chronologically sorted'); if not Assigned(FDataProvider) then
if FDataSeries.Count > 0 then FDataProvider := FNull;
Assert(Data[First].Time >= FDataSeries[0].Time, 'First new item is older than last existing item');
{$endif}
Result := FDataSeries.Add(Data, First, Count);
end; end;
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>): TDataSeries<T>; class operator TDataProvider<T>.Initialize(out Dest: TDataProvider<T>);
begin begin
Result := Add(Data, 0, Length(Data)); // Initialize new record instances with the null object.
Dest.FDataProvider := FNull;
end; end;
function TDataSeries<T>.Convert<S>(const Func: TConvertFunc<S>): TDataSeries<S>; class operator TDataProvider<T>.Implicit(const A: IDataProvider): TDataProvider<T>;
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>;
begin begin
// Allow implicit conversion from the interface to the helper.
Result.Create(A); Result.Create(A);
end; end;
function TDataSeries<T>.GetCount: Int64; class operator TDataProvider<T>.Implicit(const A: TDataProvider<T>): IDataProvider;
begin begin
Result := FDataSeries.GetCount; // Allow implicit conversion from the helper to the interface.
Result := A.FDataProvider;
end; end;
function TDataSeries<T>.GetItems(Idx: Int64): TDataPoint<T>; function TDataProvider<T>.Link(const Receiver: IMycProcessor<T>): TTag;
begin begin
Result := FDataSeries[Idx]; // Forward the call to the wrapped interface.
Result := FDataProvider.Link(Receiver);
end; end;
function TDataSeries<T>.GetData(Idx: Int64): T; procedure TDataProvider<T>.Unlink(Tag: TTag);
begin begin
Result := FDataSeries[Idx].Data; // Forward the call to the wrapped interface.
FDataProvider.Unlink(Tag);
end; end;
function TDataSeries<T>.GetLookback: Int64; { TMycGenericProcessor<T> }
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;
constructor TMycGenericProcessor<T>.Create(const AProc: TProc); constructor TMycGenericProcessor<T>.Create(const AProc: TProc);
begin begin
@@ -442,6 +178,8 @@ begin
Result := FProc(Value); Result := FProc(Value);
end; end;
{ TMycContainedProcessor<T> }
constructor TMycContainedProcessor<T>.Create(const Controller: IInterface; const AProc: TProc); constructor TMycContainedProcessor<T>.Create(const Controller: IInterface; const AProc: TProc);
begin begin
inherited Create(Controller); inherited Create(Controller);
@@ -453,7 +191,7 @@ begin
Result := FProc(Value); Result := FProc(Value);
end; end;
{ TMycDataProvider<S, T> } { TMycDataProvider<T> }
constructor TMycDataProvider<T>.Create(const Controller: IInterface); constructor TMycDataProvider<T>.Create(const Controller: IInterface);
begin begin
@@ -504,7 +242,7 @@ begin
end; end;
end; end;
function TMycDataProvider<T>.Link(const Processor: IMycProcessor<T>): TTag; function TMycDataProvider<T>.Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
begin begin
// Add the Processor to the notification list // Add the Processor to the notification list
FListeners.Lock; FListeners.Lock;
@@ -515,7 +253,7 @@ begin
end; end;
end; end;
procedure TMycDataProvider<T>.Unlink(Tag: TTag); procedure TMycDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
begin begin
FListeners.Lock; FListeners.Lock;
try try
@@ -525,28 +263,4 @@ begin
end; end;
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. end.
+16 -15
View File
@@ -6,7 +6,8 @@ uses
System.SysUtils, System.SysUtils,
System.Math, System.Math,
Myc.Trade.Types, Myc.Trade.Types,
Myc.Trade.DataArray; Myc.Trade.DataArray,
Myc.Trade.DataConverter;
type type
// Result for the Moving Average Convergence Divergence (MACD) indicator. // 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; class function CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double; static;
public public
// Simple Moving Average // Simple Moving Average
class function CreateSMA(Period: Integer): TIndicatorFunc<Double, Double>; static; class function CreateSMA(Period: Integer): TConstFunc<Double, Double>; static;
// Exponential Moving Average // Exponential Moving Average
class function CreateEMA(Period: Integer): TIndicatorFunc<Double, Double>; static; class function CreateEMA(Period: Integer): TConstFunc<Double, Double>; static;
// Hull Moving Average // Hull Moving Average
class function CreateHMA(Period: Integer): TIndicatorFunc<Double, Double>; static; class function CreateHMA(Period: Integer): TConstFunc<Double, Double>; static;
// Relative Strength Index // 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 // 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 // Stochastic Oscillator
class function CreateStochastic(KPeriod, DPeriod: Integer): TIndicatorFunc<TOhlcItem, TStochasticResult>; static; class function CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>; static;
// Bollinger Bands // 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; end;
implementation implementation
@@ -112,7 +113,7 @@ begin
Result := numerator / denominator; Result := numerator / denominator;
end; end;
class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TIndicatorFunc<Double, TBollingerBandsResult>; class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TConstFunc<Double, TBollingerBandsResult>;
begin begin
var sourceData := TMycDataArray<Double>.CreateEmpty; var sourceData := TMycDataArray<Double>.CreateEmpty;
Result := Result :=
@@ -135,7 +136,7 @@ begin
end; end;
end; end;
class function TIndicators.CreateEMA(Period: Integer): TIndicatorFunc<Double, Double>; class function TIndicators.CreateEMA(Period: Integer): TConstFunc<Double, Double>;
begin begin
var lastEma: Double := Double.NaN; var lastEma: Double := Double.NaN;
var sourceData := TMycDataArray<Double>.CreateEmpty; var sourceData := TMycDataArray<Double>.CreateEmpty;
@@ -166,7 +167,7 @@ begin
end; end;
end; end;
class function TIndicators.CreateHMA(Period: Integer): TIndicatorFunc<Double, Double>; class function TIndicators.CreateHMA(Period: Integer): TConstFunc<Double, Double>;
begin begin
var periodHalf := Period div 2; var periodHalf := Period div 2;
var periodSqrt := Round(Sqrt(Period)); var periodSqrt := Round(Sqrt(Period));
@@ -208,7 +209,7 @@ begin
end; end;
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 begin
var emaFast := CreateEMA(FastPeriod); var emaFast := CreateEMA(FastPeriod);
var emaSlow := CreateEMA(SlowPeriod); var emaSlow := CreateEMA(SlowPeriod);
@@ -240,7 +241,7 @@ begin
end; end;
end; end;
class function TIndicators.CreateRSI(Period: Integer): TIndicatorFunc<Double, Double>; class function TIndicators.CreateRSI(Period: Integer): TConstFunc<Double, Double>;
begin begin
var avgGain: Double := Double.NaN; var avgGain: Double := Double.NaN;
var avgLoss: Double := Double.NaN; var avgLoss: Double := Double.NaN;
@@ -299,7 +300,7 @@ begin
end; end;
end; end;
class function TIndicators.CreateSMA(Period: Integer): TIndicatorFunc<Double, Double>; class function TIndicators.CreateSMA(Period: Integer): TConstFunc<Double, Double>;
begin begin
var sourceData := TMycDataArray<Double>.CreateEmpty; var sourceData := TMycDataArray<Double>.CreateEmpty;
Result := Result :=
@@ -313,7 +314,7 @@ begin
end; end;
end; end;
class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TIndicatorFunc<TOhlcItem, TStochasticResult>; class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>;
begin begin
var sourceData := TMycDataArray<TOhlcItem>.CreateEmpty; var sourceData := TMycDataArray<TOhlcItem>.CreateEmpty;
var smaD := CreateSMA(DPeriod); var smaD := CreateSMA(DPeriod);
+2 -2
View File
@@ -5,8 +5,6 @@ interface
type 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); 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. // A data record for an Ask/Bid price pair.
TAskBidItem = packed record TAskBidItem = packed record
Ask: Double; Ask: Double;
@@ -23,6 +21,8 @@ type
constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Double); constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
end; end;
TConstFunc<S, T> = reference to function(const Value: S): T;
implementation implementation
{ TAskBidItem } { TAskBidItem }
+26 -26
View File
@@ -5,32 +5,32 @@ program MycTests;
{$ENDIF} {$ENDIF}
{$STRONGLINKTYPES ON} {$STRONGLINKTYPES ON}
uses uses
FastMM5, FastMM5,
DUnitX.MemoryLeakMonitor.FastMM5, DUnitX.MemoryLeakMonitor.FastMM5,
System.SysUtils, System.SysUtils,
{$IFDEF TESTINSIGHT} {$IFDEF TESTINSIGHT}
TestInsight.DUnitX, TestInsight.DUnitX,
{$ELSE} {$ELSE}
DUnitX.Loggers.Console, DUnitX.Loggers.Console,
{$ENDIF } {$ENDIF }
DUnitX.TestFramework, DUnitX.TestFramework,
TestNotifier in 'TestNotifier.pas', TestNotifier in 'TestNotifier.pas',
TestNotifier_Threading in 'TestNotifier_Threading.pas' {/TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',}, TestNotifier_Threading in 'TestNotifier_Threading.pas' {/TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',},
TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas', TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',
TestTasks in 'TestTasks.pas', TestTasks in 'TestTasks.pas',
Myc.Futures in '..\Src\Myc.Futures.pas', Myc.Futures in '..\Src\Myc.Futures.pas',
TestCoreFutures in 'TestCoreFutures.pas', TestCoreFutures in 'TestCoreFutures.pas',
Myc.TaskManager in '..\Src\Myc.TaskManager.pas', Myc.TaskManager in '..\Src\Myc.TaskManager.pas',
TestFutures in 'TestFutures.pas', TestFutures in 'TestFutures.pas',
Myc.Test.Core.Atomic in '..\Src\Myc.Test.Core.Atomic.pas', Myc.Test.Core.Atomic in '..\Src\Myc.Test.Core.Atomic.pas',
Myc.Test.Signals.Latch in '..\Src\Myc.Test.Signals.Latch.pas', Myc.Test.Signals.Latch in '..\Src\Myc.Test.Signals.Latch.pas',
Myc.Test.Signals.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas', Myc.Test.Signals.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas',
Myc.Trade.DataPoint in '..\Src\Myc.Trade.DataPoint.pas', Myc.Trade.DataPoint in '..\Src\Myc.Trade.DataPoint.pas',
Myc.Trade.Node in '..\Src\Myc.Trade.Node.pas', Myc.Trade.Node in '..\Src\Myc.Trade.Node.pas',
Myc.Trade.DataStream in '..\Src\Myc.Trade.DataStream.pas', Myc.Trade.DataStream in '..\Src\Myc.Trade.DataStream.pas',
Myc.Test.Trade.DataPoint in '..\Src\Myc.Test.Trade.DataPoint.pas', Myc.Test.Trade.DataPoint in '..\Src\Myc.Test.Trade.DataPoint.pas',
Myc.Mutable in '..\Src\Myc.Mutable.pas', Myc.Mutable in '..\Src\Myc.Mutable.pas',
Test.Core.Mutable in 'Test.Core.Mutable.pas'; Test.Core.Mutable in 'Test.Core.Mutable.pas';
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit } { keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
{$IFNDEF TESTINSIGHT} {$IFNDEF TESTINSIGHT}