Work in Progress
This commit is contained in:
@@ -116,7 +116,7 @@ type
|
||||
protected
|
||||
function GetSeries: TMycChart.TSeries; override;
|
||||
procedure Update; override;
|
||||
function GetCaption(Idx: Int64): String; override; abstract;
|
||||
function GetCaption(Idx: Int64): String; override;
|
||||
public
|
||||
constructor Create(AOwner: TMycChart; const ADataProvider: TDataProvider<T>);
|
||||
destructor Destroy; override;
|
||||
@@ -374,6 +374,11 @@ begin
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TChartXAxisLayer<T>.GetCaption(Idx: Int64): String;
|
||||
begin
|
||||
Result := IntToStr(Idx);
|
||||
end;
|
||||
|
||||
function TChartXAxisLayer<T>.GetSeries: TMycChart.TSeries;
|
||||
begin
|
||||
Result := FSeries;
|
||||
|
||||
+12
-1
@@ -166,7 +166,8 @@ type
|
||||
function AddPanel: TPanel;
|
||||
|
||||
// Sets the master series that defines the time scale (X-axis).
|
||||
function SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: TDataProvider<TDateTime>): TMycChart.TXAxisLayer;
|
||||
function SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: TDataProvider<TDateTime>): TMycChart.TXAxisLayer; overload;
|
||||
function SetXAxisCounter<T>(const DataProvider: TDataProvider<T>): TMycChart.TXAxisLayer; overload;
|
||||
|
||||
property Lookback: TWriteable<Int64> read FLookback write FLookback;
|
||||
property NeedRepaint: TFlag read FNeedRepaint;
|
||||
@@ -639,6 +640,16 @@ begin
|
||||
Repaint;
|
||||
end;
|
||||
|
||||
function TMycChart.SetXAxisCounter<T>(const DataProvider: TDataProvider<T>): TMycChart.TXAxisLayer;
|
||||
begin
|
||||
FXAxisSeries.Free;
|
||||
|
||||
var counter := TConverter.CreateCounter<T>;
|
||||
DataProvider.Link(counter);
|
||||
FXAxisSeries := TChartXAxisLayer<Int64>.Create(Self, counter.Sender);
|
||||
Result := FXAxisSeries;
|
||||
end;
|
||||
|
||||
function TMycChart.SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: TDataProvider<TDateTime>): TMycChart.TXAxisLayer;
|
||||
begin
|
||||
FXAxisSeries.Free;
|
||||
|
||||
+6
-1
@@ -77,7 +77,7 @@ type
|
||||
class operator Initialize(out Dest: TState);
|
||||
class operator Implicit(const A: IState): TState; overload;
|
||||
class operator Implicit(const A: TState): IState; overload;
|
||||
|
||||
class operator Add(const A, B: TState): TState;
|
||||
class function All(const States: TArray<TState>): TState; static;
|
||||
class function Any(const States: TArray<TState>; Count: Integer = 1): TState; static;
|
||||
|
||||
@@ -271,6 +271,11 @@ begin
|
||||
Result := FState.IsSet;
|
||||
end;
|
||||
|
||||
class operator TState.Add(const A, B: TState): TState;
|
||||
begin
|
||||
Result := All([A, B]);
|
||||
end;
|
||||
|
||||
class operator TState.Implicit(const A: TState): IState;
|
||||
begin
|
||||
Result := A.FState;
|
||||
|
||||
@@ -31,6 +31,8 @@ type
|
||||
// Returns a State to await thread completion.
|
||||
function RunTask(const Gate: TState; const Proc: TFunc<TState>): TState;
|
||||
|
||||
class function RunSequence(const Gate: TState; First, Count: Integer; const Proc: TFunc<Integer, TState>): TState; static;
|
||||
|
||||
// Waits for the operation associated with State to complete.
|
||||
// Must not be called from a task of this factory.
|
||||
// After waiting, or if the state is already set, any first stored exception
|
||||
@@ -146,6 +148,15 @@ begin
|
||||
);
|
||||
end;
|
||||
|
||||
class function TTaskManager.RunSequence(const Gate: TState; First, Count: Integer; const Proc: TFunc<Integer, TState>): TState;
|
||||
begin
|
||||
if First >= Count then
|
||||
exit;
|
||||
|
||||
var cProc: TFunc<Integer, TState> := Proc;
|
||||
Result := TaskManager.RunTask(Gate, function: TState begin Result := RunSequence(Proc(First), 1 + First, Count, cProc); end);
|
||||
end;
|
||||
|
||||
function TTaskManager.RunTask(const Gate: TState; const Proc: TFunc<TState>): TState;
|
||||
begin
|
||||
var cProc: TFunc<TState> := Proc;
|
||||
|
||||
@@ -24,7 +24,7 @@ type
|
||||
|
||||
class operator Initialize(out Dest: TSeries<T>);
|
||||
// Add a singe item
|
||||
function Add(const Data: T; Lookback: Int64): TSeries<T>; overload;
|
||||
function Add(const Data: T; Lookback: Int64 = -1): TSeries<T>; overload;
|
||||
// Add a ranmge of items
|
||||
function Add(const Data: array of T; First, Count, Lookback: Int64): TSeries<T>; overload;
|
||||
// Helper to create a data array from a raw TArray.
|
||||
@@ -36,6 +36,9 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Math;
|
||||
|
||||
{ TSeries<T> }
|
||||
|
||||
constructor TSeries<T>.Create(const AChunks: TArray<TChunk>; ACount, ATotalCount: Int64);
|
||||
@@ -62,14 +65,14 @@ var
|
||||
begin
|
||||
if Count < 0 then
|
||||
Count := Length(Data) - First;
|
||||
if (Lookback <= 0) or (Count = 0) then
|
||||
if 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
|
||||
if (Lookback >= 0) and (newCount > Lookback) then
|
||||
newCount := Lookback;
|
||||
itemsToSkip := sumCount - newCount;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ type
|
||||
end;
|
||||
|
||||
// Concrete data provider that manages a list of processors (listeners).
|
||||
TMycDataProvider<T> = class abstract(TContainedObject, TDataProvider<T>.IDataProvider)
|
||||
TMycContainedDataProvider<T> = class abstract(TContainedObject, TDataProvider<T>.IDataProvider)
|
||||
private
|
||||
FListeners: TMycNotifyList<IMycProcessor<T>>;
|
||||
public
|
||||
@@ -33,6 +33,19 @@ type
|
||||
procedure Unlink(Tag: TDataProvider<T>.TTag);
|
||||
end;
|
||||
|
||||
TMycSequence<T> = class(TMycProcessor<T>, IMycDataSequence<T>)
|
||||
private
|
||||
FDataProviders: TArray<TMycContainedDataProvider<T>>;
|
||||
function GetCount: Integer;
|
||||
function GetDataProvider(Idx: Integer): TDataProvider<T>;
|
||||
protected
|
||||
function ProcessData(const Value: T): TState; override;
|
||||
function ProcessDataProvider(Idx: Integer; const Value: T): TState;
|
||||
public
|
||||
constructor Create(ACount: Integer);
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
// Null object implementation for IDataProvider.
|
||||
TNullDataProvider<T> = class(TInterfacedObject, TDataProvider<T>.IDataProvider)
|
||||
public
|
||||
@@ -43,7 +56,7 @@ type
|
||||
// Abstract base class for components that process data of type S and provide data of type T.
|
||||
TMycConverter<S, T> = class abstract(TMycProcessor<S>, TConverter<S, T>.IConverter)
|
||||
private
|
||||
FSender: TMycDataProvider<T>;
|
||||
FSender: TMycContainedDataProvider<T>;
|
||||
function GetSender: TDataProvider<T>.IDataProvider;
|
||||
protected
|
||||
function ProcessData(const Value: S): TState; override; abstract;
|
||||
@@ -73,6 +86,12 @@ type
|
||||
constructor Create(const AFunc: TConstFunc<S, T>);
|
||||
end;
|
||||
|
||||
// A converter specialized for calculating indicators.
|
||||
TMycIdentityConverter<T> = class(TMycConverter<T, T>)
|
||||
protected
|
||||
function ProcessData(const Value: T): TState; override; final;
|
||||
end;
|
||||
|
||||
// A converter specialized for calculating indicators.
|
||||
TMycIndicator<S, T> = class(TMycConverter<S, T>)
|
||||
protected
|
||||
@@ -145,26 +164,43 @@ type
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
TTickAggregation = class(TMycConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>)
|
||||
private
|
||||
FTimeframe: TTimeframe;
|
||||
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);
|
||||
function ProcessData(const Value: TDataPoint<Double>): TState; override;
|
||||
property CurrentBar: TDataPoint<TOhlcItem> read GetCurrentBar;
|
||||
property Timeframe: TTimeframe read GetTimeframe;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.TypInfo,
|
||||
System.RTTI;
|
||||
System.RTTI,
|
||||
System.DateUtils,
|
||||
System.Math,
|
||||
Myc.TaskManager;
|
||||
|
||||
{ TMycDataProvider<T> }
|
||||
{ TMycContainedDataProvider<T> }
|
||||
|
||||
constructor TMycDataProvider<T>.Create(const Controller: IInterface);
|
||||
constructor TMycContainedDataProvider<T>.Create(const Controller: IInterface);
|
||||
begin
|
||||
inherited Create(Controller);
|
||||
end;
|
||||
|
||||
destructor TMycDataProvider<T>.Destroy;
|
||||
destructor TMycContainedDataProvider<T>.Destroy;
|
||||
begin
|
||||
FListeners.Finalize;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TMycDataProvider<T>.Broadcast(const Value: T): TState;
|
||||
function TMycContainedDataProvider<T>.Broadcast(const Value: T): TState;
|
||||
begin
|
||||
FListeners.Lock;
|
||||
try
|
||||
@@ -192,7 +228,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMycDataProvider<T>.Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
|
||||
function TMycContainedDataProvider<T>.Link(const Processor: IMycProcessor<T>): TDataProvider<T>.TTag;
|
||||
begin
|
||||
// Add the Processor to the notification list
|
||||
FListeners.Lock;
|
||||
@@ -203,7 +239,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMycDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
|
||||
procedure TMycContainedDataProvider<T>.Unlink(Tag: TDataProvider<T>.TTag);
|
||||
begin
|
||||
FListeners.Lock;
|
||||
try
|
||||
@@ -230,7 +266,7 @@ end;
|
||||
constructor TMycConverter<S, T>.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
FSender := TMycDataProvider<T>.Create(Self);
|
||||
FSender := TMycContainedDataProvider<T>.Create(Self);
|
||||
end;
|
||||
|
||||
destructor TMycConverter<S, T>.Destroy;
|
||||
@@ -411,4 +447,156 @@ begin
|
||||
FChanged.Notify;
|
||||
end;
|
||||
|
||||
{ TTickAggregation }
|
||||
|
||||
constructor TTickAggregation.Create(const ATimeframe: TTimeframe);
|
||||
begin
|
||||
inherited Create;
|
||||
FTimeframe := ATimeframe;
|
||||
end;
|
||||
|
||||
function TTickAggregation.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
|
||||
var
|
||||
baseTime: TDateTime;
|
||||
begin
|
||||
// Align the time grid to UTC 0:00 using functions from System.DateUtils
|
||||
baseTime := RecodeMilliSecond(TimeStamp, 0);
|
||||
|
||||
case Timeframe of
|
||||
S: Result := baseTime;
|
||||
S5: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 5));
|
||||
S15: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 15));
|
||||
S30: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 30));
|
||||
|
||||
M: Result := RecodeSecond(baseTime, 0);
|
||||
M2: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 2));
|
||||
M3: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 3));
|
||||
M5: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 5));
|
||||
M10: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 10));
|
||||
M15: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 15));
|
||||
M30: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 30));
|
||||
|
||||
H: Result := RecodeMinute(RecodeSecond(baseTime, 0), 0);
|
||||
H2: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 2));
|
||||
H3: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 3));
|
||||
H4: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 4));
|
||||
H8: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 8));
|
||||
H12: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 12));
|
||||
|
||||
D: Result := StartOfTheDay(TimeStamp);
|
||||
// D2, D3 are uncommon; this is a simple modulo-based approach relative to TDateTime's epoch.
|
||||
D2: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 2);
|
||||
D3: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 3);
|
||||
|
||||
W: Result := TimeStamp.StartOfTheWeek;
|
||||
|
||||
MN: Result := TimeStamp.StartOfTheMonth;
|
||||
// Quarter alignment
|
||||
MN3: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 3 * 3 + 1);
|
||||
// Half-year alignment
|
||||
MN6: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 6 * 6 + 1);
|
||||
|
||||
Y: Result := TimeStamp.StartOfTheYear;
|
||||
else
|
||||
// Fallback for any undefined timeframe
|
||||
Result := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TTickAggregation.GetCurrentBar: TDataPoint<TOhlcItem>;
|
||||
begin
|
||||
Result := FCurrentBar;
|
||||
end;
|
||||
|
||||
function TTickAggregation.GetTimeframe: TTimeframe;
|
||||
begin
|
||||
Result := FTimeframe;
|
||||
end;
|
||||
|
||||
function TTickAggregation.ProcessData(const Value: TDataPoint<Double>): TState;
|
||||
var
|
||||
barStartTime: TDateTime;
|
||||
lastBarTime: TDateTime;
|
||||
begin
|
||||
// Update bar for the strategy's timeframe
|
||||
barStartTime := GetBarStartTime(Value.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
|
||||
Result := Broadcast(FCurrentBar);
|
||||
end;
|
||||
|
||||
// Start a new bar, Volume is 1 because this is the first tick.
|
||||
FCurrentBar.Data.Open := Value.Data;
|
||||
FCurrentBar.Data.High := Value.Data;
|
||||
FCurrentBar.Data.Low := Value.Data;
|
||||
FCurrentBar.Data.Close := Value.Data;
|
||||
FCurrentBar.Data.Volume := 1;
|
||||
FCurrentBar.Time := barStartTime;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Update the currently aggregating bar
|
||||
if Value.Data > FCurrentBar.Data.High then
|
||||
FCurrentBar.Data.High := Value.Data;
|
||||
if Value.Data < FCurrentBar.Data.Low then
|
||||
FCurrentBar.Data.Low := Value.Data;
|
||||
FCurrentBar.Data.Close := Value.Data;
|
||||
// Volume is the number of ticks needed to build the complete bar.
|
||||
FCurrentBar.Data.Volume := FCurrentBar.Data.Volume + 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TMycSequence<T> }
|
||||
|
||||
constructor TMycSequence<T>.Create(ACount: Integer);
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
SetLength(FDataProviders, ACount);
|
||||
for var i := 0 to High(FDataProviders) do
|
||||
FDataProviders[i] := TMycContainedDataProvider<T>.Create(Self);
|
||||
end;
|
||||
|
||||
destructor TMycSequence<T>.Destroy;
|
||||
begin
|
||||
for var i := High(FDataProviders) downto 0 do
|
||||
FDataProviders[i].Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TMycSequence<T>.GetCount: Integer;
|
||||
begin
|
||||
Result := Length(FDataProviders);
|
||||
end;
|
||||
|
||||
function TMycSequence<T>.GetDataProvider(Idx: Integer): TDataProvider<T>;
|
||||
begin
|
||||
Result := FDataProviders[Idx];
|
||||
end;
|
||||
|
||||
function TMycSequence<T>.ProcessData(const Value: T): TState;
|
||||
begin
|
||||
Result := ProcessDataProvider(0, Value);
|
||||
end;
|
||||
|
||||
function TMycSequence<T>.ProcessDataProvider(Idx: Integer; const Value: T): TState;
|
||||
begin
|
||||
if Idx >= Length(FDataProviders) then
|
||||
exit;
|
||||
|
||||
Result :=
|
||||
TaskManager
|
||||
.RunTask(FDataProviders[idx].Broadcast(Value), function: TState begin Result := ProcessDataProvider(1 + idx, Value); end);
|
||||
end;
|
||||
|
||||
function TMycIdentityConverter<T>.ProcessData(const Value: T): TState;
|
||||
begin
|
||||
Result := Broadcast(Value);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
@@ -48,6 +48,13 @@ type
|
||||
class property Null: IDataProvider read FNull;
|
||||
end;
|
||||
|
||||
IMycDataSequence<T> = interface(IMycProcessor<T>)
|
||||
function GetCount: Integer;
|
||||
function GetDataProvider(Idx: Integer): TDataProvider<T>;
|
||||
property Count: Integer read GetCount;
|
||||
property DataProvider[Idx: Integer]: TDataProvider<T> read GetDataProvider; default;
|
||||
end;
|
||||
|
||||
// Interface helper for IConverter<S,T> providing the null object pattern.
|
||||
TConverter<S, T> = record
|
||||
public
|
||||
@@ -59,7 +66,6 @@ type
|
||||
property Sender: TDataProvider<T>.IDataProvider read GetSender;
|
||||
end;
|
||||
|
||||
{$region 'private'}
|
||||
strict private
|
||||
class var
|
||||
FNull: IConverter;
|
||||
@@ -67,7 +73,6 @@ type
|
||||
private
|
||||
FConverter: IConverter;
|
||||
function GetSender: TDataProvider<T>; inline;
|
||||
{$endregion}
|
||||
public
|
||||
constructor Create(const AConverter: IConverter);
|
||||
|
||||
@@ -78,15 +83,15 @@ type
|
||||
|
||||
class function CreateGeneric(const Func: TConstFunc<S, T>): TConverter<S, T>; static;
|
||||
|
||||
// Wrapper for IMycProcessor.ProcessData
|
||||
function ProcessData(const Value: S): TState; inline;
|
||||
|
||||
function Chain<R>(const Next: TConverter<T, R>): TConverter<T, R>; overload; inline;
|
||||
function Chain<R>(const Func: TConstFunc<T, R>): TConverter<T, R>; overload; inline;
|
||||
|
||||
// Extracts the field of a record by it's name (using RTTI).
|
||||
function Field<R>(const FieldName: String): TConverter<T, R>; overload; inline;
|
||||
|
||||
function Sequence(Count: Integer): IMycDataSequence<T>; overload;
|
||||
function Sequence(const Items: TArray<IConverter>): IMycDataSequence<S>; overload;
|
||||
|
||||
// Provides access to the null object instance.
|
||||
class property Null: IConverter read FNull;
|
||||
// Wrapper for IConverter.Sender
|
||||
@@ -95,10 +100,18 @@ type
|
||||
|
||||
// Factory for creating specific converter instances.
|
||||
TConverter = record
|
||||
class function CreateEndpoint<T>(const DataProvider: TDataProvider<T>; Lookback: Int64): TMutable<TSeries<T>>; static;
|
||||
|
||||
class function CreateCounter<T>: TConverter<T, Int64>; static;
|
||||
class function CreateTicker<T>: TConverter<TArray<T>, T>; static;
|
||||
class function CreateRecordField<S, T>(const FieldName: String): TConverter<S, T>; static;
|
||||
class function CreateEndpoint<T>(const DataProvider: TDataProvider<T>; Lookback: Int64): TMutable<TSeries<T>>; static;
|
||||
class function CreateIdentity<T>: TConverter<T, T>; static;
|
||||
|
||||
class function CreateAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>; static;
|
||||
|
||||
class function CreateDataPointConverter<S, T>(const Func: TConstFunc<S, T>): TConverter<TDataPoint<S>, TDataPoint<T>>; static;
|
||||
|
||||
class function CreateSequence<T>(Count: Integer; const Parent: TDataProvider<T>): TArray<TConverter<T, T>>; overload; static;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -186,6 +199,22 @@ begin
|
||||
Result := FConverter.Sender;
|
||||
end;
|
||||
|
||||
function TConverter<S, T>.Sequence(Count: Integer): IMycDataSequence<T>;
|
||||
begin
|
||||
Result := TMycSequence<T>.Create(Count);
|
||||
FConverter.Sender.Link(Result);
|
||||
end;
|
||||
|
||||
function TConverter<S, T>.Sequence(const Items: TArray<IConverter>): IMycDataSequence<S>;
|
||||
begin
|
||||
var seq: IMycDataSequence<S> := TMycSequence<S>.Create(Length(Items));
|
||||
|
||||
for var i := 0 to High(Items) do
|
||||
seq[i].Link(Items[i]);
|
||||
|
||||
Result := seq;
|
||||
end;
|
||||
|
||||
class operator TConverter<S, T>.Initialize(out Dest: TConverter<S, T>);
|
||||
begin
|
||||
Dest.FConverter := FNull;
|
||||
@@ -201,9 +230,9 @@ begin
|
||||
Result := A.FConverter;
|
||||
end;
|
||||
|
||||
function TConverter<S, T>.ProcessData(const Value: S): TState;
|
||||
class function TConverter.CreateAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>;
|
||||
begin
|
||||
Result := FConverter.ProcessData(Value);
|
||||
Result := TTickAggregation.Create(Timeframe);
|
||||
end;
|
||||
|
||||
{ TConverter }
|
||||
@@ -213,11 +242,29 @@ begin
|
||||
Result := TMycDataCounter<T>.Create;
|
||||
end;
|
||||
|
||||
class function TConverter.CreateDataPointConverter<S, T>(const Func: TConstFunc<S, T>): TConverter<TDataPoint<S>, TDataPoint<T>>;
|
||||
begin
|
||||
var cFunc: TConstFunc<S, T> := Func;
|
||||
Result :=
|
||||
TMycGenericConverter<TDataPoint<S>, TDataPoint<T>>.Create(
|
||||
function(const Value: TDataPoint<S>): TDataPoint<T>
|
||||
begin
|
||||
Result.Time := Value.Time;
|
||||
Result.Data := cFunc(Value.Data);
|
||||
end
|
||||
);
|
||||
end;
|
||||
|
||||
class function TConverter.CreateEndpoint<T>(const DataProvider: TDataProvider<T>; Lookback: Int64): TMutable<TSeries<T>>;
|
||||
begin
|
||||
Result := TMycDataEndpoint<T>.Create(DataProvider, Lookback);
|
||||
end;
|
||||
|
||||
class function TConverter.CreateIdentity<T>: TConverter<T, T>;
|
||||
begin
|
||||
Result := TMycIdentityConverter<T>.Create;
|
||||
end;
|
||||
|
||||
class function TConverter.CreateRecordField<S, T>(const FieldName: String): TConverter<S, T>;
|
||||
begin
|
||||
Result := TMycRecordFieldReader<S, T>.Create(FieldName);
|
||||
@@ -228,4 +275,17 @@ begin
|
||||
Result := TMycTicker<T>.Create;
|
||||
end;
|
||||
|
||||
class function TConverter.CreateSequence<T>(Count: Integer; const Parent: TDataProvider<T>): TArray<TConverter<T, T>>;
|
||||
begin
|
||||
var seq: IMycDataSequence<T> := TMycSequence<T>.Create(Count);
|
||||
|
||||
SetLength(Result, Count);
|
||||
for var i := 0 to High(Result) do
|
||||
begin
|
||||
Result[i] := TMycIdentityConverter<T>.Create;
|
||||
seq[i].Link(Result[i]);
|
||||
end;
|
||||
Parent.Link(seq);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+124
-10
@@ -29,6 +29,13 @@ type
|
||||
LowerBand: Double;
|
||||
end;
|
||||
|
||||
// Result for the Keltner Channels indicator.
|
||||
TKeltnerChannelsResult = record
|
||||
UpperBand: Double;
|
||||
MiddleBand: Double;
|
||||
LowerBand: Double;
|
||||
end;
|
||||
|
||||
TIndicators = record
|
||||
private
|
||||
class function CalculateSMA(const Series: TSeries<Double>; const Period: Integer): Double; static;
|
||||
@@ -44,11 +51,33 @@ type
|
||||
// Relative Strength Index
|
||||
class function CreateRSI(Period: Integer): TConstFunc<Double, Double>; static;
|
||||
// Moving Average Convergence Divergence
|
||||
class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConstFunc<Double, TMacdResult>; static;
|
||||
class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConstFunc<Double, TMacdResult>; overload; static;
|
||||
class function CreateMACD(
|
||||
const EmaFast,
|
||||
EmaSlow,
|
||||
EmaSignal: TConstFunc<Double, Double>
|
||||
): TConstFunc<Double, TMacdResult>; overload; static;
|
||||
// Stochastic Oscillator
|
||||
class function CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>; static;
|
||||
class function CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>; overload; static;
|
||||
class function CreateStochastic(
|
||||
KPeriod: Integer;
|
||||
const SmaD: TConstFunc<Double, Double>
|
||||
): TConstFunc<TOhlcItem, TStochasticResult>; overload; static;
|
||||
// Bollinger Bands
|
||||
class function CreateBollingerBands(Period: Integer; Multiplier: Double): TConstFunc<Double, TBollingerBandsResult>; static;
|
||||
// Average True Range
|
||||
class function CreateATR(Period: Integer): TConstFunc<TOhlcItem, Double>; overload; static;
|
||||
class function CreateATR(const MovAvgTR: TConstFunc<Double, Double>): TConstFunc<TOhlcItem, Double>; overload; static;
|
||||
// Keltner Channels
|
||||
class function CreateKeltnerChannels(
|
||||
Period: Integer;
|
||||
Multiplier: Double
|
||||
): TConstFunc<TOhlcItem, TKeltnerChannelsResult>; overload; static;
|
||||
class function CreateKeltnerChannels(
|
||||
const MovAvgMiddle: TConstFunc<Double, Double>;
|
||||
const AtrFunc: TConstFunc<TOhlcItem, Double>;
|
||||
Multiplier: Double
|
||||
): TConstFunc<TOhlcItem, TKeltnerChannelsResult>; overload; static;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -208,19 +237,22 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// Standard MACD using EMAs.
|
||||
class function TIndicators.CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConstFunc<Double, TMacdResult>;
|
||||
begin
|
||||
var emaFast := CreateEMA(FastPeriod);
|
||||
var emaSlow := CreateEMA(SlowPeriod);
|
||||
var emaSignal := CreateEMA(SignalPeriod);
|
||||
Result := CreateMACD(CreateEMA(FastPeriod), CreateEMA(SlowPeriod), CreateEMA(SignalPeriod));
|
||||
end;
|
||||
|
||||
// Creates a MACD indicator from three provided moving average functions.
|
||||
class function TIndicators.CreateMACD(const EmaFast, EmaSlow, EmaSignal: TConstFunc<Double, Double>): TConstFunc<Double, TMacdResult>;
|
||||
begin
|
||||
Result :=
|
||||
function(const Value: Double): TMacdResult
|
||||
var
|
||||
fastVal, slowVal: Double;
|
||||
begin
|
||||
fastVal := emaFast(Value);
|
||||
slowVal := emaSlow(Value);
|
||||
fastVal := EmaFast(Value);
|
||||
slowVal := EmaSlow(Value);
|
||||
|
||||
if IsNan(slowVal) then // slowVal will be the last one to become non-NaN
|
||||
begin
|
||||
@@ -231,7 +263,7 @@ begin
|
||||
else
|
||||
begin
|
||||
Result.MacdLine := fastVal - slowVal;
|
||||
Result.SignalLine := emaSignal(Result.MacdLine);
|
||||
Result.SignalLine := EmaSignal(Result.MacdLine);
|
||||
if not IsNan(Result.SignalLine) then
|
||||
Result.Histogram := Result.MacdLine - Result.SignalLine
|
||||
else
|
||||
@@ -313,10 +345,19 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// Standard Stochastic Oscillator using an SMA for the %D line.
|
||||
class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>;
|
||||
begin
|
||||
Result := CreateStochastic(KPeriod, CreateSMA(DPeriod));
|
||||
end;
|
||||
|
||||
// Creates a Stochastic Oscillator using an injectable moving average for the %D line.
|
||||
class function TIndicators.CreateStochastic(
|
||||
KPeriod: Integer;
|
||||
const SmaD: TConstFunc<Double, Double>
|
||||
): TConstFunc<TOhlcItem, TStochasticResult>;
|
||||
begin
|
||||
var sourceData: TSeries<TOhlcItem>;
|
||||
var smaD := CreateSMA(DPeriod);
|
||||
|
||||
Result :=
|
||||
function(const Value: TOhlcItem): TStochasticResult
|
||||
@@ -347,7 +388,80 @@ begin
|
||||
else
|
||||
Result.K := 100; // Or 50, depends on convention
|
||||
|
||||
Result.D := smaD(Result.K);
|
||||
Result.D := SmaD(Result.K);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Standard ATR using an EMA for smoothing.
|
||||
class function TIndicators.CreateATR(Period: Integer): TConstFunc<TOhlcItem, Double>;
|
||||
begin
|
||||
Result := CreateATR(CreateEMA(Period));
|
||||
end;
|
||||
|
||||
// Calculates the Average True Range (ATR) using an injectable moving average.
|
||||
class function TIndicators.CreateATR(const MovAvgTR: TConstFunc<Double, Double>): TConstFunc<TOhlcItem, Double>;
|
||||
begin
|
||||
var sourceData: TSeries<TOhlcItem>;
|
||||
|
||||
Result :=
|
||||
function(const Value: TOhlcItem): Double
|
||||
var
|
||||
tr: Double;
|
||||
begin
|
||||
// We only need the previous bar to calculate true range.
|
||||
sourceData := sourceData.Add(Value, 2);
|
||||
|
||||
if (sourceData.Count < 2) then
|
||||
begin
|
||||
// Feed a dummy value to keep the moving average count in sync. It will correctly return NaN.
|
||||
Result := MovAvgTR(0);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// Calculate current True Range.
|
||||
tr := Max(Value.High - Value.Low, Max(Abs(Value.High - sourceData[1].Close), Abs(Value.Low - sourceData[1].Close)));
|
||||
|
||||
// Feed the calculated TR into the provided moving average function.
|
||||
Result := MovAvgTR(tr);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Standard Keltner Channels using an EMA for the middle line and an EMA-based ATR.
|
||||
class function TIndicators.CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConstFunc<TOhlcItem, TKeltnerChannelsResult>;
|
||||
begin
|
||||
Result := CreateKeltnerChannels(CreateEMA(Period), CreateATR(Period), Multiplier);
|
||||
end;
|
||||
|
||||
// Calculates Keltner Channels using an injectable ATR and middle band moving average.
|
||||
class function TIndicators.CreateKeltnerChannels(
|
||||
const MovAvgMiddle: TConstFunc<Double, Double>;
|
||||
const AtrFunc: TConstFunc<TOhlcItem, Double>;
|
||||
Multiplier: Double
|
||||
): TConstFunc<TOhlcItem, TKeltnerChannelsResult>;
|
||||
begin
|
||||
Result :=
|
||||
function(const Value: TOhlcItem): TKeltnerChannelsResult
|
||||
var
|
||||
atrValue, middleValue, typicalPrice: Double;
|
||||
begin
|
||||
// Calculate Typical Price for the middle band.
|
||||
typicalPrice := (Value.High + Value.Low + Value.Close) / 3.0;
|
||||
|
||||
// Get values from the provided indicator functions.
|
||||
middleValue := MovAvgMiddle(typicalPrice);
|
||||
atrValue := AtrFunc(Value);
|
||||
|
||||
// Set default NaN values for the warm-up period.
|
||||
Result.MiddleBand := middleValue;
|
||||
Result.UpperBand := Double.NaN;
|
||||
Result.LowerBand := Double.NaN;
|
||||
|
||||
// Once both middle band and ATR have valid (non-NaN) values, calculate the channels.
|
||||
if not IsNan(middleValue) and not IsNan(atrValue) then
|
||||
begin
|
||||
Result.UpperBand := middleValue + (atrValue * Multiplier);
|
||||
Result.LowerBand := middleValue - (atrValue * Multiplier);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -29,6 +29,7 @@ type
|
||||
end;
|
||||
|
||||
TConstFunc<S, T> = reference to function(const Value: S): T;
|
||||
TConstFuncPredicate<S, T> = reference to function(const Value: S; out Res: T): Boolean;
|
||||
|
||||
implementation
|
||||
|
||||
|
||||
Reference in New Issue
Block a user