DataFlow refactoring

This commit is contained in:
Michael Schimmel
2025-07-23 20:14:24 +02:00
parent b623be13fa
commit 7b2446b220
6 changed files with 260 additions and 486 deletions
+37 -253
View File
@@ -111,7 +111,6 @@ type
procedure NewWorkspace;
function CurrLayout<T: TControl>: T;
procedure AlignControl(Control: TControl);
function CreateStrategy1(Timeframe: TTimeframe): IMycProcessor<TDataPoint<TOhlcItem>>;
function CreateStrategy2(Timeframe: TTimeframe): IMycProcessor<TDataPoint<TOhlcItem>>;
published
@@ -286,221 +285,6 @@ begin
Control.Align := TAlignLayout.Top;
end;
function TForm1.CreateStrategy1(Timeframe: TTimeframe): IMycProcessor<TDataPoint<TOhlcItem>>;
type
TSignal = record
Sig: Double;
SL: Double;
Entry: Double;
pnl: Double;
end;
var
panel: TMycChart.TPanel;
begin
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
Result := ticker;
var OhlcPoint := ticker.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateOhlcAggregation(Timeframe));
var Ohlc := TConverter.CreateSequence<TOhlcItem>(2, OhlcPoint.Field<TOhlcItem>('Data').Sender);
var Closes := Ohlc[0].Field<Double>('Close');
var Hull := Closes.Chain<Double>(TIndicators.CreateHMA(250)).MakeParallel;
var Sma := Closes.Chain<Double>(TIndicators.CreateSMA(200)).MakeParallel;
var Lowest: Double := Double.MaxValue;
var Highest: Double := Double.MinValue;
var ATR := Ohlc[0].Chain<Double>(TIndicators.CreateATR(50)).MakeParallel;
// next stage
var ATREndPoint := TConverter.CreateEndpoint<Double>(ATR.Sender, 5);
var ATRSeries: TSeries<Double>;
var HullEndPoint := TConverter.CreateEndpoint<Double>(Hull.Sender, 5);
var HullSeries: TSeries<Double>;
var SmaEndPoint := TConverter.CreateEndpoint<Double>(Sma.Sender, 5);
var SmaSeries: TSeries<Double>;
var curr: TSignal;
curr.SL := Double.NaN;
curr.Entry := Double.NaN;
var Signal :=
Ohlc[1]
.Chain<TSignal>(
function(const Ohlc: TOhlcItem): TSignal
begin
if Ohlc.Low < Lowest then
Lowest := Ohlc.Low;
if Ohlc.High > Highest then
Highest := Ohlc.High;
Result := curr;
Result.Sig := 0;
var pnl: double := NaN;
ATREndPoint.Update(ATRSeries);
HullEndPoint.Update(HullSeries);
SmaEndPoint.Update(SmaSeries);
if (HullSeries[0] < SmaSeries[0]) and (HullSeries[1] >= SmaSeries[1]) then
begin
if curr.Sig > 0 then
pnl := Ohlc.Close - curr.Entry;
curr.Sig := -1;
curr.SL := Highest;
curr.Entry := Ohlc.Close;
Result := curr;
end
else if (HullSeries[0] > SmaSeries[0]) and (HullSeries[1] <= SmaSeries[1]) then
begin
if curr.Sig < 0 then
pnl := curr.Entry - Ohlc.Close;
curr.Sig := 1;
curr.SL := Lowest;
curr.Entry := Ohlc.Close;
Result := curr;
end;
var atr := 15 * ATRSeries[0];
if curr.Sig > 0 then
begin
if Ohlc.Close > curr.SL then
begin
if curr.SL < Ohlc.Close - atr then
curr.SL := Ohlc.Close - atr;
Result.SL := curr.SL;
end;
if Ohlc.Low <= curr.SL then
begin
pnl := curr.SL - curr.Entry;
curr.Sig := 0;
Result.Sig := 0;
curr.SL := NaN;
end;
end
else if curr.Sig < 0 then
begin
if Ohlc.Close < curr.SL then
begin
if curr.SL > Ohlc.Close + atr then
curr.SL := Ohlc.Close + atr;
Result.SL := curr.SL;
end;
if Ohlc.High >= curr.SL then
begin
pnl := curr.Entry - curr.SL;
curr.Sig := 0;
Result.Sig := 0;
curr.SL := NaN;
end;
end;
if Result.Sig <> 0 then
begin
Lowest := Double.MaxValue;
Highest := Double.MinValue;
Result.SL := Double.NaN;
Result.Entry := Double.NaN;
end;
Result.pnl := pnl;
end);
var pnl := Signal.Field<Double>('pnl');
var FEquity: Double;
var FInit: Boolean;
var equity :=
TConverter<Double, Double>.CreateAggregation(
function(const Value: Double; const Broadcast: TConverter<Double, Double>.TBroadcastProc): TState
begin
if not FInit then
begin
FInit := true;
Broadcast(FEquity);
end;
if not IsNan(Value) then
begin
FEquity := FEquity + Value;
Result := Broadcast(FEquity);
end;
end
);
// var equity: TConverter<Double, Double> := TEquitySum.Create(10000);
pnl.Sender.Link(equity);
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(M15, OhlcPoint.Field<TDateTime>('Time').Sender);
panel := chart.AddPanel;
panel.AddOhlcSeries(Ohlc[0].Sender);
panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Cornflowerblue, 2);
panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Brown, 1.5);
panel.AddDoubleSeries(Signal.Field<Double>('Entry').Sender, TAlphaColors.Green, 1);
panel.AddDoubleSeries(Signal.Field<Double>('SL').Sender, TAlphaColors.Red, 2);
var mean := TConverter<TArray<Double>, Double>.CreateGeneric(TIndicators.CreateMean());
TConverter.Join<Double>([Hull.Sender, Sma.Sender]).Link(mean);
panel.AddDoubleSeries(mean.Sender, TAlphaColors.Blue, 5);
var pnlChart := TMycChart.Create(Self);
AlignControl(pnlChart);
pnlChart.Height := Layout.ChildrenRect.Width * 9 / 24;
pnlChart.Lookback.Value := 50000;
pnlChart.SetXAxisCounter<Double>(equity.Sender);
////////////
var EMAFactory := TEMA.Create;
var Params := TDataRecord.Create(EMAFactory.Params);
Params.SetValue<Integer>('Period', 20);
var indi := EMAFActory.CreateIndicator(Params);
var EMAConv := TConverter<TDataRecord, TDataRecord>.CreateGeneric(indi);
var equityEMA :=
equity
.Chain<TDataRecord>(TConverter.FieldToRecord<Double>(EMAFactory.Input, 'Price'))
.Chain<TDataRecord>(EMAConv)
.Chain<Double>(TConverter.FieldOfRecord<Double>(EMAFactory.Output, 'MA'));
//////////////
panel := pnlChart.AddPanel;
panel.AddDoubleSeries(equity.Sender, TAlphaColors.Blue, 3);
panel.AddDoubleSeries(equityEMA.Sender, TAlphaColors.Gray, 2);
/////
end;
function TForm1.CreateStrategy2(Timeframe: TTimeframe): IMycProcessor<TDataPoint<TOhlcItem>>;
type
TSignal = record
@@ -515,7 +299,7 @@ begin
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
Result := ticker;
var OhlcPoint := ticker.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateOhlcAggregation(Timeframe));
var OhlcPoint := ticker.Sender.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateOhlcAggregation(Timeframe));
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
@@ -531,13 +315,13 @@ begin
// next stage
var ATREndPoint := TConverter.CreateEndpoint<Double>(ATR.Sender, 5);
var ATREndPoint := ATR.CreateEndpoint(5);
var ATRSeries: TSeries<Double>;
var HullEndPoint := TConverter.CreateEndpoint<Double>(Hull.Sender, 5);
var HullEndPoint := Hull.CreateEndpoint(5);
var HullSeries: TSeries<Double>;
var SmaEndPoint := TConverter.CreateEndpoint<Double>(Sma.Sender, 5);
var SmaEndPoint := Sma.CreateEndpoint(5);
var SmaSeries: TSeries<Double>;
var curr: TSignal;
@@ -546,10 +330,7 @@ begin
var lastHull, lastSma: Double;
var conv :=
TConverter.Join<Double>(
[Ohlc.Field<Double>('Low').Sender, Ohlc.Field<Double>('High').Sender, Closes.Sender, ATR.Sender, Hull.Sender, Sma.Sender]
);
var conv := TConverter.Join<Double>([Ohlc.Field<Double>('Low'), Ohlc.Field<Double>('High'), Closes, ATR, Hull, Sma]);
var Signal :=
TConverter<TArray<Double>, TSignal>.CreateGeneric(
@@ -643,9 +424,9 @@ begin
end
);
conv.Link(Signal);
conv.Chain<TSignal>(Signal);
var pnl := Signal.Field<Double>('pnl');
var pnl := Signal.Sender.Field<Double>('pnl');
var FEquity: Double := 10000;
var FInit: Boolean := false;
@@ -668,7 +449,7 @@ begin
end
);
pnl.Sender.Link(equity);
pnl.Chain<Double>(equity);
var Layout := CurrLayout<TVertScrollBox>;
if Layout = nil then
@@ -683,18 +464,18 @@ begin
chart.Height := Layout.ChildrenRect.Width * 9 / 16;
chart.Lookback.Value := 50000;
chart.SetXAxisSeries(M15, OhlcPoint.Field<TDateTime>('Time').Sender);
chart.SetXAxisSeries(M15, OhlcPoint.Field<TDateTime>('Time'));
panel := chart.AddPanel;
panel.AddOhlcSeries(Ohlc.Sender);
panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Cornflowerblue, 2);
panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Brown, 1.5);
panel.AddDoubleSeries(Signal.Field<Double>('Entry').Sender, TAlphaColors.Green, 1);
panel.AddDoubleSeries(Signal.Field<Double>('SL').Sender, TAlphaColors.Red, 2);
panel.AddOhlcSeries(Ohlc);
panel.AddDoubleSeries(Hull, TAlphaColors.Cornflowerblue, 2);
panel.AddDoubleSeries(Sma, TAlphaColors.Brown, 1.5);
panel.AddDoubleSeries(Signal.Sender.Field<Double>('Entry'), TAlphaColors.Green, 1);
panel.AddDoubleSeries(Signal.Sender.Field<Double>('SL'), TAlphaColors.Red, 2);
var mean := TConverter<TArray<Double>, Double>.CreateGeneric(TIndicators.CreateMean());
TConverter.Join<Double>([Hull.Sender, Sma.Sender]).Link(mean);
TConverter.Join<Double>([Hull, Sma]).Chain<Double>(mean);
panel.AddDoubleSeries(mean.Sender, TAlphaColors.Blue, 5);
@@ -717,6 +498,7 @@ begin
var equityEMA :=
equity
.Sender
.Chain<TDataRecord>(TConverter.FieldToRecord<Double>(EMAFactory.Input, 'Price'))
.Chain<TDataRecord>(EMAConv)
.Chain<Double>(TConverter.FieldOfRecord<Double>(EMAFactory.Output, 'MA'));
@@ -725,7 +507,7 @@ begin
panel := pnlChart.AddPanel;
panel.AddDoubleSeries(equity.Sender, TAlphaColors.Blue, 3);
panel.AddDoubleSeries(equityEMA.Sender, TAlphaColors.Gray, 2);
panel.AddDoubleSeries(equityEMA, TAlphaColors.Gray, 2);
/////
end;
@@ -791,7 +573,7 @@ begin
var ticker := TConverter.CreateTicker<TDataPoint<TOhlcItem>>;
ticker.Sender.Link(Processor);
ticker.Sender.Chain(Processor);
FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, ticker);
{$endif}
@@ -823,7 +605,9 @@ begin
/////
var OhlcPoint := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
var OhlcTicker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
var OhlcPoint := OhlcTicker.Sender;
var timeframe := TTimeframe.H;
@@ -839,28 +623,28 @@ begin
var Macd := Closes.MakeParallel.Chain<TMacdResult>(TIndicators.CreateMACD(12, 26, 9));
var Stoch := Ohlc.MakeParallel.Chain<TStochasticResult>(TIndicators.CreateStochastic(14, 3));
chart.SetXAxisSeries(timeframe, Timestamps.Sender);
chart.SetXAxisSeries(timeframe, Timestamps);
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.AddOhlcSeries(Ohlc);
Panel.AddDoubleSeries(Hull, TAlphaColors.Aliceblue);
Panel.AddDoubleSeries(Sma, TAlphaColors.Yellow);
Panel.AddDoubleSeries(Ema, TAlphaColors.Aqua);
Panel.AddDoubleSeries(Boli.Field<Double>('UpperBand'), TAlphaColors.Gray);
Panel.AddDoubleSeries(Boli.Field<Double>('MiddleBand'), TAlphaColors.Darkgray, 1.0);
Panel.AddDoubleSeries(Boli.Field<Double>('LowerBand'), TAlphaColors.Gray);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
Panel.AddDoubleSeries(Rsi, 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.AddDoubleSeries(Macd.Field<Double>('MacdLine'), TAlphaColors.Orange);
Panel.AddDoubleSeries(Macd.Field<Double>('SignalLine'), TAlphaColors.Dodgerblue);
Panel.AddDoubleSeries(Macd.Field<Double>('Histogram'), TAlphaColors.Lightgreen);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Stoch.Field<Double>('K').Sender, TAlphaColors.Green);
Panel.AddDoubleSeries(Stoch.Field<Double>('D').Sender, TAlphaColors.Red);
Panel.AddDoubleSeries(Stoch.Field<Double>('K'), TAlphaColors.Green);
Panel.AddDoubleSeries(Stoch.Field<Double>('D'), TAlphaColors.Red);
/////
{
@@ -887,7 +671,7 @@ begin
}
/////
ExecuteStrategy(Symbol, timeframe, OhlcPoint);
ExecuteStrategy(Symbol, timeframe, OhlcTicker);
end;
procedure TForm1.Strat2ButtonClick(Sender: TObject);
@@ -897,7 +681,7 @@ begin
exit;
var timeframe := TTimeframe.M15;
// ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe));
//ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe));
var tstStrat := StrategyTest.CreateStrategy1(timeframe);
ExecuteStrategy(Symbol, timeframe, tstStrat);
+106 -109
View File
@@ -10,7 +10,7 @@ uses
Myc.DataRecord,
Myc.Trade.Indicators;
function CreateStrategy1(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, Double>;
function CreateStrategy1(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, Double>; overload;
implementation
@@ -30,7 +30,7 @@ type
begin
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
var OhlcPoint := ticker.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateOhlcAggregation(Timeframe));
var OhlcPoint := ticker.Sender.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateOhlcAggregation(Timeframe));
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
@@ -40,10 +40,7 @@ begin
var Sma := Closes.Chain<Double>(TIndicators.CreateSMA(200)).MakeParallel;
var ATR := Ohlc.Chain<Double>(TIndicators.CreateATR(50)).MakeParallel;
var conv :=
TConverter.Join<Double>(
[Ohlc.Field<Double>('Low').Sender, Ohlc.Field<Double>('High').Sender, Closes.Sender, ATR.Sender, Hull.Sender, Sma.Sender]
);
var conv := TConverter.Join<Double>([Ohlc.Field<Double>('Low'), Ohlc.Field<Double>('High'), Closes, ATR, Hull, Sma]);
// STAGE 1: Signal Generation. This converter is stateless regarding the trade itself.
// It only detects the crossover event and prepares the data for the next stage.
@@ -52,42 +49,44 @@ begin
var lastHull, lastSma: Double;
var signalGenerator :=
TConverter<TArray<Double>, TSignalEvent>.CreateGeneric(
function(const Values: TArray<Double>): TSignalEvent
begin
Result.Low := Values[0];
Result.High := Values[1];
Result.Close := Values[2];
Result.ATR := Values[3];
var hull := Values[4];
var sma := Values[5];
if Result.Low < Lowest then
Lowest := Result.Low;
if Result.High > Highest then
Highest := Result.High;
Result.Signal := 0;
Result.InitialSL := Double.NaN;
if (hull < sma) and (lastHull >= lastSma) then
conv.Chain<TSignalEvent>(
TConverter<TArray<Double>, TSignalEvent>.CreateGeneric(
function(const Values: TArray<Double>): TSignalEvent
begin
Result.Signal := -1;
Result.InitialSL := Highest;
Highest := Double.MinValue; // Reset for next trend
Lowest := Double.MaxValue;
Result.Low := Values[0];
Result.High := Values[1];
Result.Close := Values[2];
Result.ATR := Values[3];
var hull := Values[4];
var sma := Values[5];
if Result.Low < Lowest then
Lowest := Result.Low;
if Result.High > Highest then
Highest := Result.High;
Result.Signal := 0;
Result.InitialSL := Double.NaN;
if (hull < sma) and (lastHull >= lastSma) then
begin
Result.Signal := -1;
Result.InitialSL := Highest;
Highest := Double.MinValue; // Reset for next trend
Lowest := Double.MaxValue;
end
else if (hull > sma) and (lastHull <= lastSma) then
begin
Result.Signal := 1;
Result.InitialSL := Lowest;
Highest := Double.MinValue; // Reset for next trend
Lowest := Double.MaxValue;
end;
lastHull := hull;
lastSma := sma;
end
else if (hull > sma) and (lastHull <= lastSma) then
begin
Result.Signal := 1;
Result.InitialSL := Lowest;
Highest := Double.MinValue; // Reset for next trend
Lowest := Double.MaxValue;
end;
lastHull := hull;
lastSma := sma;
end
)
);
// STAGE 2: Position Management. This stateful converter manages the lifecycle
@@ -98,95 +97,93 @@ begin
var currEntry := Double.NaN;
var positionManager :=
TConverter<TSignalEvent, Double>.CreateAggregation(
function(const Value: TSignalEvent; const Broadcast: TConverter<TSignalEvent, Double>.TBroadcastProc): TState
var
pnl: Double;
begin
Result := TState.Null;
pnl := Double.NaN;
// 1. Check for a new signal to open or reverse a position
if Value.Signal <> 0 then
signalGenerator.Chain<Double>(
TConverter<TSignalEvent, Double>.CreateAggregation(
function(const Value: TSignalEvent; const Broadcast: TConverter<TSignalEvent, Double>.TBroadcastProc): TState
var
pnl: Double;
begin
// If a position is already open, close it first
if currSig > 0 then
pnl := Value.Close - currEntry
else if currSig < 0 then
pnl := currEntry - Value.Close;
Result := TState.Null;
pnl := Double.NaN;
// Open new position
currSig := Value.Signal;
currEntry := Value.Close;
currSL := Value.InitialSL;
end
// 2. If no new signal, manage the currently open position
else
begin
var atrValue := 15 * Value.ATR;
if currSig > 0 then // Manage long position
// 1. Check for a new signal to open or reverse a position
if Value.Signal <> 0 then
begin
if Value.Close > currSL then
if currSL < Value.Close - atrValue then
currSL := Value.Close - atrValue;
// If a position is already open, close it first
if currSig > 0 then
pnl := Value.Close - currEntry
else if currSig < 0 then
pnl := currEntry - Value.Close;
if Value.Low <= currSL then
begin
pnl := currSL - currEntry;
currSig := 0; // Close position
end;
// Open new position
currSig := Value.Signal;
currEntry := Value.Close;
currSL := Value.InitialSL;
end
else if currSig < 0 then // Manage short position
// 2. If no new signal, manage the currently open position
else
begin
if Value.Close < currSL then
if currSL > Value.Close + atrValue then
currSL := Value.Close + atrValue;
if Value.High >= currSL then
var atrValue := 15 * Value.ATR;
if currSig > 0 then // Manage long position
begin
pnl := currEntry - currSL;
currSig := 0; // Close position
if Value.Close > currSL then
if currSL < Value.Close - atrValue then
currSL := Value.Close - atrValue;
if Value.Low <= currSL then
begin
pnl := currSL - currEntry;
currSig := 0; // Close position
end;
end
else if currSig < 0 then // Manage short position
begin
if Value.Close < currSL then
if currSL > Value.Close + atrValue then
currSL := Value.Close + atrValue;
if Value.High >= currSL then
begin
pnl := currEntry - currSL;
currSig := 0; // Close position
end;
end;
end;
end;
// 3. If a PnL was generated (trade closed), broadcast it
if not IsNan(pnl) then
begin
currSL := Double.NaN;
Broadcast(pnl);
end;
end
// 3. If a PnL was generated (trade closed), broadcast it
if not IsNan(pnl) then
begin
currSL := Double.NaN;
Broadcast(pnl);
end;
end
)
);
// Chain the stages together
conv.Link(signalGenerator);
signalGenerator.Sender.Link(positionManager);
// The final equity calculation remains the same, it just consumes the PnL from the position manager
var FEquity: Double := 10000;
var FInit: Boolean := false;
var equity :=
TConverter<Double, Double>.CreateAggregation(
function(const Value: Double; const Broadcast: TConverter<Double, Double>.TBroadcastProc): TState
begin
if not FInit then
positionManager.Chain<Double>(
TConverter<Double, Double>.CreateAggregation(
function(const Value: Double; const Broadcast: TConverter<Double, Double>.TBroadcastProc): TState
begin
FInit := true;
Broadcast(FEquity);
end;
if not FInit then
begin
FInit := true;
Broadcast(FEquity);
end;
if not IsNan(Value) then
begin
FEquity := FEquity + Value;
Result := Broadcast(FEquity);
end;
end
if not IsNan(Value) then
begin
FEquity := FEquity + Value;
Result := Broadcast(FEquity);
end;
end
)
);
positionManager.Sender.Link(equity);
Result := TConverter<TDataPoint<TOhlcItem>, Double>.Construct(ticker, equity.Sender);
Result := TConverter<TDataPoint<TOhlcItem>, Double>.Construct(ticker, equity);
end;
end.