This commit is contained in:
Michael Schimmel
2025-07-14 16:44:29 +02:00
parent d0ad547aa3
commit 6f0b927a05
7 changed files with 177 additions and 169 deletions
+1 -1
View File
@@ -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)'==''">Debug</Config> <Config Condition="'$(Config)'==''">Release</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>
+71 -68
View File
@@ -24,24 +24,6 @@ type
constructor Create(const AFunc: TConvertFunc); constructor Create(const AFunc: TConvertFunc);
end; end;
TTicksToTimeframe = class(TMycConverter<TArray<TDataPoint<TAskBidItem>>, TDataPoint<TOhlcItem>>)
private
FTimeframe: TTimeframe;
// Stores the currently aggregating OHLC data.
FCurrentBar: TDataPoint<TOhlcItem>;
function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
function GetCurrentBar: TDataPoint<TOhlcItem>;
function GetTimeframe: TTimeframe;
public
constructor Create(const ATimeframe: TTimeframe);
// Process new data. This is called concurrently and must not have side effects out of the scope of this class!
function ProcessData(const Values: TArray<TDataPoint<TAskBidItem>>): TState; override;
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
property Timeframe: TTimeframe read GetTimeframe;
end;
TIndicator<S, T> = class(TMycConverter<S, T>) TIndicator<S, T> = class(TMycConverter<S, T>)
protected protected
function ProcessData(const Value: S): TState; override; final; function ProcessData(const Value: S): TState; override; final;
@@ -57,21 +39,77 @@ type
constructor Create(const AFunc: TIndicatorFunc<S, T>); constructor Create(const AFunc: TIndicatorFunc<S, T>);
end; end;
TTicksToBars = class(TMycConverter<TDataPoint<TAskBidItem>, TDataPoint<TOhlcItem>>)
private
FTimeframe: TTimeframe;
// Stores the currently aggregating OHLC data.
FCurrentBar: TDataPoint<TOhlcItem>;
function GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
function GetCurrentBar: TDataPoint<TOhlcItem>;
function GetTimeframe: TTimeframe;
public
constructor Create(const ATimeframe: TTimeframe);
// Process new data. This is called concurrently and must not have side effects out of the scope of this class!
function ProcessData(const Value: TDataPoint<TAskBidItem>): TState; override;
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
property Timeframe: TTimeframe read GetTimeframe;
end;
TTicker<T> = class(TMycConverter<TArray<T>, T>)
public
function ProcessData(const Values: TArray<T>): TState; override;
end;
implementation implementation
uses uses
System.DateUtils, System.DateUtils,
System.Math; System.Math;
{ TTicksToTimeframe } { TMycGenericConverter<S, T> }
constructor TTicksToTimeframe.Create(const ATimeframe: TTimeframe); constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc);
begin
inherited Create;
FFunc := AFunc;
end;
function TMycGenericConverter<S, T>.ProcessData(const Value: S): TState;
begin
Result := Broadcast(FFunc(Value));
end;
{ TIndicator<S,T> }
function TIndicator<S, T>.ProcessData(const Value: S): TState;
begin
Result := Broadcast(Calculate(Value));
end;
{ TGenericIndicator<S,T> }
constructor TGenericIndicator<S, T>.Create(const AFunc: TIndicatorFunc<S, T>);
begin
inherited Create;
FFunc := AFunc;
end;
function TGenericIndicator<S, T>.Calculate(const Value: S): T;
begin
Result := FFunc(Value);
end;
{ TTicksToBars }
constructor TTicksToBars.Create(const ATimeframe: TTimeframe);
begin begin
inherited Create; inherited Create;
FTimeframe := ATimeframe; FTimeframe := ATimeframe;
end; end;
function TTicksToTimeframe.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime; function TTicksToBars.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
var var
baseTime: TDateTime; baseTime: TDateTime;
begin begin
@@ -119,34 +157,27 @@ begin
end; end;
end; end;
function TTicksToTimeframe.GetCurrentBar: TDataPoint<TOhlcItem>; function TTicksToBars.GetCurrentBar: TDataPoint<TOhlcItem>;
begin begin
Result := FCurrentBar; Result := FCurrentBar;
end; end;
function TTicksToTimeframe.GetTimeframe: TTimeframe; function TTicksToBars.GetTimeframe: TTimeframe;
begin begin
Result := FTimeframe; Result := FTimeframe;
end; end;
function TTicksToTimeframe.ProcessData(const Values: TArray<TDataPoint<TAskBidItem>>): TState; function TTicksToBars.ProcessData(const Value: TDataPoint<TAskBidItem>): TState;
var var
point: TDataPoint<TAskBidItem>;
midPrice: Single; midPrice: Single;
barStartTime: TDateTime; barStartTime: TDateTime;
lastBarTime: TDateTime; lastBarTime: TDateTime;
currentBar: TOhlcItem; currentBar: TOhlcItem;
states: TList<TState>;
begin begin
states := TList<TState>.Create; midPrice := (Value.Data.Ask + Value.Data.Bid) / 2;
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 // Update bar for the strategy's timeframe
barStartTime := GetBarStartTime(point.Time, FTimeframe); barStartTime := GetBarStartTime(Value.Time, FTimeframe);
lastBarTime := FCurrentBar.Time; lastBarTime := FCurrentBar.Time;
if (barStartTime > lastBarTime) then if (barStartTime > lastBarTime) then
@@ -154,7 +185,7 @@ begin
// A new bar starts, so the previous one is now complete. // A new bar starts, so the previous one is now complete.
if (lastBarTime > 0) then if (lastBarTime > 0) then
begin begin
states.Add(Broadcast(FCurrentBar)); Result := Broadcast(FCurrentBar);
end; end;
// Start a new bar, Volume is 1 because this is the first tick. // Start a new bar, Volume is 1 because this is the first tick.
@@ -175,43 +206,15 @@ begin
end; end;
end; end;
Result := TState.All(states.ToArray); function TTicker<T>.ProcessData(const Values: TArray<T>): TState;
finally
states.Free;
end;
end;
{ TMycGenericConverter<S, T> }
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc);
begin begin
inherited Create; var done := TLatch.CreateLatch( Length(Values) );
FFunc := AFunc;
end;
function TMycGenericConverter<S, T>.ProcessData(const Value: S): TState; // Process each incoming data point
begin for var i:=0 to High(Values) do
Result := Broadcast(FFunc(Value)); Broadcast(Values[i]).Signal.Subscribe(done);
end;
{ TIndicator<S,T> } Result := done.State;
function TIndicator<S, T>.ProcessData(const Value: S): TState;
begin
Result := Broadcast(Calculate(Value));
end;
{ TGenericIndicator<S,T> }
constructor TGenericIndicator<S, T>.Create(const AFunc: TIndicatorFunc<S, T>);
begin
inherited Create;
FFunc := AFunc;
end;
function TGenericIndicator<S, T>.Calculate(const Value: S): T;
begin
Result := FFunc(Value);
end; end;
end. end.
+5 -2
View File
@@ -167,7 +167,10 @@ begin
var timeframe := TTimeframe.S15; var timeframe := TTimeframe.S15;
var OhlcPoint: IMycConverter<TArray<TDataPoint<TAskBidItem>>, TDataPoint<TOhlcItem>> := TTicksToTimeframe.Create(timeframe); var ticker := TTicker<TDataPoint<TAskBidItem>>.Create;
var OhlcPoint := TTicksToBars.Create(timeframe);
ticker.Sender.Link(OhlcPoint);
var Timestamps: IMycConverter<TDataPoint<TOhlcItem>, TDateTime> := var Timestamps: IMycConverter<TDataPoint<TOhlcItem>, TDateTime> :=
TMycGenericConverter<TDataPoint<TOhlcItem>, TDateTime> TMycGenericConverter<TDataPoint<TOhlcItem>, TDateTime>
@@ -272,7 +275,7 @@ begin
Stoch.Sender.Link(StochD); Stoch.Sender.Link(StochD);
Panel.AddDoubleSeries(StochD.Sender, TAlphaColors.Red); Panel.AddDoubleSeries(StochD.Sender, TAlphaColors.Red);
var done := ExecuteStrategy(Symbol, OhlcPoint); var done := ExecuteStrategy(Symbol, ticker);
///// /////
+6 -2
View File
@@ -112,7 +112,11 @@ begin
FWorkGate := TSemaphore.Create(nil, 0, MaxInt, ''); // Initial count is 0, workers will wait FWorkGate := TSemaphore.Create(nil, 0, MaxInt, ''); // Initial count is 0, workers will wait
SetLength(FWorkThreads, TThread.ProcessorCount); // Create one thread per processor core var threadCnt := TThread.ProcessorCount - 1;
if threadCnt < 2 then
threadCnt := 2;
SetLength(FWorkThreads, threadCnt);
for var i := 0 to High(FWorkThreads) do for var i := 0 to High(FWorkThreads) do
begin begin
FWorkThreads[i] := CreateAnonymousThread('Thread pool [' + IntToStr(i) + ']', WorkerThread); FWorkThreads[i] := CreateAnonymousThread('Thread pool [' + IntToStr(i) + ']', WorkerThread);
@@ -158,7 +162,7 @@ begin
if TThread.CurrentThread.ThreadID = MainThreadID then if TThread.CurrentThread.ThreadID = MainThreadID then
begin begin
prio := TThread.CurrentThread.Priority; prio := TThread.CurrentThread.Priority;
if prio > Low(TThreadPriority) then if prio > tpLowest then
prio := TThreadPriority(Ord(prio) - 1); // Decrease priority by one step prio := TThreadPriority(Ord(prio) - 1); // Decrease priority by one step
Result.Priority := prio; Result.Priority := prio;
+18 -5
View File
@@ -65,8 +65,12 @@ type
protected protected
function GetOwner: TMycChart; override; final; function GetOwner: TMycChart; override; final;
// Paint crosshair and other axis related overlays // Paint crosshair and other axis related overlays
procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; ViewStartIndex, ViewCount: Int64; IdxAtMousePos: Integer); virtual; procedure Paint(
abstract; const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
IdxAtMousePos: Integer
); virtual; abstract;
public public
constructor Create(AOwner: TMycChart); constructor Create(AOwner: TMycChart);
end; end;
@@ -74,7 +78,12 @@ type
TXAxisLayer = class(TAxisLayer) TXAxisLayer = class(TAxisLayer)
protected protected
// Paint crosshair and time caption // Paint crosshair and time caption
procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; ViewStartIndex, ViewCount: Int64; IdxAtMousePos: Integer); override; procedure Paint(
const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
IdxAtMousePos: Integer
); override;
// This function delivers the text for the caption. // This function delivers the text for the caption.
function GetCaption(Idx: Int64): String; virtual; abstract; function GetCaption(Idx: Int64): String; virtual; abstract;
end; end;
@@ -819,8 +828,12 @@ end;
{ TMycChart.TXAxisLayer } { TMycChart.TXAxisLayer }
procedure TMycChart.TXAxisLayer.Paint(const Canvas: TCanvas; const Viewport: TRectF; ViewStartIndex, ViewCount: Int64; IdxAtMousePos: procedure TMycChart.TXAxisLayer.Paint(
Integer); const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
IdxAtMousePos: Integer
);
var var
caption: string; caption: string;
captionRect: TRectF; captionRect: TRectF;
+7 -22
View File
@@ -34,9 +34,6 @@ type
[TestCase('Writeable', '10,20')] [TestCase('Writeable', '10,20')]
procedure TestWriteableMutable(InitialValue, NewValue: Integer); procedure TestWriteableMutable(InitialValue, NewValue: Integer);
[Test]
procedure TestWriteableMutable_SignalOnSameValue;
[Test] [Test]
[TestCase('Protected', '100,200')] [TestCase('Protected', '100,200')]
procedure TestProtectedMutable_Delegation(InitialValue, NewValue: Integer); procedure TestProtectedMutable_Delegation(InitialValue, NewValue: Integer);
@@ -106,7 +103,8 @@ var
func: TFunc<Integer>; func: TFunc<Integer>;
begin begin
callCount := 0; callCount := 0;
func := function: Integer func :=
function: Integer
begin begin
Inc(callCount); Inc(callCount);
Result := 42; Result := 42;
@@ -125,10 +123,7 @@ var
func: TFunc<Integer>; func: TFunc<Integer>;
begin begin
source := TEvent.CreateEvent; source := TEvent.CreateEvent;
func := function: Integer func := function: Integer begin Result := 123; end;
begin
Result := 123;
end;
mutable := TMutable<Integer>.Construct(source.Signal, func); mutable := TMutable<Integer>.Construct(source.Signal, func);
lazy := TLazy<Integer>.Create(mutable); lazy := TLazy<Integer>.Create(mutable);
@@ -156,19 +151,6 @@ begin
subscription.Unsubscribe; subscription.Unsubscribe;
end; end;
procedure TTestCoreMutable.TestWriteableMutable_SignalOnSameValue;
var
writeable: TWriteable<Integer>;
subscriber: TSignal.ISubscriber;
subscription: TSignal.TSubscription;
begin
writeable := TWriteable<Integer>.CreateWriteable(100);
subscriber := TTestSubscriber.Create(SubscriberCallback);
subscription := writeable.Changed.Subscribe(subscriber);
subscription.Unsubscribe;
end;
procedure TTestCoreMutable.TestProtectedMutable_Delegation(InitialValue, NewValue: Integer); procedure TTestCoreMutable.TestProtectedMutable_Delegation(InitialValue, NewValue: Integer);
var var
writeable: TWriteable<Integer>; writeable: TWriteable<Integer>;
@@ -203,7 +185,10 @@ begin
for i := 0 to ThreadCount - 1 do for i := 0 to ThreadCount - 1 do
begin begin
threads := threads + [TThread.CreateAnonymousThread( threads :=
threads
+ [
TThread.CreateAnonymousThread(
procedure procedure
var var
j: Integer; j: Integer;