Polished DataProvider & Converter, RTTI field access
This commit is contained in:
@@ -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}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user