Implementing first "strategy" for proof of concept
This commit is contained in:
@@ -10,13 +10,15 @@ uses
|
|||||||
Myc.Aura.Module in '..\Src\Myc.Aura.Module.pas',
|
Myc.Aura.Module in '..\Src\Myc.Aura.Module.pas',
|
||||||
Myc.Aura.Parameter in '..\Src\Myc.Aura.Parameter.pas',
|
Myc.Aura.Parameter in '..\Src\Myc.Aura.Parameter.pas',
|
||||||
TestModule in 'TestModule.pas',
|
TestModule in 'TestModule.pas',
|
||||||
TestChartControl in 'TestChartControl.pas' {TestChartForm};
|
DynamicFMXControl in 'DynamicFMXControl.pas',
|
||||||
|
FirstStrategy in 'FirstStrategy.pas',
|
||||||
|
Myc.Fmx.Chart in 'Myc.Fmx.Chart.pas',
|
||||||
|
Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Application.Initialize;
|
Application.Initialize;
|
||||||
Application.CreateForm(TForm1, Form1);
|
Application.CreateForm(TForm1, Form1);
|
||||||
Application.CreateForm(TTestChartForm, TestChartForm);
|
|
||||||
Application.Run;
|
Application.Run;
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<ProjectVersion>20.3</ProjectVersion>
|
<ProjectVersion>20.3</ProjectVersion>
|
||||||
<FrameworkType>FMX</FrameworkType>
|
<FrameworkType>FMX</FrameworkType>
|
||||||
<Base>True</Base>
|
<Base>True</Base>
|
||||||
<Config Condition="'$(Config)'==''">Release</Config>
|
<Config Condition="'$(Config)'==''">Debug</Config>
|
||||||
<Platform Condition="'$(Platform)'==''">Win64</Platform>
|
<Platform Condition="'$(Platform)'==''">Win64</Platform>
|
||||||
<ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName>
|
<ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName>
|
||||||
<TargetedPlatforms>3</TargetedPlatforms>
|
<TargetedPlatforms>3</TargetedPlatforms>
|
||||||
@@ -138,10 +138,10 @@
|
|||||||
<DCCReference Include="..\Src\Myc.Aura.Module.pas"/>
|
<DCCReference Include="..\Src\Myc.Aura.Module.pas"/>
|
||||||
<DCCReference Include="..\Src\Myc.Aura.Parameter.pas"/>
|
<DCCReference Include="..\Src\Myc.Aura.Parameter.pas"/>
|
||||||
<DCCReference Include="TestModule.pas"/>
|
<DCCReference Include="TestModule.pas"/>
|
||||||
<DCCReference Include="TestChartControl.pas">
|
<DCCReference Include="DynamicFMXControl.pas"/>
|
||||||
<Form>TestChartForm</Form>
|
<DCCReference Include="FirstStrategy.pas"/>
|
||||||
<FormType>fmx</FormType>
|
<DCCReference Include="Myc.Fmx.Chart.pas"/>
|
||||||
</DCCReference>
|
<DCCReference Include="..\Src\Myc.Trade.DataArray.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
@@ -0,0 +1,379 @@
|
|||||||
|
unit DynamicFMXControl;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils, System.Types, System.UITypes, System.Classes,
|
||||||
|
FMX.Controls, FMX.Graphics, FMX.Forms, FMX.StdCtrls, FMX.Types;
|
||||||
|
|
||||||
|
type
|
||||||
|
// Dezidierte Event-Typen für virtuelle Methoden von TControl
|
||||||
|
TControlPaintEvent = reference to procedure;
|
||||||
|
TControlMouseEvent = reference to procedure(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
TControlMouseMoveEvent = reference to procedure(Shift: TShiftState; X, Y: Single);
|
||||||
|
TControlMouseWheelEvent = reference to procedure(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
|
||||||
|
TControlKeyEvent = reference to procedure(var Key: Word; var KeyChar: WideChar; Shift: TShiftState);
|
||||||
|
TControlDragEvent = reference to procedure(const Data: TDragObject; const Point: TPointF);
|
||||||
|
TControlDragOverEvent = reference to procedure(const Data: TDragObject; const Point: TPointF; var Operation: TDragOperation);
|
||||||
|
TControlNotifyEvent = reference to procedure;
|
||||||
|
TControlCanFocusEvent = reference to function: Boolean;
|
||||||
|
TControlShowContextMenuEvent = reference to function(const ScreenPosition: TPointF): Boolean;
|
||||||
|
TControlSetHintEvent = reference to procedure(const AHint: string);
|
||||||
|
TControlGetHintStringEvent = reference to function: string;
|
||||||
|
TControlHasHintEvent = reference to function: Boolean;
|
||||||
|
TControlCanShowHintEvent = reference to function: Boolean;
|
||||||
|
TControlGetDefaultSizeEvent = reference to function: TSizeF;
|
||||||
|
TControlSetVisibleEvent = reference to procedure(const Value: Boolean);
|
||||||
|
TControlSetEnabledEvent = reference to procedure(const Value: Boolean);
|
||||||
|
TControlDoAbsoluteChangedEvent = reference to procedure;
|
||||||
|
|
||||||
|
TDynamicControl = class(TControl)
|
||||||
|
private
|
||||||
|
// Private Felder für die Event-Handler
|
||||||
|
FPaintEvent: TControlPaintEvent;
|
||||||
|
FMouseDownEvent: TControlMouseEvent;
|
||||||
|
FMouseUpEvent: TControlMouseEvent;
|
||||||
|
FMouseMoveEvent: TControlMouseMoveEvent;
|
||||||
|
FMouseWheelEvent: TControlMouseWheelEvent;
|
||||||
|
FKeyDownEvent: TControlKeyEvent;
|
||||||
|
FKeyUpEvent: TControlKeyEvent;
|
||||||
|
FClickEvent: TControlNotifyEvent;
|
||||||
|
FDblClickEvent: TControlNotifyEvent;
|
||||||
|
FDragEnterEvent: TControlDragEvent;
|
||||||
|
FDragOverEvent: TControlDragOverEvent;
|
||||||
|
FDragDropEvent: TControlDragEvent;
|
||||||
|
FDragLeaveEvent: TControlNotifyEvent;
|
||||||
|
FDragEndEvent: TControlNotifyEvent;
|
||||||
|
FEnterEvent: TControlNotifyEvent;
|
||||||
|
FExitEvent: TControlNotifyEvent;
|
||||||
|
FMouseEnterEvent: TControlNotifyEvent;
|
||||||
|
FMouseLeaveEvent: TControlNotifyEvent;
|
||||||
|
FResizeEvent: TControlNotifyEvent;
|
||||||
|
FResizedEvent: TControlNotifyEvent;
|
||||||
|
FCanFocusEvent: TControlCanFocusEvent;
|
||||||
|
FShowContextMenuEvent: TControlShowContextMenuEvent;
|
||||||
|
FSetHintEvent: TControlSetHintEvent;
|
||||||
|
FGetHintStringEvent: TControlGetHintStringEvent;
|
||||||
|
FHasHintEvent: TControlHasHintEvent;
|
||||||
|
FCanShowHintEvent: TControlCanShowHintEvent;
|
||||||
|
FGetDefaultSizeEvent: TControlGetDefaultSizeEvent;
|
||||||
|
FRecalculateAbsoluteMatricesEvent: TControlNotifyEvent;
|
||||||
|
FSetVisibleEvent: TControlSetVisibleEvent;
|
||||||
|
FSetEnabledEvent: TControlSetEnabledEvent;
|
||||||
|
FDoAbsoluteChangedEvent: TControlDoAbsoluteChangedEvent;
|
||||||
|
|
||||||
|
protected
|
||||||
|
// Überschriebene virtuelle Methoden, die die Events auslösen
|
||||||
|
procedure Paint; override;
|
||||||
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
|
||||||
|
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
|
||||||
|
procedure MouseMove(Shift: TShiftState; X, Y: Single); override;
|
||||||
|
procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override;
|
||||||
|
procedure Click; override;
|
||||||
|
procedure DblClick; override;
|
||||||
|
procedure KeyDown(var Key: Word; var KeyChar: WideChar; Shift: TShiftState); override;
|
||||||
|
procedure KeyUp(var Key: Word; var KeyChar: WideChar; Shift: TShiftState); override;
|
||||||
|
procedure DragEnter(const Data: TDragObject; const Point: TPointF); override;
|
||||||
|
procedure DragOver(const Data: TDragObject; const Point: TPointF; var Operation: TDragOperation); override;
|
||||||
|
procedure DragDrop(const Data: TDragObject; const Point: TPointF); override;
|
||||||
|
procedure DragLeave; override;
|
||||||
|
procedure DragEnd; override;
|
||||||
|
procedure DoEnter; override;
|
||||||
|
procedure DoExit; override;
|
||||||
|
procedure DoMouseEnter; override;
|
||||||
|
procedure DoMouseLeave; override;
|
||||||
|
procedure Resize; override;
|
||||||
|
procedure DoResized; override;
|
||||||
|
function GetCanFocus: Boolean; override;
|
||||||
|
function ShowContextMenu(const ScreenPosition: TPointF): Boolean; override;
|
||||||
|
procedure SetHint(const AHint: string); override;
|
||||||
|
function GetHintString: string; override;
|
||||||
|
function HasHint: Boolean; override;
|
||||||
|
function CanShowHint: Boolean; override;
|
||||||
|
function GetDefaultSize: TSizeF; override;
|
||||||
|
procedure RecalculateAbsoluteMatrices; override;
|
||||||
|
procedure SetVisible(const Value: Boolean); override;
|
||||||
|
procedure SetEnabled(const Value: Boolean); override;
|
||||||
|
procedure DoAbsoluteChanged; override;
|
||||||
|
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TComponent); override;
|
||||||
|
|
||||||
|
// Public Properties, die die Events exponieren
|
||||||
|
property OnPaint: TControlPaintEvent read FPaintEvent write FPaintEvent;
|
||||||
|
property OnMouseDown: TControlMouseEvent read FMouseDownEvent write FMouseDownEvent;
|
||||||
|
property OnMouseUp: TControlMouseEvent read FMouseUpEvent write FMouseUpEvent;
|
||||||
|
property OnMouseMove: TControlMouseMoveEvent read FMouseMoveEvent write FMouseMoveEvent;
|
||||||
|
property OnMouseWheel: TControlMouseWheelEvent read FMouseWheelEvent write FMouseWheelEvent;
|
||||||
|
property OnClick: TControlNotifyEvent read FClickEvent write FClickEvent;
|
||||||
|
property OnDblClick: TControlNotifyEvent read FDblClickEvent write FDblClickEvent;
|
||||||
|
property OnKeyDown: TControlKeyEvent read FKeyDownEvent write FKeyDownEvent;
|
||||||
|
property OnKeyUp: TControlKeyEvent read FKeyUpEvent write FKeyUpEvent;
|
||||||
|
property OnDragEnter: TControlDragEvent read FDragEnterEvent write FDragEnterEvent;
|
||||||
|
property OnDragOver: TControlDragOverEvent read FDragOverEvent write FDragOverEvent;
|
||||||
|
property OnDragDrop: TControlDragEvent read FDragDropEvent write FDragDropEvent;
|
||||||
|
property OnDragLeave: TControlNotifyEvent read FDragLeaveEvent write FDragLeaveEvent;
|
||||||
|
property OnDragEnd: TControlNotifyEvent read FDragEndEvent write FDragEndEvent;
|
||||||
|
property OnEnter: TControlNotifyEvent read FEnterEvent write FEnterEvent;
|
||||||
|
property OnExit: TControlNotifyEvent read FExitEvent write FExitEvent;
|
||||||
|
property OnMouseEnter: TControlNotifyEvent read FMouseEnterEvent write FMouseEnterEvent;
|
||||||
|
property OnMouseLeave: TControlNotifyEvent read FMouseLeaveEvent write FMouseLeaveEvent;
|
||||||
|
property OnResize: TControlNotifyEvent read FResizeEvent write FResizeEvent;
|
||||||
|
property OnResized: TControlNotifyEvent read FResizedEvent write FResizedEvent;
|
||||||
|
property OnCanFocus: TControlCanFocusEvent read FCanFocusEvent write FCanFocusEvent;
|
||||||
|
property OnShowContextMenu: TControlShowContextMenuEvent read FShowContextMenuEvent write FShowContextMenuEvent;
|
||||||
|
property OnSetHint: TControlSetHintEvent read FSetHintEvent write FSetHintEvent;
|
||||||
|
property OnGetHintString: TControlGetHintStringEvent read FGetHintStringEvent write FGetHintStringEvent;
|
||||||
|
property OnHasHint: TControlHasHintEvent read FHasHintEvent write FHasHintEvent;
|
||||||
|
property OnCanShowHint: TControlCanShowHintEvent read FCanShowHintEvent write FCanShowHintEvent;
|
||||||
|
property OnGetDefaultSize: TControlGetDefaultSizeEvent read FGetDefaultSizeEvent write FGetDefaultSizeEvent;
|
||||||
|
property OnRecalculateAbsoluteMatrices: TControlNotifyEvent read FRecalculateAbsoluteMatricesEvent write FRecalculateAbsoluteMatricesEvent;
|
||||||
|
property OnSetVisible: TControlSetVisibleEvent read FSetVisibleEvent write FSetVisibleEvent;
|
||||||
|
property OnSetEnabled: TControlSetEnabledEvent read FSetEnabledEvent write FSetEnabledEvent;
|
||||||
|
property OnDoAbsoluteChanged: TControlDoAbsoluteChangedEvent read FDoAbsoluteChangedEvent write FDoAbsoluteChangedEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TDynamicControl }
|
||||||
|
|
||||||
|
constructor TDynamicControl.Create(AOwner: TComponent);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner);
|
||||||
|
Width := 150;
|
||||||
|
Height := 100;
|
||||||
|
HitTest := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDynamicControl.CanShowHint: Boolean;
|
||||||
|
begin
|
||||||
|
if Assigned(FCanShowHintEvent) then
|
||||||
|
Result := FCanShowHintEvent
|
||||||
|
else
|
||||||
|
Result := inherited CanShowHint;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.Click;
|
||||||
|
begin
|
||||||
|
inherited Click;
|
||||||
|
if Assigned(FClickEvent) then
|
||||||
|
FClickEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DblClick;
|
||||||
|
begin
|
||||||
|
inherited DblClick;
|
||||||
|
if Assigned(FDblClickEvent) then
|
||||||
|
FDblClickEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DoAbsoluteChanged;
|
||||||
|
begin
|
||||||
|
inherited DoAbsoluteChanged;
|
||||||
|
if Assigned(FDoAbsoluteChangedEvent) then
|
||||||
|
FDoAbsoluteChangedEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DragDrop(const Data: TDragObject; const Point: TPointF);
|
||||||
|
begin
|
||||||
|
inherited DragDrop(Data, Point);
|
||||||
|
if Assigned(FDragDropEvent) then
|
||||||
|
FDragDropEvent(Data, Point);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DragEnd;
|
||||||
|
begin
|
||||||
|
inherited DragEnd;
|
||||||
|
if Assigned(FDragEndEvent) then
|
||||||
|
FDragEndEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DragEnter(const Data: TDragObject; const Point: TPointF);
|
||||||
|
begin
|
||||||
|
inherited DragEnter(Data, Point);
|
||||||
|
if Assigned(FDragEnterEvent) then
|
||||||
|
FDragEnterEvent(Data, Point);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DragLeave;
|
||||||
|
begin
|
||||||
|
inherited DragLeave;
|
||||||
|
if Assigned(FDragLeaveEvent) then
|
||||||
|
FDragLeaveEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DragOver(const Data: TDragObject; const Point: TPointF; var Operation: TDragOperation);
|
||||||
|
begin
|
||||||
|
inherited DragOver(Data, Point, Operation);
|
||||||
|
if Assigned(FDragOverEvent) then
|
||||||
|
FDragOverEvent(Data, Point, Operation);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DoEnter;
|
||||||
|
begin
|
||||||
|
inherited DoEnter;
|
||||||
|
if Assigned(FEnterEvent) then
|
||||||
|
FEnterEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DoExit;
|
||||||
|
begin
|
||||||
|
inherited DoExit;
|
||||||
|
if Assigned(FExitEvent) then
|
||||||
|
FExitEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DoMouseEnter;
|
||||||
|
begin
|
||||||
|
inherited DoMouseEnter;
|
||||||
|
if Assigned(FMouseEnterEvent) then
|
||||||
|
FMouseEnterEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DoMouseLeave;
|
||||||
|
begin
|
||||||
|
inherited DoMouseLeave;
|
||||||
|
if Assigned(FMouseLeaveEvent) then
|
||||||
|
FMouseLeaveEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.DoResized;
|
||||||
|
begin
|
||||||
|
inherited DoResized;
|
||||||
|
if Assigned(FResizedEvent) then
|
||||||
|
FResizedEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDynamicControl.GetCanFocus: Boolean;
|
||||||
|
begin
|
||||||
|
if Assigned(FCanFocusEvent) then
|
||||||
|
Result := FCanFocusEvent
|
||||||
|
else
|
||||||
|
Result := inherited GetCanFocus;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDynamicControl.GetDefaultSize: TSizeF;
|
||||||
|
begin
|
||||||
|
if Assigned(FGetDefaultSizeEvent) then
|
||||||
|
Result := FGetDefaultSizeEvent
|
||||||
|
else
|
||||||
|
Result := inherited GetDefaultSize;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDynamicControl.GetHintString: string;
|
||||||
|
begin
|
||||||
|
if Assigned(FGetHintStringEvent) then
|
||||||
|
Result := FGetHintStringEvent
|
||||||
|
else
|
||||||
|
Result := inherited GetHintString;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDynamicControl.HasHint: Boolean;
|
||||||
|
begin
|
||||||
|
if Assigned(FHasHintEvent) then
|
||||||
|
Result := FHasHintEvent
|
||||||
|
else
|
||||||
|
Result := inherited HasHint;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.KeyDown(var Key: Word; var KeyChar: WideChar; Shift: TShiftState);
|
||||||
|
begin
|
||||||
|
inherited KeyDown(Key, KeyChar, Shift);
|
||||||
|
if Assigned(FKeyDownEvent) then
|
||||||
|
FKeyDownEvent(Key, KeyChar, Shift);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.KeyUp(var Key: Word; var KeyChar: WideChar; Shift: TShiftState);
|
||||||
|
begin
|
||||||
|
inherited KeyUp(Key, KeyChar, Shift);
|
||||||
|
if Assigned(FKeyUpEvent) then
|
||||||
|
FKeyUpEvent(Key, KeyChar, Shift);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
begin
|
||||||
|
inherited MouseDown(Button, Shift, X, Y);
|
||||||
|
if Assigned(FMouseDownEvent) then
|
||||||
|
FMouseDownEvent(Button, Shift, X, Y);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.MouseMove(Shift: TShiftState; X, Y: Single);
|
||||||
|
begin
|
||||||
|
inherited MouseMove(Shift, X, Y);
|
||||||
|
if Assigned(FMouseMoveEvent) then
|
||||||
|
FMouseMoveEvent(Shift, X, Y);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
begin
|
||||||
|
inherited MouseUp(Button, Shift, X, Y);
|
||||||
|
if Assigned(FMouseUpEvent) then
|
||||||
|
FMouseUpEvent(Button, Shift, X, Y);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
|
||||||
|
begin
|
||||||
|
inherited MouseWheel(Shift, WheelDelta, Handled);
|
||||||
|
if Assigned(FMouseWheelEvent) then
|
||||||
|
FMouseWheelEvent(Shift, WheelDelta, Handled);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.Paint;
|
||||||
|
begin
|
||||||
|
inherited Paint;
|
||||||
|
if Assigned(FPaintEvent) then
|
||||||
|
FPaintEvent
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Canvas.Fill.Color := TAlphaColors.LightSteelBlue;
|
||||||
|
Canvas.FillRect(LocalRect, 0, 0, [], 1);
|
||||||
|
Canvas.Stroke.Color := TAlphaColors.Gray;
|
||||||
|
Canvas.Stroke.Thickness := 1;
|
||||||
|
Canvas.DrawRect(LocalRect, 0, 0, [], 1);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.RecalculateAbsoluteMatrices;
|
||||||
|
begin
|
||||||
|
inherited RecalculateAbsoluteMatrices;
|
||||||
|
if Assigned(FRecalculateAbsoluteMatricesEvent) then
|
||||||
|
FRecalculateAbsoluteMatricesEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.Resize;
|
||||||
|
begin
|
||||||
|
inherited Resize;
|
||||||
|
if Assigned(FResizeEvent) then
|
||||||
|
FResizeEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.SetEnabled(const Value: Boolean);
|
||||||
|
begin
|
||||||
|
inherited SetEnabled(Value);
|
||||||
|
if Assigned(FSetEnabledEvent) then
|
||||||
|
FSetEnabledEvent(Value);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.SetHint(const AHint: string);
|
||||||
|
begin
|
||||||
|
inherited SetHint(AHint);
|
||||||
|
if Assigned(FSetHintEvent) then
|
||||||
|
FSetHintEvent(AHint);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDynamicControl.SetVisible(const Value: Boolean);
|
||||||
|
begin
|
||||||
|
inherited SetVisible(Value);
|
||||||
|
if Assigned(FSetVisibleEvent) then
|
||||||
|
FSetVisibleEvent(Value);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDynamicControl.ShowContextMenu(const ScreenPosition: TPointF): Boolean;
|
||||||
|
begin
|
||||||
|
if Assigned(FShowContextMenuEvent) then
|
||||||
|
Result := FShowContextMenuEvent(ScreenPosition)
|
||||||
|
else
|
||||||
|
Result := inherited ShowContextMenu(ScreenPosition);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -0,0 +1,457 @@
|
|||||||
|
unit FirstStrategy;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.Generics.Collections,
|
||||||
|
Myc.Signals,
|
||||||
|
Myc.Lazy,
|
||||||
|
Myc.Trade.DataPoint,
|
||||||
|
Myc.Trade.DataArray,
|
||||||
|
Myc.Core.Notifier;
|
||||||
|
|
||||||
|
type
|
||||||
|
TTimeframe = (M1, M5, H1, D);
|
||||||
|
|
||||||
|
TTag = Pointer;
|
||||||
|
|
||||||
|
IMycBroadcast<T> = interface
|
||||||
|
function Link(const Strategy: IMycProcessor<T>): TTag;
|
||||||
|
procedure Unlink(Tag: TTag);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// A contained object that broadcasts Values to linked strategies
|
||||||
|
TMycBroadcast<T> = class(TContainedObject, IMycBroadcast<T>)
|
||||||
|
private
|
||||||
|
FLinkedStrategies: TMycNotifyList<IMycProcessor<T>>;
|
||||||
|
protected
|
||||||
|
// Link a strategy
|
||||||
|
function Link(const Strategy: IMycProcessor<T>): TTag;
|
||||||
|
// Unlink a linked strategy
|
||||||
|
procedure Unlink(Tag: TTag);
|
||||||
|
// Broadcasts the given data points to all linked strategies.
|
||||||
|
procedure Broadcast(const Value: T);
|
||||||
|
public
|
||||||
|
constructor Create(const Controller: IInterface);
|
||||||
|
destructor Destroy; override;
|
||||||
|
end;
|
||||||
|
|
||||||
|
IMycConverter<S, T> = interface(IMycProcessor<S>)
|
||||||
|
function GetObservers: IMycBroadcast<T>;
|
||||||
|
property Observers: IMycBroadcast<T> read GetObservers;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TMycConverter<S, T> = class abstract(TInterfacedObject, IMycProcessor<S>, IMycConverter<S, T>)
|
||||||
|
private
|
||||||
|
FObservers: TMycBroadcast<T>;
|
||||||
|
protected
|
||||||
|
procedure Broadcast(const Value: T);
|
||||||
|
function GetObservers: IMycBroadcast<T>;
|
||||||
|
procedure ProcessData(const Value: S); virtual; abstract;
|
||||||
|
public
|
||||||
|
constructor Create;
|
||||||
|
destructor Destroy; override;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TMycGenericConverter<S, T> = class(TMycConverter<S, T>)
|
||||||
|
type
|
||||||
|
TConvertFunc = reference to function(const Value: S): T;
|
||||||
|
private
|
||||||
|
FFunc: TConvertFunc;
|
||||||
|
protected
|
||||||
|
procedure ProcessData(const Value: S); override;
|
||||||
|
public
|
||||||
|
constructor Create(const AFunc: TConvertFunc);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Series
|
||||||
|
|
||||||
|
IMycSeriesConverter<S, T> = interface(IMycConverter<TArray<S>, T>)
|
||||||
|
function GetLookback: Integer;
|
||||||
|
property Lookback: Integer read GetLookback;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Indicator
|
||||||
|
|
||||||
|
IMycIndicator<S, T> = interface(IMycSeriesConverter<S, TArray<T>>)
|
||||||
|
end;
|
||||||
|
|
||||||
|
TMycIndicator<S, T> = class abstract(TMycConverter<TArray<S>, TArray<T>>, IMycIndicator<S, T>)
|
||||||
|
protected
|
||||||
|
function GetLookback: Integer; virtual; abstract;
|
||||||
|
procedure ProcessData(const Value: TArray<S>); override; abstract;
|
||||||
|
public
|
||||||
|
property Lookback: Integer read GetLookback;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TMycGenericIndicator<S, T> = class(TMycIndicator<S, T>)
|
||||||
|
type
|
||||||
|
TConvertFunc = reference to function(const Value: S): T;
|
||||||
|
private
|
||||||
|
FLookback: Integer;
|
||||||
|
FFunc: TConvertFunc;
|
||||||
|
protected
|
||||||
|
function GetLookback: Integer; override;
|
||||||
|
procedure ProcessData(const Value: TArray<S>); override;
|
||||||
|
public
|
||||||
|
constructor Create(ALookback: Integer; const AFunc: TConvertFunc);
|
||||||
|
end;
|
||||||
|
|
||||||
|
ITicksToTimeframe = interface(IMycConverter<TArray<TDataPoint<TAskBidItem>>, TArray<TDataPoint<TOhlcItem>>>)
|
||||||
|
function GetCurrentBar: TDataPoint<TOhlcItem>;
|
||||||
|
function GetStateText: TWriteable<String>;
|
||||||
|
function GetTimeframe: TTimeframe;
|
||||||
|
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
|
||||||
|
property StateText: TWriteable<String> read GetStateText;
|
||||||
|
property Timeframe: TTimeframe read GetTimeframe;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TTicksToTimeframe = class(TMycConverter<TArray<TDataPoint<TAskBidItem>>, TArray<TDataPoint<TOhlcItem>>>, ITicksToTimeframe)
|
||||||
|
private
|
||||||
|
FTimeframe: TTimeframe;
|
||||||
|
FStateText: TWriteable<String>;
|
||||||
|
// Stores the currently aggregating OHLC data.
|
||||||
|
FCurrentBar: TDataPoint<TOhlcItem>;
|
||||||
|
function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
|
||||||
|
function GetCurrentBar: TDataPoint<TOhlcItem>;
|
||||||
|
function GetStateText: TWriteable<String>;
|
||||||
|
function GetTimeframe: TTimeframe;
|
||||||
|
public
|
||||||
|
constructor Create(const ATimeframe: TTimeframe; const AStateText: TWriteable<String>);
|
||||||
|
|
||||||
|
// Process new data. This is called concurrently and must not have side effects out of the scope of this class!
|
||||||
|
procedure ProcessData(const Values: TArray<TDataPoint<TAskBidItem>>); override;
|
||||||
|
|
||||||
|
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
|
||||||
|
property StateText: TWriteable<String> read GetStateText;
|
||||||
|
property Timeframe: TTimeframe read GetTimeframe;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Implements the Hull Moving Average indicator.
|
||||||
|
THullMovingAverage = class(TMycIndicator<Double, Double>)
|
||||||
|
private
|
||||||
|
FPeriod: Integer;
|
||||||
|
FPeriodHalf: Integer;
|
||||||
|
FPeriodSqrt: Integer;
|
||||||
|
// Source data for HMA calculation
|
||||||
|
FSourceData: TMycDataArray<Double>;
|
||||||
|
// Intermediate data series for HMA calculation (2*WMA(n/2) - WMA(n))
|
||||||
|
FDiffSeries: TMycDataArray<Double>;
|
||||||
|
// Calculates the Weighted Moving Average for the most recent data.
|
||||||
|
function CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double;
|
||||||
|
protected
|
||||||
|
procedure ProcessData(const Values: TArray<Double>); override;
|
||||||
|
function GetLookback: Integer; override;
|
||||||
|
public
|
||||||
|
constructor Create(const APeriod: Integer);
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.DateUtils,
|
||||||
|
System.Math;
|
||||||
|
|
||||||
|
{ TMycBroadcast<T> }
|
||||||
|
|
||||||
|
constructor TMycBroadcast<T>.Create(const Controller: IInterface);
|
||||||
|
begin
|
||||||
|
inherited Create(Controller);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TMycBroadcast<T>.Destroy;
|
||||||
|
begin
|
||||||
|
FLinkedStrategies.Finalize;
|
||||||
|
inherited Destroy;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycBroadcast<T>.Broadcast(const Value: T);
|
||||||
|
begin
|
||||||
|
var cValue := Value;
|
||||||
|
|
||||||
|
FLinkedStrategies.Lock;
|
||||||
|
try
|
||||||
|
FLinkedStrategies.Notify(
|
||||||
|
function(const Processor: IMycProcessor<T>): Boolean
|
||||||
|
begin
|
||||||
|
Processor.ProcessData(cValue);
|
||||||
|
Result := true;
|
||||||
|
end
|
||||||
|
);
|
||||||
|
finally
|
||||||
|
FLinkedStrategies.Release;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycBroadcast<T>.Link(const Strategy: IMycProcessor<T>): TTag;
|
||||||
|
begin
|
||||||
|
// Add the strategy to the notification list
|
||||||
|
FLinkedStrategies.Lock;
|
||||||
|
try
|
||||||
|
Result := FLinkedStrategies.Advise(Strategy);
|
||||||
|
finally
|
||||||
|
FLinkedStrategies.Release;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycBroadcast<T>.Unlink(Tag: TTag);
|
||||||
|
begin
|
||||||
|
FLinkedStrategies.Lock;
|
||||||
|
try
|
||||||
|
FLinkedStrategies.Unadvise(Tag);
|
||||||
|
finally
|
||||||
|
FLinkedStrategies.Release;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TTicksToTimeframe }
|
||||||
|
|
||||||
|
constructor TTicksToTimeframe.Create(const ATimeframe: TTimeframe; const AStateText: TWriteable<String>);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FStateText := AStateText;
|
||||||
|
FTimeframe := ATimeframe;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTicksToTimeframe.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
|
||||||
|
begin
|
||||||
|
// Align the time grid to UTC 0:00 using functions from System.DateUtils
|
||||||
|
case Timeframe of
|
||||||
|
M1: Result := RecodeSecond(RecodeMilliSecond(TimeStamp, 0), 0);
|
||||||
|
M5: Result := RecodeMinute(RecodeSecond(RecodeMilliSecond(TimeStamp, 0), 0), MinuteOf(TimeStamp) - MinuteOf(TimeStamp) mod 5);
|
||||||
|
H1: Result := RecodeMinute(RecodeSecond(RecodeMilliSecond(TimeStamp, 0), 0), 0);
|
||||||
|
D: Result := StartOfTheDay(TimeStamp);
|
||||||
|
else
|
||||||
|
Result := 0;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTicksToTimeframe.GetCurrentBar: TDataPoint<TOhlcItem>;
|
||||||
|
begin
|
||||||
|
Result := FCurrentBar;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTicksToTimeframe.GetStateText: TWriteable<String>;
|
||||||
|
begin
|
||||||
|
Result := FStateText;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTicksToTimeframe.GetTimeframe: TTimeframe;
|
||||||
|
begin
|
||||||
|
Result := FTimeframe;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTicksToTimeframe.ProcessData(const Values: TArray<TDataPoint<TAskBidItem>>);
|
||||||
|
var
|
||||||
|
point: TDataPoint<TAskBidItem>;
|
||||||
|
midPrice: Single;
|
||||||
|
barStartTime: TDateTime;
|
||||||
|
lastBarTime: TDateTime;
|
||||||
|
currentBar: TOhlcItem;
|
||||||
|
producedBars: TList<TDataPoint<TOhlcItem>>;
|
||||||
|
begin
|
||||||
|
producedBars := TList<TDataPoint<TOhlcItem>>.Create;
|
||||||
|
try
|
||||||
|
// Process each incoming data point
|
||||||
|
for point in Values do
|
||||||
|
begin
|
||||||
|
midPrice := (point.Data.Ask + point.Data.Bid) / 2;
|
||||||
|
|
||||||
|
// Update bar for the strategy's timeframe
|
||||||
|
barStartTime := GetBarStartTime(point.Time, FTimeframe);
|
||||||
|
lastBarTime := FCurrentBar.Time;
|
||||||
|
|
||||||
|
if (barStartTime > lastBarTime) then
|
||||||
|
begin
|
||||||
|
// A new bar starts, so the previous one is now complete.
|
||||||
|
if (lastBarTime > 0) then
|
||||||
|
begin
|
||||||
|
producedBars.Add(FCurrentBar);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Start a new bar, Volume is 1 because this is the first tick.
|
||||||
|
currentBar := TOhlcItem.Create(midPrice, midPrice, midPrice, midPrice, 1);
|
||||||
|
FCurrentBar.Data := currentBar;
|
||||||
|
FCurrentBar.Time := barStartTime;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// Update the currently aggregating bar
|
||||||
|
currentBar := FCurrentBar.Data;
|
||||||
|
currentBar.High := Max(currentBar.High, midPrice);
|
||||||
|
currentBar.Low := Min(currentBar.Low, midPrice);
|
||||||
|
currentBar.Close := midPrice;
|
||||||
|
// Volume is the number of ticks needed to build the complete bar.
|
||||||
|
currentBar.Volume := currentBar.Volume + 1;
|
||||||
|
FCurrentBar.Data := currentBar;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
with FCurrentBar do
|
||||||
|
FStateText.Value :=
|
||||||
|
Format('Cuur Bar: O:%.5f H:%.5f L:%.5f C:%.5f V:%.0f', [Data.Open, Data.High, Data.Low, Data.Close, Data.Volume]);
|
||||||
|
|
||||||
|
Broadcast(producedBars.ToArray);
|
||||||
|
finally
|
||||||
|
producedBars.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ THullMovingAverage }
|
||||||
|
|
||||||
|
constructor THullMovingAverage.Create(const APeriod: Integer);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FPeriod := APeriod;
|
||||||
|
FPeriodHalf := APeriod div 2;
|
||||||
|
FPeriodSqrt := Round(Sqrt(APeriod));
|
||||||
|
|
||||||
|
// Initialize data arrays.
|
||||||
|
FSourceData := TMycDataArray<Double>.CreateEmpty;
|
||||||
|
FDiffSeries := TMycDataArray<Double>.CreateEmpty;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function THullMovingAverage.CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double;
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
numerator: Double;
|
||||||
|
denominator: Int64;
|
||||||
|
begin
|
||||||
|
// Ensure there is enough data to calculate the WMA
|
||||||
|
if (Series.Count < Period) or (Period <= 0) then
|
||||||
|
Exit(0.0);
|
||||||
|
|
||||||
|
numerator := 0;
|
||||||
|
// The sum of weights (1 + 2 + ... + Period)
|
||||||
|
denominator := Period * (Period + 1) div 2;
|
||||||
|
|
||||||
|
if (denominator = 0) then
|
||||||
|
Exit(0.0);
|
||||||
|
|
||||||
|
for i := 0 to Period - 1 do
|
||||||
|
begin
|
||||||
|
// Newest data (index 0) gets the highest weight (Period)
|
||||||
|
numerator := numerator + Series[i] * (Period - i);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := numerator / denominator;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function THullMovingAverage.GetLookback: Integer;
|
||||||
|
begin
|
||||||
|
Result := FPeriod + FPeriodSqrt - 1;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure THullMovingAverage.ProcessData(const Values: TArray<Double>);
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
price: Double;
|
||||||
|
wmaHalf, wmaFull, diff: Double;
|
||||||
|
hma: Double;
|
||||||
|
resultArray: TArray<Double>;
|
||||||
|
begin
|
||||||
|
// Pre-allocate the result array since its size is known in advance.
|
||||||
|
SetLength(resultArray, Length(Values));
|
||||||
|
|
||||||
|
for i := 0 to High(Values) do
|
||||||
|
begin
|
||||||
|
price := Values[i];
|
||||||
|
|
||||||
|
// Default HMA to 0.0 for the warm-up period.
|
||||||
|
hma := 0.0;
|
||||||
|
|
||||||
|
// Add new price to the source data array, respecting the lookback period.
|
||||||
|
FSourceData := FSourceData.Add(price, FPeriod);
|
||||||
|
|
||||||
|
// Check if there is enough data to start the first stage of calculation.
|
||||||
|
if (FSourceData.Count >= FPeriod) then
|
||||||
|
begin
|
||||||
|
// Calculate the two WMAs for the first step.
|
||||||
|
wmaHalf := CalculateWMA(FSourceData, FPeriodHalf);
|
||||||
|
wmaFull := CalculateWMA(FSourceData, FPeriod);
|
||||||
|
|
||||||
|
// Calculate the difference and add to the intermediate series.
|
||||||
|
diff := 2 * wmaHalf - wmaFull;
|
||||||
|
FDiffSeries := FDiffSeries.Add(diff, FPeriodSqrt);
|
||||||
|
|
||||||
|
// Check if there is enough intermediate data for the final calculation.
|
||||||
|
if (FDiffSeries.Count >= FPeriodSqrt) then
|
||||||
|
begin
|
||||||
|
// Calculate the final HMA value, overwriting the default 0.0.
|
||||||
|
hma := CalculateWMA(FDiffSeries, FPeriodSqrt);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Assign the result (either the calculated HMA or 0.0) directly into the array.
|
||||||
|
resultArray[i] := hma;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Broadcast the result array if the input was not empty.
|
||||||
|
if (Length(resultArray) > 0) then
|
||||||
|
begin
|
||||||
|
Broadcast(resultArray);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TMycGenericConverter<S, T> }
|
||||||
|
|
||||||
|
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FFunc := AFunc;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycGenericConverter<S, T>.ProcessData(const Value: S);
|
||||||
|
begin
|
||||||
|
Broadcast(FFunc(Value));
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TMycGenericIndicator<S, T>.Create(ALookback: Integer; const AFunc: TConvertFunc);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FFunc := AFunc;
|
||||||
|
FLookback := ALookback;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycGenericIndicator<S, T>.GetLookback: Integer;
|
||||||
|
begin
|
||||||
|
Result := FLookback;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycGenericIndicator<S, T>.ProcessData(const Value: TArray<S>);
|
||||||
|
var
|
||||||
|
Arr: TArray<T>;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
SetLength(Arr, Length(Value));
|
||||||
|
for var i := 0 to High(Arr) do
|
||||||
|
Arr[i] := FFunc(Value[i]);
|
||||||
|
Broadcast(Arr);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TMycConverter<S, T> }
|
||||||
|
|
||||||
|
constructor TMycConverter<S, T>.Create;
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FObservers := TMycBroadcast<T>.Create(Self);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TMycConverter<S, T>.Destroy;
|
||||||
|
begin
|
||||||
|
FObservers.Free;
|
||||||
|
inherited Destroy;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycConverter<S, T>.Broadcast(const Value: T);
|
||||||
|
begin
|
||||||
|
FObservers.Broadcast(Value);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycConverter<S, T>.GetObservers: IMycBroadcast<T>;
|
||||||
|
begin
|
||||||
|
Result := FObservers;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -70,7 +70,7 @@ object Form1: TForm1
|
|||||||
Size.Height = 400.00000000000000000
|
Size.Height = 400.00000000000000000
|
||||||
Size.PlatformDefault = False
|
Size.PlatformDefault = False
|
||||||
TabOrder = 7
|
TabOrder = 7
|
||||||
object FlowLayout1: TFlowLayout
|
object FlowLayout: TFlowLayout
|
||||||
Position.X = 224.00000000000000000
|
Position.X = 224.00000000000000000
|
||||||
Position.Y = 167.00000000000000000
|
Position.Y = 167.00000000000000000
|
||||||
TabOrder = 4
|
TabOrder = 4
|
||||||
@@ -128,12 +128,15 @@ object Form1: TForm1
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
object SpeedButton1: TSpeedButton
|
object StrategyButton: TSpeedButton
|
||||||
Position.X = 376.00000000000000000
|
Align = FitLeft
|
||||||
Position.Y = 8.00000000000000000
|
Position.X = 290.90911865234370000
|
||||||
Text = 'SpeedButton1'
|
Size.Width = 145.45446777343750000
|
||||||
|
Size.Height = 40.00000000000000000
|
||||||
|
Size.PlatformDefault = False
|
||||||
|
Text = 'Strategy'
|
||||||
TextSettings.Trimming = None
|
TextSettings.Trimming = None
|
||||||
OnClick = SpeedButton1Click
|
OnClick = StrategyButtonClick
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+145
-48
@@ -44,7 +44,9 @@ uses
|
|||||||
FMX.ImgList,
|
FMX.ImgList,
|
||||||
System.Actions,
|
System.Actions,
|
||||||
FMX.ActnList,
|
FMX.ActnList,
|
||||||
TestChartControl;
|
DynamicFMXControl,
|
||||||
|
FirstStrategy,
|
||||||
|
Myc.FMX.Chart;
|
||||||
|
|
||||||
type
|
type
|
||||||
TForm1 = class(TForm)
|
TForm1 = class(TForm)
|
||||||
@@ -69,9 +71,9 @@ type
|
|||||||
TreeView: TTreeView;
|
TreeView: TTreeView;
|
||||||
TestButton: TSpeedButton;
|
TestButton: TSpeedButton;
|
||||||
TestAction: TAction;
|
TestAction: TAction;
|
||||||
SpeedButton1: TSpeedButton;
|
|
||||||
TestPopup: TPopup;
|
TestPopup: TPopup;
|
||||||
FlowLayout1: TFlowLayout;
|
FlowLayout: TFlowLayout;
|
||||||
|
StrategyButton: TSpeedButton;
|
||||||
procedure RandomButtonClick(Sender: TObject);
|
procedure RandomButtonClick(Sender: TObject);
|
||||||
procedure FormCreate(Sender: TObject);
|
procedure FormCreate(Sender: TObject);
|
||||||
procedure FormDestroy(Sender: TObject);
|
procedure FormDestroy(Sender: TObject);
|
||||||
@@ -82,31 +84,33 @@ type
|
|||||||
procedure TreeViewDblClick(Sender: TObject);
|
procedure TreeViewDblClick(Sender: TObject);
|
||||||
procedure AddWorkspaceActionExecute(Sender: TObject);
|
procedure AddWorkspaceActionExecute(Sender: TObject);
|
||||||
procedure TestActionExecute(Sender: TObject);
|
procedure TestActionExecute(Sender: TObject);
|
||||||
procedure SpeedButton1Click(Sender: TObject);
|
procedure StrategyButtonClick(Sender: TObject);
|
||||||
private
|
private
|
||||||
const
|
const
|
||||||
cnt = 20;
|
cnt = 20;
|
||||||
type
|
type
|
||||||
TRndItem = record
|
TRndItem = record
|
||||||
Stream: IDataStream<TAskBidItem>;
|
Stream: IDataStream<TAuraAskBidFileItem>;
|
||||||
Data: TMutable<TDataSeries<TAskBidItem>>;
|
Data: TMutable<TDataSeries<TAuraAskBidFileItem>>;
|
||||||
Labl: TLabel;
|
Labl: TLabel;
|
||||||
end;
|
end;
|
||||||
private
|
private
|
||||||
FOnEvent: TNotifyEvent;
|
FOnEvent: TNotifyEvent;
|
||||||
{ Private declarations }
|
{ Private declarations }
|
||||||
FServer: IDataServer<TAskBidItem>;
|
FServer: IDataServer<TAuraAskBidFileItem>;
|
||||||
FSymbols: TFuture<TArray<String>>;
|
FSymbols: TFuture<TArray<String>>;
|
||||||
FRandom: TList<TRndItem>;
|
FRandom: TList<TRndItem>;
|
||||||
FTerminate: TEvent;
|
FTerminate: TEvent;
|
||||||
FLoadDone: TState;
|
FProcessDone: TState;
|
||||||
FApplication: IAuraApplication;
|
FApplication: IAuraApplication;
|
||||||
FModulesItem: TTreeViewItem;
|
FModulesItem: TTreeViewItem;
|
||||||
function SelectedSymbol: String;
|
function SelectedSymbol: String;
|
||||||
|
function ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor<TArray<TDataPoint<TAskBidItem>>>): TState;
|
||||||
|
|
||||||
public
|
public
|
||||||
procedure NewWorkspace;
|
procedure NewWorkspace;
|
||||||
function CurrLayout: TFlowLayout;
|
function CurrLayout<T: TControl>: T;
|
||||||
|
procedure AlignControl(Control: TControl);
|
||||||
published
|
published
|
||||||
property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
|
property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
|
||||||
end;
|
end;
|
||||||
@@ -134,10 +138,6 @@ begin
|
|||||||
scrollbox.Parent := tab;
|
scrollbox.Parent := tab;
|
||||||
scrollbox.Align := TAlignLayout.Client;
|
scrollbox.Align := TAlignLayout.Client;
|
||||||
|
|
||||||
var flow := TFlowLayout.Create(Self);
|
|
||||||
flow.Parent := tab;
|
|
||||||
flow.Align := TAlignLayout.Client;
|
|
||||||
|
|
||||||
tab.ProcessSignal(ws.Name.Changed, procedure begin tab.Text := ws.Caption; end);
|
tab.ProcessSignal(ws.Name.Changed, procedure begin tab.Text := ws.Caption; end);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -146,6 +146,68 @@ begin
|
|||||||
FTerminate.Notify;
|
FTerminate.Notify;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TForm1.StrategyButtonClick(Sender: TObject);
|
||||||
|
begin
|
||||||
|
var Layout := CurrLayout<TVertScrollBox>;
|
||||||
|
if Layout = nil then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
var Symbol := SelectedSymbol;
|
||||||
|
if Symbol = '' then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
var stateText := TWriteable<String>.CreateWriteable.Protect;
|
||||||
|
var stateLabel := TLabel.Create(Self);
|
||||||
|
AlignControl( stateLabel );
|
||||||
|
|
||||||
|
stateLabel.Text := 'Initializing...';
|
||||||
|
|
||||||
|
var chart := TMycChart.Create(Self);
|
||||||
|
AlignControl( chart );
|
||||||
|
chart.Height := Layout.ChildrenRect.Width*9/16;
|
||||||
|
|
||||||
|
/////
|
||||||
|
|
||||||
|
var strategy: ITicksToTimeframe := TTicksToTimeframe.Create( D, stateText );
|
||||||
|
|
||||||
|
var Ohlc: IMycIndicator<TDataPoint<TOhlcItem>, TOhlcItem> := TMycGenericIndicator<TDataPoint<TOhlcItem>, TOhlcItem>.Create( 0,
|
||||||
|
function( const Ohlc: TDataPoint<TOhlcItem> ): TOhlcItem
|
||||||
|
begin
|
||||||
|
Result := Ohlc.Data;
|
||||||
|
end );
|
||||||
|
|
||||||
|
strategy.Observers.Link(Ohlc);
|
||||||
|
|
||||||
|
var Closes: IMycIndicator<TOhlcItem, Double> := TMycGenericIndicator<TOhlcItem, Double>.Create( 0,
|
||||||
|
function( const Ohlc: TOhlcItem ): Double
|
||||||
|
begin
|
||||||
|
Result := Ohlc.Close;
|
||||||
|
end );
|
||||||
|
|
||||||
|
Ohlc.Observers.Link( Closes );
|
||||||
|
|
||||||
|
var Hull: IMycIndicator<Double, Double> := THullMovingAverage.Create( 50 );
|
||||||
|
|
||||||
|
Closes.Observers.Link( Hull );
|
||||||
|
|
||||||
|
Hull.Observers.Link(chart.CreateDoubleListener);
|
||||||
|
|
||||||
|
var done := ExecuteStrategy( Symbol, strategy );
|
||||||
|
|
||||||
|
/////
|
||||||
|
|
||||||
|
stateLabel.ProcessSignal(
|
||||||
|
stateText.Changed, done,
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
stateLabel.Text := stateText.Value;
|
||||||
|
end );
|
||||||
|
|
||||||
|
FProcessDone := TState.All([FProcessDone, done]);
|
||||||
|
|
||||||
|
strategy.Observers.Link(chart.CreateOhlcListener);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TForm1.TreeViewDblClick(Sender: TObject);
|
procedure TForm1.TreeViewDblClick(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
var sel := TreeView.Selected as TTreeViewItem;
|
var sel := TreeView.Selected as TTreeViewItem;
|
||||||
@@ -209,6 +271,7 @@ begin
|
|||||||
// Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364]
|
// Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364]
|
||||||
FServer := TAuraTABFileServer.Create('\\COFFEE\TickData\Pepperstone');
|
FServer := TAuraTABFileServer.Create('\\COFFEE\TickData\Pepperstone');
|
||||||
|
|
||||||
|
|
||||||
SymbolsComboBox.Enabled := false;
|
SymbolsComboBox.Enabled := false;
|
||||||
ChartButton.Enabled := false;
|
ChartButton.Enabled := false;
|
||||||
LoadButton.Enabled := false;
|
LoadButton.Enabled := false;
|
||||||
@@ -244,7 +307,7 @@ procedure TForm1.FormDestroy(Sender: TObject);
|
|||||||
begin
|
begin
|
||||||
FSymbols.WaitFor;
|
FSymbols.WaitFor;
|
||||||
FTerminate.Notify;
|
FTerminate.Notify;
|
||||||
TaskManager.WaitFor(FLoadDone);
|
TaskManager.WaitFor(FProcessDone);
|
||||||
FRandom.Free;
|
FRandom.Free;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -257,12 +320,12 @@ procedure TForm1.LoadButtonClick(Sender: TObject);
|
|||||||
begin
|
begin
|
||||||
if SymbolsComboBox.ItemIndex < 0 then
|
if SymbolsComboBox.ItemIndex < 0 then
|
||||||
exit;
|
exit;
|
||||||
var Layout := CurrLayout;
|
var Layout := CurrLayout<TVertScrollBox>;
|
||||||
if Layout = nil then
|
if Layout = nil then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
var Stream := FServer.CreateStream(FSymbols.WaitFor[SymbolsComboBox.ItemIndex]);
|
var Stream := FServer.CreateStream(FSymbols.WaitFor[SymbolsComboBox.ItemIndex]);
|
||||||
var Data := TDataStreamProvider.Create<TAskBidItem>(30000, 10000, Stream);
|
var Data := TDataStreamProvider.Create<TAuraAskBidFileItem>(30000, 10000, Stream);
|
||||||
|
|
||||||
var path := TPath.Create(Self);
|
var path := TPath.Create(Self);
|
||||||
path.Parent := Layout;
|
path.Parent := Layout;
|
||||||
@@ -292,14 +355,14 @@ end;
|
|||||||
|
|
||||||
procedure TForm1.RandomButtonClick(Sender: TObject);
|
procedure TForm1.RandomButtonClick(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
var Layout := CurrLayout;
|
var Layout := CurrLayout<TVertScrollBox>;
|
||||||
if Layout = nil then
|
if Layout = nil then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
var rnd: TRndItem;
|
var rnd: TRndItem;
|
||||||
|
|
||||||
rnd.Stream := FServer.CreateStream(FSymbols.WaitFor[Random(Length(FSymbols.WaitFor))]);
|
rnd.Stream := FServer.CreateStream(FSymbols.WaitFor[Random(Length(FSymbols.WaitFor))]);
|
||||||
rnd.Data := TDataStreamProvider.Create<TAskBidItem>(3000, 1000, rnd.Stream);
|
rnd.Data := TDataStreamProvider.Create<TAuraAskBidFileItem>(3000, 1000, rnd.Stream);
|
||||||
|
|
||||||
rnd.Labl := TLabel.Create(Self);
|
rnd.Labl := TLabel.Create(Self);
|
||||||
rnd.Labl.Parent := Layout;
|
rnd.Labl.Parent := Layout;
|
||||||
@@ -346,9 +409,22 @@ begin
|
|||||||
NewWorkspace;
|
NewWorkspace;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TForm1.AlignControl(Control: TControl);
|
||||||
|
begin
|
||||||
|
var Layout := CurrLayout<TVertScrollBox>;
|
||||||
|
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);
|
procedure TForm1.ChartButtonClick(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
var Layout := CurrLayout;
|
var Layout := CurrLayout<TVertScrollBox>;
|
||||||
if Layout = nil then
|
if Layout = nil then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
@@ -361,41 +437,44 @@ begin
|
|||||||
|
|
||||||
const width = 300;
|
const width = 300;
|
||||||
|
|
||||||
|
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
|
||||||
|
|
||||||
var done :=
|
var done :=
|
||||||
TaskManager.RunTask(
|
TaskManager.RunTask(
|
||||||
nil,
|
nil,
|
||||||
function: TState
|
function: TState
|
||||||
begin
|
begin
|
||||||
var Prices := TDataSeries<TAskBidItem>.CreateDataSeries(width);
|
var Prices := TDataSeries<TAuraAskBidFileItem>.CreateDataSeries(width);
|
||||||
Result :=
|
Result :=
|
||||||
FServer.ProcessData(
|
FServer.ProcessData(
|
||||||
Symbol,
|
Symbol,
|
||||||
FTerminate.Signal,
|
terminated,
|
||||||
procedure(const Values: TArray<TDataPoint<TAskBidItem>>; const Terminated: TState)
|
TMycGenericProcessor<TArray<TDataPoint<TAuraAskBidFileItem>>>.Create(
|
||||||
begin
|
procedure(const Values: TArray<TDataPoint<TAuraAskBidFileItem>>)
|
||||||
Prices := Prices.Add(Values);
|
|
||||||
currLog.Value := Prices.TotalCount.ToString;
|
|
||||||
|
|
||||||
var PathData := TPathData.Create;
|
|
||||||
if Prices.Count > 0 then
|
|
||||||
begin
|
begin
|
||||||
var n := Prices.Count;
|
Prices := Prices.Add(Values);
|
||||||
PathData.MoveTo(PointF(n - 1, Prices[0].Data.Ask));
|
currLog.Value := Prices.TotalCount.ToString;
|
||||||
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
|
var PathData := TPathData.Create;
|
||||||
PathData.LineTo(PointF(n - i - 1, Prices[i].Data.Bid));
|
if Prices.Count > 0 then
|
||||||
end;
|
begin
|
||||||
PathData.ClosePath;
|
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));
|
||||||
|
|
||||||
currPathData.Value := TObjectRef<TPathData>.Create(PathData);
|
for var i := n - 1 downto 0 do
|
||||||
end
|
PathData.LineTo(PointF(n - i - 1, Prices[i].Data.Bid));
|
||||||
|
end;
|
||||||
|
PathData.ClosePath;
|
||||||
|
|
||||||
|
currPathData.Value := TObjectRef<TPathData>.Create(PathData);
|
||||||
|
end )
|
||||||
);
|
);
|
||||||
end
|
end
|
||||||
);
|
);
|
||||||
|
|
||||||
FLoadDone := TState.All([FLoadDone, done]);
|
FProcessDone := TState.All([FProcessDone, done]);
|
||||||
|
|
||||||
var path := TPath.Create(Self);
|
var path := TPath.Create(Self);
|
||||||
path.Parent := Layout;
|
path.Parent := Layout;
|
||||||
@@ -412,19 +491,19 @@ begin
|
|||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TForm1.CurrLayout: TFlowLayout;
|
function TForm1.CurrLayout<T>: T;
|
||||||
begin
|
begin
|
||||||
if TabControl.ActiveTab = nil then
|
if TabControl.ActiveTab = nil then
|
||||||
exit(nil);
|
exit(nil);
|
||||||
|
|
||||||
var Res: TFlowLayout := nil;
|
var Res: T := nil;
|
||||||
TabControl.ActiveTab.EnumControls(
|
TabControl.ActiveTab.EnumControls(
|
||||||
function(Control: TControl): TEnumControlsResult
|
function(Control: TControl): TEnumControlsResult
|
||||||
begin
|
begin
|
||||||
Result := TEnumControlsResult.Continue;
|
Result := TEnumControlsResult.Continue;
|
||||||
if Control is TFlowLayout then
|
if Control is T then
|
||||||
begin
|
begin
|
||||||
Res := Control as TFlowLayout;
|
Res := Control as T;
|
||||||
Result := TEnumControlsResult.Stop;
|
Result := TEnumControlsResult.Stop;
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
@@ -433,6 +512,29 @@ begin
|
|||||||
Result := Res;
|
Result := Res;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TForm1.ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor<TArray<TDataPoint<TAskBidItem>>>): TState;
|
||||||
|
var
|
||||||
|
dataProvider: IMycConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>;
|
||||||
|
begin
|
||||||
|
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
|
||||||
|
|
||||||
|
dataProvider := TMycGenericConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>.Create(
|
||||||
|
function( const Values: TArray<TDataPoint<TAuraAskBidFileItem>> ): TArray<TDataPoint<TAskBidItem>>
|
||||||
|
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.Observers.Link( Processor );
|
||||||
|
|
||||||
|
Result := FServer.ProcessData( Symbol, terminated, dataProvider );
|
||||||
|
end;
|
||||||
|
|
||||||
function TForm1.SelectedSymbol: String;
|
function TForm1.SelectedSymbol: String;
|
||||||
begin
|
begin
|
||||||
Result := '';
|
Result := '';
|
||||||
@@ -442,9 +544,4 @@ begin
|
|||||||
Result := FSymbols.WaitFor[SymbolsComboBox.ItemIndex];
|
Result := FSymbols.WaitFor[SymbolsComboBox.ItemIndex];
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.SpeedButton1Click(Sender: TObject);
|
|
||||||
begin
|
|
||||||
TestChartForm.Visible := true;
|
|
||||||
end;
|
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -0,0 +1,419 @@
|
|||||||
|
unit Myc.Fmx.Chart;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.Classes,
|
||||||
|
System.Types,
|
||||||
|
System.Generics.Collections,
|
||||||
|
System.UITypes,
|
||||||
|
System.UIConsts,
|
||||||
|
System.Messaging,
|
||||||
|
System.Math.Vectors,
|
||||||
|
FMX.Types,
|
||||||
|
FMX.Controls,
|
||||||
|
FMX.Graphics,
|
||||||
|
Myc.Trade.DataPoint,
|
||||||
|
Myc.Signals;
|
||||||
|
|
||||||
|
type
|
||||||
|
TCandleStyle = (csCandleStick, csHiLoBar);
|
||||||
|
|
||||||
|
TMycChart = class(TStyledControl)
|
||||||
|
type
|
||||||
|
TSeries = class abstract(TContainedObject)
|
||||||
|
private
|
||||||
|
function GetMainSeries: TSeries;
|
||||||
|
function GetOwner: TMycChart;
|
||||||
|
protected
|
||||||
|
function GetCount: Int64; virtual; abstract;
|
||||||
|
function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual; abstract;
|
||||||
|
function Update: Boolean; virtual; abstract;
|
||||||
|
procedure Paint(const ACanvas: TCanvas; const AXForm, AYForm: TFunc<Double, Single>); virtual; abstract;
|
||||||
|
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TMycChart);
|
||||||
|
property Count: Int64 read GetCount;
|
||||||
|
property MainSeries: TSeries read GetMainSeries;
|
||||||
|
property Owner: TMycChart read GetOwner;
|
||||||
|
end;
|
||||||
|
|
||||||
|
private
|
||||||
|
FSeriesList: TList<TSeries>;
|
||||||
|
FLookback: Integer;
|
||||||
|
FIdleSubscrId: TMessageSubscriptionId;
|
||||||
|
protected
|
||||||
|
procedure Paint; override;
|
||||||
|
procedure DoIdle;
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TComponent); override;
|
||||||
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
// Creates an OHLC candlestick/bar series
|
||||||
|
function CreateOhlcListener(
|
||||||
|
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||||
|
const ADownColor: TAlphaColor = TAlphaColors.Red;
|
||||||
|
const AStyle: TCandleStyle = csCandleStick
|
||||||
|
): IMycProcessor<TArray<TDataPoint<TOhlcItem>>>;
|
||||||
|
|
||||||
|
// Creates a simple line series for double values
|
||||||
|
function CreateDoubleListener(const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue; const ALineWidth: Single = 1.5):
|
||||||
|
IMycProcessor<TArray<Double>>;
|
||||||
|
|
||||||
|
// The maximum number of data points to display from the main series.
|
||||||
|
property Lookback: Integer read FLookback write FLookback;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.Math,
|
||||||
|
System.SyncObjs, WinApi.Windows,
|
||||||
|
Myc.Trade.DataArray;
|
||||||
|
|
||||||
|
type
|
||||||
|
TChartSeriesProcessor<T> = class(TMycChart.TSeries, IMycProcessor<TArray<T>>)
|
||||||
|
strict private
|
||||||
|
FDataSeries: TMycDataArray<T>;
|
||||||
|
FChanged: Boolean;
|
||||||
|
FLock: TSpinLock;
|
||||||
|
procedure ProcessData(const Values: TArray<T>);
|
||||||
|
private
|
||||||
|
FData: TMycDataArray<T>;
|
||||||
|
protected
|
||||||
|
function GetCount: Int64; override;
|
||||||
|
function Update: Boolean; override;
|
||||||
|
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TMycChart);
|
||||||
|
property Data: TMycDataArray<T> read FData;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartOhlcSeries }
|
||||||
|
TChartOhlcSeries = class(TChartSeriesProcessor<TDataPoint<TOhlcItem>>)
|
||||||
|
private
|
||||||
|
FUpColor: TAlphaColor;
|
||||||
|
FDownColor: TAlphaColor;
|
||||||
|
FStyle: TCandleStyle;
|
||||||
|
protected
|
||||||
|
function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; override;
|
||||||
|
procedure Paint(const ACanvas: TCanvas; const AXForm, AYForm: TFunc<Double, Single>); override;
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TMycChart; const AUpColor, ADownColor: TAlphaColor; AStyle: TCandleStyle);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartLineSeries }
|
||||||
|
TChartLineSeries = class(TChartSeriesProcessor<Double>)
|
||||||
|
private
|
||||||
|
FLineColor: TAlphaColor;
|
||||||
|
FLineWidth: Single;
|
||||||
|
protected
|
||||||
|
function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; override;
|
||||||
|
procedure Paint(const ACanvas: TCanvas; const AXForm, AYForm: TFunc<Double, Single>); override;
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TMycChart; const ALineColor: TAlphaColor; ALineWidth: Single);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TMycChart.TSeries }
|
||||||
|
|
||||||
|
constructor TMycChart.TSeries.Create(AOwner: TMycChart);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.TSeries.GetMainSeries: TSeries;
|
||||||
|
begin
|
||||||
|
Result := nil;
|
||||||
|
if Owner.FSeriesList.Count > 0 then
|
||||||
|
Result := Owner.FSeriesList[0];
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.TSeries.GetOwner: TMycChart;
|
||||||
|
begin
|
||||||
|
Result := Controller as TMycChart;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TMycChart }
|
||||||
|
|
||||||
|
constructor TMycChart.Create(AOwner: TComponent);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner);
|
||||||
|
FSeriesList := TObjectList<TSeries>.Create(true);
|
||||||
|
FLookback := 100; // Default lookback
|
||||||
|
|
||||||
|
FIdleSubscrId :=
|
||||||
|
TMessageManager
|
||||||
|
.DefaultManager
|
||||||
|
.SubscribeToMessage(TIdleMessage, procedure(const Sender: TObject; const M: TMessage) begin DoIdle; end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TMycChart.Destroy;
|
||||||
|
begin
|
||||||
|
TMessageManager.DefaultManager.Unsubscribe(TIdleMessage, FIdleSubscrId);
|
||||||
|
|
||||||
|
FSeriesList.Free;
|
||||||
|
inherited;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.CreateDoubleListener(const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue; const ALineWidth: Single = 1.5):
|
||||||
|
IMycProcessor<TArray<Double>>;
|
||||||
|
var
|
||||||
|
series: TChartLineSeries;
|
||||||
|
begin
|
||||||
|
series := TChartLineSeries.Create(Self, ALineColor, ALineWidth);
|
||||||
|
FSeriesList.Add(series);
|
||||||
|
Result := series;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycChart.CreateOhlcListener(
|
||||||
|
const AUpColor: TAlphaColor = TAlphaColors.Green;
|
||||||
|
const ADownColor: TAlphaColor = TAlphaColors.Red;
|
||||||
|
const AStyle: TCandleStyle = csCandleStick
|
||||||
|
): IMycProcessor<TArray<TDataPoint<TOhlcItem>>>;
|
||||||
|
var
|
||||||
|
series: TChartOhlcSeries;
|
||||||
|
begin
|
||||||
|
series := TChartOhlcSeries.Create(Self, AUpColor, ADownColor, AStyle);
|
||||||
|
FSeriesList.Add(series);
|
||||||
|
Result := series;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycChart.DoIdle;
|
||||||
|
begin
|
||||||
|
var doRepaint := false;
|
||||||
|
for var series in FSeriesList do
|
||||||
|
if series.Update then
|
||||||
|
doRepaint := true;
|
||||||
|
if doRepaint then
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycChart.Paint;
|
||||||
|
var
|
||||||
|
rect: TRectF;
|
||||||
|
series: TSeries;
|
||||||
|
globalMin, globalMax, seriesMin, seriesMax, padding: Double;
|
||||||
|
rangeInitialized: Boolean;
|
||||||
|
xTransform: TFunc<Double, Single>;
|
||||||
|
yTransform: TFunc<Double, Single>;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
rect := Self.LocalRect;
|
||||||
|
|
||||||
|
if (FSeriesList.Count = 0) or (FLookback <= 1) then
|
||||||
|
begin
|
||||||
|
Canvas.Fill.Color := TAlphaColors.Gray;
|
||||||
|
Canvas.FillText(rect, 'No Data', false, 1, [], TTextAlign.Center, TTextAlign.Center);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
rangeInitialized := false;
|
||||||
|
|
||||||
|
for series in FSeriesList do
|
||||||
|
begin
|
||||||
|
if series.GetValueRange(0, FLookback, seriesMin, seriesMax) then
|
||||||
|
begin
|
||||||
|
if not rangeInitialized then
|
||||||
|
begin
|
||||||
|
globalMin := seriesMin;
|
||||||
|
globalMax := seriesMax;
|
||||||
|
rangeInitialized := true;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
globalMin := Min(globalMin, seriesMin);
|
||||||
|
globalMax := Max(globalMax, seriesMax);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if not rangeInitialized then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
padding := (globalMax - globalMin) * 0.1;
|
||||||
|
globalMin := globalMin - padding;
|
||||||
|
globalMax := globalMax + padding;
|
||||||
|
|
||||||
|
if (globalMax - globalMin) = 0 then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
xTransform := function(index: Double): Single begin Result := rect.Right - (index / (FLookback - 1)) * rect.Width; end;
|
||||||
|
|
||||||
|
yTransform :=
|
||||||
|
function(value: Double): Single
|
||||||
|
begin
|
||||||
|
Result := rect.Top + (1 - (value - globalMin) / (globalMax - globalMin)) * rect.Height;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// var T :=
|
||||||
|
// TMatrix.CreateTranslation(rect.Left, rect.Top + rect.Height*globalMax / (globalMax - globalMin)) *
|
||||||
|
// TMatrix.CreateScaling(rect.Width / (FLookback - 1), -rect.Height / (globalMax - globalMin));
|
||||||
|
|
||||||
|
for series in FSeriesList do
|
||||||
|
begin
|
||||||
|
series.Paint(Self.Canvas, xTransform, yTransform);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartSeriesProcessor<T> }
|
||||||
|
|
||||||
|
constructor TChartSeriesProcessor<T>.Create(AOwner: TMycChart);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner);
|
||||||
|
FLock := TSpinLock.Create(false);
|
||||||
|
FDataSeries := TMycDataArray<T>.CreateEmpty;
|
||||||
|
FData := FDataSeries;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartSeriesProcessor<T>.GetCount: Int64;
|
||||||
|
begin
|
||||||
|
Result := FData.Count;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TChartSeriesProcessor<T>.ProcessData(const Values: TArray<T>);
|
||||||
|
begin
|
||||||
|
FLock.Enter;
|
||||||
|
try
|
||||||
|
FDataSeries := FDataSeries.Add(Values, 0, Length(Values), Owner.Lookback);
|
||||||
|
FChanged := true;
|
||||||
|
finally
|
||||||
|
FLock.Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartSeriesProcessor<T>.Update: Boolean;
|
||||||
|
begin
|
||||||
|
FLock.Enter;
|
||||||
|
try
|
||||||
|
Result := FChanged;
|
||||||
|
FChanged := false;
|
||||||
|
if Result then
|
||||||
|
FData := FDataSeries;
|
||||||
|
finally
|
||||||
|
FLock.Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartOhlcSeries }
|
||||||
|
|
||||||
|
constructor TChartOhlcSeries.Create(AOwner: TMycChart; const AUpColor, ADownColor: TAlphaColor; AStyle: TCandleStyle);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner);
|
||||||
|
FUpColor := AUpColor;
|
||||||
|
FDownColor := ADownColor;
|
||||||
|
FStyle := AStyle;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartOhlcSeries.GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean;
|
||||||
|
var
|
||||||
|
i: Int64;
|
||||||
|
begin
|
||||||
|
Result := GetCount > 0;
|
||||||
|
if not Result then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
Min := MaxDouble;
|
||||||
|
Max := -MaxDouble;
|
||||||
|
for i := StartIndex to System.Math.Min(GetCount - 1, StartIndex + Count - 1) do
|
||||||
|
begin
|
||||||
|
Min := System.Math.Min(Min, Data.Items[i].Data.Low);
|
||||||
|
Max := System.Math.Max(Max, Data.Items[i].Data.High);
|
||||||
|
end;
|
||||||
|
Result := (Min <> MaxDouble);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TChartOhlcSeries.Paint(const ACanvas: TCanvas; const AXForm, AYForm: TFunc<Double, Single>);
|
||||||
|
var
|
||||||
|
i, displayCount: Int64;
|
||||||
|
x, candleWidth: Single;
|
||||||
|
yOpen, yHigh, yLow, yClose: Single;
|
||||||
|
item: TDataPoint<TOhlcItem>;
|
||||||
|
isUp: boolean;
|
||||||
|
begin
|
||||||
|
displayCount := System.Math.Min(Data.Count, Owner.Lookback);
|
||||||
|
if displayCount <= 0 then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
if displayCount > 1 then
|
||||||
|
candleWidth := Max(2, 0.8 * Abs((AXForm(1) - AXForm(0))))
|
||||||
|
else
|
||||||
|
candleWidth := 10;
|
||||||
|
|
||||||
|
for i := 0 to displayCount - 1 do
|
||||||
|
begin
|
||||||
|
item := Data.Items[i];
|
||||||
|
x := AXForm(i);
|
||||||
|
yOpen := AYForm(item.Data.Open);
|
||||||
|
yClose := AYForm(item.Data.Close);
|
||||||
|
yHigh := AYForm(item.Data.High);
|
||||||
|
yLow := AYForm(item.Data.Low);
|
||||||
|
isUp := item.Data.Close >= item.Data.Open;
|
||||||
|
|
||||||
|
if isUp then
|
||||||
|
ACanvas.Stroke.Color := FUpColor
|
||||||
|
else
|
||||||
|
ACanvas.Stroke.Color := FDownColor;
|
||||||
|
ACanvas.Stroke.Thickness := 1.0;
|
||||||
|
|
||||||
|
ACanvas.DrawLine(TPointF.Create(x, yHigh), TPointF.Create(x, yLow), 1);
|
||||||
|
|
||||||
|
if FStyle = csCandleStick then
|
||||||
|
begin
|
||||||
|
ACanvas.Fill.Color := ACanvas.Stroke.Color;
|
||||||
|
if isUp then
|
||||||
|
ACanvas.FillRect(TRectF.Create(x - candleWidth / 2, yClose, x + candleWidth / 2, yOpen), 0, 0, AllCorners, 1)
|
||||||
|
else
|
||||||
|
ACanvas.FillRect(TRectF.Create(x - candleWidth / 2, yOpen, x + candleWidth / 2, yClose), 0, 0, AllCorners, 1);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TChartLineSeries }
|
||||||
|
|
||||||
|
constructor TChartLineSeries.Create(AOwner: TMycChart; const ALineColor: TAlphaColor; ALineWidth: Single);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner);
|
||||||
|
FLineColor := ALineColor;
|
||||||
|
FLineWidth := ALineWidth;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TChartLineSeries.GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean;
|
||||||
|
var
|
||||||
|
i: Int64;
|
||||||
|
begin
|
||||||
|
Result := GetCount > 0;
|
||||||
|
if not Result then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
Min := MaxDouble;
|
||||||
|
Max := -MaxDouble;
|
||||||
|
for i := StartIndex to System.Math.Min(GetCount - 1, StartIndex + Count - 1) do
|
||||||
|
begin
|
||||||
|
Min := System.Math.Min(Min, Data.Items[i]);
|
||||||
|
Max := System.Math.Max(Max, Data.Items[i]);
|
||||||
|
end;
|
||||||
|
Result := (Min <> MaxDouble);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TChartLineSeries.Paint(const ACanvas: TCanvas; const AXForm, AYForm: TFunc<Double, Single>);
|
||||||
|
begin
|
||||||
|
if not Assigned(MainSeries) or (Data.Count = 0) or (MainSeries.Count = 0) then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
var points := TPathData.Create;
|
||||||
|
|
||||||
|
var displayCount := System.Math.Min(Data.Count, Owner.Lookback);
|
||||||
|
if displayCount < 2 then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
points.MoveTo( TPointF.Create(AXForm(0), AYForm(Data[0])) );
|
||||||
|
for var i := 1 to displayCount - 1 do
|
||||||
|
points.LineTo( TPointF.Create(AXForm(i), AYForm(Data[i])) );
|
||||||
|
|
||||||
|
ACanvas.Stroke.Color := FLineColor;
|
||||||
|
ACanvas.Stroke.Thickness := FLineWidth;
|
||||||
|
ACanvas.DrawPath(points, 1);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -23,6 +23,9 @@ type
|
|||||||
Receiver: T; // The registered interface instance (the event sink).
|
Receiver: T; // The registered interface instance (the event sink).
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Notify function. If this results false, it will be removed from the notify list
|
||||||
|
TNotifyProc = reference to function(const Obj: T): Boolean;
|
||||||
|
|
||||||
strict private
|
strict private
|
||||||
// Head of the linked list. The pointer value itself is repurposed for a spinlock.
|
// Head of the linked list. The pointer value itself is repurposed for a spinlock.
|
||||||
// Bit 0 of the address stores the lock state (0 = locked, 1 = unlocked).
|
// Bit 0 of the address stores the lock state (0 = locked, 1 = unlocked).
|
||||||
@@ -36,9 +39,9 @@ type
|
|||||||
|
|
||||||
public
|
public
|
||||||
// Initializes the list in an unlocked state.
|
// Initializes the list in an unlocked state.
|
||||||
procedure Create;
|
class operator Initialize(out Dest: TMycNotifyList<T>);
|
||||||
// Safely clears all registered receivers and cleans up resources.
|
// Safely clears all registered receivers and cleans up resources.
|
||||||
procedure Destroy;
|
procedure Finalize;
|
||||||
// Registers a receiver interface and returns an opaque tag for later unsubscription.
|
// Registers a receiver interface and returns an opaque tag for later unsubscription.
|
||||||
function Advise(const Receiver: T): TTag;
|
function Advise(const Receiver: T): TTag;
|
||||||
// Unregisters a single receiver using its subscription tag.
|
// Unregisters a single receiver using its subscription tag.
|
||||||
@@ -54,17 +57,17 @@ type
|
|||||||
// Invokes a predicate for each registered receiver.
|
// Invokes a predicate for each registered receiver.
|
||||||
// If the predicate returns false, the receiver is detached from further notifications
|
// If the predicate returns false, the receiver is detached from further notifications
|
||||||
// by setting its interface reference to nil. The list item itself is not freed here.
|
// by setting its interface reference to nil. The list item itself is not freed here.
|
||||||
procedure Notify(Func: TPredicate<T>);
|
procedure Notify(const Func: TNotifyProc);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
procedure TMycNotifyList<T>.Create;
|
class operator TMycNotifyList<T>.Initialize(out Dest: TMycNotifyList<T>);
|
||||||
begin
|
begin
|
||||||
NativeUInt(FList) := 1;
|
NativeUInt(Dest.FList) := 1;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMycNotifyList<T>.Destroy;
|
procedure TMycNotifyList<T>.Finalize;
|
||||||
begin
|
begin
|
||||||
Lock;
|
Lock;
|
||||||
UnadviseAll;
|
UnadviseAll;
|
||||||
@@ -114,7 +117,7 @@ begin
|
|||||||
Result := NativeUInt(FList) and 1 = 0;
|
Result := NativeUInt(FList) and 1 = 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMycNotifyList<T>.Notify(Func: TPredicate<T>);
|
procedure TMycNotifyList<T>.Notify(const Func: TNotifyProc);
|
||||||
var
|
var
|
||||||
Item: PItem;
|
Item: PItem;
|
||||||
Last: PItem;
|
Last: PItem;
|
||||||
|
|||||||
@@ -184,12 +184,11 @@ end;
|
|||||||
constructor TMycEvent.Create;
|
constructor TMycEvent.Create;
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FSubscribers.Create;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TMycEvent.Destroy;
|
destructor TMycEvent.Destroy;
|
||||||
begin
|
begin
|
||||||
FSubscribers.Destroy;
|
FSubscribers.Finalize;
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -202,7 +201,7 @@ function TMycEvent.Notify: Boolean;
|
|||||||
begin
|
begin
|
||||||
FSubscribers.Lock;
|
FSubscribers.Lock;
|
||||||
try
|
try
|
||||||
FSubscribers.Notify(function(Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
|
FSubscribers.Notify(function(const Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
|
||||||
finally
|
finally
|
||||||
FSubscribers.Release;
|
FSubscribers.Release;
|
||||||
end;
|
end;
|
||||||
@@ -275,12 +274,11 @@ begin
|
|||||||
inherited Create;
|
inherited Create;
|
||||||
Assert(ACount >= 0);
|
Assert(ACount >= 0);
|
||||||
FCount := ACount;
|
FCount := ACount;
|
||||||
FSubscribers.Create;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TMycLatch.Destroy;
|
destructor TMycLatch.Destroy;
|
||||||
begin
|
begin
|
||||||
FSubscribers.Destroy;
|
FSubscribers.Finalize;
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -300,7 +298,7 @@ begin
|
|||||||
|
|
||||||
if shouldNotifySubscribers then
|
if shouldNotifySubscribers then
|
||||||
begin
|
begin
|
||||||
FSubscribers.Notify(function(Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
|
FSubscribers.Notify(function(const Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Returns true if the latch has not yet been set by this Notify call (count > 0).
|
// Returns true if the latch has not yet been set by this Notify call (count > 0).
|
||||||
@@ -395,12 +393,11 @@ constructor TMycFlag.Create(AInit: Boolean);
|
|||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FFlag := AInit;
|
FFlag := AInit;
|
||||||
FSubscribers.Create;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TMycFlag.Destroy;
|
destructor TMycFlag.Destroy;
|
||||||
begin
|
begin
|
||||||
FSubscribers.Destroy; // Clean up the subscriber list.
|
FSubscribers.Finalize;
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -438,7 +435,7 @@ begin
|
|||||||
|
|
||||||
if wasPreviouslyClean then // Only notify subscribers if state changed from clean to dirty.
|
if wasPreviouslyClean then // Only notify subscribers if state changed from clean to dirty.
|
||||||
begin
|
begin
|
||||||
FSubscribers.Notify(function(Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
|
FSubscribers.Notify(function(const Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
|
||||||
end;
|
end;
|
||||||
finally
|
finally
|
||||||
FSubscribers.Release;
|
FSubscribers.Release;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type
|
|||||||
public
|
public
|
||||||
function ProcessSignal(const Signal: TSignal; const Proc: TMsgProc): TComponent; overload;
|
function ProcessSignal(const Signal: TSignal; const Proc: TMsgProc): TComponent; overload;
|
||||||
function ProcessSignal(const Signal: TSignal; const Proc: TProc): TComponent; overload;
|
function ProcessSignal(const Signal: TSignal; const Proc: TProc): TComponent; overload;
|
||||||
|
function ProcessSignal(const Signal: TSignal; const Done: TState; const Proc: TProc): TComponent; overload;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TSignalSyncHelper = record helper for TSignal
|
TSignalSyncHelper = record helper for TSignal
|
||||||
@@ -198,6 +199,21 @@ begin
|
|||||||
Result := TSignalSubscriber.Create(Self, Signal, Proc);
|
Result := TSignalSubscriber.Create(Self, Signal, Proc);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TSignalComponentHelper.ProcessSignal(const Signal: TSignal; const Done: TState; const Proc: TProc): TComponent;
|
||||||
|
begin
|
||||||
|
var cProc: TProc := Proc;
|
||||||
|
var cDone: TState := Done;
|
||||||
|
Result :=
|
||||||
|
ProcessSignal(
|
||||||
|
Signal,
|
||||||
|
procedure(out IsDone: Boolean)
|
||||||
|
begin
|
||||||
|
cProc();
|
||||||
|
IsDone := cDone.IsSet;
|
||||||
|
end
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
{ TSignalSyncHelper }
|
{ TSignalSyncHelper }
|
||||||
|
|
||||||
function TSignalSyncHelper.Queue(const Proc: TProc; Delay: Integer = 0): TSignal.TSubscription;
|
function TSignalSyncHelper.Queue(const Proc: TProc; Delay: Integer = 0): TSignal.TSubscription;
|
||||||
|
|||||||
+1
-1
@@ -153,7 +153,7 @@ type
|
|||||||
|
|
||||||
class function CreateLatch(Count: Integer): ILatch; static;
|
class function CreateLatch(Count: Integer): ILatch; static;
|
||||||
|
|
||||||
class function Enqueue1(var Gate: TLatch): TState; overload; static;
|
class function Enqueue1(var Gate: TLatch): TState; overload; static; deprecated; experimental;
|
||||||
function Enqueue: TState; overload;
|
function Enqueue: TState; overload;
|
||||||
|
|
||||||
class property Null: ILatch read FNull;
|
class property Null: ILatch read FNull;
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ procedure TTestDataSeries.TestSetupSeriesWithDataVerification;
|
|||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
baseTime, expectedTime: TDateTime;
|
baseTime, expectedTime: TDateTime;
|
||||||
expectedAsk: Single;
|
expectedAsk: Double;
|
||||||
begin
|
begin
|
||||||
SetupSeriesWithData;
|
SetupSeriesWithData;
|
||||||
baseTime := EncodeDate(2020, 7, 7);
|
baseTime := EncodeDate(2020, 7, 7);
|
||||||
@@ -200,7 +200,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
expectedTime := baseTime + (9 - i);
|
expectedTime := baseTime + (9 - i);
|
||||||
Assert.AreEqual(expectedTime, FSeries.Items[i].Time, 'Item at logical index should have reversed chronological time');
|
Assert.AreEqual(expectedTime, FSeries.Items[i].Time, 'Item at logical index should have reversed chronological time');
|
||||||
Assert.AreEqual(Single(9 - i), FSeries.Items[i].Data.Ask, 'Item data at logical index should match reversed insertion order');
|
Assert.AreEqual(Double(9 - i), FSeries.Items[i].Data.Ask, 'Item data at logical index should match reversed insertion order');
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -648,9 +648,9 @@ procedure TTestDataSeries.TestDataAndTimePropertiesRespectLookback;
|
|||||||
var
|
var
|
||||||
baseTime: TDateTime;
|
baseTime: TDateTime;
|
||||||
expectedOldestTime: TDateTime;
|
expectedOldestTime: TDateTime;
|
||||||
expectedOldestAsk: Single;
|
expectedOldestAsk: Double;
|
||||||
expectedNewestTime: TDateTime;
|
expectedNewestTime: TDateTime;
|
||||||
expectedNewestAsk: Single;
|
expectedNewestAsk: Double;
|
||||||
begin
|
begin
|
||||||
SetupSeriesWithData(20, 15); // Add 20 items, lookback 15.
|
SetupSeriesWithData(20, 15); // Add 20 items, lookback 15.
|
||||||
// Visible items are those originally at index 5 through 19.
|
// Visible items are those originally at index 5 through 19.
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ type
|
|||||||
[IgnoreMemoryLeaks(true)]
|
[IgnoreMemoryLeaks(true)]
|
||||||
TTest_TABFileServer_Equivalence = class(TObject)
|
TTest_TABFileServer_Equivalence = class(TObject)
|
||||||
private
|
private
|
||||||
FServer: IDataServer<TAskBidItem>;
|
FServer: IDataServer<TAuraAskBidFileItem>;
|
||||||
FStream: IDataStream<TAskBidItem>;
|
FStream: IDataStream<TAuraAskBidFileItem>;
|
||||||
public
|
public
|
||||||
[SetupFixture]
|
[SetupFixture]
|
||||||
procedure SetupFixture;
|
procedure SetupFixture;
|
||||||
@@ -77,17 +77,17 @@ end;
|
|||||||
|
|
||||||
procedure TTest_TABFileServer_Equivalence.Test_ServerReturnsSameDataAs_LoadDataSeries;
|
procedure TTest_TABFileServer_Equivalence.Test_ServerReturnsSameDataAs_LoadDataSeries;
|
||||||
var
|
var
|
||||||
chunk: array[0..C_MAX_FETCH - 1] of TDataPoint<TAskBidItem>;
|
chunk: array[0..C_MAX_FETCH - 1] of TDataPoint<TAuraAskBidFileItem>;
|
||||||
dst: TArray<TDataPoint<TAskBidItem>>;
|
dst: TArray<TDataPoint<TAuraAskBidFileItem>>;
|
||||||
i: Int64;
|
i: Int64;
|
||||||
n: Integer;
|
n: Integer;
|
||||||
cnt: Int64;
|
cnt: Int64;
|
||||||
timeout: Integer;
|
timeout: Integer;
|
||||||
filename: TAuraDataFile;
|
filename: TAuraDataFile;
|
||||||
expectedData: TArray<TDataPoint<TAskBidItem>>;
|
expectedData: TArray<TDataPoint<TAuraAskBidFileItem>>;
|
||||||
begin
|
begin
|
||||||
// 1. Expected data is loaded directly using LoadDataSeries.
|
// 1. Expected data is loaded directly using LoadDataSeries.
|
||||||
filename := (FServer as TAuraDataServer<TAskBidItem>).FindFirstFile(C_TEST_SYMBOL).WaitFor;
|
filename := (FServer as TAuraDataServer<TAuraAskBidFileItem>).FindFirstFile(C_TEST_SYMBOL).WaitFor;
|
||||||
Assert.IsTrue(filename.IsValid, 'Test data file could not be found.');
|
Assert.IsTrue(filename.IsValid, 'Test data file could not be found.');
|
||||||
|
|
||||||
expectedData := (FServer as TAuraTABFileServer).LoadDataSeries(filename).WaitFor;
|
expectedData := (FServer as TAuraTABFileServer).LoadDataSeries(filename).WaitFor;
|
||||||
|
|||||||
@@ -5,35 +5,14 @@ interface
|
|||||||
uses
|
uses
|
||||||
System.Generics.Collections,
|
System.Generics.Collections,
|
||||||
System.TimeSpan,
|
System.TimeSpan,
|
||||||
Myc.Trade.DataPoint;
|
Myc.Trade.DataPoint,
|
||||||
|
Myc.Trade.DataArray;
|
||||||
|
|
||||||
type
|
type
|
||||||
TMycDataArray<T> = record
|
|
||||||
private
|
|
||||||
const
|
|
||||||
ChunkSize = 1024;
|
|
||||||
type
|
|
||||||
TChunk = TArray<TDataPoint<T>>;
|
|
||||||
private
|
|
||||||
FChunks: TArray<TChunk>;
|
|
||||||
FCount: Int64;
|
|
||||||
|
|
||||||
function LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; inline;
|
|
||||||
function GetItems(Idx: Int64): TDataPoint<T>; inline;
|
|
||||||
public
|
|
||||||
constructor Create(const AChunks: TArray<TChunk>; ACount: Int64);
|
|
||||||
function Add(const Data: array of TDataPoint<T>; First, Count, Lookback: Int64): TMycDataArray<T>;
|
|
||||||
class function CreateEmpty: TMycDataArray<T>; static;
|
|
||||||
// Helper to create a data array from a raw TArray.
|
|
||||||
class function CreateFromArray(const AData: TArray<TDataPoint<T>>; First, Count: Integer): TMycDataArray<T>; static;
|
|
||||||
property Count: Int64 read FCount;
|
|
||||||
property Items[Idx: Int64]: TDataPoint<T> read GetItems; default;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// The implementation class for IDataSeries<T>.
|
// The implementation class for IDataSeries<T>.
|
||||||
TMycDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
|
TMycDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
|
||||||
private
|
private
|
||||||
FData: TMycDataArray<T>;
|
FData: TMycDataArray<TDataPoint<T>>;
|
||||||
FLookback: Int64;
|
FLookback: Int64;
|
||||||
FTotalCount: Int64;
|
FTotalCount: Int64;
|
||||||
function GetCount: Int64;
|
function GetCount: Int64;
|
||||||
@@ -41,10 +20,14 @@ type
|
|||||||
function GetLookback: Int64;
|
function GetLookback: Int64;
|
||||||
function GetTotalCount: Int64;
|
function GetTotalCount: Int64;
|
||||||
public
|
public
|
||||||
constructor Create(ALookback: Int64; const AData: TMycDataArray<T>; ATotalCount: Int64);
|
constructor Create(ALookback: Int64; const AData: TMycDataArray<TDataPoint<T>>; ATotalCount: Int64);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||||
class function CreateDataSeries(Lookback: Int64; const AData: TMycDataArray<T>; ATotalCount: Int64): IDataSeries<T>; static;
|
class function CreateDataSeries(
|
||||||
|
Lookback: Int64;
|
||||||
|
const AData: TMycDataArray<TDataPoint<T>>;
|
||||||
|
ATotalCount: Int64
|
||||||
|
): IDataSeries<T>; static;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Null object implementation for IDataSeries<T>
|
// Null object implementation for IDataSeries<T>
|
||||||
@@ -69,7 +52,7 @@ type
|
|||||||
TCompositeDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
|
TCompositeDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
|
||||||
private
|
private
|
||||||
FBaseSeries: IDataSeries<T>;
|
FBaseSeries: IDataSeries<T>;
|
||||||
FAddedData: TMycDataArray<T>;
|
FAddedData: TMycDataArray<TDataPoint<T>>;
|
||||||
FLookback: Int64;
|
FLookback: Int64;
|
||||||
FCount: Int64;
|
FCount: Int64;
|
||||||
function GetCount: Int64;
|
function GetCount: Int64;
|
||||||
@@ -77,7 +60,7 @@ type
|
|||||||
function GetLookback: Int64;
|
function GetLookback: Int64;
|
||||||
function GetTotalCount: Int64;
|
function GetTotalCount: Int64;
|
||||||
public
|
public
|
||||||
constructor Create(const ABaseSeries: IDataSeries<T>; const AAddedData: TMycDataArray<T>);
|
constructor Create(const ABaseSeries: IDataSeries<T>; const AAddedData: TMycDataArray<TDataPoint<T>>);
|
||||||
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||||
class function CreateComposite(
|
class function CreateComposite(
|
||||||
const BaseSeries: IDataSeries<T>;
|
const BaseSeries: IDataSeries<T>;
|
||||||
@@ -128,97 +111,9 @@ uses
|
|||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
System.Math;
|
System.Math;
|
||||||
|
|
||||||
{ TMycDataArray<T> }
|
|
||||||
|
|
||||||
constructor TMycDataArray<T>.Create(const AChunks: TArray<TChunk>; ACount: Int64);
|
|
||||||
begin
|
|
||||||
FChunks := AChunks;
|
|
||||||
FCount := ACount;
|
|
||||||
end;
|
|
||||||
|
|
||||||
class function TMycDataArray<T>.CreateEmpty: TMycDataArray<T>;
|
|
||||||
begin
|
|
||||||
Result.FChunks := nil;
|
|
||||||
Result.FCount := 0;
|
|
||||||
end;
|
|
||||||
|
|
||||||
class function TMycDataArray<T>.CreateFromArray(const AData: TArray<TDataPoint<T>>; First, Count: Integer): TMycDataArray<T>;
|
|
||||||
begin
|
|
||||||
// Use the Add method on an empty array to perform the chunking logic.
|
|
||||||
Result := CreateEmpty.Add(AData, First, Count, Count);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TMycDataArray<T>.Add(const Data: array of TDataPoint<T>; First, Count, Lookback: Int64): TMycDataArray<T>;
|
|
||||||
var
|
|
||||||
destPhysicalIdx, sourcePhysicalIdx: Int64;
|
|
||||||
itemsToSkip: Int64;
|
|
||||||
numNewChunks: Integer;
|
|
||||||
newChunks: TArray<TChunk>;
|
|
||||||
destChunkIdx, destSubIdx: Integer;
|
|
||||||
sumCount, newCount: Int64;
|
|
||||||
begin
|
|
||||||
if Count < 0 then
|
|
||||||
Count := Length(Data) - First;
|
|
||||||
if (Lookback <= 0) or (Count = 0) then
|
|
||||||
exit(Self);
|
|
||||||
|
|
||||||
Assert(Count <= (Length(Data) - First), 'Count cannot be larger than the source array');
|
|
||||||
for var i := First + 1 to First + Count - 1 do
|
|
||||||
Assert(Data[i].Time >= Data[i - 1].Time, 'Input array for Add is not chronologically sorted');
|
|
||||||
if FCount > 0 then
|
|
||||||
Assert(Data[First].Time >= Self.Items[0].Time, 'First new item is older than last existing item');
|
|
||||||
|
|
||||||
sumCount := FCount + Count;
|
|
||||||
newCount := sumCount;
|
|
||||||
if (Lookback > 0) and (newCount > Lookback) then
|
|
||||||
newCount := Lookback;
|
|
||||||
itemsToSkip := sumCount - newCount;
|
|
||||||
|
|
||||||
numNewChunks := 0;
|
|
||||||
if newCount > 0 then
|
|
||||||
numNewChunks := (newCount - 1) div ChunkSize + 1;
|
|
||||||
SetLength(newChunks, numNewChunks);
|
|
||||||
|
|
||||||
for destPhysicalIdx := 0 to newCount - 1 do
|
|
||||||
begin
|
|
||||||
destChunkIdx := destPhysicalIdx div ChunkSize;
|
|
||||||
destSubIdx := destPhysicalIdx mod ChunkSize;
|
|
||||||
|
|
||||||
if destSubIdx = 0 then
|
|
||||||
SetLength(newChunks[destChunkIdx], ChunkSize);
|
|
||||||
|
|
||||||
sourcePhysicalIdx := itemsToSkip + destPhysicalIdx;
|
|
||||||
|
|
||||||
if sourcePhysicalIdx < FCount then
|
|
||||||
begin
|
|
||||||
newChunks[destChunkIdx][destSubIdx] := FChunks[sourcePhysicalIdx div ChunkSize][sourcePhysicalIdx mod ChunkSize];
|
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
newChunks[destChunkIdx][destSubIdx] := Data[First + (sourcePhysicalIdx - FCount)];
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
Result := TMycDataArray<T>.Create(newChunks, newCount);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TMycDataArray<T>.GetItems(Idx: Int64): TDataPoint<T>;
|
|
||||||
var
|
|
||||||
physicalIndex: Int64;
|
|
||||||
begin
|
|
||||||
Assert((Idx >= 0) and (Idx < FCount), 'Logical index is out of bounds.');
|
|
||||||
physicalIndex := LogicalToPhysicalIndex(Idx);
|
|
||||||
Result := FChunks[physicalIndex div ChunkSize][physicalIndex mod ChunkSize];
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TMycDataArray<T>.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64;
|
|
||||||
begin
|
|
||||||
Result := FCount - LogicalIndex - 1;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TMycDataSeries<T> }
|
{ TMycDataSeries<T> }
|
||||||
|
|
||||||
constructor TMycDataSeries<T>.Create(ALookback: Int64; const AData: TMycDataArray<T>; ATotalCount: Int64);
|
constructor TMycDataSeries<T>.Create(ALookback: Int64; const AData: TMycDataArray<TDataPoint<T>>; ATotalCount: Int64);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FLookback := ALookback;
|
FLookback := ALookback;
|
||||||
@@ -231,7 +126,11 @@ begin
|
|||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TMycDataSeries<T>.CreateDataSeries(Lookback: Int64; const AData: TMycDataArray<T>; ATotalCount: Int64): IDataSeries<T>;
|
class function TMycDataSeries<T>.CreateDataSeries(
|
||||||
|
Lookback: Int64;
|
||||||
|
const AData: TMycDataArray<TDataPoint<T>>;
|
||||||
|
ATotalCount: Int64
|
||||||
|
): IDataSeries<T>;
|
||||||
begin
|
begin
|
||||||
if Lookback > 0 then
|
if Lookback > 0 then
|
||||||
Result := TMycDataSeries<T>.Create(Lookback, AData, ATotalCount)
|
Result := TMycDataSeries<T>.Create(Lookback, AData, ATotalCount)
|
||||||
@@ -241,7 +140,7 @@ end;
|
|||||||
|
|
||||||
function TMycDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
function TMycDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||||
var
|
var
|
||||||
newData: TMycDataArray<T>;
|
newData: TMycDataArray<TDataPoint<T>>;
|
||||||
newTotalCount: Int64;
|
newTotalCount: Int64;
|
||||||
begin
|
begin
|
||||||
if Count < 0 then
|
if Count < 0 then
|
||||||
@@ -319,7 +218,7 @@ end;
|
|||||||
|
|
||||||
{ TCompositeDataSeries<T> }
|
{ TCompositeDataSeries<T> }
|
||||||
|
|
||||||
constructor TCompositeDataSeries<T>.Create(const ABaseSeries: IDataSeries<T>; const AAddedData: TMycDataArray<T>);
|
constructor TCompositeDataSeries<T>.Create(const ABaseSeries: IDataSeries<T>; const AAddedData: TMycDataArray<TDataPoint<T>>);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FBaseSeries := ABaseSeries;
|
FBaseSeries := ABaseSeries;
|
||||||
@@ -334,7 +233,7 @@ end;
|
|||||||
|
|
||||||
function TCompositeDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
function TCompositeDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||||
var
|
var
|
||||||
newAddedData: TMycDataArray<T>;
|
newAddedData: TMycDataArray<TDataPoint<T>>;
|
||||||
itemsInBase: Int64;
|
itemsInBase: Int64;
|
||||||
lookbackForAdd: Int64;
|
lookbackForAdd: Int64;
|
||||||
begin
|
begin
|
||||||
@@ -373,7 +272,7 @@ begin
|
|||||||
if Count = 0 then
|
if Count = 0 then
|
||||||
exit(BaseSeries);
|
exit(BaseSeries);
|
||||||
|
|
||||||
Result := TCompositeDataSeries<T>.Create(BaseSeries, TMycDataArray<T>.CreateFromArray(Data, First, Count));
|
Result := TCompositeDataSeries<T>.Create(BaseSeries, TMycDataArray<TDataPoint<T>>.CreateFromArray(Data, First, Count));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCompositeDataSeries<T>.GetCount: Int64;
|
function TCompositeDataSeries<T>.GetCount: Int64;
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
unit Myc.Trade.DataArray;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
Myc.Trade.DataPoint;
|
||||||
|
|
||||||
|
type
|
||||||
|
TMycDataArray<T> = record
|
||||||
|
private
|
||||||
|
const
|
||||||
|
ChunkSize = 1024;
|
||||||
|
type
|
||||||
|
TChunk = TArray<T>;
|
||||||
|
private
|
||||||
|
FChunks: TArray<TChunk>;
|
||||||
|
FCount: Int64;
|
||||||
|
|
||||||
|
function LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; inline;
|
||||||
|
function GetItems(Idx: Int64): T; inline;
|
||||||
|
public
|
||||||
|
constructor Create(const AChunks: TArray<TChunk>; ACount: Int64);
|
||||||
|
function Add(const Data: T; Lookback: Int64): TMycDataArray<T>; overload;
|
||||||
|
function Add(const Data: array of T; First, Count, Lookback: Int64): TMycDataArray<T>; overload;
|
||||||
|
class function CreateEmpty: TMycDataArray<T>; static;
|
||||||
|
// Helper to create a data array from a raw TArray.
|
||||||
|
class function CreateFromArray(const AData: TArray<T>; First, Count: Integer): TMycDataArray<T>; static;
|
||||||
|
property Count: Int64 read FCount;
|
||||||
|
property Items[Idx: Int64]: T read GetItems; default;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TMycDataArray<T> }
|
||||||
|
|
||||||
|
constructor TMycDataArray<T>.Create(const AChunks: TArray<TChunk>; ACount: Int64);
|
||||||
|
begin
|
||||||
|
FChunks := AChunks;
|
||||||
|
FCount := ACount;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TMycDataArray<T>.CreateEmpty: TMycDataArray<T>;
|
||||||
|
begin
|
||||||
|
Result.FChunks := nil;
|
||||||
|
Result.FCount := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TMycDataArray<T>.CreateFromArray(const AData: TArray<T>; First, Count: Integer): TMycDataArray<T>;
|
||||||
|
begin
|
||||||
|
// Use the Add method on an empty array to perform the chunking logic.
|
||||||
|
Result := CreateEmpty.Add(AData, First, Count, Count);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycDataArray<T>.Add(const Data: array of T; First, Count, Lookback: Int64): TMycDataArray<T>;
|
||||||
|
var
|
||||||
|
destPhysicalIdx, sourcePhysicalIdx: Int64;
|
||||||
|
itemsToSkip: Int64;
|
||||||
|
numNewChunks: Integer;
|
||||||
|
newChunks: TArray<TChunk>;
|
||||||
|
destChunkIdx, destSubIdx: Integer;
|
||||||
|
sumCount, newCount: Int64;
|
||||||
|
begin
|
||||||
|
if Count < 0 then
|
||||||
|
Count := Length(Data) - First;
|
||||||
|
if (Lookback <= 0) or (Count = 0) then
|
||||||
|
exit(Self);
|
||||||
|
|
||||||
|
Assert(Count <= (Length(Data) - First), 'Count cannot be larger than the source array');
|
||||||
|
|
||||||
|
sumCount := FCount + Count;
|
||||||
|
newCount := sumCount;
|
||||||
|
if (Lookback > 0) and (newCount > Lookback) then
|
||||||
|
newCount := Lookback;
|
||||||
|
itemsToSkip := sumCount - newCount;
|
||||||
|
|
||||||
|
numNewChunks := 0;
|
||||||
|
if newCount > 0 then
|
||||||
|
numNewChunks := (newCount - 1) div ChunkSize + 1;
|
||||||
|
SetLength(newChunks, numNewChunks);
|
||||||
|
|
||||||
|
for destPhysicalIdx := 0 to newCount - 1 do
|
||||||
|
begin
|
||||||
|
destChunkIdx := destPhysicalIdx div ChunkSize;
|
||||||
|
destSubIdx := destPhysicalIdx mod ChunkSize;
|
||||||
|
|
||||||
|
if destSubIdx = 0 then
|
||||||
|
SetLength(newChunks[destChunkIdx], ChunkSize);
|
||||||
|
|
||||||
|
sourcePhysicalIdx := itemsToSkip + destPhysicalIdx;
|
||||||
|
|
||||||
|
if sourcePhysicalIdx < FCount then
|
||||||
|
begin
|
||||||
|
newChunks[destChunkIdx][destSubIdx] := FChunks[sourcePhysicalIdx div ChunkSize][sourcePhysicalIdx mod ChunkSize];
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
newChunks[destChunkIdx][destSubIdx] := Data[First + (sourcePhysicalIdx - FCount)];
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := TMycDataArray<T>.Create(newChunks, newCount);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycDataArray<T>.Add(const Data: T; Lookback: Int64): TMycDataArray<T>;
|
||||||
|
begin
|
||||||
|
Result := Add([Data], 0, 1, Lookback);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycDataArray<T>.GetItems(Idx: Int64): T;
|
||||||
|
var
|
||||||
|
physicalIndex: Int64;
|
||||||
|
begin
|
||||||
|
Assert((Idx >= 0) and (Idx < FCount), 'Logical index is out of bounds.');
|
||||||
|
physicalIndex := LogicalToPhysicalIndex(Idx);
|
||||||
|
Result := FChunks[physicalIndex div ChunkSize][physicalIndex mod ChunkSize];
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycDataArray<T>.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64;
|
||||||
|
begin
|
||||||
|
Result := FCount - LogicalIndex - 1;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
+105
-19
@@ -8,18 +8,18 @@ uses
|
|||||||
type
|
type
|
||||||
// A data record for an Ask/Bid price pair.
|
// A data record for an Ask/Bid price pair.
|
||||||
TAskBidItem = packed record
|
TAskBidItem = packed record
|
||||||
Ask: Single;
|
Ask: Double;
|
||||||
Bid: Single;
|
Bid: Double;
|
||||||
constructor Create(AAsk, ABid: Single);
|
constructor Create(AAsk, ABid: Double);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TOhlcItem = record
|
TOhlcItem = record
|
||||||
Open: Single;
|
Open: Double;
|
||||||
High: Single;
|
High: Double;
|
||||||
Low: Single;
|
Low: Double;
|
||||||
Close: Single;
|
Close: Double;
|
||||||
Volume: Single;
|
Volume: Double;
|
||||||
constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Single);
|
constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Represents a time-stamped data point in a series.
|
// Represents a time-stamped data point in a series.
|
||||||
@@ -29,13 +29,38 @@ type
|
|||||||
constructor Create(ATime: TDateTime; const AData: T);
|
constructor Create(ATime: TDateTime; const AData: T);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// A time-ordered series of data points, optimized for chronological additions.
|
IMycProcessor<T> = interface
|
||||||
// The most recently added element has the logical index 0.
|
procedure ProcessData(const Value: T);
|
||||||
|
end;
|
||||||
|
|
||||||
|
TMycGenericProcessor<T> = class(TInterfacedObject, IMycProcessor<T>)
|
||||||
|
type
|
||||||
|
TProc = reference to procedure(const Value: T);
|
||||||
|
private
|
||||||
|
FProc: TProc;
|
||||||
|
procedure ProcessData(const Value: T);
|
||||||
|
public
|
||||||
|
constructor Create(const AProc: TProc);
|
||||||
|
end;
|
||||||
|
|
||||||
|
TMycContainedProcessor<T> = class(TContainedObject, IMycProcessor<T>)
|
||||||
|
type
|
||||||
|
TProc = procedure(const Value: T) of object;
|
||||||
|
private
|
||||||
|
FProc: TProc;
|
||||||
|
procedure ProcessData(const Value: T);
|
||||||
|
public
|
||||||
|
constructor Create(const Controller: IInterface; const AProc: TProc);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// An immutable time-ordered series of data points.
|
||||||
|
// The most recent element has the logical index 0.
|
||||||
IDataSeries<T> = interface
|
IDataSeries<T> = interface
|
||||||
function GetCount: Int64;
|
function GetCount: Int64;
|
||||||
function GetItems(Idx: Int64): TDataPoint<T>;
|
function GetItems(Idx: Int64): TDataPoint<T>;
|
||||||
function GetTotalCount: Int64;
|
function GetTotalCount: Int64;
|
||||||
function GetLookback: Int64;
|
function GetLookback: Int64;
|
||||||
|
// Add data and result the new series.
|
||||||
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||||
property Count: Int64 read GetCount;
|
property Count: Int64 read GetCount;
|
||||||
// Accesses data points by their logical index.
|
// Accesses data points by their logical index.
|
||||||
@@ -74,7 +99,6 @@ type
|
|||||||
|
|
||||||
function Add(const Data: TArray<TDataPoint<T>>): TDataSeries<T>; overload;
|
function Add(const Data: TArray<TDataPoint<T>>): TDataSeries<T>; overload;
|
||||||
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): TDataSeries<T>; overload;
|
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): TDataSeries<T>; overload;
|
||||||
|
|
||||||
function Convert<S>(const Func: TConvertFunc<S>): TDataSeries<S>;
|
function Convert<S>(const Func: TConvertFunc<S>): TDataSeries<S>;
|
||||||
|
|
||||||
// Searches for a data point by its timestamp.
|
// Searches for a data point by its timestamp.
|
||||||
@@ -99,12 +123,21 @@ type
|
|||||||
function ToOhlc(TimeFrame: TTimeSpan): TDataSeries<TOhlcItem>;
|
function ToOhlc(TimeFrame: TTimeSpan): TDataSeries<TOhlcItem>;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TDataSeriesOhlcHelper = record helper for TDataSeries<TOhlcItem>
|
||||||
|
function ToOpen: TDataSeries<Single>;
|
||||||
|
function ToClose: TDataSeries<Single>;
|
||||||
|
function ToHigh: TDataSeries<Single>;
|
||||||
|
function ToLow: TDataSeries<Single>;
|
||||||
|
function ToVolume: TDataSeries<Single>;
|
||||||
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
System.Math,
|
System.Math,
|
||||||
System.Generics.Collections,
|
System.Generics.Collections,
|
||||||
|
Myc.Trade.DataArray,
|
||||||
Myc.Trade.Core.DataPoint;
|
Myc.Trade.Core.DataPoint;
|
||||||
|
|
||||||
// Optimized helper function using direct TTimeSpan features and integer arithmetic.
|
// Optimized helper function using direct TTimeSpan features and integer arithmetic.
|
||||||
@@ -180,17 +213,40 @@ begin
|
|||||||
if not firstPointInBar then
|
if not firstPointInBar then
|
||||||
ohlcPoints.Add(TDataPoint<TOhlcItem>.Create(windowEndTime, currentBar));
|
ohlcPoints.Add(TDataPoint<TOhlcItem>.Create(windowEndTime, currentBar));
|
||||||
|
|
||||||
var dataArray := TMycDataArray<TOhlcItem>.CreateFromArray(ohlcPoints.ToArray, 0, ohlcPoints.Count);
|
Result := TDataSeries<TOhlcItem>.CreateDataSeries(ohlcPoints.Count, ohlcPoints.ToArray);
|
||||||
var seriesImpl := TMycDataSeries<TOhlcItem>.Create(ohlcPoints.Count, dataArray, ohlcPoints.Count);
|
|
||||||
Result := TDataSeries<TOhlcItem>.Create(seriesImpl);
|
|
||||||
finally
|
finally
|
||||||
ohlcPoints.Free;
|
ohlcPoints.Free;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TDataSeriesOhlcHelper.ToClose: TDataSeries<Single>;
|
||||||
|
begin
|
||||||
|
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Close; end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataSeriesOhlcHelper.ToHigh: TDataSeries<Single>;
|
||||||
|
begin
|
||||||
|
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.High; end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataSeriesOhlcHelper.ToLow: TDataSeries<Single>;
|
||||||
|
begin
|
||||||
|
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Low; end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataSeriesOhlcHelper.ToOpen: TDataSeries<Single>;
|
||||||
|
begin
|
||||||
|
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Open; end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataSeriesOhlcHelper.ToVolume: TDataSeries<Single>;
|
||||||
|
begin
|
||||||
|
Result := Self.Convert<Single>(function(const Ohlc: TDataPoint<TOhlcItem>): Single begin Result := Ohlc.Data.Volume; end);
|
||||||
|
end;
|
||||||
|
|
||||||
{ TAskBidItem }
|
{ TAskBidItem }
|
||||||
|
|
||||||
constructor TAskBidItem.Create(AAsk, ABid: Single);
|
constructor TAskBidItem.Create(AAsk, ABid: Double);
|
||||||
begin
|
begin
|
||||||
Ask := AAsk;
|
Ask := AAsk;
|
||||||
Bid := ABid;
|
Bid := ABid;
|
||||||
@@ -198,7 +254,7 @@ end;
|
|||||||
|
|
||||||
{ TOhlcItem }
|
{ TOhlcItem }
|
||||||
|
|
||||||
constructor TOhlcItem.Create(AOpen, AHigh, ALow, AClose, AVolume: Single);
|
constructor TOhlcItem.Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
|
||||||
begin
|
begin
|
||||||
Open := AOpen;
|
Open := AOpen;
|
||||||
High := AHigh;
|
High := AHigh;
|
||||||
@@ -225,12 +281,19 @@ end;
|
|||||||
|
|
||||||
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): TDataSeries<T>;
|
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): TDataSeries<T>;
|
||||||
begin
|
begin
|
||||||
|
{$ifdef DEBUG}
|
||||||
|
for var i := First + 1 to First + Count - 1 do
|
||||||
|
Assert(Data[i].Time >= Data[i - 1].Time, 'Input array for Add is not chronologically sorted');
|
||||||
|
if FDataSeries.Count > 0 then
|
||||||
|
Assert(Data[First].Time >= FDataSeries[0].Time, 'First new item is older than last existing item');
|
||||||
|
{$endif}
|
||||||
|
|
||||||
Result := FDataSeries.Add(Data, First, Count);
|
Result := FDataSeries.Add(Data, First, Count);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>): TDataSeries<T>;
|
function TDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>): TDataSeries<T>;
|
||||||
begin
|
begin
|
||||||
Result := FDataSeries.Add(Data, 0, Length(Data));
|
Result := Add(Data, 0, Length(Data));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataSeries<T>.Convert<S>(const Func: TConvertFunc<S>): TDataSeries<S>;
|
function TDataSeries<T>.Convert<S>(const Func: TConvertFunc<S>): TDataSeries<S>;
|
||||||
@@ -240,7 +303,8 @@ end;
|
|||||||
|
|
||||||
class function TDataSeries<T>.CreateDataSeries(Lookback: Int64; const Data: TArray<TDataPoint<T>> = nil): TDataSeries<T>;
|
class function TDataSeries<T>.CreateDataSeries(Lookback: Int64; const Data: TArray<TDataPoint<T>> = nil): TDataSeries<T>;
|
||||||
begin
|
begin
|
||||||
Result := TMycDataSeries<T>.CreateDataSeries(Lookback, TMycDataArray<T>.CreateFromArray(Data, 0, Length(Data)), Length(Data));
|
Result :=
|
||||||
|
TMycDataSeries<T>.CreateDataSeries(Lookback, TMycDataArray<TDataPoint<T>>.CreateFromArray(Data, 0, Length(Data)), Length(Data));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class operator TDataSeries<T>.Finalize(var Dest: TDataSeries<T>);
|
class operator TDataSeries<T>.Finalize(var Dest: TDataSeries<T>);
|
||||||
@@ -350,4 +414,26 @@ begin
|
|||||||
Result[i] := FDataSeries[n - i].Data;
|
Result[i] := FDataSeries[n - i].Data;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
constructor TMycGenericProcessor<T>.Create(const AProc: TProc);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FProc := AProc;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycGenericProcessor<T>.ProcessData(const Value: T);
|
||||||
|
begin
|
||||||
|
FProc(Value);
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TMycContainedProcessor<T>.Create(const Controller: IInterface; const AProc: TProc);
|
||||||
|
begin
|
||||||
|
inherited Create(Controller);
|
||||||
|
FProc := AProc;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMycContainedProcessor<T>.ProcessData(const Value: T);
|
||||||
|
begin
|
||||||
|
FProc(Value);
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -54,14 +54,12 @@ type
|
|||||||
function IsHistory: Boolean; virtual; abstract;
|
function IsHistory: Boolean; virtual; abstract;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TDataProc<T> = reference to procedure(const Values: TArray<TDataPoint<T>>; const Terminated: TState);
|
|
||||||
|
|
||||||
// Interface for an instantiable data server.
|
// Interface for an instantiable data server.
|
||||||
IDataServer<T: record> = interface
|
IDataServer<T: record> = interface
|
||||||
['{1F8E5A9D-E92A-44C1-9F3F-C4B82A6E94B3}']
|
['{1F8E5A9D-E92A-44C1-9F3F-C4B82A6E94B3}']
|
||||||
function CreateStream(const Symbol: String): IDataStream<T>;
|
function CreateStream(const Symbol: String): IDataStream<T>;
|
||||||
procedure ClearCache;
|
procedure ClearCache;
|
||||||
function ProcessData(const Symbol: String; const Terminate: TSignal; const Proc: TDataProc<T>): TState;
|
function ProcessData(const Symbol: String; const Terminated: TState; const Processor: IMycProcessor<TArray<TDataPoint<T>>>): TState;
|
||||||
function EnumerateSymbols: TFuture<TArray<String>>;
|
function EnumerateSymbols: TFuture<TArray<String>>;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -104,8 +102,8 @@ type
|
|||||||
function ProcessFile(
|
function ProcessFile(
|
||||||
FileInfo: TAuraDataFile;
|
FileInfo: TAuraDataFile;
|
||||||
const DataFile: TFuture<TArray<TDataPoint<T>>>;
|
const DataFile: TFuture<TArray<TDataPoint<T>>>;
|
||||||
Terminated: TState;
|
const Terminated: TState;
|
||||||
Proc: TDataProc<T>
|
Processor: IMycProcessor<TArray<TDataPoint<T>>>
|
||||||
): TState;
|
): TState;
|
||||||
|
|
||||||
strict private
|
strict private
|
||||||
@@ -129,7 +127,7 @@ type
|
|||||||
function EnumerateSymbols: TFuture<TArray<String>>;
|
function EnumerateSymbols: TFuture<TArray<String>>;
|
||||||
function LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TDataPoint<T>>>;
|
function LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TDataPoint<T>>>;
|
||||||
|
|
||||||
function ProcessData(const Symbol: String; const Terminate: TSignal; const Proc: TDataProc<T>): TState;
|
function ProcessData(const Symbol: String; const Terminated: TState; const Processor: IMycProcessor<TArray<TDataPoint<T>>>): TState;
|
||||||
|
|
||||||
property Path: String read GetPath;
|
property Path: String read GetPath;
|
||||||
end;
|
end;
|
||||||
@@ -166,14 +164,19 @@ type
|
|||||||
|
|
||||||
// Aura tick data file Ask-Bid
|
// Aura tick data file Ask-Bid
|
||||||
|
|
||||||
TAuraTABFileServer = class(TAuraDataServer<TAskBidItem>)
|
TAuraAskBidFileItem = packed record
|
||||||
|
Ask: Single;
|
||||||
|
Bid: Single;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TAuraTABFileServer = class(TAuraDataServer<TAuraAskBidFileItem>)
|
||||||
protected
|
protected
|
||||||
// Scans a directory and returns the oldest file found for each symbol.
|
// Scans a directory and returns the oldest file found for each symbol.
|
||||||
class function ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<TAskBidItem>>; override;
|
class function ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<TAuraAskBidFileItem>>; override;
|
||||||
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<TAskBidItem>>; override;
|
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<TAuraAskBidFileItem>>; override;
|
||||||
public
|
public
|
||||||
function CreateStream(const Symbol: String): IDataStream<TAskBidItem>; override;
|
function CreateStream(const Symbol: String): IDataStream<TAuraAskBidFileItem>; override;
|
||||||
function LoadDataSeries(const InitialFile: TAuraDataFile): TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
function LoadDataSeries(const InitialFile: TAuraDataFile): TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>;
|
||||||
function ParseFileName(const FileName: string): TAuraDataFile; override;
|
function ParseFileName(const FileName: string): TAuraDataFile; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -390,17 +393,21 @@ begin
|
|||||||
Result := FPath;
|
Result := FPath;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAuraDataServer<T>.ProcessData(const Symbol: String; const Terminate: TSignal; const Proc: TDataProc<T>): TState;
|
function TAuraDataServer<T>.ProcessData(
|
||||||
|
const Symbol: String;
|
||||||
|
const Terminated: TState;
|
||||||
|
const Processor: IMycProcessor<TArray<TDataPoint<T>>>
|
||||||
|
): TState;
|
||||||
begin
|
begin
|
||||||
var capProc := Proc;
|
var cProc := Processor;
|
||||||
var terminated := TFlag.CreateObserver(Terminate).State;
|
var cTerminated := Terminated;
|
||||||
|
|
||||||
Result :=
|
Result :=
|
||||||
FindFirstFile(Symbol)
|
FindFirstFile(Symbol)
|
||||||
.Chain(
|
.Chain(
|
||||||
function(const FirstFileInfo: TAuraDataFile): TState
|
function(const FirstFileInfo: TAuraDataFile): TState
|
||||||
begin
|
begin
|
||||||
Result := ProcessFile(FirstFileInfo, LoadDataFile(FirstFileInfo), terminated, capProc);
|
Result := ProcessFile(FirstFileInfo, LoadDataFile(FirstFileInfo), cTerminated, cProc);
|
||||||
end);
|
end);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -413,23 +420,24 @@ end;
|
|||||||
function TAuraDataServer<T>.ProcessFile(
|
function TAuraDataServer<T>.ProcessFile(
|
||||||
FileInfo: TAuraDataFile;
|
FileInfo: TAuraDataFile;
|
||||||
const DataFile: TFuture<TArray<TDataPoint<T>>>;
|
const DataFile: TFuture<TArray<TDataPoint<T>>>;
|
||||||
Terminated: TState;
|
const Terminated: TState;
|
||||||
Proc: TDataProc<T>
|
Processor: IMycProcessor<TArray<TDataPoint<T>>>
|
||||||
): TState;
|
): TState;
|
||||||
begin
|
begin
|
||||||
if not FileInfo.IsValid or Terminated.IsSet then
|
if not FileInfo.IsValid or Terminated.IsSet then
|
||||||
exit(TState.Null);
|
exit(DataFile.Done);
|
||||||
|
|
||||||
// Read ahead the next file, while processing the current one
|
// Read ahead the next file, while processing the current one
|
||||||
var nextFileInfo := FileInfo.GetNextFile;
|
var nextFileInfo := FileInfo.GetNextFile;
|
||||||
var nextFile := LoadDataFile(nextFileInfo);
|
var nextFile := LoadDataFile(nextFileInfo);
|
||||||
|
|
||||||
|
var cTerminated := Terminated;
|
||||||
Result :=
|
Result :=
|
||||||
DataFile.Chain(
|
DataFile.Chain(
|
||||||
function(const Data: TArray<TDataPoint<T>>): TState
|
function(const Data: TArray<TDataPoint<T>>): TState
|
||||||
begin
|
begin
|
||||||
Proc(Data, Terminated);
|
Processor.ProcessData(Data);
|
||||||
Result := ProcessFile(nextFileInfo, nextFile, Terminated, Proc);
|
Result := ProcessFile(nextFileInfo, nextFile, cTerminated, Processor);
|
||||||
end
|
end
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
@@ -574,16 +582,16 @@ end;
|
|||||||
|
|
||||||
{ TAuraTABFileServer }
|
{ TAuraTABFileServer }
|
||||||
|
|
||||||
function TAuraTABFileServer.CreateStream(const Symbol: String): IDataStream<TAskBidItem>;
|
function TAuraTABFileServer.CreateStream(const Symbol: String): IDataStream<TAuraAskBidFileItem>;
|
||||||
begin
|
begin
|
||||||
Result := TAuraFileStream<TAskBidItem>.Create(Self, Symbol);
|
Result := TAuraFileStream<TAuraAskBidFileItem>.Create(Self, Symbol);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAuraTABFileServer.LoadDataSeries(const InitialFile: TAuraDataFile): TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
function TAuraTABFileServer.LoadDataSeries(const InitialFile: TAuraDataFile): TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>;
|
||||||
var
|
var
|
||||||
loadedState: TState;
|
loadedState: TState;
|
||||||
loadedFiles: TArray<TFuture<TArray<TDataPoint<TAskBidItem>>>>;
|
loadedFiles: TArray<TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>>;
|
||||||
liveData: TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
liveData: TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>;
|
||||||
tabFiles: TList<TAuraDataFile>;
|
tabFiles: TList<TAuraDataFile>;
|
||||||
liveFile: TAuraDataFile;
|
liveFile: TAuraDataFile;
|
||||||
currentFileInfo: TAuraDataFile;
|
currentFileInfo: TAuraDataFile;
|
||||||
@@ -607,7 +615,7 @@ begin
|
|||||||
liveFile := ParseFileName(potentialLivePath);
|
liveFile := ParseFileName(potentialLivePath);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
var loadedFileList := TList<TFuture<TArray<TDataPoint<TAskBidItem>>>>.Create;
|
var loadedFileList := TList<TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>>.Create;
|
||||||
var loadStates := TList<TState>.Create;
|
var loadStates := TList<TState>.Create;
|
||||||
try
|
try
|
||||||
for var fileInfo in tabFiles do
|
for var fileInfo in tabFiles do
|
||||||
@@ -617,7 +625,7 @@ begin
|
|||||||
loadStates.Add(data.Done);
|
loadStates.Add(data.Done);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
liveData := TFuture<TArray<TDataPoint<TAskBidItem>>>.Null;
|
liveData := TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>.Null;
|
||||||
if liveFile.IsValid then
|
if liveFile.IsValid then
|
||||||
begin
|
begin
|
||||||
liveData := LoadDataFile(liveFile);
|
liveData := LoadDataFile(liveFile);
|
||||||
@@ -636,11 +644,11 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
Result :=
|
Result :=
|
||||||
TFuture<TArray<TDataPoint<TAskBidItem>>>.Construct(
|
TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>.Construct(
|
||||||
loadedState,
|
loadedState,
|
||||||
function: TArray<TDataPoint<TAskBidItem>>
|
function: TArray<TDataPoint<TAuraAskBidFileItem>>
|
||||||
begin
|
begin
|
||||||
var tickList := TList<TDataPoint<TAskBidItem>>.Create;
|
var tickList := TList<TDataPoint<TAuraAskBidFileItem>>.Create;
|
||||||
try
|
try
|
||||||
var overallLastTabTickTime: TDateTime := 0;
|
var overallLastTabTickTime: TDateTime := 0;
|
||||||
var cnt := 0;
|
var cnt := 0;
|
||||||
@@ -710,7 +718,7 @@ begin
|
|||||||
Result := TAuraDataFile.Create(path, symbol, fileExt, year, month);
|
Result := TAuraDataFile.Create(path, symbol, fileExt, year, month);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TAuraTABFileServer.ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<TAskBidItem>>;
|
class function TAuraTABFileServer.ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<TAuraAskBidFileItem>>;
|
||||||
var
|
var
|
||||||
decompressionStream: TStream;
|
decompressionStream: TStream;
|
||||||
localHeader: TZipHeader;
|
localHeader: TZipHeader;
|
||||||
@@ -749,11 +757,11 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TAuraTABFileServer.ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<TAskBidItem>>;
|
class function TAuraTABFileServer.ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<TAuraAskBidFileItem>>;
|
||||||
type
|
type
|
||||||
TFileRecord = packed record
|
TFileRecord = packed record
|
||||||
TimeStamp: TDateTime;
|
TimeStamp: TDateTime;
|
||||||
Data: TAskBidItem;
|
Data: TAuraAskBidFileItem;
|
||||||
end;
|
end;
|
||||||
var
|
var
|
||||||
fileSize: Int64;
|
fileSize: Int64;
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ end;
|
|||||||
|
|
||||||
procedure TMycTestNotifierTests.Setup;
|
procedure TMycTestNotifierTests.Setup;
|
||||||
begin
|
begin
|
||||||
FNotifier.Create;
|
|
||||||
// FNotifier's internal FFirst is now 1 (unlocked, not finalized)
|
// FNotifier's internal FFirst is now 1 (unlocked, not finalized)
|
||||||
|
FNotifier := Default(TMycNotifyList<IMyTestInterface>);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMycTestNotifierTests.TearDown;
|
procedure TMycTestNotifierTests.TearDown;
|
||||||
@@ -92,7 +92,7 @@ procedure TMycTestNotifierTests.Test01_DestroyExecutesWithoutErrors;
|
|||||||
begin
|
begin
|
||||||
// This call is expected to succeed without raising EAssertionFailed or other exceptions
|
// This call is expected to succeed without raising EAssertionFailed or other exceptions
|
||||||
// if TMycNotifyList.Destroy is implemented correctly.
|
// if TMycNotifyList.Destroy is implemented correctly.
|
||||||
FNotifier.Destroy;
|
FNotifier.Finalize;
|
||||||
Assert.IsTrue(True, 'FNotifier.Destroy completed without raising an exception.');
|
Assert.IsTrue(True, 'FNotifier.Destroy completed without raising an exception.');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -102,11 +102,11 @@ end;
|
|||||||
procedure TMycTestNotifierTests.Test02_NotifyOnFinalizedLockedEmptyListExecutesWithoutErrors;
|
procedure TMycTestNotifierTests.Test02_NotifyOnFinalizedLockedEmptyListExecutesWithoutErrors;
|
||||||
var
|
var
|
||||||
predicateCallCount: Integer;
|
predicateCallCount: Integer;
|
||||||
dummyPredicate: TPredicate<IMyTestInterface>;
|
dummyPredicate: TMycNotifyList<IMyTestInterface>.TNotifyProc;
|
||||||
begin
|
begin
|
||||||
predicateCallCount := 0;
|
predicateCallCount := 0;
|
||||||
dummyPredicate :=
|
dummyPredicate :=
|
||||||
function(Item: IMyTestInterface): Boolean
|
function(const Item: IMyTestInterface): Boolean
|
||||||
begin
|
begin
|
||||||
Inc(predicateCallCount);
|
Inc(predicateCallCount);
|
||||||
Result := True;
|
Result := True;
|
||||||
@@ -209,7 +209,7 @@ begin
|
|||||||
|
|
||||||
itemsProcessedCount := 0;
|
itemsProcessedCount := 0;
|
||||||
FNotifier.Notify(
|
FNotifier.Notify(
|
||||||
function(Item: IMyTestInterface): Boolean
|
function(const Item: IMyTestInterface): Boolean
|
||||||
begin
|
begin
|
||||||
Inc(itemsProcessedCount);
|
Inc(itemsProcessedCount);
|
||||||
TMyTestReceiver(Item).Foo;
|
TMyTestReceiver(Item).Foo;
|
||||||
@@ -229,7 +229,7 @@ begin
|
|||||||
itemsProcessedCount := 0;
|
itemsProcessedCount := 0;
|
||||||
|
|
||||||
FNotifier.Notify(
|
FNotifier.Notify(
|
||||||
function(Item: IMyTestInterface): Boolean
|
function(const Item: IMyTestInterface): Boolean
|
||||||
begin
|
begin
|
||||||
Inc(itemsProcessedCount);
|
Inc(itemsProcessedCount);
|
||||||
TMyTestReceiver(Item).Foo;
|
TMyTestReceiver(Item).Foo;
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ begin
|
|||||||
// if not FOwnerFixture.FNotifier.IsFinalized then // Don't notify if finalized
|
// if not FOwnerFixture.FNotifier.IsFinalized then // Don't notify if finalized
|
||||||
begin
|
begin
|
||||||
FOwnerFixture.FNotifier.Notify(
|
FOwnerFixture.FNotifier.Notify(
|
||||||
function(Item: IMyStressTestInterface): Boolean
|
function(const Item: IMyStressTestInterface): Boolean
|
||||||
begin
|
begin
|
||||||
Item.Foo(FThreadID); // Pass ThreadID as notification type for context
|
Item.Foo(FThreadID); // Pass ThreadID as notification type for context
|
||||||
Result := True; // Keep item
|
Result := True; // Keep item
|
||||||
@@ -255,7 +255,6 @@ end;
|
|||||||
procedure TMycNotifierChaosStressTests.Setup;
|
procedure TMycNotifierChaosStressTests.Setup;
|
||||||
begin
|
begin
|
||||||
Randomize; // Initialize random number generator
|
Randomize; // Initialize random number generator
|
||||||
FNotifier.Create;
|
|
||||||
FAllReceiversCreated := TList<IMyStressTestInterface>.Create;
|
FAllReceiversCreated := TList<IMyStressTestInterface>.Create;
|
||||||
FCriticalSectionForList := TCriticalSection.Create;
|
FCriticalSectionForList := TCriticalSection.Create;
|
||||||
FNextReceiverInstanceID := 0;
|
FNextReceiverInstanceID := 0;
|
||||||
@@ -359,7 +358,7 @@ begin
|
|||||||
// if not FNotifier.IsFinalized then
|
// if not FNotifier.IsFinalized then
|
||||||
begin
|
begin
|
||||||
FNotifier.Notify(
|
FNotifier.Notify(
|
||||||
function(Item: IMyStressTestInterface): Boolean
|
function(const Item: IMyStressTestInterface): Boolean
|
||||||
begin
|
begin
|
||||||
actualLiveReceiversInNotifier.Add(Item);
|
actualLiveReceiversInNotifier.Add(Item);
|
||||||
Result := True;
|
Result := True;
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ end;
|
|||||||
procedure TMycNotifierThreadingTests.Setup;
|
procedure TMycNotifierThreadingTests.Setup;
|
||||||
begin
|
begin
|
||||||
Randomize;
|
Randomize;
|
||||||
FNotifier.Create;
|
|
||||||
FReceiverMasterList := TList<IMyTestInterface>.Create;
|
FReceiverMasterList := TList<IMyTestInterface>.Create;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -256,7 +255,7 @@ begin
|
|||||||
FNotifier.Lock;
|
FNotifier.Lock;
|
||||||
try
|
try
|
||||||
FNotifier.Notify(
|
FNotifier.Notify(
|
||||||
function(Item: IMyTestInterface): Boolean
|
function(const Item: IMyTestInterface): Boolean
|
||||||
begin
|
begin
|
||||||
Inc(currentNotifyCount);
|
Inc(currentNotifyCount);
|
||||||
Result := True; // Keep item in the Notifier
|
Result := True; // Keep item in the Notifier
|
||||||
@@ -288,7 +287,7 @@ begin
|
|||||||
FNotifier.Lock;
|
FNotifier.Lock;
|
||||||
try
|
try
|
||||||
FNotifier.Notify(
|
FNotifier.Notify(
|
||||||
function(Item: IMyTestInterface): Boolean
|
function(const Item: IMyTestInterface): Boolean
|
||||||
begin
|
begin
|
||||||
Inc(currentNotifyCount);
|
Inc(currentNotifyCount);
|
||||||
Result := True;
|
Result := True;
|
||||||
|
|||||||
Reference in New Issue
Block a user