From b453236b1ea17712f0913fcbe6e6540914a684d2 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sat, 28 Jun 2025 12:43:19 +0200 Subject: [PATCH] 1st Aura Project Layout --- AuraTrader/AuraTrader.dpr | 4 +- AuraTrader/AuraTrader.dproj | 2 + AuraTrader/MainForm.fmx | 105 +++++---- AuraTrader/MainForm.pas | 87 ++++--- Src/Myc.Aura.Module.pas | 412 +++++++++++++++++++++++++++++++++ Src/Myc.Aura.Parameter.pas | 151 ++++++++++++ Src/Myc.Core.Lazy.pas | 58 ++--- Src/Myc.Lazy.pas | 108 ++++++--- Src/Myc.Signals.FMX.pas | 147 +++++++++--- Src/Myc.Trade.DataProvider.pas | 6 - 10 files changed, 891 insertions(+), 189 deletions(-) create mode 100644 Src/Myc.Aura.Module.pas create mode 100644 Src/Myc.Aura.Parameter.pas diff --git a/AuraTrader/AuraTrader.dpr b/AuraTrader/AuraTrader.dpr index d2f369c..0dd1b4e 100644 --- a/AuraTrader/AuraTrader.dpr +++ b/AuraTrader/AuraTrader.dpr @@ -6,7 +6,9 @@ uses FMX.Forms, MainForm in 'MainForm.pas' {Form1}, Myc.Trade.Core.DataPoint in '..\Src\Myc.Trade.Core.DataPoint.pas', - Myc.Trade.Ticker in '..\Src\Myc.Trade.Ticker.pas'; + Myc.Trade.Ticker in '..\Src\Myc.Trade.Ticker.pas', + Myc.Aura.Module in '..\Src\Myc.Aura.Module.pas', + Myc.Aura.Parameter in '..\Src\Myc.Aura.Parameter.pas'; {$R *.res} diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj index 78cf432..11c0b7b 100644 --- a/AuraTrader/AuraTrader.dproj +++ b/AuraTrader/AuraTrader.dproj @@ -135,6 +135,8 @@ + + Base diff --git a/AuraTrader/MainForm.fmx b/AuraTrader/MainForm.fmx index bc310da..e1c14a6 100644 --- a/AuraTrader/MainForm.fmx +++ b/AuraTrader/MainForm.fmx @@ -11,85 +11,108 @@ object Form1: TForm1 OnDestroy = FormDestroy DesignerMasterStyle = 0 object Panel1: TPanel - Align = Top + Align = Client Size.Width = 796.00000000000000000 - Size.Height = 529.00000000000000000 + Size.Height = 616.00000000000000000 Size.PlatformDefault = False TabOrder = 2 - object RandomButton: TButton - Position.X = 697.00000000000000000 - Position.Y = 38.00000000000000000 - TabOrder = 0 - Text = 'Random' - TextSettings.Trimming = None - OnClick = RandomButtonClick - end - object Path1: TPath - Anchors = [akLeft, akTop, akRight] - Position.X = 8.00000000000000000 - Position.Y = 8.00000000000000000 - Size.Width = 261.00000000000000000 - Size.Height = 513.00000000000000000 + object Layout: TFlowLayout + Align = Client + Size.Width = 627.00000000000000000 + Size.Height = 616.00000000000000000 Size.PlatformDefault = False + TabOrder = 1 + Justify = Left + JustifyLastLine = Left + FlowDirection = LeftToRight + HorizontalGap = 2.00000000000000000 + VerticalGap = 2.00000000000000000 + OnResized = LayoutResized end object SymbolsComboBox: TComboBox Anchors = [akTop, akRight] - Position.X = 493.00000000000000000 - Position.Y = 8.00000000000000000 - Size.Width = 217.00000000000000000 + Position.X = 608.00000000000000000 + Position.Y = 16.00000000000000000 + Size.Width = 177.00000000000000000 Size.Height = 22.00000000000000000 Size.PlatformDefault = False + TabOrder = 5 + end + object RandomBox: TCheckBox + Anchors = [akTop, akRight] + Position.X = 609.00000000000000000 + Position.Y = 46.00000000000000000 TabOrder = 2 + Text = 'Random' end object LoadButton: TButton Anchors = [akTop, akRight] - Position.X = 718.00000000000000000 - Position.Y = 8.00000000000000000 + Position.X = 641.00000000000000000 + Position.Y = 86.00000000000000000 Size.Width = 47.00000000000000000 Size.Height = 22.00000000000000000 Size.PlatformDefault = False - TabOrder = 3 + TabOrder = 6 Text = 'Load' TextSettings.Trimming = None OnClick = LoadButtonClick end - object FlowLayout: TFlowLayout - Position.X = 304.00000000000000000 - Position.Y = 104.00000000000000000 - Size.Width = 449.00000000000000000 - Size.Height = 361.00000000000000000 - Size.PlatformDefault = False - TabOrder = 5 - Justify = Left - JustifyLastLine = Left - FlowDirection = LeftToRight + object RandomButton: TButton + Anchors = [akTop, akRight] + Position.X = 705.00000000000000000 + Position.Y = 86.00000000000000000 + TabOrder = 3 + Text = 'Random' + TextSettings.Trimming = None + OnClick = RandomButtonClick end object ChartButton: TButton - Position.X = 712.00000000000000000 - Position.Y = 72.00000000000000000 - TabOrder = 6 + Anchors = [akTop, akRight] + Position.X = 665.00000000000000000 + Position.Y = 130.00000000000000000 + TabOrder = 8 Text = 'ChartButton' TextSettings.Trimming = None OnClick = ChartButtonClick end object StopButton: TButton - Position.X = 720.00000000000000000 - Position.Y = 96.00000000000000000 - TabOrder = 8 + Anchors = [akTop, akRight] + Position.X = 665.00000000000000000 + Position.Y = 160.00000000000000000 + TabOrder = 10 Text = 'StopButton' TextSettings.Trimming = None OnClick = StopButtonClick end + object TreeView1: TTreeView + Align = Left + Size.Width = 161.00000000000000000 + Size.Height = 616.00000000000000000 + Size.PlatformDefault = False + TabOrder = 0 + Viewport.Width = 157.00000000000000000 + Viewport.Height = 612.00000000000000000 + end + object Splitter1: TSplitter + Align = Left + Cursor = crHSplit + MinSize = 20.00000000000000000 + Position.X = 161.00000000000000000 + Size.Width = 8.00000000000000000 + Size.Height = 616.00000000000000000 + Size.PlatformDefault = False + end end object LogMemo: TMemo Touch.InteractiveGestures = [Pan, LongTap, DoubleTap] DataDetectorTypes = [] - Align = Client + Align = Bottom + Position.Y = 616.00000000000000000 Size.Width = 796.00000000000000000 - Size.Height = 311.00000000000000000 + Size.Height = 224.00000000000000000 Size.PlatformDefault = False TabOrder = 1 Viewport.Width = 792.00000000000000000 - Viewport.Height = 307.00000000000000000 + Viewport.Height = 220.00000000000000000 end end diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas index ae399be..4356635 100644 --- a/AuraTrader/MainForm.pas +++ b/AuraTrader/MainForm.pas @@ -34,25 +34,30 @@ uses Myc.Signals.FMX, Myc.TaskManager, FMX.ListBox, - FMX.Layouts; + FMX.Layouts, + FMX.MultiView, + FMX.TreeView; type TForm1 = class(TForm) LogMemo: TMemo; Panel1: TPanel; - RandomButton: TButton; - Path1: TPath; + Layout: TFlowLayout; SymbolsComboBox: TComboBox; + RandomBox: TCheckBox; LoadButton: TButton; - FlowLayout: TFlowLayout; + RandomButton: TButton; ChartButton: TButton; StopButton: TButton; + TreeView1: TTreeView; + Splitter1: TSplitter; procedure RandomButtonClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure LoadButtonClick(Sender: TObject); procedure ChartButtonClick(Sender: TObject); procedure StopButtonClick(Sender: TObject); + procedure LayoutResized(Sender: TObject); private const cnt = 20; @@ -70,6 +75,8 @@ type FRandom: TList; FTerminate: TEvent; FLoadDone: TState; + function SelectedSymbol: String; + function SelectedStream: IDataStream; public { Public declarations } published @@ -102,7 +109,7 @@ begin FSymbols := FServer.EnumerateSymbols; - SymbolsComboBox.AddIdleHandler( + SymbolsComboBox.ProcessSignal( FSymbols.Done.Signal, procedure begin @@ -130,10 +137,14 @@ begin FSymbols.WaitFor; FTerminate.Notify; TaskManager.WaitFor(FLoadDone); - Application.OnIdle := nil; FRandom.Free; end; +procedure TForm1.LayoutResized(Sender: TObject); +begin + LogMemo.Lines.Add('resize'); +end; + procedure TForm1.LoadButtonClick(Sender: TObject); begin if SymbolsComboBox.ItemIndex < 0 then @@ -142,7 +153,11 @@ begin var Stream := FServer.CreateStream(FSymbols.WaitFor[SymbolsComboBox.ItemIndex]); var Data := TDataStreamProvider.Create(30000, 10000, Stream); - Path1.AddIdleHandler( + var path := TPath.Create(Self); + path.Parent := Layout; + path.Align := TAlignLayout.None; + + path.ProcessSignal( Data.Changed, procedure begin @@ -150,14 +165,14 @@ begin if Prices.Count > 0 then begin - Path1.BeginUpdate; + path.BeginUpdate; try - Path1.Data.Clear; - Path1.Data.MoveTo(PointF(Path1.Width - 1, Prices[0].Data.Ask)); + path.Data.Clear; + path.Data.MoveTo(PointF(path.Width - 1, Prices[0].Data.Ask)); for var i := 1 to Prices.Count - 1 do - Path1.Data.LineTo(PointF(Path1.Width - i - 1, Prices[i].Data.Ask)); + path.Data.LineTo(PointF(path.Width - i - 1, Prices[i].Data.Ask)); finally - Path1.EndUpdate; + path.EndUpdate; end; end; end @@ -168,12 +183,11 @@ procedure TForm1.RandomButtonClick(Sender: TObject); begin var rnd: TRndItem; - var ass := FSymbols.WaitFor; - rnd.Stream := FServer.CreateStream(ass[Random(Length(FSymbols.WaitFor))]); + rnd.Stream := FServer.CreateStream(FSymbols.WaitFor[Random(Length(FSymbols.WaitFor))]); rnd.Data := TDataStreamProvider.Create(3000, 1000, rnd.Stream); rnd.Labl := TLabel.Create(Self); - rnd.Labl.Parent := FlowLayout; + rnd.Labl.Parent := Layout; rnd.Labl.Align := TAlignLayout.None; rnd.Labl.WordWrap := false; rnd.Labl.Width := 300; @@ -185,7 +199,7 @@ begin begin FRandom[idx] .Labl - .AddIdleHandler( + .ProcessSignal( FRandom[idx].Data.Changed, procedure begin @@ -209,15 +223,14 @@ end; procedure TForm1.ChartButtonClick(Sender: TObject); begin - if SymbolsComboBox.ItemIndex < 0 then + var Symbol := SelectedSymbol; + if Symbol = '' then exit; - var Symbol := FSymbols.WaitFor[SymbolsComboBox.ItemIndex]; + var currPathData := TWriteable>.CreateWriteable.Protect; + var currLog := TWriteable.CreateWriteable.Protect; - var currPathData := TMutable>.CreateWriteable.Protect; - var currLog := TMutable.CreateWriteable.Protect; - - const width = 3000; + const width = 300; var done := TaskManager.RunTask( @@ -255,18 +268,36 @@ begin FLoadDone := TState.All([FLoadDone, done]); - LogMemo.AddIdleHandler(currLog.Changed, procedure begin LogMemo.Lines.Add(Symbol + ': ' + currLog.Value); end); + var path := TPath.Create(Self); + path.Parent := Layout; + path.Align := TAlignLayout.None; - Path1.AddIdleHandler( + path.ProcessSignal( currPathData.Changed, - procedure + procedure(out IsDone: Boolean) begin if currPathData.Value <> nil then - begin - Path1.Data.Assign(currPathData.Value.Obj); - end; + path.Data.Assign(currPathData.Value.Obj); + IsDone := done.IsSet; end ); end; +function TForm1.SelectedStream: IDataStream; +begin + var sym := SelectedSymbol; + if sym = '' then + exit(nil); + Result := FServer.CreateStream(sym); +end; + +function TForm1.SelectedSymbol: String; +begin + Result := ''; + if RandomBox.IsChecked then + Result := FSymbols.WaitFor[Random(Length(FSymbols.WaitFor))] + else if SymbolsComboBox.ItemIndex >= 0 then + Result := FSymbols.WaitFor[SymbolsComboBox.ItemIndex]; +end; + end. diff --git a/Src/Myc.Aura.Module.pas b/Src/Myc.Aura.Module.pas new file mode 100644 index 0000000..da2e0a3 --- /dev/null +++ b/Src/Myc.Aura.Module.pas @@ -0,0 +1,412 @@ +(* +--- +## Aura Module API Documentation + +This unit defines the core interfaces and records for the Aura system, designed for robust algorithmic trading strategy development, +backtesting, and optimization. It encapsulates key concepts for defining strategies, executing backtests, performing walk-forward +optimizations, and conducting statistical robustness analysis via Monte Carlo simulations. + +--- + +### Core Interfaces and Records + +#### `IAuraObject` +Base interface for all Aura objects, providing a common `Name` property for identification. + * `Name`: A writeable string representing the object's name. + +#### `TAuraArray` +A record providing a mutable, observable array-like collection for `IAuraObject` instances. It wraps a `TWriteable>` and offers basic array manipulation methods. + * `Items`: A writeable array of `IAuraObject` instances. + * `Insert(Idx: Integer; const Item: T)`: Inserts an item at a specified index. + * `Delete(Idx: Integer)`: Deletes an item at a specified index. + * `IndexOf(const Item: T)`: Returns the index of a given item. + +#### `IAuraLiveObject` +Extends `IAuraObject` for entities that are dynamically produced and actively executing operations, providing logging capabilities and a running status. + * `Log`: A `TStrings` object for logging events and status messages. + * `IsRunning`: Indicates whether the object is currently active. + +#### `IAuraNode` +Extends `IAuraObject` for objects that can be part of a hierarchical structure and can be serialized. + * `Caption`: A human-readable representation of the object's name. + * `Serialize(const Write: TJsonWriter)`: Serializes the object's state to a JSON writer. + +#### `IAuraChilds` +A generic interface for `IAuraNode`s that can have child nodes, enabling hierarchical organization within the Aura system. + * `Items`: A `TAuraArray` containing the child nodes. + +#### `TAuraParameterDef` +Defines the metadata for a single strategy parameter, including its name, type, and default value. + * `Name`: The name of the parameter. + * `ParamType`: The data type of the parameter (e.g., integer, float, string, UTC time). + * `DefaultValue`: The default value for the parameter, stored as a `TAuraParameterValue`. + +#### `TAuraParameterRecordDef` +Represents a collection of `TAuraParameterDef` records, defining all parameters for a specific strategy. This is an alias for `TArray`. + +#### `IAuraBot` +An interface representing an instance that executes a trading strategy with given parameters over a specified time period. + * `PnL`: A mutable data series representing the Profit and Loss (PnL) curve of the bot's execution. + +#### `IAuraStrategy` +Defines a trading strategy, providing methods to create `IAuraBot` instances and specifying its parameter structure. + * `CreateBot(const Parameters: TArray; StartTime, EndTime: TDateTime)`: Creates and returns an `IAuraBot` instance configured with the given parameters and time range. + * `ParameterRecordDef`: Defines the set of parameters expected by this strategy. + +#### `TAuraTradePerformance` +A record encapsulating key performance metrics from a backtest or optimization run. + * `PnL`: Total Profit and Loss. + * `SharpeRatio`: Risk-adjusted return. + * `MaxDrawdown`: Maximum peak-to-trough decline. + * `ProfitFactor`: Ratio of gross profits to gross losses. + * `CalmarRatio`: Annualized return divided by the maximum drawdown. + * `WinRate`: Percentage of winning trades. + +#### `TAuraMonteCarloResult` +Stores the results of a Monte Carlo simulation, providing a probabilistic distribution of `TAuraTradePerformance` outcomes. + * `Percentile`: An array of `TAuraTradePerformance` records, sorted by PnL and distributed into 10% percentiles, allowing for statistical risk assessment. + +#### `IAuraTradeResult` +Represents the comprehensive outcome of a trade simulation (e.g., from a backtest or optimization), including performance metrics, trade details, and the ability to perform Monte Carlo analysis. + * `Performance`: The `TAuraTradePerformance` metrics for this result. + * `Trades`: A `TDataSeries` representing the PnLs of individual trades, serving as the basis for Monte Carlo simulation. + * `CalcMonteCarloSimulation(Steps: Integer)`: Asynchronously performs a Monte Carlo simulation on the `Trades` data, returning a `TFuture` that resolves to a `TAuraMonteCarloResult`. + +#### `TAuraTimeRange` +A record defining a specific time window with a start and end date/time. + * `StartTime`: The start of the time range. + * `EndTime`: The end of the time range. + +#### `IAuraBacktest` +An interface for executing a single backtest of a trading strategy. + * `BacktestResult`: A `TFuture` that resolves to the `IAuraTradeResult` of this backtest. + * `Bot`: The `IAuraBot` instance executing the backtest. + * `Parameters`: The specific parameters used for this backtest. + * `Strategy`: The `IAuraStrategy` being backtested. + * `TimeRange`: The historical time window over which the backtest is conducted. + +#### `IAuraParameterOptimization` +Represents a single optimization slot (window) within a Walk-Forward Optimization process. It manages the execution of multiple in-sample backtests to find optimal parameters, followed by an out-of-sample test. + * `InSampleTests`: A mutable array of `IAuraBacktest` instances representing the backtests run during the in-sample optimization phase. New tests are added, and less optimal ones may be removed. + * `OutOfSampleTest`: A mutable `IAuraBacktest` representing the test run with the best-fit parameters from the in-sample optimization on the subsequent out-of-sample data. + +#### `IAuraWalkForwardOptimizer` +Orchestrates the entire Walk-Forward Optimization (WFO) process, coordinating multiple `IAuraParameterOptimization` slots sequentially to test strategy adaptability across various market phases. + * `OptimizationResult`: A `TFuture` that resolves to the aggregated `IAuraTradeResult` representing the combined performance of all out-of-sample tests. + * `Slot`: An array of `IAuraParameterOptimization` instances, each representing a distinct optimization window. + +#### `IAuraParameterNode` +Represents a single configurable parameter for optimization algorithms, allowing its value to be set and observed. + * `Value`: A mutable `TAuraParameterValue` representing the parameter's current setting. + +#### `IAuraParameterList` +An alias for `IAuraChilds`, providing a structured way to manage collections of `IAuraParameterNode`s. + +#### `IAuraStudy` +Base interface for all types of strategy analysis studies (e.g., single backtests, parameter optimizations, WFOs). + * `EventLog`: A `TStrings` object for logging events specific to the study. + * `Strategy`: The `IAuraStrategy` that is the subject of the study. + +#### `TAuraParameterRange` +Defines the allowed range for a strategy parameter during optimization. + * `MinValue`: The minimum allowed value for the parameter, as a `TAuraParameterValue`. + * `MaxValue`: The maximum allowed value for the parameter, as a `TAuraParameterValue`. + +#### `IAuraBacktestStudy` +A specialized `IAuraStudy` for setting up and initiating a single backtest. + * `TimeRange`: The `TAuraTimeRange` for the backtest. + * `Start`: Initiates and returns an `IAuraBacktest` instance. + +#### `IAuraParameterOptimizationStudy` +A specialized `IAuraStudy` for configuring and initiating a parameter optimization process (e.g., using genetic algorithms) over a specific time range. + * `Parameters`: An `IAuraChilds` collection of `IAuraParameterNode`s defining the configuration parameters for the optimization algorithm itself (e.g., max generations, population size). + * `TimeRange`: The `TAuraTimeRange` for the optimization. + * `Start`: Initiates and returns an `IAuraParameterOptimization` instance. + +#### `IAuraWFOStudy` +A specialized `IAuraStudy` for configuring and initiating a Walk-Forward Optimization process. + * `ParameterRange[Idx: Integer]`: A writeable `TAuraParameterRange` defining the search space for each strategy parameter during the optimization phases. + * `NumSlots`: A writeable integer indicating the number of time windows (slots) for the WFO. + * `Start`: Initiates and returns an `IAuraWalkForwardOptimizer` instance. + +#### `IAuraTradingMode` +An enumeration defining different operational modes for a trading environment. + * `tmTesting`: Mode for historical backtesting. + * `tmSim`: Mode for simulated live trading (paper trading). + * `tmLive`: Mode for actual live trading. + +#### `IAuraWorkspace` +Represents the scope of a complete testing and trading environment, containing various studies and managing the overall trading mode. + * `WalkForwardAnalysis`: An `IAuraChilds` collection of `IAuraWFOStudy` instances. + * `Backtests`: An `IAuraChilds` collection of `IAuraBacktestStudy` instances. + * `ParameterOptimizations`: An `IAuraChilds` collection of `IAuraParameterOptimizationStudy` instances. + * `TradingMode`: A writeable `IAuraTradingMode` indicating the current operational mode. + +#### `IAuraStrategyFactory` +An interface for creating instances of `IAuraStrategy`. Used for managing available strategy types. + * `CreateStrategy`: Creates and returns a new `IAuraStrategy` instance. + +#### `IAuraApplication` +The application-level singleton, serving as the root for serialization and providing access to all defined strategies and workspaces. + * `Strategies`: An `IAuraChilds` collection of `IAuraStrategyFactory` instances. + * `Workspaces`: An `IAuraChilds` collection of `IAuraWorkspace` instances. + +--- +*) + +unit Myc.Aura.Module; + +interface + +uses + System.JSON.Writers, + System.Classes, + Myc.Signals, + Myc.Futures, + Myc.Lazy, + Myc.Trade.DataPoint, + Myc.Aura.Parameter; + +type + // Base class of all Aura objects + IAuraObject = interface + function GetName: TWriteable; + property Name: TWriteable read GetName; + end; + + TAuraArray = record + private + FArray: TWriteable>; + function GetItems: TWriteable>; + public + procedure Insert(Idx: Integer; const Item: T); + procedure Delete(Idx: Integer); + function IndexOf(const Item: T): Integer; + property Items: TWriteable> read GetItems; + end; + + // Base class for objects that are dynamically produced and are executing + IAuraLiveObject = interface(IAuraObject) + function GetLog: TStrings; + function IsRunning: Boolean; + property Log: TStrings read GetLog; + end; + + IAuraNode = interface(IAuraObject) + function GetCaption: string; + // Serialize the whole project + procedure Serialize(const Write: TJsonWriter); + // Pretty print of Name + property Caption: string read GetCaption; + end; + + IAuraChilds = interface(IAuraNode) + function GetItems: TAuraArray; + // Nodes to add as childs in hierarchical representation + property Items: TAuraArray read GetItems; + end; + + TAuraParameterDef = record + Name: String; + ParamType: TAuraParameterType; + DefaultValue: TAuraParameterValue; + end; + + TAuraParameterRecordDef = TArray; + + // A bot executes a strategy + IAuraBot = interface(IAuraLiveObject) + function GetPnL: TMutable>; + // Zero-based PnL series + property PnL: TMutable> read GetPnL; + end; + + // A strategy creates a bot that executes that strategy with given parameters and within a given time slot. + IAuraStrategy = interface(IAuraObject) + function CreateBot(const Parameters: TArray; StartTime, EndTime: TDateTime): IAuraBot; + function GetParameterRecordDef: TAuraParameterRecordDef; + // Defines the parameters of this strategy + property ParameterRecordDef: TAuraParameterRecordDef read GetParameterRecordDef; + end; + + // The performance of a backtest or walk forward optimization + TAuraTradePerformance = record + PnL: Double; + SharpeRatio, MaxDrawdown, ProfitFactor, CalmarRatio, WinRate: Double; + end; + + TAuraMonteCarloResult = record + // Performance sorted by PnL and distributed to percentiles of 10% each + Percentile: array[0..10] of TAuraTradePerformance; + end; + + IAuraTradeResult = interface + function GetPerformance: TAuraTradePerformance; + function GetTrades: TDataSeries; + // Perform Monte Carlo Simulation and generate performance distribution + function CalcMonteCarloSimulation(Steps: Integer): TFuture; + // PnLs of each trade = Equity curve + property Trades: TDataSeries read GetTrades; + // The performance of this equity curve + property Performance: TAuraTradePerformance read GetPerformance; + end; + + TAuraTimeRange = record + StartTime: TDateTime; + EndTime: TDateTime; + end; + + // Backtesting + IAuraBacktest = interface(IAuraLiveObject) + function GetParameters: TArray; + function GetStrategy: IAuraStrategy; + function GetBot: IAuraBot; + function GetBacktestResult: TFuture; + function GetTimeRange: TAuraTimeRange; + // The bot executing the backtest (generated by Strategy.CreateBot with the given parameters). + property Bot: IAuraBot read GetBot; + // The backtest result + property BacktestResult: TFuture read GetBacktestResult; + // The strategy parameters for this backtest + property Parameters: TArray read GetParameters; + // The strategy to backtest + property Strategy: IAuraStrategy read GetStrategy; + // The tested time window + property TimeRange: TAuraTimeRange read GetTimeRange; + end; + + // Generates parameter sets using genetic algorithms (or other techniques). + // Executes backtests and finds the best run by evaluating the backtest results. + IAuraParameterOptimization = interface(IAuraLiveObject) + function GetInSampleTests: TMutable>; + function GetOutOfSampleTest: TMutable; + // The running in-sample-tests. New tests will be added until the optimization stops. + property InSampleTests: TMutable> read GetInSampleTests; + // After in sample testing is finished, the out-of-sample-test with the best parameter set is executed. + property OutOfSampleTest: TMutable read GetOutOfSampleTest; + end; + + // Walk-Forward-Optimizer + IAuraWalkForwardOptimizer = interface(IAuraLiveObject) + function GetOptimizationResult: TFuture; + function GetSlot: TArray; + // When optimization is ready, this will contain the result for the combined WFO slots + property OptimizationResult: TFuture read GetOptimizationResult; + // The concurrently running optimization slots + property Slot: TArray read GetSlot; + end; + + // A Parameter Value + IAuraParameterNode = interface(IAuraNode) + function GetValue: TMutable; + property Value: TMutable read GetValue; + end; + IAuraParameterList = IAuraChilds; + + // A study on a strategy + IAuraStudy = interface(IAuraNode) + function GetEventLog: TStrings; + function GetStrategy: IAuraStrategy; + property EventLog: TStrings read GetEventLog; + // The Strategy to optimize + property Strategy: IAuraStrategy read GetStrategy; + end; + + TAuraParameterRange = record + MinValue, MaxValue: TAuraParameterValue; + end; + + // Study on a single backtest + IAuraBacktestStudy = interface(IAuraStudy) + function GetTimeRange: TAuraTimeRange; + function Start: IAuraBacktest; + property TimeRange: TAuraTimeRange read GetTimeRange; + end; + + // Study parameter optimization + IAuraParameterOptimizationStudy = interface(IAuraStudy) + function GetParameters: IAuraChilds; + function GetTimeRange: TAuraTimeRange; + function Start: IAuraParameterOptimization; + // Depending on the implementation this may be MaxGenerations, PopulationSize, Mutations, etc + property Parameters: IAuraChilds read GetParameters; + property TimeRange: TAuraTimeRange read GetTimeRange; + end; + + // Walk forward optimization + IAuraWFOStudy = interface(IAuraStudy) + function GetParameterRange(Idx: Integer): TWriteable; + function GetNumSlots: TWriteable; + // Starts WalkForwardOptimizer with the given number of NumSlots. + function Start: IAuraWalkForwardOptimizer; + // A list of parameter values to test, one for each parameter in the Strategy's parameter definition. + // This defines the parameter space for optimization algorithms. + property ParameterRange[Idx: Integer]: TWriteable read GetParameterRange; + // Number of time slots (windows) to test. + property NumSlots: TWriteable read GetNumSlots; + end; + + IAuraTradingMode = (tmTesting, tmSim, tmLive); + + // Repesents the scope of a workspace + IAuraWorkspace = interface(IAuraNode) + function GetWalkForwardAnalysis: IAuraChilds; + function GetBacktests: IAuraChilds; + function GetParameterOptimizations: IAuraChilds; + function GetTradingMode: TWriteable; + property WalkForwardAnalysis: IAuraChilds read GetWalkForwardAnalysis; + property Backtests: IAuraChilds read GetBacktests; + property ParameterOptimizations: IAuraChilds read GetParameterOptimizations; + property TradingMode: TWriteable read GetTradingMode; + end; + + IAuraStrategyFactory = interface(IAuraNode) + function CreateStrategy: IAuraStrategy; + end; + + // Application singleton, the root for serialization + IAuraApplication = interface(IAuraNode) + function GetStrategies: IAuraChilds; + function GetWorkspaces: IAuraChilds; + property Strategies: IAuraChilds read GetStrategies; + property Workspaces: IAuraChilds read GetWorkspaces; + end; + +implementation + +uses + System.Generics.Collections; + +procedure TAuraArray.Delete(Idx: Integer); +begin + if Idx < 0 then + exit; + + var Arr: TArray; + SetLength(Arr, Length(FArray.Value) - 1); + TArray.Copy(FArray.Value, Arr, 0, 0, Idx - 1); + TArray.Copy(FArray.Value, Arr, Idx + 1, Idx, High(Arr) - Idx); + FArray.Value := Arr; +end; + +procedure TAuraArray.Insert(Idx: Integer; const Item: T); +begin + var Arr: TArray; + SetLength(Arr, Length(FArray.Value) + 1); + TArray.Copy(FArray.Value, Arr, 0, 0, Idx - 1); + Arr[Idx] := Item; + TArray.Copy(FArray.Value, Arr, Idx, Idx + 1, High(Arr) - Idx); + FArray.Value := Arr; +end; + +function TAuraArray.GetItems: TWriteable>; +begin + Result := FArray; +end; + +function TAuraArray.IndexOf(const Item: T): Integer; +begin + Result := TArray.IndexOf(FArray.Value, Item); +end; + +end. diff --git a/Src/Myc.Aura.Parameter.pas b/Src/Myc.Aura.Parameter.pas new file mode 100644 index 0000000..516ecf8 --- /dev/null +++ b/Src/Myc.Aura.Parameter.pas @@ -0,0 +1,151 @@ +unit Myc.Aura.Parameter; + +interface + +type + TAuraParameterType = (apNone, apInteger, apFloat, apString, apUTC); + + TAuraParameterValue = record + private + FKind: TAuraParameterType; + FPtr: Pointer; + function GetAsInteger: Int64; + procedure SetAsInteger(const Value: Int64); + procedure SetKind(Kind: TAuraParameterType); inline; + procedure FreeValue; + function GetAsFloat: Double; + function GetAsString: string; + function GetAsUTC: TDateTime; + procedure SetAsFloat(const Value: Double); + procedure SetAsString(const Value: string); + procedure SetAsUTC(const Value: TDateTime); + public + class operator Initialize(out Dest: TAuraParameterValue); + class operator Finalize(var Dest: TAuraParameterValue); + class operator Assign(var Dest: TAuraParameterValue; const [ref] Src: TAuraParameterValue); + property AsInteger: Int64 read GetAsInteger write SetAsInteger; + property AsFloat: Double read GetAsFloat write SetAsFloat; + property AsString: string read GetAsString write SetAsString; + property AsUTC: TDateTime read GetAsUTC write SetAsUTC; + end; + +implementation + +{ TAuraParameterValue } + +function TAuraParameterValue.GetAsInteger: Int64; +begin + SetKind(apInteger); + if FPtr = nil then + exit(0); + Result := Int64(FPtr^); +end; + +procedure TAuraParameterValue.SetAsInteger(const Value: Int64); +begin + SetKind(apInteger); + if FPtr = nil then + GetMem(FPtr, sizeof(Int64)); + Int64(FPtr^) := Value; +end; + +procedure TAuraParameterValue.SetKind(Kind: TAuraParameterType); +begin + if FKind <> Kind then + begin + FreeValue; + FKind := Kind; + FPtr := nil; + end; +end; + +class operator TAuraParameterValue.Assign(var Dest: TAuraParameterValue; const [ref] Src: TAuraParameterValue); +begin + case Src.FKind of + apInteger: Dest.AsInteger := Src.AsInteger; + apFloat: Dest.AsInteger := Src.AsInteger; + apString: Dest.AsString := Src.AsString; + apUTC: Dest.AsUTC := Src.AsUTC; + end; +end; + +class operator TAuraParameterValue.Finalize(var Dest: TAuraParameterValue); +begin + if Dest.FPtr <> nil then + FreeMem(Dest.FPtr); +end; + +class operator TAuraParameterValue.Initialize(out Dest: TAuraParameterValue); +begin + Dest.FPtr := nil; + Dest.FKind := apInteger; +end; + +procedure TAuraParameterValue.FreeValue; +begin + if FPtr <> nil then + begin + case FKind of + apInteger, apFloat: FreeMem(FPtr); + apString: + begin + String(FPtr^) := ''; + FreeMem(FPtr); + end; + end; + FPtr := nil; + end; +end; + +function TAuraParameterValue.GetAsFloat: Double; +begin + SetKind(apFloat); + if FPtr = nil then + exit(0); + Result := Double(FPtr^); +end; + +function TAuraParameterValue.GetAsString: string; +begin + SetKind(apString); + if FPtr = nil then + exit(''); + Result := String(FPtr^); +end; + +function TAuraParameterValue.GetAsUTC: TDateTime; +begin + SetKind(apUTC); + if FPtr = nil then + exit(0); + Result := TDateTime(FPtr^); +end; + +procedure TAuraParameterValue.SetAsFloat(const Value: Double); +begin + SetKind(apFloat); + if FPtr = nil then + GetMem(FPtr, sizeof(Double)); + Double(FPtr^) := Value; +end; + +procedure TAuraParameterValue.SetAsString(const Value: string); +begin + SetKind(apString); + if FPtr = nil then + begin + GetMem(FPtr, sizeof(String)); + FillChar(FPtr^, sizeof(String), 0); + end; + String(FPtr^) := Value; +end; + +procedure TAuraParameterValue.SetAsUTC(const Value: TDateTime); +begin + SetKind(apUTC); + if FPtr = nil then + GetMem(FPtr, sizeof(TDateTime)); + TDateTime(FPtr^) := Value; +end; + +end. diff --git a/Src/Myc.Core.Lazy.pas b/Src/Myc.Core.Lazy.pas index 5873ac0..134ee95 100644 --- a/Src/Myc.Core.Lazy.pas +++ b/Src/Myc.Core.Lazy.pas @@ -13,7 +13,6 @@ type private function GetChanged: TSignal; function GetValue: T; - function GetWriter: TMutable.IWriter; end; TMycMutableBase = class(TInterfacedObject, TMutable.IMutable) @@ -21,7 +20,6 @@ type FChanged: TEvent; FChangeState: TSignal.TSubscription; function GetChanged: TSignal; - function GetWriter: TMutable.IWriter; protected function GetValue: T; virtual; abstract; public @@ -38,14 +36,13 @@ type constructor Create(const AChanged: TSignal; const AProc: TFunc); end; - TMycWriteableMutable = class(TInterfacedObject, TMutable.IMutable, TMutable.IWriter) + TMycWriteableMutable = class(TInterfacedObject, TMutable.IMutable, TWriteable.IWriteable) private FValue: T; FChanged: TEvent; protected function GetChanged: TSignal; function GetValue: T; - function GetWriter: TMutable.IWriter; function Exchange(const Value: T): T; public constructor Create(const AValue: T); @@ -81,24 +78,26 @@ type constructor Create(const AChanged: TSignal.ISignal; const AProc: TFunc); end; - TMycProtectedMutable = class(TInterfacedObject, TMutable.IMutable, TMutable.IWriter) + TMycProtectedMutable = class(TInterfacedObject, TMutable.IMutable, TWriteable.IWriteable) private - FMutable: TMutable.IMutable; + FWriteable: TWriteable.IWriteable; FLock: Integer; protected function Exchange(const Value: T): T; function GetChanged: TSignal; function GetValue: T; - function GetWriter: TMutable.IWriter; procedure Lock; inline; procedure Release; inline; public - constructor Create(const AMutable: TMutable.IMutable); + constructor Create(const AWriteable: TWriteable.IWriteable); procedure SetValue(const Value: T); end; implementation +uses + System.Generics.Defaults; + { TMycNullMutable } function TMycNullMutable.GetChanged: TSignal; @@ -111,11 +110,6 @@ begin Result := Default(T); end; -function TMycNullMutable.GetWriter: TMutable.IWriter; -begin - Result := nil; -end; - { TMycMutableBase } constructor TMycMutableBase.Create(const AChanged: TSignal); @@ -136,11 +130,6 @@ begin Result := FChanged.Signal; end; -function TMycMutableBase.GetWriter: TMutable.IWriter; -begin - Result := nil; -end; - { TMycWriteableMutable } constructor TMycWriteableMutable.Create(const AValue: T); @@ -166,15 +155,13 @@ begin Result := FValue; end; -function TMycWriteableMutable.GetWriter: TMutable.IWriter; -begin - Result := Self; -end; - procedure TMycWriteableMutable.SetValue(const Value: T); begin - FValue := Value; - FChanged.Notify; + if not TEqualityComparer.Default.Equals(FValue, Value) then + begin + FValue := Value; + FChanged.Notify; + end; end; { TMycNullLazy } @@ -248,18 +235,17 @@ end; { TMycProtectedMutable } -constructor TMycProtectedMutable.Create(const AMutable: TMutable.IMutable); +constructor TMycProtectedMutable.Create(const AWriteable: TWriteable.IWriteable); begin inherited Create; - FMutable := AMutable; + FWriteable := AWriteable; end; function TMycProtectedMutable.Exchange(const Value: T): T; begin Lock; try - Assert(FMutable.Writer <> nil); - Result := FMutable.Writer.Exchange(Value); + Result := FWriteable.Exchange(Value); finally Release; end; @@ -267,26 +253,19 @@ end; function TMycProtectedMutable.GetChanged: TSignal; begin - Result := FMutable.Changed; + Result := FWriteable.Changed; end; function TMycProtectedMutable.GetValue: T; begin Lock; try - Result := FMutable.Value; + Result := FWriteable.Value; finally Release; end; end; -function TMycProtectedMutable.GetWriter: TMutable.IWriter; -begin - Result := nil; - if FMutable.Writer <> nil then - Result := Self; -end; - procedure TMycProtectedMutable.Lock; begin while AtomicExchange(FLock, 1) = 1 do @@ -302,8 +281,7 @@ procedure TMycProtectedMutable.SetValue(const Value: T); begin Lock; try - Assert(FMutable.Writer <> nil); - FMutable.Writer.SetValue(Value); + FWriteable.SetValue(Value); finally Release; end; diff --git a/Src/Myc.Lazy.pas b/Src/Myc.Lazy.pas index 65e0290..b7faefe 100644 --- a/Src/Myc.Lazy.pas +++ b/Src/Myc.Lazy.pas @@ -9,20 +9,13 @@ uses type TMutable = record type - IWriter = interface - procedure SetValue(const Value: T); - function Exchange(const Value: T): T; - end; - IMutable = interface {$REGION 'property access'} function GetChanged: TSignal; function GetValue: T; - function GetWriter: IWriter; {$ENDREGION} property Changed: TSignal read GetChanged; property Value: T read GetValue; - property Writer: IWriter read GetWriter; end; {$REGION 'private'} @@ -34,9 +27,7 @@ type private FMutable: IMutable; function GetChanged: TSignal; inline; - function GetIsWriteable: Boolean; function GetValue: T; inline; - procedure SetValue(const Value: T); {$ENDREGION} public constructor Create(const AMutable: IMutable); @@ -47,13 +38,36 @@ type class property Null: IMutable read FNull; class function Construct(const Changing: TSignal; const Proc: TFunc): TMutable; overload; static; - class function CreateWriteable: TMutable; overload; static; - function Protect: TMutable; + property Value: T read GetValue; + property Changed: TSignal read GetChanged; + end; + + TWriteable = record + type + IWriteable = interface(TMutable.IMutable) + procedure SetValue(const Value: T); + function Exchange(const Value: T): T; + end; + + {$REGION 'private'} + private + FWriteable: IWriteable; + function GetChanged: TSignal; inline; + function GetValue: T; inline; + procedure SetValue(const Value: T); + {$ENDREGION} + public + constructor Create(const AWriteable: IWriteable); + class operator Implicit(const A: IWriteable): TWriteable; overload; + class operator Implicit(const A: TWriteable): IWriteable; overload; + + class function CreateWriteable: TWriteable; overload; static; + + function Protect: TWriteable; property Value: T read GetValue write SetValue; property Changed: TSignal read GetChanged; - property IsWriteable: Boolean read GetIsWriteable; end; TLazy = record @@ -114,40 +128,16 @@ begin Result := TMycFuncMutable.Create(Changing, Proc); end; -class function TMutable.CreateWriteable: TMutable; -begin - Result := TMycWriteableMutable.Create(Default(T)); -end; - function TMutable.GetChanged: TSignal; begin Result := FMutable.Changed; end; -function TMutable.GetIsWriteable: Boolean; -begin - Result := Assigned(FMutable.Writer); -end; - function TMutable.GetValue: T; begin Result := FMutable.Value; end; -function TMutable.Protect: TMutable; -begin - if not (FMutable is TMycProtectedMutable) then - Result := TMycProtectedMutable.Create(FMutable) - else - Result := FMutable; -end; - -procedure TMutable.SetValue(const Value: T); -begin - Assert(IsWriteable); - FMutable.Writer.SetValue(Value); -end; - class operator TMutable.Implicit(const A: TMutable): IMutable; begin Result := A.FMutable; @@ -212,4 +202,50 @@ begin Dest.FLazy := FNull; end; +{ TWriteable } + +constructor TWriteable.Create(const AWriteable: IWriteable); +begin + FWriteable := AWriteable; + Assert(Assigned(FWriteable)); +end; + +class function TWriteable.CreateWriteable: TWriteable; +begin + Result := TMycWriteableMutable.Create(Default(T)); +end; + +function TWriteable.GetChanged: TSignal; +begin + Result := FWriteable.Changed; +end; + +function TWriteable.GetValue: T; +begin + Result := FWriteable.Value; +end; + +function TWriteable.Protect: TWriteable; +begin + if not (FWriteable is TMycProtectedMutable) then + Result := TMycProtectedMutable.Create(FWriteable) + else + Result := FWriteable; +end; + +procedure TWriteable.SetValue(const Value: T); +begin + FWriteable.SetValue(Value); +end; + +class operator TWriteable.Implicit(const A: IWriteable): TWriteable; +begin + Result.Create(A); +end; + +class operator TWriteable.Implicit(const A: TWriteable): IWriteable; +begin + Result := A.FWriteable; +end; + end. diff --git a/Src/Myc.Signals.FMX.pas b/Src/Myc.Signals.FMX.pas index 3d1af8e..f770161 100644 --- a/Src/Myc.Signals.FMX.pas +++ b/Src/Myc.Signals.FMX.pas @@ -5,72 +5,145 @@ interface uses System.Classes, System.SysUtils, + System.Generics.Collections, + System.Diagnostics, System.Messaging, Myc.Signals; type - TMsgProc = reference to procedure(const Sender: TObject; const M: TMessage); - - TComponentValidation = class(TComponent) - private - FReceived: TFlag; - FProc: TMsgProc; - FSubscription: TMessageSubscriptionId; - FMsgClass: TClass; - procedure DoMsg(const Sender: TObject; const M: TMessage); + TSignalComponentHelper = class helper for TComponent + type + TMsgProc = reference to procedure(out IsDone: Boolean); + TSignalSubscription = class(TComponent, TSignal.ISubscriber) + private + FSignal: TSignal; + FSigSubscr: TSignal.TSubscription; + FProc: TMsgProc; + FNotified: Integer; + FIdleSubscrId: TMessageSubscriptionId; + class var + FQueued: Integer; + FCount: Integer; + FItems: TList; + FIdx: Integer; + class procedure HandleSignals(Timeout: Int64); + function Notify: Boolean; + public + constructor Create(AOwner: TComponent; const ASignal: TSignal; const AProc: TMsgProc); reintroduce; + destructor Destroy; override; + end; public - constructor Create(AOwner: TComponent; const AMsgClass: TClass; const ASignal: TSignal; const AProc: TMsgProc); reintroduce; - destructor Destroy; override; - end; - - TComponentValidationHelper = class helper for TComponent - procedure AddMsgHandler(const MsgClass: TClass; const Signal: TSignal; const Proc: TMsgProc); - procedure AddIdleHandler(const Signal: TSignal; const Proc: TProc); + procedure ProcessSignal(const Signal: TSignal; const Proc: TMsgProc); overload; + procedure ProcessSignal(const Signal: TSignal; const Proc: TProc); overload; end; implementation uses -{$IFDEF FRAMEWORK_FMX} - FMX.Types -{$ELSE} - VCL.Types // to be checked -{$IFEND} - ; + FMX.Types; -{ TComponentValidation } +{ TSignalComponentHelper.TSignalSubscription } -constructor TComponentValidation.Create(AOwner: TComponent; const AMsgClass: TClass; const ASignal: TSignal; const AProc: TMsgProc); +constructor TSignalComponentHelper.TSignalSubscription.Create(AOwner: TComponent; const ASignal: TSignal; const AProc: TMsgProc); begin inherited Create(AOwner); - FMsgClass := AMsgClass; + FSignal := ASignal; FProc := AProc; - FReceived := TFlag.CreateObserver(ASignal); - FSubscription := TMessageManager.DefaultManager.SubscribeToMessage(FMsgClass, DoMsg); + FIdx := 0; + + if FItems = nil then + begin + FItems := TList.Create; + FIdleSubscrId := + TMessageManager + .DefaultManager + .SubscribeToMessage(TIdleMessage, procedure(const Sender: TObject; const M: TMessage) begin HandleSignals(50); end); + end; + FItems.Add(Self); + + FSigSubscr := FSignal.Subscribe(Self); end; -destructor TComponentValidation.Destroy; +destructor TSignalComponentHelper.TSignalSubscription.Destroy; begin - TMessageManager.DefaultManager.Unsubscribe(FMsgClass, FSubscription); + FSigSubscr.Unsubscribe; + + FItems.Remove(Self); + if FItems.Count = 0 then + begin + TMessageManager.DefaultManager.Unsubscribe(TIdleMessage, FIdleSubscrId); + FreeAndNil(FItems); + end; + inherited; end; -procedure TComponentValidation.DoMsg(const Sender: TObject; const M: TMessage); +class procedure TSignalComponentHelper.TSignalSubscription.HandleSignals(Timeout: Int64); begin - if FReceived.Reset then - FProc(Sender, M); + if AtomicExchange(FQueued, 0) = 0 then + exit; + + var Stopwatch := TStopwatch.StartNew; + + if (FIdx = 0) or (FIdx > FItems.Count) then + FIdx := FItems.Count; + + while FIdx > 0 do + begin + dec(FIdx); + var sub := FItems[FIdx]; + + if AtomicExchange(sub.FNotified, 0) = 1 then + begin + var done := false; + try + sub.FProc(done); + finally + if done then + sub.Free; + end; + + if AtomicDecrement(FCount) = 1 then + break; + end; + + if Stopwatch.ElapsedMilliseconds > Timeout then + begin + AtomicExchange(FQueued, 1); + break; + end; + + if FIdx = 0 then + FIdx := FItems.Count; + end; end; -procedure TComponentValidationHelper.AddIdleHandler(const Signal: TSignal; const Proc: TProc); +function TSignalComponentHelper.TSignalSubscription.Notify: Boolean; begin - var capProc := Proc; - AddMsgHandler(TIdleMessage, Signal, procedure(const Sender: TObject; const M: TMessage) begin capProc(); end); + if AtomicExchange(FNotified, 1) = 0 then + AtomicIncrement(FCount); + AtomicExchange(FQueued, 1); + + // if AtomicExchange(FQueued, 1) = 0 then + // TThread.Queue( nil, + // procedure + // begin + // if AtomicExchange(FQueued, 0) = 1 then + // HandleSignals( 40 ); + // end); + Result := true; end; -procedure TComponentValidationHelper.AddMsgHandler(const MsgClass: TClass; const Signal: TSignal; const Proc: TMsgProc); +procedure TSignalComponentHelper.ProcessSignal(const Signal: TSignal; const Proc: TProc); begin - TComponentValidation.Create(Self, MsgClass, Signal, Proc); + var cProc: TProc := Proc; + TSignalSubscription.Create(Self, Signal, procedure(out IsDone: Boolean) begin cProc(); end); +end; + +procedure TSignalComponentHelper.ProcessSignal(const Signal: TSignal; const Proc: TMsgProc); +begin + TSignalSubscription.Create(Self, Signal, Proc); end; end. diff --git a/Src/Myc.Trade.DataProvider.pas b/Src/Myc.Trade.DataProvider.pas index ccf8fcc..6d87bda 100644 --- a/Src/Myc.Trade.DataProvider.pas +++ b/Src/Myc.Trade.DataProvider.pas @@ -18,7 +18,6 @@ type FLookback: Int64; function GetChanged: TSignal; function GetValue: TDataSeries; - function GetWriter: TMutable>.IWriter; public constructor Create(ALookback, AMaxChunkSize: Int64; const AStream: IDataStream); destructor Destroy; override; @@ -63,11 +62,6 @@ begin Result := FDataSeries; end; -function TDataStreamProvider.GetWriter: TMutable>.IWriter; -begin - Result := nil; -end; - { TDataStreamProvider } class function TDataStreamProvider.Create(Lookback, MaxChunkSize: Int64; const Stream: IDataStream): TMutable>;