Polished DataProvider & Converter, RTTI field access

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