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>;