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.Ast.RTL.TypeRegistry in '..\Src\AST\Myc.Ast.RTL.TypeRegistry.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}
|
||||
|
||||
|
||||
@@ -170,6 +170,7 @@
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.TypeRegistry.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Trade.Broker.pas"/>
|
||||
<DCCReference Include="..\Src\Data\Myc.Data.Stream.Pipes.pas"/>
|
||||
<DCCReference Include="..\Src\Data\Myc.Data.Stream.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</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 (Observer) um neue Daten zu bekommen
|
||||
; Es handelt sich um reaktives Producer-Consumer-Pattern
|
||||
(*
|
||||
; Record-Streams sind Record-Series, an die "Pipes" andocken können.
|
||||
; Es handelt sich um ein reaktives Producer-Consumer-Pattern.
|
||||
|
||||
;; Beispiel: SMA auf Close
|
||||
(def btc (create-ticker "BTCUSD")) ; ein Open-High-Low-Close-Record-Stream
|
||||
(def sma (create-sma 20)) ; erzeugt eine Lambda (sma(price)), die den sma20 berechnet
|
||||
(def btc (create-ticker "BTCUSD"))
|
||||
(def sma (create-sma 20))
|
||||
|
||||
|
||||
; Das Ergebnis eines pipe-Befehls ist wieder ein Record-Stream.
|
||||
; btc-sma ist also ein Record-Stream, der {:ma ..} records enthält:
|
||||
(def btc-sma
|
||||
(pipe [btc [:Close]]
|
||||
(fn [price]
|
||||
; pipe mappt die Elemente des Record-Streams (hier :Close) auf die Parameter der fn. Price ist als eine ScalarSeries.
|
||||
; (Index=0 ist das neueste Element)
|
||||
; 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))})
|
||||
)
|
||||
)
|
||||
; TRANSFORMATION
|
||||
; btc-sma ist ein Record-Stream, der {:ma ..} records enthält.
|
||||
(def btc-sma
|
||||
(pipe [btc [:Close]]
|
||||
(fn [price]
|
||||
; KEIN emit mehr. Wir geben einfach die Map zurück.
|
||||
; Die Engine nimmt diesen Rückgabewert und publiziert ihn.
|
||||
{: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
|
||||
(pipe [btc [:Close] btc-sma [:ma]]
|
||||
(fn [price ma]
|
||||
(emit {:signal (? (> (get price 0) (get ma 0)) :buy : sell)})
|
||||
)
|
||||
)
|
||||
(pipe [btc [:Close] btc-sma [:ma]]
|
||||
(fn [price ma]
|
||||
; 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.
|
||||
TPipeStream = class(TInterfacedObject, IStream)
|
||||
type
|
||||
TEmitProc = reference to procedure(const Data: array of TScalar.TValue);
|
||||
TProc = reference to procedure(const Sources: TArray<ISeries>; const Emit: TEmitProc);
|
||||
// Functional lambda: Maps inputs (Series) to a result value (TArray<TScalar.TValue>).
|
||||
// Returns false to filter (emit nothing).
|
||||
TPipeLambda = reference to function(const Sources: array of ISeries; out Results: array of TScalar.TValue): Boolean;
|
||||
private
|
||||
FSources: TArray<TPipeSource>;
|
||||
FObservers: TMycNotifyList<IStreamObserver>;
|
||||
@@ -89,24 +101,23 @@ type
|
||||
FLastFiredCycleID: Int64;
|
||||
|
||||
// Execution Logic
|
||||
// The compiled lambda function representing (fn [inputs...] ...)
|
||||
FLambda: TProc;
|
||||
FLambda: TPipeLambda;
|
||||
|
||||
procedure CheckBarrierAndFire(CurrentCycle: Int64);
|
||||
function GetSeries: IScalarRecordSeries;
|
||||
|
||||
// Extract values from the lambda result and map them to the stream definition
|
||||
procedure Emit(const Value: TArray<TScalar.TValue>);
|
||||
|
||||
public
|
||||
constructor Create(
|
||||
const AConfig: TPipeConfig;
|
||||
const ADef: IScalarRecordDefinition;
|
||||
const ASources: TArray<IStream>;
|
||||
const ALambda: TProc
|
||||
const ALambda: TPipeLambda
|
||||
);
|
||||
destructor Destroy; override;
|
||||
|
||||
// Injected into lambda scope as "emit"
|
||||
procedure Emit(const Data: array of TScalar.TValue);
|
||||
|
||||
// IStream Implementation
|
||||
function Subscribe(const Observer: IStreamObserver): TSubscriptionTag;
|
||||
procedure Unsubscribe(Tag: TSubscriptionTag);
|
||||
@@ -147,7 +158,7 @@ constructor TPipeStream.Create(
|
||||
const AConfig: TPipeConfig;
|
||||
const ADef: IScalarRecordDefinition;
|
||||
const ASources: TArray<IStream>;
|
||||
const ALambda: TProc
|
||||
const ALambda: TPipeLambda
|
||||
);
|
||||
begin
|
||||
inherited Create;
|
||||
@@ -233,7 +244,14 @@ begin
|
||||
if Assigned(FLambda) then
|
||||
begin
|
||||
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
|
||||
// Error handling strategy: Propagate/Crash for now.
|
||||
raise;
|
||||
@@ -244,17 +262,13 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TPipeStream.Emit(const Data: array of TScalar.TValue);
|
||||
procedure TPipeStream.Emit(const Value: TArray<TScalar.TValue>);
|
||||
begin
|
||||
Assert(Length(Data) = FSeries.Def.Count);
|
||||
|
||||
// Emit has to be called from the Lambda, which is called in CheckBarrierAndFire!
|
||||
Assert(FObservers.IsLocked);
|
||||
|
||||
FSeries.Add(Data);
|
||||
// Add to internal series
|
||||
FSeries.Add(Value);
|
||||
|
||||
// Notify downstream
|
||||
var signal := TStreamSignal.Create(skData, FLastFiredCycleID);
|
||||
|
||||
FObservers.Notify(
|
||||
function(const Obs: IStreamObserver): Boolean
|
||||
begin
|
||||
|
||||
Reference in New Issue
Block a user