diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj
index 5c1bb79..b01b2ad 100644
--- a/AuraTrader/AuraTrader.dproj
+++ b/AuraTrader/AuraTrader.dproj
@@ -4,7 +4,7 @@
20.3
FMX
True
- Release
+ Debug
Win64
AuraTrader
3
diff --git a/AuraTrader/FirstStrategy.pas b/AuraTrader/FirstStrategy.pas
index 404388f..3f6ab48 100644
--- a/AuraTrader/FirstStrategy.pas
+++ b/AuraTrader/FirstStrategy.pas
@@ -42,17 +42,13 @@ type
TTicksToBars = class(TMycConverter, TDataPoint>)
private
FTimeframe: TTimeframe;
- // Stores the currently aggregating OHLC data.
FCurrentBar: TDataPoint;
function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
function GetCurrentBar: TDataPoint;
function GetTimeframe: TTimeframe;
public
constructor Create(const ATimeframe: TTimeframe);
-
- // Process new data. This is called concurrently and must not have side effects out of the scope of this class!
function ProcessData(const Value: TDataPoint): TState; override;
-
property CurrentBar: TDataPoint read GetCurrentBar;
property Timeframe: TTimeframe read GetTimeframe;
end;
diff --git a/AuraTrader/MainForm.fmx b/AuraTrader/MainForm.fmx
index 080fd1d..8b924e8 100644
--- a/AuraTrader/MainForm.fmx
+++ b/AuraTrader/MainForm.fmx
@@ -3,7 +3,7 @@ object Form1: TForm1
Top = 0
Caption = 'Form1'
ClientHeight = 840
- ClientWidth = 796
+ ClientWidth = 1017
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
@@ -12,7 +12,7 @@ object Form1: TForm1
DesignerMasterStyle = 0
object MainPanel: TPanel
Align = Client
- Size.Width = 796.000000000000000000
+ Size.Width = 1017.000000000000000000
Size.Height = 712.000000000000000000
Size.PlatformDefault = False
TabOrder = 2
@@ -27,30 +27,30 @@ object Form1: TForm1
end
object WorkspacePanel: TPanel
Align = Client
- Size.Width = 619.000000000000000000
+ Size.Width = 840.000000000000000000
Size.Height = 712.000000000000000000
Size.PlatformDefault = False
TabOrder = 3
object TabControl: TTabControl
Align = Client
- Size.Width = 619.000000000000000000
- Size.Height = 672.000000000000000000
+ Size.Width = 840.000000000000000000
+ Size.Height = 678.000000000000000000
Size.PlatformDefault = False
TabIndex = 0
TabOrder = 10
TabPosition = PlatformDefault
end
object ToolBar: TToolBar
- Size.Width = 619.000000000000000000
- Size.Height = 40.000000000000000000
+ Size.Width = 840.000000000000000000
+ Size.Height = 34.000000000000000000
Size.PlatformDefault = False
TabOrder = 3
object AddWorkspaceButton: TSpeedButton
Action = AddWorkspaceAction
Align = FitLeft
ImageIndex = -1
- Size.Width = 145.454589843750000000
- Size.Height = 40.000000000000000000
+ Size.Width = 123.636383056640600000
+ Size.Height = 34.000000000000000000
Size.PlatformDefault = False
Text = 'Add'
TextSettings.Trimming = None
@@ -59,9 +59,9 @@ object Form1: TForm1
Action = TestAction
Align = FitLeft
ImageIndex = -1
- Position.X = 145.454589843750000000
- Size.Width = 145.454544067382800000
- Size.Height = 40.000000000000000000
+ Position.X = 123.636383056640600000
+ Size.Width = 123.636322021484400000
+ Size.Height = 34.000000000000000000
Size.PlatformDefault = False
TextSettings.Trimming = None
object TestPopup: TPopup
@@ -88,10 +88,9 @@ object Form1: TForm1
Size.Width = 47.000000000000000000
Size.Height = 22.000000000000000000
Size.PlatformDefault = False
- TabOrder = 5
+ TabOrder = 4
Text = 'Load'
TextSettings.Trimming = None
- OnClick = LoadButtonClick
end
object RandomButton: TButton
Anchors = [akTop, akRight]
@@ -99,45 +98,54 @@ object Form1: TForm1
TabOrder = 2
Text = 'Random'
TextSettings.Trimming = None
- OnClick = RandomButtonClick
- end
- object StopButton: TButton
- Anchors = [akTop, akRight]
- Position.Y = 63.000000000000000000
- TabOrder = 8
- Text = 'StopButton'
- TextSettings.Trimming = None
- OnClick = StopButtonClick
end
object ChartButton: TButton
Anchors = [akTop, akRight]
- Position.Y = 85.000000000000000000
- TabOrder = 7
+ Position.Y = 63.000000000000000000
+ TabOrder = 6
Text = 'ChartButton'
TextSettings.Trimming = None
- OnClick = ChartButtonClick
- end
- object SymbolsComboBox: TComboBox
- Anchors = [akTop, akRight]
- Position.Y = 107.000000000000000000
- Size.Width = 177.000000000000000000
- Size.Height = 22.000000000000000000
- Size.PlatformDefault = False
- TabOrder = 4
end
end
end
end
object StrategyButton: TSpeedButton
Align = FitLeft
- Position.X = 290.909118652343800000
- Size.Width = 145.454467773437500000
- Size.Height = 40.000000000000000000
+ Position.X = 247.272705078125000000
+ Size.Width = 123.250000000000000000
+ Size.Height = 34.000000000000000000
Size.PlatformDefault = False
Text = 'Strategy'
TextSettings.Trimming = None
OnClick = StrategyButtonClick
end
+ object SymbolsComboBox: TComboBox
+ Align = FitLeft
+ Margins.Top = 4.000000000000000000
+ Margins.Bottom = 4.000000000000000000
+ Position.X = 370.522735595703100000
+ Position.Y = 4.000000000000000000
+ Size.Width = 141.192199707031300000
+ Size.Height = 26.000000000000000000
+ Size.PlatformDefault = False
+ TabOrder = 3
+ end
+ object StopButton: TButton
+ Align = FitLeft
+ Margins.Left = 8.000000000000000000
+ Margins.Top = 4.000000000000000000
+ Margins.Right = 8.000000000000000000
+ Margins.Bottom = 4.000000000000000000
+ Position.X = 519.714965820312500000
+ Position.Y = 4.000000000000000000
+ Size.Width = 111.404327392578100000
+ Size.Height = 26.000000000000000000
+ Size.PlatformDefault = False
+ TabOrder = 7
+ Text = 'StopButton'
+ TextSettings.Trimming = None
+ OnClick = StopButtonClick
+ end
end
end
object ObjectsPanel: TPanel
@@ -191,11 +199,11 @@ object Form1: TForm1
DataDetectorTypes = []
Align = Bottom
Position.Y = 712.000000000000000000
- Size.Width = 796.000000000000000000
+ Size.Width = 1017.000000000000000000
Size.Height = 128.000000000000000000
Size.PlatformDefault = False
TabOrder = 1
- Viewport.Width = 792.000000000000000000
+ Viewport.Width = 1013.000000000000000000
Viewport.Height = 124.000000000000000000
end
object ActionList: TActionList
diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas
index e9b8b41..f86b112 100644
--- a/AuraTrader/MainForm.pas
+++ b/AuraTrader/MainForm.pas
@@ -75,32 +75,18 @@ type
TestPopup: TPopup;
FlowLayout: TFlowLayout;
StrategyButton: TSpeedButton;
- 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);
procedure TreeViewDblClick(Sender: TObject);
procedure AddWorkspaceActionExecute(Sender: TObject);
procedure TestActionExecute(Sender: TObject);
procedure StrategyButtonClick(Sender: TObject);
- private
- const
- cnt = 20;
- type
- TRndItem = record
- Stream: IDataStream;
- Data: TMutable>;
- Labl: TLabel;
- end;
private
FOnEvent: TNotifyEvent;
{ Private declarations }
FServer: IDataServer;
FSymbols: TFuture>;
- FRandom: TList;
FTerminate: TEvent;
FProcessDone: TState;
FApplication: IAuraApplication;
@@ -148,6 +134,184 @@ begin
FTerminate.Notify;
end;
+procedure TForm1.TreeViewDblClick(Sender: TObject);
+begin
+ var sel := TreeView.Selected as TTreeViewItem;
+
+ var parent := sel.ParentItem;
+ if parent = nil then
+ exit;
+
+ if parent = FModulesItem then
+ begin
+ if (TabControl.ActiveTab <> nil) and (TabControl.ActiveTab.Tag <> 0) then
+ begin
+ var ws := IAuraWorkspace(TabControl.ActiveTab.Tag);
+ var module := FApplication.Modules[sel.TagString];
+ if (ws <> nil) and (module <> nil) then
+ module.SetupWorkspace(ws);
+ end;
+ end;
+end;
+
+procedure TForm1.FormCreate(Sender: TObject);
+begin
+ FApplication := TMycAuraApplication.Create;
+
+ FModulesItem := TTreeViewItem.Create(Self);
+ FModulesItem.Text := 'Modules';
+
+ const modName = 'Test_Module';
+
+ FApplication.RegisterModule(modName, TTestModule.Create('Test-Module', 1));
+
+ TreeView.AddObject(FModulesItem);
+ FModulesItem.ProcessSignal(
+ FApplication.ModuleNames.Changed,
+ procedure
+ begin
+ FModulesItem.BeginUpdate;
+ try
+ while FModulesItem.Count > 0 do
+ FModulesItem[0].Free;
+ var mods := FApplication.ModuleNames.Value;
+ for var i := 0 to High(mods) do
+ begin
+ var ModItem := TTreeViewItem.Create(Self);
+ ModItem.Text := mods[i];
+ ModItem.DragMode := TDragMode.dmAutomatic;
+ ModItem.Text := 'Module-' + mods[i];
+ ModItem.TagString := mods[i];
+ FModulesItem.AddObject(ModItem);
+ end;
+ FModulesItem.ExpandAll;
+ finally
+ FModulesItem.EndUpdate;
+ end;
+ end
+ );
+
+ FTerminate := TEvent.CreateEvent;
+
+ // Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364]
+ FServer := TAuraTABFileServer.Create('\\COFFEE\TickData\Pepperstone');
+
+ SymbolsComboBox.Enabled := false;
+ ChartButton.Enabled := false;
+ LoadButton.Enabled := false;
+
+ FSymbols := FServer.EnumerateSymbols;
+
+ SymbolsComboBox.ProcessSignal(
+ FSymbols.Done.Signal,
+ procedure
+ begin
+ SymbolsComboBox.BeginUpdate;
+ try
+ SymbolsComboBox.Items.Clear;
+ SymbolsComboBox.Items.AddStrings(FSymbols.WaitFor);
+ if SymbolsComboBox.Items.Count > 0 then
+ begin
+ if SymbolsComboBox.ItemIndex < 0 then
+ SymbolsComboBox.ItemIndex := SymbolsComboBox.Items.IndexOf('GER40');
+ SymbolsComboBox.Enabled := true;
+ ChartButton.Enabled := true;
+ LoadButton.Enabled := true;
+ end;
+ finally
+ SymbolsComboBox.EndUpdate;
+ end;
+ end
+ );
+
+ NewWorkspace;
+end;
+
+procedure TForm1.FormDestroy(Sender: TObject);
+begin
+ FSymbols.WaitFor;
+ FTerminate.Notify;
+ TaskManager.WaitFor(FProcessDone);
+end;
+
+procedure TForm1.TestActionExecute(Sender: TObject);
+begin
+ TestPopup.IsOpen := TestAction.Checked;
+end;
+
+procedure TForm1.AddWorkspaceActionExecute(Sender: TObject);
+begin
+ NewWorkspace;
+end;
+
+procedure TForm1.AlignControl(Control: TControl);
+begin
+ var Layout := CurrLayout;
+ if Layout = nil then
+ exit;
+
+ Control.Parent := Layout;
+ Control.Width := Layout.Width;
+ Control.Position.Y := Layout.ChildrenRect.Bottom + 1;
+ Control.Anchors := [TAnchorKind.akLeft, TAnchorKind.akRight];
+ Control.Align := TAlignLayout.Top;
+end;
+
+function TForm1.CurrLayout: T;
+begin
+ if TabControl.ActiveTab = nil then
+ exit(nil);
+
+ var Res: T := nil;
+ TabControl.ActiveTab.EnumControls(
+ function(Control: TControl): TEnumControlsResult
+ begin
+ Result := TEnumControlsResult.Continue;
+ if Control is T then
+ begin
+ Res := Control as T;
+ Result := TEnumControlsResult.Stop;
+ end;
+ end
+ );
+
+ Result := Res;
+end;
+
+function TForm1.ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor>>): TState;
+var
+ dataProvider: IMycConverter>, TArray>>;
+begin
+ var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
+
+ dataProvider :=
+ TMycGenericConverter>, TArray>>.Create(
+ function(const Values: TArray>): TArray>
+ begin
+ SetLength(Result, Length(Values));
+ for var i := 0 to High(Result) do
+ begin
+ Result[i].Time := Values[i].Time;
+ Result[i].Data.Ask := Values[i].Data.Ask;
+ Result[i].Data.Bid := Values[i].Data.Bid;
+ end;
+ end
+ );
+
+ dataProvider.Sender.Link(Processor);
+
+ Result := FServer.ProcessData(Symbol, terminated, dataProvider);
+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;
+
procedure TForm1.StrategyButtonClick(Sender: TObject);
begin
var Layout := CurrLayout;
@@ -179,7 +343,7 @@ begin
OhlcPoint.Sender.Link(TimeStamps);
chart.SetXAxisSeries(timeframe, Timestamps.Sender);
- var Panel := chart.Panels[0];
+ var Panel := chart.AddPanel;
var Ohlc: IMycConverter, TOhlcItem> :=
TMycGenericConverter, TOhlcItem>
@@ -275,351 +439,45 @@ begin
Stoch.Sender.Link(StochD);
Panel.AddDoubleSeries(StochD.Sender, TAlphaColors.Red);
- var done := ExecuteStrategy(Symbol, ticker);
+ /////
+
+ var tickChart := TMycChart.Create(Self);
+ tickChart.Height := Layout.ChildrenRect.Width * 9 / 16;
+ AlignControl(tickChart);
+ tickChart.Lookback.Value := 1000000;
+
+ var TickTime: IMycConverter, TDateTime> :=
+ TMycGenericConverter, TDateTime>
+ .Create(function(const Tick: TDataPoint): TDateTime begin Result := Tick.Time; end);
+
+ var TickAsk: IMycConverter, Double> :=
+ TMycGenericConverter, Double>
+ .Create(function(const Tick: TDataPoint): Double begin Result := Tick.Data.Ask; end);
+
+ var TickBid: IMycConverter, Double> :=
+ TMycGenericConverter, Double>
+ .Create(function(const Tick: TDataPoint): Double begin Result := Tick.Data.Bid; end);
+
+ var TickSpread: IMycConverter, Double> :=
+ TMycGenericConverter, Double>
+ .Create(function(const Tick: TDataPoint): Double begin Result := Tick.Data.Bid-Tick.Data.Ask; end);
+
+ ticker.Sender.Link( TickTime );
+ ticker.Sender.Link( TickAsk );
+ ticker.Sender.Link( TickBid );
+ ticker.Sender.Link( TickSpread );
+
+ tickChart.SetXAxisSeries( TTimeframe.S, TickTime.Sender );
+ panel := tickChart.AddPanel;
+ panel.AddDoubleSeries(TickAsk.Sender, TAlphaColors.Blue);
+ panel.AddDoubleSeries(TickBid.Sender, TAlphaColors.Red);
+ panel := tickChart.AddPanel;
+ panel.AddDoubleSeries(TickSpread.Sender);
/////
+ var done := ExecuteStrategy(Symbol, ticker);
FProcessDone := TState.All([FProcessDone, done]);
end;
-procedure TForm1.TreeViewDblClick(Sender: TObject);
-begin
- var sel := TreeView.Selected as TTreeViewItem;
-
- var parent := sel.ParentItem;
- if parent = nil then
- exit;
-
- if parent = FModulesItem then
- begin
- if (TabControl.ActiveTab <> nil) and (TabControl.ActiveTab.Tag <> 0) then
- begin
- var ws := IAuraWorkspace(TabControl.ActiveTab.Tag);
- var module := FApplication.Modules[sel.TagString];
- if (ws <> nil) and (module <> nil) then
- module.SetupWorkspace(ws);
- end;
- end;
-end;
-
-procedure TForm1.FormCreate(Sender: TObject);
-begin
- FApplication := TMycAuraApplication.Create;
-
- FModulesItem := TTreeViewItem.Create(Self);
- FModulesItem.Text := 'Modules';
-
- const modName = 'Test_Module';
-
- FApplication.RegisterModule(modName, TTestModule.Create('Test-Module', 1));
-
- TreeView.AddObject(FModulesItem);
- FModulesItem.ProcessSignal(
- FApplication.ModuleNames.Changed,
- procedure
- begin
- FModulesItem.BeginUpdate;
- try
- while FModulesItem.Count > 0 do
- FModulesItem[0].Free;
- var mods := FApplication.ModuleNames.Value;
- for var i := 0 to High(mods) do
- begin
- var ModItem := TTreeViewItem.Create(Self);
- ModItem.Text := mods[i];
- ModItem.DragMode := TDragMode.dmAutomatic;
- ModItem.Text := 'Module-' + mods[i];
- ModItem.TagString := mods[i];
- FModulesItem.AddObject(ModItem);
- end;
- FModulesItem.ExpandAll;
- finally
- FModulesItem.EndUpdate;
- end;
- end
- );
-
- FTerminate := TEvent.CreateEvent;
- FRandom := TList.Create;
-
- // Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364]
- FServer := TAuraTABFileServer.Create('\\COFFEE\TickData\Pepperstone');
-
- SymbolsComboBox.Enabled := false;
- ChartButton.Enabled := false;
- LoadButton.Enabled := false;
-
- FSymbols := FServer.EnumerateSymbols;
-
- SymbolsComboBox.ProcessSignal(
- FSymbols.Done.Signal,
- procedure
- begin
- SymbolsComboBox.BeginUpdate;
- try
- SymbolsComboBox.Items.Clear;
- SymbolsComboBox.Items.AddStrings(FSymbols.WaitFor);
- if SymbolsComboBox.Items.Count > 0 then
- begin
- if SymbolsComboBox.ItemIndex < 0 then
- SymbolsComboBox.ItemIndex := SymbolsComboBox.Items.IndexOf('GER40');
- SymbolsComboBox.Enabled := true;
- ChartButton.Enabled := true;
- LoadButton.Enabled := true;
- end;
- finally
- SymbolsComboBox.EndUpdate;
- end;
- end
- );
-
- NewWorkspace;
-end;
-
-procedure TForm1.FormDestroy(Sender: TObject);
-begin
- FSymbols.WaitFor;
- FTerminate.Notify;
- TaskManager.WaitFor(FProcessDone);
- 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
- exit;
- var Layout := CurrLayout;
- if Layout = nil then
- exit;
-
- var Stream := FServer.CreateStream(FSymbols.WaitFor[SymbolsComboBox.ItemIndex]);
- var Data := TDataStreamProvider.Create(30000, 10000, Stream);
-
- var path := TPath.Create(Self);
- path.Parent := Layout;
- path.Align := TAlignLayout.None;
-
- path.ProcessSignal(
- Data.Changed,
- procedure
- begin
- var Prices := Data.Value;
-
- if Prices.Count > 0 then
- begin
- path.BeginUpdate;
- try
- path.Data.Clear;
- path.Data.MoveTo(PointF(path.Width - 1, Prices[0].Data.Ask));
- for var i := 1 to Prices.Count - 1 do
- path.Data.LineTo(PointF(path.Width - i - 1, Prices[i].Data.Ask));
- finally
- path.EndUpdate;
- end;
- end;
- end
- );
-end;
-
-procedure TForm1.RandomButtonClick(Sender: TObject);
-begin
- var Layout := CurrLayout;
- if Layout = nil then
- exit;
-
- var rnd: TRndItem;
-
- 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 := Layout;
- rnd.Labl.Align := TAlignLayout.None;
- rnd.Labl.WordWrap := false;
- rnd.Labl.Width := 300;
- rnd.Labl.Text := rnd.Stream.Symbol + ' loading...';
- FRandom.Add(rnd);
-
- var Proc :=
- procedure(idx: Integer)
- begin
- FRandom[idx]
- .Labl
- .ProcessSignal(
- FRandom[idx].Data.Changed,
- procedure
- begin
- var data := FRandom[idx].Data.Value;
- if data.Count > 0 then
- begin
- var dp := data[0];
- FRandom[idx].Labl.Text :=
- FRandom[idx].Stream.Symbol
- + ' '
- + dp.Time.ToString
- + ' '
- + data.TotalCount.ToString
- + ' '
- + dp.Data.Ask.ToString;
- end;
- end);
- end;
- Proc(FRandom.Count - 1);
-end;
-
-procedure TForm1.TestActionExecute(Sender: TObject);
-begin
- TestPopup.IsOpen := TestAction.Checked;
-end;
-
-procedure TForm1.AddWorkspaceActionExecute(Sender: TObject);
-begin
- NewWorkspace;
-end;
-
-procedure TForm1.AlignControl(Control: TControl);
-begin
- var Layout := CurrLayout;
- if Layout = nil then
- exit;
-
- Control.Parent := Layout;
- Control.Width := Layout.Width;
- Control.Position.Y := Layout.ChildrenRect.Bottom + 1;
- Control.Anchors := [TAnchorKind.akLeft, TAnchorKind.akRight];
- Control.Align := TAlignLayout.Top;
-end;
-
-procedure TForm1.ChartButtonClick(Sender: TObject);
-begin
- var Layout := CurrLayout;
- if Layout = nil then
- exit;
-
- var Symbol := SelectedSymbol;
- if Symbol = '' then
- exit;
-
- var currPathData := TWriteable>.CreateWriteable.Protect;
- var currLog := TWriteable.CreateWriteable.Protect;
-
- const width = 300;
-
- var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
-
- var done :=
- TaskManager.RunTask(
- nil,
- function: TState
- begin
- var Prices := TDataSeries.CreateDataSeries(width);
- Result :=
- FServer.ProcessData(
- Symbol,
- terminated,
- TMycGenericProcessor>>.Create(
- function(const Values: TArray>): TState
- begin
- Result := TState.Null;
-
- Prices := Prices.Add(Values);
- currLog.Value := Prices.TotalCount.ToString;
-
- var PathData := TPathData.Create;
- if Prices.Count > 0 then
- begin
- var n := Prices.Count;
- PathData.MoveTo(PointF(n - 1, Prices[0].Data.Ask));
- for var i := 1 to n - 1 do
- PathData.LineTo(PointF(n - i - 1, Prices[i].Data.Ask));
-
- for var i := n - 1 downto 0 do
- PathData.LineTo(PointF(n - i - 1, Prices[i].Data.Bid));
- end;
- PathData.ClosePath;
-
- currPathData.Value := TObjectRef.Create(PathData);
- end
- )
- );
- end
- );
-
- FProcessDone := TState.All([FProcessDone, done]);
-
- var path := TPath.Create(Self);
- path.Parent := Layout;
- path.Align := TAlignLayout.None;
-
- path.ProcessSignal(
- currPathData.Changed,
- procedure(out IsDone: Boolean)
- begin
- if currPathData.Value <> nil then
- path.Data.Assign(currPathData.Value.Obj);
- IsDone := done.IsSet;
- end
- );
-end;
-
-function TForm1.CurrLayout: T;
-begin
- if TabControl.ActiveTab = nil then
- exit(nil);
-
- var Res: T := nil;
- TabControl.ActiveTab.EnumControls(
- function(Control: TControl): TEnumControlsResult
- begin
- Result := TEnumControlsResult.Continue;
- if Control is T then
- begin
- Res := Control as T;
- Result := TEnumControlsResult.Stop;
- end;
- end
- );
-
- Result := Res;
-end;
-
-function TForm1.ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor>>): TState;
-var
- dataProvider: IMycConverter>, TArray>>;
-begin
- var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
-
- dataProvider :=
- TMycGenericConverter>, TArray>>.Create(
- function(const Values: TArray>): TArray>
- begin
- SetLength(Result, Length(Values));
- for var i := 0 to High(Result) do
- begin
- Result[i].Time := Values[i].Time;
- Result[i].Data.Ask := Values[i].Data.Ask;
- Result[i].Data.Bid := Values[i].Data.Bid;
- end;
- end
- );
-
- dataProvider.Sender.Link(Processor);
-
- Result := FServer.ProcessData(Symbol, terminated, dataProvider);
-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.Fmx.Chart.Series.pas b/Src/Myc.Fmx.Chart.Series.pas
index 616d7f4..a3d66f7 100644
--- a/Src/Myc.Fmx.Chart.Series.pas
+++ b/Src/Myc.Fmx.Chart.Series.pas
@@ -65,7 +65,8 @@ type
function GetSeries: TMycChart.TSeries; override;
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override; abstract;
procedure Update; override;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override; abstract;
+ procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc);
+ override; abstract;
public
constructor Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider);
destructor Destroy; override;
@@ -78,7 +79,7 @@ type
FDownColor: TMutable;
protected
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
+ procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc); override;
public
constructor Create(
AParent: TMycChart.TPanel;
@@ -94,7 +95,7 @@ type
FLineWidth: Single;
protected
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
+ procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc); override;
public
constructor Create(
AParent: TMycChart.TPanel;
@@ -227,30 +228,41 @@ begin
MaxValue := Max(MaxValue, v);
end;
end;
- Result := (MinValue <> MaxDouble);
+ Result := (MinValue < MaxValue);
end;
-procedure TChartLineLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc);
+procedure TChartLineLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc);
var
points: TPathData;
- i, n: Int64;
+ n: Int64;
begin
points := TPathData.Create;
try
- // Skip warmup data
n := First;
- while (n <= Last) and (IsNaN(FSeries.Data[n])) do
- inc(n);
- if (n > Last) then
- exit;
+ while n < Last do
+ begin
+ // Skip warmup/gap data
+ while (n <= Last) and (IsNaN(FSeries.Data[n])) do
+ inc(n);
- points.MoveTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
- for i := n + 1 to Last do
- if not IsNaN(FSeries.Data[i]) then
- points.LineTo(TPointF.Create(XForm(i), YForm(FSeries.Data[i])));
+ if n < Last then
+ begin
+ points.MoveTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
+ inc( n );
+ while n <= Last do
+ begin
+ if IsNaN(FSeries.Data[n]) then
+ break;
+ points.LineTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
+ inc( n );
+ end;
+ end;
+ end;
Canvas.Stroke.Color := FLineColor;
- Canvas.Stroke.Thickness := FLineWidth;
+ Canvas.Stroke.Thickness := 3; //FLineWidth;
+ Canvas.Stroke.Join := TStrokeJoin.Bevel;
Canvas.DrawPath(points, 1);
finally
points.Free;
@@ -289,7 +301,8 @@ begin
Result := (MinValue <> MaxDouble);
end;
-procedure TChartOhlcLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc);
+procedure TChartOhlcLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc);
var
i: Int64;
x, candleWidth: Single;
diff --git a/Src/Myc.Fmx.Chart.pas b/Src/Myc.Fmx.Chart.pas
index d7f1f43..21f92b4 100644
--- a/Src/Myc.Fmx.Chart.pas
+++ b/Src/Myc.Fmx.Chart.pas
@@ -54,7 +54,8 @@ type
protected
function GetOwner: TMycChart; override; final;
function GetValueRange(First, Last: Int64; out Min, Max: Double): Boolean; virtual; abstract;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); virtual; abstract;
+ procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm: TFunc; const YForm: TFunc); virtual;
+ abstract;
public
constructor Create(AParent: TPanel);
end;
@@ -100,7 +101,7 @@ type
procedure Update(MasterTotalCount: Int64; ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
public
- constructor Create(AOwner: TMycChart);
+ constructor Create(AOwner: TMycChart; AWeight: Single);
destructor Destroy; override;
function AddOhlcSeries(
@@ -206,9 +207,6 @@ begin
TMessageManager
.DefaultManager
.SubscribeToMessage(TIdleMessage, procedure(const Sender: TObject; const M: TMessage) begin DoIdle; end);
-
- // Create the default panel.
- AddPanel.Weight := 1.0;
end;
destructor TMycChart.Destroy;
@@ -222,7 +220,7 @@ end;
function TMycChart.AddPanel: TPanel;
begin
- Result := TPanel.Create(Self);
+ Result := TPanel.Create(Self, IfThen(FPanelList.Count=0, 1, 0.2));
FPanelList.Add(Result);
Repaint;
end;
@@ -305,7 +303,8 @@ var
buttonColor: TAlphaColor;
path: TPathData;
masterTotalCount: Int64;
- panelHeight, totalWeight, currentY: Single;
+ panelHeight, totalWeight: Double;
+ currentY: Single;
begin
inherited;
rect := Self.LocalRect;
@@ -330,7 +329,7 @@ begin
for var i := 0 to PanelCount - 1 do
begin
var panel := Panels[i];
- panelHeight := (panel.Weight / totalWeight) * rect.Height;
+ panelHeight := panel.Weight * rect.Height / totalWeight;
panelRect := TRectF.Create(rect.Left, currentY, rect.Right, currentY + panelHeight);
// Draw panel content
@@ -468,7 +467,6 @@ end;
procedure TMycChart.MouseMove(Shift: TShiftState; X, Y: Single);
var
- dx, dy: Single;
indexDelta: Int64;
maxIndex: Int64;
begin
@@ -481,8 +479,8 @@ begin
// --- Panel Resizing Logic ---
if FIsResizing then
begin
- dy := Y - FDragStartPoint.Point.Y;
- if Abs(dx) < 2 then // Threshold to avoid jitter
+ var dy := FMousePos.Point.Y - FDragStartPoint.Point.Y;
+ if Abs(dy) < 2 then // Threshold to avoid jitter
exit;
var panel1 := Panels[FResizePanelIndex];
@@ -529,8 +527,7 @@ begin
// --- Chart Panning Logic ---
if FIsDragging then
begin
- dx := X - FDragStartPoint.Point.X;
- if Abs(dx) < 2 then // Threshold to avoid jitter
+ if Abs(FMousePos.Point.X - FDragStartPoint.Point.X) < 2 then // Threshold to avoid jitter
exit;
if not Assigned(FXAxisSeries) or (FViewCount <= 0) then
@@ -653,12 +650,12 @@ end;
{ TMycChart.TPanel }
-constructor TMycChart.TPanel.Create(AOwner: TMycChart);
+constructor TMycChart.TPanel.Create(AOwner: TMycChart; AWeight: Single);
begin
inherited Create;
FOwner := AOwner;
FSeriesList := TObjectList.Create(true);
- FWeight := 0.2;
+ FWeight := AWeight;
end;
destructor TMycChart.TPanel.Destroy;
@@ -690,10 +687,11 @@ end;
procedure TMycChart.TPanel.Paint(const Canvas: TCanvas; const Viewport: TRectF; MasterTotalCount, ViewStartIndex, ViewCount: Int64);
var
- localMin, localMax, seriesMin, seriesMax, padding: Double;
+ localMin, localMax, seriesMin, seriesMax: Double;
rangeInitialized: Boolean;
seriesFirst, seriesLast: Int64;
- xTransform, yTransform: TFunc;
+ xTransform: TFunc;
+ yTransform: TFunc;
function CalcView(Series: TMycChart.TSeries; out First, Last: Int64): Boolean;
var
@@ -710,6 +708,8 @@ var
begin
rangeInitialized := false;
+ localMin := 0;
+ localMax := 0;
// 1. Calculate value range (Y-Axis) for this panel only
for var series in FSeriesList do
@@ -736,18 +736,18 @@ begin
if not rangeInitialized then
Exit; // Nothing to draw in this panel
- padding := (localMax - localMin) * 0.1;
- localMin := localMin - padding;
- localMax := localMax + padding;
-
if (localMax - localMin) = 0 then
localMax := localMin + 1; // Avoid division by zero
// 2. Create transforms for this panel
+ const padding = 2;
+ var top: Double := Viewport.Top + padding;
+ var height: Double := Viewport.Height-(2*padding);
+ var range: Double := localMax - localMin;
yTransform :=
function(value: Double): Single
begin
- Result := Viewport.Top + (1 - (value - localMin) / (localMax - localMin)) * Viewport.Height;
+ Result := top + (1 - (value - localMin) / range) * height;
end;
// 3. Paint all layers in this panel
@@ -757,7 +757,7 @@ begin
begin
var offset := MasterTotalCount - series.Series.TotalCount;
xTransform :=
- function(index: Double): Single
+ function(index: Int64): Single
begin
Result := Viewport.Right - (((index + offset - ViewStartIndex) / (ViewCount - 1)) * Viewport.Width);
end;
@@ -885,7 +885,7 @@ begin
Canvas.Stroke.Kind := TBrushKind.Solid;
Canvas.Stroke.Color := TAlphaColors.Gray;
Canvas.Stroke.Thickness := 1;
- Canvas.FillRect(captionRect, 2, 2, AllCorners, 1.0);
+ Canvas.FillRect(captionRect, 2, 2, AllCorners, 0.7);
Canvas.DrawRect(captionRect, 2, 2, AllCorners, 1.0);
// Draw the text
diff --git a/Src/Myc.Trade.DataProvider.pas b/Src/Myc.Trade.DataProvider.pas
index d9313c7..6b167d7 100644
--- a/Src/Myc.Trade.DataProvider.pas
+++ b/Src/Myc.Trade.DataProvider.pas
@@ -8,65 +8,6 @@ uses
Myc.Trade.DataPoint,
Myc.Trade.DataStream;
-type
- TDataStreamProvider = class(TInterfacedObject, TMutable>.IMutable)
- private
- FStream: IDataStream;
- FDataSeries: TDataSeries;
- FNewDataAvailable: TFlag;
- FChunk: TArray>;
- FLookback: Int64;
- function GetChanged: TSignal;
- function GetValue: TDataSeries;
- public
- constructor Create(ALookback, AMaxChunkSize: Int64; const AStream: IDataStream);
- destructor Destroy; override;
- end;
-
- TDataStreamProvider = record
- class function Create(Lookback, MaxChunkSize: Int64; const Stream: IDataStream): TMutable>; static;
- end;
-
implementation
-{ TDataStreamProvider }
-
-constructor TDataStreamProvider.Create(ALookback, AMaxChunkSize: Int64; const AStream: IDataStream);
-begin
- inherited Create;
- FLookback := ALookback;
- SetLength(FChunk, AMaxChunkSize);
- FStream := AStream;
- FNewDataAvailable := TFlag.CreateObserver(FStream.HasData);
- FDataSeries := TDataSeries.CreateDataSeries(ALookback);
-end;
-
-destructor TDataStreamProvider.Destroy;
-begin
- inherited;
-end;
-
-function TDataStreamProvider.GetChanged: TSignal;
-begin
- Result := FStream.HasData;
-end;
-
-function TDataStreamProvider.GetValue: TDataSeries;
-begin
- if FNewDataAvailable.Reset then
- begin
- var cnt := FStream.GetChunk(FChunk);
- if cnt > 0 then
- FDataSeries := FDataSeries.Add(FChunk, 0, cnt);
- end;
- Result := FDataSeries;
-end;
-
-{ TDataStreamProvider }
-
-class function TDataStreamProvider.Create(Lookback, MaxChunkSize: Int64; const Stream: IDataStream): TMutable>;
-begin
- Result := TDataStreamProvider.Create(Lookback, MaxChunkSize, Stream);
-end;
-
end.
diff --git a/Src/Myc.Trade.DataStream.pas b/Src/Myc.Trade.DataStream.pas
index 8a6126f..0e69f5b 100644
--- a/Src/Myc.Trade.DataStream.pas
+++ b/Src/Myc.Trade.DataStream.pas
@@ -1,20 +1,5 @@
unit Myc.Trade.DataStream;
-(*
- Myc.Trade.DataStream
- Provides a data server for loading time-series data and streaming it.
-
- This unit contains the core components for handling historical data:
- - TFileRecord: A generic record for a single time-stamped data entry.
- - IDataServer/TAuraDataServer: A server component responsible for loading and caching
- series of data files from disk. Each server instance manages its own cache,
- but all instances share a central load gate.
- - IDataStream/TDataStream: An interface representing a stream of data points that
- can be consumed sequentially.
- - TAuraFileStream: A concrete implementation of IDataStream that is fed by an
- IDataServer instance.
-*)
-
interface
uses
@@ -30,34 +15,9 @@ uses
Myc.Core.FileCache;
type
- // Represents a generic data stream capable of providing sequential data chunks.
- // IsHistory:
- // - true, if this stream is a history stream. Once HasData becomes false, it reached it's end and will not provide more data.
- // - false, we expect more Data to come. This stream has no end.
- // HasData: set, if a call to GetChunk will return new data.
- IDataStream = interface
- ['{A6E246A2-E84E-49AB-A63E-333E561E488C}']
- function GetHasData: TSignal;
- function GetChunk(var Data: array of TDataPoint): Integer;
- function GetSymbol: String;
- function IsHistory: Boolean;
- property HasData: TSignal read GetHasData;
- property Symbol: String read GetSymbol;
- end;
-
- // Abstract base class for IDataStream implementations.
- TDataStream = class(TInterfacedObject, IDataStream)
- protected
- function GetSymbol: String; virtual; abstract;
- function GetHasData: TSignal; virtual; abstract;
- function GetChunk(var Data: array of TDataPoint): Integer; virtual; abstract;
- function IsHistory: Boolean; virtual; abstract;
- end;
-
// Interface for an instantiable data server.
IDataServer = interface
['{1F8E5A9D-E92A-44C1-9F3F-C4B82A6E94B3}']
- function CreateStream(const Symbol: String): IDataStream;
procedure ClearCache;
function ProcessData(const Symbol: String; const Terminated: TState; const Processor: IMycProcessor>>): TState;
function EnumerateSymbols: TFuture>;
@@ -120,7 +80,6 @@ type
constructor Create(const APath: String);
destructor Destroy; override;
procedure AfterConstruction; override;
- function CreateStream(const Symbol: String): IDataStream; virtual; abstract;
procedure ClearCache;
procedure UpdateSymbols;
// Scans a directory to find the oldest file for a specific symbol.
@@ -136,37 +95,6 @@ type
property Path: String read GetPath;
end;
- // Implements a data stream that reads from Aura-specific historical data files.
- TAuraFileStream = class(TDataStream, IDataStream)
- private
- type
- TDataStream = record
- FileInfo: TAuraDataFile;
- Data: TFuture>>>;
- end;
- private
- FDataServer: TAuraDataServer;
- FSymbol: String;
- FHasData: TEvent;
- FNextReady: TFlag;
- FCurrent: TFuture;
- FNext: TFuture;
- FCurrChunkInFile: Int64;
- FCurrPosInFile: Int64;
- FLastTimeStamp: TDateTime;
- procedure PreloadNext;
- protected
- function GetSymbol: String; override;
- function GetHasData: TSignal; override;
- function GetChunk(var Data: array of TDataPoint): Integer; override;
- function IsHistory: Boolean; override;
- public
- constructor Create(ADataServer: TAuraDataServer; const ASymbol: String);
- destructor Destroy; override;
- procedure AfterConstruction; override;
- procedure BeforeDestruction; override;
- end;
-
// Aura tick data file Ask-Bid
TAuraAskBidFileItem = packed record
@@ -180,7 +108,6 @@ type
class function ReadCompressedData(const InputStream: TStream): TArray>>; override;
class function ReadUncompressedData(const InputStream: TStream): TArray>>; override;
public
- function CreateStream(const Symbol: String): IDataStream; override;
function LoadDataSeries(const InitialFile: TAuraDataFile): TFuture>>;
function ParseFileName(const FileName: string): TAuraDataFile; override;
end;
@@ -195,7 +122,7 @@ uses
const
// The number of records per data chunk when loading files.
- ChunkSize = 8192;
+ ChunkSize = 512;
{ TAuraDataFile }
@@ -463,169 +390,6 @@ begin
);
end;
-{ TAuraFileStream }
-
-constructor TAuraFileStream.Create(ADataServer: TAuraDataServer; const ASymbol: String);
-begin
- inherited Create;
- Assert(Assigned(ADataServer));
- FDataServer := ADataServer;
- FSymbol := ASymbol;
- FNextReady := TFlag.CreateFlag;
- FHasData := TEvent.CreateRouter(FNextReady.State.Signal);
-end;
-
-destructor TAuraFileStream.Destroy;
-begin
- inherited;
-end;
-
-procedure TAuraFileStream.AfterConstruction;
-var
- firstFile: TFuture;
-begin
- inherited;
-
- // We need the path from the server instance to find the file.
- firstFile := FDataServer.FindFirstFile(FSymbol);
-
- FCurrent :=
- firstFile.Chain(
- function(const FileInfo: TAuraDataFile): TDataStream
- begin
- if FileInfo.IsValid then
- begin
- Result.FileInfo := FileInfo;
- Result.Data := FDataServer.LoadDataFile(FileInfo);
- Result.Data.Done.Signal.Subscribe(FNextReady);
- end;
- end
- );
-
- FLastTimeStamp := 0;
- FCurrChunkInFile := 0;
- FCurrPosInFile := 0;
-
- PreloadNext;
-end;
-
-procedure TAuraFileStream.BeforeDestruction;
-begin
- FCurrent.WaitFor;
- FCurrent.Value.Data.WaitFor;
- FNext.WaitFor;
- FNext.Value.Data.WaitFor;
- inherited;
-end;
-
-function TAuraFileStream.GetChunk(var Data: array of TDataPoint): Integer;
-var
- item: TDataPoint;
- fileChunks: TArray>>;
- currentChunk: TArray>;
-label
- loop;
-begin
- Result := 0;
-
- loop:
- if not FCurrent.Done.IsSet then
- exit;
- if not FCurrent.Value.Data.Done.IsSet then
- exit;
-
- if not FCurrent.Value.FileInfo.IsValid then
- exit;
-
- fileChunks := FCurrent.Value.Data.Value;
- if (fileChunks = nil) or (FCurrChunkInFile >= Length(fileChunks)) then
- begin
- // Finished with current file, move to next
- FCurrChunkInFile := 0;
- FCurrPosInFile := 0;
- FCurrent := FNext;
- PreloadNext;
- goto loop;
- end;
-
- var maxLen := Length(Data);
- while (Result < maxLen) do
- begin
- // Check if we need to advance to the next chunk
- if (FCurrChunkInFile < Length(fileChunks)) and (FCurrPosInFile >= Length(fileChunks[FCurrChunkInFile])) then
- begin
- Inc(FCurrChunkInFile);
- FCurrPosInFile := 0;
- end;
-
- // Exit loop if all chunks are processed for the current file
- if FCurrChunkInFile >= Length(fileChunks) then
- break;
-
- currentChunk := fileChunks[FCurrChunkInFile];
- item := currentChunk[FCurrPosInFile];
- if FLastTimeStamp < item.Time then
- begin
- FLastTimeStamp := item.Time;
- Data[Result].Create(FLastTimeStamp, item.Data);
- Inc(Result);
- end;
- Inc(FCurrPosInFile);
- end;
-
- // Notify if there is potentially more data
- if (FCurrChunkInFile < Length(fileChunks)) or FNextReady.Reset then
- begin
- FHasData.Notify;
- end;
-end;
-
-function TAuraFileStream.GetHasData: TSignal;
-begin
- Result := FHasData.Signal;
-end;
-
-function TAuraFileStream.GetSymbol: String;
-begin
- Result := FSymbol;
-end;
-
-function TAuraFileStream.IsHistory: Boolean;
-begin
- Result := True;
-end;
-
-procedure TAuraFileStream.PreloadNext;
-begin
- FNextReady.Reset;
-
- FNext :=
- FCurrent.Chain(
- function(const Prev: TDataStream): TDataStream
- var
- nextFileInfo: TAuraDataFile;
- begin
- if Prev.FileInfo.IsValid then
- begin
- nextFileInfo := Prev.FileInfo.GetNextFile;
- if nextFileInfo.IsValid then
- begin
- Result.FileInfo := nextFileInfo;
- Result.Data := FDataServer.LoadDataFile(nextFileInfo);
- Result.Data.Done.Signal.Subscribe(FNextReady);
- end;
- end;
- end
- );
-end;
-
-{ TAuraTABFileServer }
-
-function TAuraTABFileServer.CreateStream(const Symbol: String): IDataStream;
-begin
- Result := TAuraFileStream.Create(Self, Symbol);
-end;
-
function TAuraTABFileServer.LoadDataSeries(const InitialFile: TAuraDataFile): TFuture>>;
var
loadedState: TState;