Adding Pipes
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -39,7 +39,8 @@ uses
|
|||||||
Myc.Fmx.AstEditor.Handlers.Data in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Data.pas',
|
Myc.Fmx.AstEditor.Handlers.Data in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Data.pas',
|
||||||
Myc.Ast.RTL.TypeRegistry in '..\Src\AST\Myc.Ast.RTL.TypeRegistry.pas',
|
Myc.Ast.RTL.TypeRegistry in '..\Src\AST\Myc.Ast.RTL.TypeRegistry.pas',
|
||||||
Myc.Trade.Broker in '..\Src\Myc.Trade.Broker.pas',
|
Myc.Trade.Broker in '..\Src\Myc.Trade.Broker.pas',
|
||||||
Myc.Data.Stream.Pipes in '..\Src\Data\Myc.Data.Stream.Pipes.pas';
|
Myc.Data.Stream.Pipes in '..\Src\Data\Myc.Data.Stream.Pipes.pas',
|
||||||
|
Myc.Data.Stream in '..\Src\Data\Myc.Data.Stream.pas';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,7 @@
|
|||||||
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.TypeRegistry.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.TypeRegistry.pas"/>
|
||||||
<DCCReference Include="..\Src\Myc.Trade.Broker.pas"/>
|
<DCCReference Include="..\Src\Myc.Trade.Broker.pas"/>
|
||||||
<DCCReference Include="..\Src\Data\Myc.Data.Stream.Pipes.pas"/>
|
<DCCReference Include="..\Src\Data\Myc.Data.Stream.Pipes.pas"/>
|
||||||
|
<DCCReference Include="..\Src\Data\Myc.Data.Stream.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
@@ -1,35 +1,46 @@
|
|||||||
(*TODO
|
(*
|
||||||
Ich möchte ein neues Sprachkonzept einbauen. Folgendes Skript skizziert, was möglich sein soll:
|
; Record-Streams sind Record-Series, an die "Pipes" andocken können.
|
||||||
|
; Es handelt sich um ein reaktives Producer-Consumer-Pattern.
|
||||||
; Record-Streams sind Record-Series, an die "Pipes" andocken können (Observer) um neue Daten zu bekommen
|
|
||||||
; Es handelt sich um reaktives Producer-Consumer-Pattern
|
|
||||||
|
|
||||||
;; Beispiel: SMA auf Close
|
;; Beispiel: SMA auf Close
|
||||||
(def btc (create-ticker "BTCUSD")) ; ein Open-High-Low-Close-Record-Stream
|
(def btc (create-ticker "BTCUSD"))
|
||||||
(def sma (create-sma 20)) ; erzeugt eine Lambda (sma(price)), die den sma20 berechnet
|
(def sma (create-sma 20))
|
||||||
|
|
||||||
|
; TRANSFORMATION
|
||||||
; Das Ergebnis eines pipe-Befehls ist wieder ein Record-Stream.
|
; btc-sma ist ein Record-Stream, der {:ma ..} records enthält.
|
||||||
; btc-sma ist also ein Record-Stream, der {:ma ..} records enthält:
|
(def btc-sma
|
||||||
(def btc-sma
|
(pipe [btc [:Close]]
|
||||||
(pipe [btc [:Close]]
|
(fn [price]
|
||||||
(fn [price]
|
; KEIN emit mehr. Wir geben einfach die Map zurück.
|
||||||
; pipe mappt die Elemente des Record-Streams (hier :Close) auf die Parameter der fn. Price ist als eine ScalarSeries.
|
; Die Engine nimmt diesen Rückgabewert und publiziert ihn.
|
||||||
; (Index=0 ist das neueste Element)
|
{:ma (sma (get price 0))}
|
||||||
; emit ist eine von pipe in den lambda-scope injizierte Funktion, die das neue Element in den Ergebnis-Stream
|
)
|
||||||
; pusht (und damit die Observer des Ergebnis-Streams benachrichtigt).
|
)
|
||||||
(emit {:ma (sma (get price 0))})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
; Diese pipe hat zwei Streams als Input: den Closing-Price und den SMA. Jede pipe kann also N Input Stream haben
|
; KOMBINATION & LOGIK
|
||||||
|
; Diese pipe hat zwei Streams als Input.
|
||||||
(def btc-signal
|
(def btc-signal
|
||||||
(pipe [btc [:Close] btc-sma [:ma]]
|
(pipe [btc [:Close] btc-sma [:ma]]
|
||||||
(fn [price ma]
|
(fn [price ma]
|
||||||
(emit {:signal (? (> (get price 0) (get ma 0)) :buy : sell)})
|
; Hier wird einfach der Record zurückgegeben.
|
||||||
)
|
; Da der ternäre Operator (?) immer ein Ergebnis hat (:buy oder :sell),
|
||||||
)
|
; feuert dieser Stream bei jedem Tick ein Signal.
|
||||||
|
{:signal (? (> (get price 0) (get ma 0)) :buy :sell)}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(def btc-buy-only
|
||||||
|
(pipe [btc [:Close] btc-sma [:ma]]
|
||||||
|
(fn [price ma]
|
||||||
|
; Ein IF ohne ELSE.
|
||||||
|
; Wenn die Bedingung FALSE ist, ist das Ergebnis der Funktion "Void".
|
||||||
|
; Die Engine erkennt "Void" und sendet NICHTS an die Observer.
|
||||||
|
(if (> (get price 0) (get ma 0))
|
||||||
|
{:signal :buy})
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
*)
|
*)
|
||||||
|
|
||||||
@@ -77,8 +88,9 @@ type
|
|||||||
// Acts as IStream (Producer) for downstream consumers and manages internal inputs.
|
// Acts as IStream (Producer) for downstream consumers and manages internal inputs.
|
||||||
TPipeStream = class(TInterfacedObject, IStream)
|
TPipeStream = class(TInterfacedObject, IStream)
|
||||||
type
|
type
|
||||||
TEmitProc = reference to procedure(const Data: array of TScalar.TValue);
|
// Functional lambda: Maps inputs (Series) to a result value (TArray<TScalar.TValue>).
|
||||||
TProc = reference to procedure(const Sources: TArray<ISeries>; const Emit: TEmitProc);
|
// Returns false to filter (emit nothing).
|
||||||
|
TPipeLambda = reference to function(const Sources: array of ISeries; out Results: array of TScalar.TValue): Boolean;
|
||||||
private
|
private
|
||||||
FSources: TArray<TPipeSource>;
|
FSources: TArray<TPipeSource>;
|
||||||
FObservers: TMycNotifyList<IStreamObserver>;
|
FObservers: TMycNotifyList<IStreamObserver>;
|
||||||
@@ -89,24 +101,23 @@ type
|
|||||||
FLastFiredCycleID: Int64;
|
FLastFiredCycleID: Int64;
|
||||||
|
|
||||||
// Execution Logic
|
// Execution Logic
|
||||||
// The compiled lambda function representing (fn [inputs...] ...)
|
FLambda: TPipeLambda;
|
||||||
FLambda: TProc;
|
|
||||||
|
|
||||||
procedure CheckBarrierAndFire(CurrentCycle: Int64);
|
procedure CheckBarrierAndFire(CurrentCycle: Int64);
|
||||||
function GetSeries: IScalarRecordSeries;
|
function GetSeries: IScalarRecordSeries;
|
||||||
|
|
||||||
|
// Extract values from the lambda result and map them to the stream definition
|
||||||
|
procedure Emit(const Value: TArray<TScalar.TValue>);
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(
|
||||||
const AConfig: TPipeConfig;
|
const AConfig: TPipeConfig;
|
||||||
const ADef: IScalarRecordDefinition;
|
const ADef: IScalarRecordDefinition;
|
||||||
const ASources: TArray<IStream>;
|
const ASources: TArray<IStream>;
|
||||||
const ALambda: TProc
|
const ALambda: TPipeLambda
|
||||||
);
|
);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
// Injected into lambda scope as "emit"
|
|
||||||
procedure Emit(const Data: array of TScalar.TValue);
|
|
||||||
|
|
||||||
// IStream Implementation
|
// IStream Implementation
|
||||||
function Subscribe(const Observer: IStreamObserver): TSubscriptionTag;
|
function Subscribe(const Observer: IStreamObserver): TSubscriptionTag;
|
||||||
procedure Unsubscribe(Tag: TSubscriptionTag);
|
procedure Unsubscribe(Tag: TSubscriptionTag);
|
||||||
@@ -147,7 +158,7 @@ constructor TPipeStream.Create(
|
|||||||
const AConfig: TPipeConfig;
|
const AConfig: TPipeConfig;
|
||||||
const ADef: IScalarRecordDefinition;
|
const ADef: IScalarRecordDefinition;
|
||||||
const ASources: TArray<IStream>;
|
const ASources: TArray<IStream>;
|
||||||
const ALambda: TProc
|
const ALambda: TPipeLambda
|
||||||
);
|
);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
@@ -233,7 +244,14 @@ begin
|
|||||||
if Assigned(FLambda) then
|
if Assigned(FLambda) then
|
||||||
begin
|
begin
|
||||||
try
|
try
|
||||||
FLambda(FSourceSeries, Emit);
|
var resultVal: TArray<TScalar.TValue>;
|
||||||
|
SetLength(resultVal, FSeries.Def.Count);
|
||||||
|
|
||||||
|
// Handle "Void" return (Filter logic)
|
||||||
|
if not FLambda(FSourceSeries, resultVal) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Emit(resultVal);
|
||||||
except
|
except
|
||||||
// Error handling strategy: Propagate/Crash for now.
|
// Error handling strategy: Propagate/Crash for now.
|
||||||
raise;
|
raise;
|
||||||
@@ -244,17 +262,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPipeStream.Emit(const Data: array of TScalar.TValue);
|
procedure TPipeStream.Emit(const Value: TArray<TScalar.TValue>);
|
||||||
begin
|
begin
|
||||||
Assert(Length(Data) = FSeries.Def.Count);
|
// Add to internal series
|
||||||
|
FSeries.Add(Value);
|
||||||
// Emit has to be called from the Lambda, which is called in CheckBarrierAndFire!
|
|
||||||
Assert(FObservers.IsLocked);
|
|
||||||
|
|
||||||
FSeries.Add(Data);
|
|
||||||
|
|
||||||
|
// Notify downstream
|
||||||
var signal := TStreamSignal.Create(skData, FLastFiredCycleID);
|
var signal := TStreamSignal.Create(skData, FLastFiredCycleID);
|
||||||
|
|
||||||
FObservers.Notify(
|
FObservers.Notify(
|
||||||
function(const Obs: IStreamObserver): Boolean
|
function(const Obs: IStreamObserver): Boolean
|
||||||
begin
|
begin
|
||||||
|
|||||||
Reference in New Issue
Block a user