Polished DataProvider & Converter, RTTI field access
This commit is contained in:
@@ -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}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -249,13 +246,13 @@ begin
|
||||
if n < Last then
|
||||
begin
|
||||
points.MoveTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
|
||||
inc( n );
|
||||
inc(n);
|
||||
while n <= Last do
|
||||
begin
|
||||
if IsNaN(FSeries.Data[n]) then
|
||||
break;
|
||||
points.LineTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
|
||||
inc( n );
|
||||
inc(n);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -273,7 +270,7 @@ end;
|
||||
|
||||
constructor TChartOhlcLayer.Create(
|
||||
AParent: TMycChart.TPanel;
|
||||
const ADataProvider: IMycDataProvider<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;
|
||||
|
||||
+15
-15
@@ -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;
|
||||
@@ -220,7 +224,7 @@ end;
|
||||
|
||||
function TMycChart.AddPanel: TPanel;
|
||||
begin
|
||||
Result := TPanel.Create(Self, IfThen(FPanelList.Count=0, 1, 0.2));
|
||||
Result := TPanel.Create(Self, IfThen(FPanelList.Count = 0, 1, 0.2));
|
||||
FPanelList.Add(Result);
|
||||
Repaint;
|
||||
end;
|
||||
@@ -635,7 +639,7 @@ begin
|
||||
Repaint;
|
||||
end;
|
||||
|
||||
function TMycChart.SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: IMycDataProvider<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;
|
||||
@@ -742,13 +746,9 @@ begin
|
||||
// 2. Create transforms for this panel
|
||||
const padding = 2;
|
||||
var top: Double := Viewport.Top + padding;
|
||||
var height: Double := Viewport.Height-(2*padding);
|
||||
var height: Double := Viewport.Height - (2 * padding);
|
||||
var range: Double := localMax - localMin;
|
||||
yTransform :=
|
||||
function(value: Double): Single
|
||||
begin
|
||||
Result := top + (1 - (value - localMin) / range) * height;
|
||||
end;
|
||||
yTransform := function(value: Double): Single begin Result := top + (1 - (value - localMin) / range) * height; end;
|
||||
|
||||
// 3. Paint all layers in this panel
|
||||
for var series in FSeriesList do
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
@@ -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;
|
||||
|
||||
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
|
||||
function Link(const Receiver: IMycProcessor<T>): TTag;
|
||||
procedure Unlink(Tag: TTag);
|
||||
{$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, IMycDataProvider<T>)
|
||||
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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 }
|
||||
|
||||
+26
-26
@@ -5,32 +5,32 @@ program MycTests;
|
||||
{$ENDIF}
|
||||
{$STRONGLINKTYPES ON}
|
||||
uses
|
||||
FastMM5,
|
||||
DUnitX.MemoryLeakMonitor.FastMM5,
|
||||
System.SysUtils,
|
||||
{$IFDEF TESTINSIGHT}
|
||||
TestInsight.DUnitX,
|
||||
{$ELSE}
|
||||
DUnitX.Loggers.Console,
|
||||
{$ENDIF }
|
||||
DUnitX.TestFramework,
|
||||
TestNotifier in 'TestNotifier.pas',
|
||||
TestNotifier_Threading in 'TestNotifier_Threading.pas' {/TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',},
|
||||
TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',
|
||||
TestTasks in 'TestTasks.pas',
|
||||
Myc.Futures in '..\Src\Myc.Futures.pas',
|
||||
TestCoreFutures in 'TestCoreFutures.pas',
|
||||
Myc.TaskManager in '..\Src\Myc.TaskManager.pas',
|
||||
TestFutures in 'TestFutures.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.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas',
|
||||
Myc.Trade.DataPoint in '..\Src\Myc.Trade.DataPoint.pas',
|
||||
Myc.Trade.Node in '..\Src\Myc.Trade.Node.pas',
|
||||
Myc.Trade.DataStream in '..\Src\Myc.Trade.DataStream.pas',
|
||||
Myc.Test.Trade.DataPoint in '..\Src\Myc.Test.Trade.DataPoint.pas',
|
||||
Myc.Mutable in '..\Src\Myc.Mutable.pas',
|
||||
Test.Core.Mutable in 'Test.Core.Mutable.pas';
|
||||
FastMM5,
|
||||
DUnitX.MemoryLeakMonitor.FastMM5,
|
||||
System.SysUtils,
|
||||
{$IFDEF TESTINSIGHT}
|
||||
TestInsight.DUnitX,
|
||||
{$ELSE}
|
||||
DUnitX.Loggers.Console,
|
||||
{$ENDIF }
|
||||
DUnitX.TestFramework,
|
||||
TestNotifier in 'TestNotifier.pas',
|
||||
TestNotifier_Threading in 'TestNotifier_Threading.pas' {/TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',},
|
||||
TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',
|
||||
TestTasks in 'TestTasks.pas',
|
||||
Myc.Futures in '..\Src\Myc.Futures.pas',
|
||||
TestCoreFutures in 'TestCoreFutures.pas',
|
||||
Myc.TaskManager in '..\Src\Myc.TaskManager.pas',
|
||||
TestFutures in 'TestFutures.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.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas',
|
||||
Myc.Trade.DataPoint in '..\Src\Myc.Trade.DataPoint.pas',
|
||||
Myc.Trade.Node in '..\Src\Myc.Trade.Node.pas',
|
||||
Myc.Trade.DataStream in '..\Src\Myc.Trade.DataStream.pas',
|
||||
Myc.Test.Trade.DataPoint in '..\Src\Myc.Test.Trade.DataPoint.pas',
|
||||
Myc.Mutable in '..\Src\Myc.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 }
|
||||
{$IFNDEF TESTINSIGHT}
|
||||
|
||||
Reference in New Issue
Block a user