Pipes refactoring, Fixes in type checker
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@ object Form1: TForm1
|
|||||||
FormFactor.Height = 480
|
FormFactor.Height = 480
|
||||||
FormFactor.Devices = [Desktop]
|
FormFactor.Devices = [Desktop]
|
||||||
OnCreate = FormCreate
|
OnCreate = FormCreate
|
||||||
|
OnDestroy = FormDestroy
|
||||||
DesignerMasterStyle = 0
|
DesignerMasterStyle = 0
|
||||||
object Panel1: TPanel
|
object Panel1: TPanel
|
||||||
Align = MostLeft
|
Align = MostLeft
|
||||||
@@ -286,4 +287,20 @@ object Form1: TForm1
|
|||||||
Text = 'Save'
|
Text = 'Save'
|
||||||
OnClick = SaveScriptButtonClick
|
OnClick = SaveScriptButtonClick
|
||||||
end
|
end
|
||||||
|
object Timer1: TTimer
|
||||||
|
Interval = 250
|
||||||
|
OnTimer = Timer1Timer
|
||||||
|
Left = 992
|
||||||
|
Top = 216
|
||||||
|
end
|
||||||
|
object WatchLabel: TLabel
|
||||||
|
Anchors = [akRight, akBottom]
|
||||||
|
Position.X = 1097.000000000000000000
|
||||||
|
Position.Y = 856.000000000000000000
|
||||||
|
Size.Width = 289.000000000000000000
|
||||||
|
Size.Height = 17.000000000000000000
|
||||||
|
Size.PlatformDefault = False
|
||||||
|
Text = 'watcher: ..............'
|
||||||
|
TabOrder = 9
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+62
-22
@@ -38,6 +38,7 @@ uses
|
|||||||
Myc.Ast,
|
Myc.Ast,
|
||||||
Myc.Ast.Visitor,
|
Myc.Ast.Visitor,
|
||||||
Myc.Data.Decimal,
|
Myc.Data.Decimal,
|
||||||
|
Myc.Data.Stream,
|
||||||
Myc.Ast.Script,
|
Myc.Ast.Script,
|
||||||
Myc.Ast.Types,
|
Myc.Ast.Types,
|
||||||
Myc.Ast.Environment,
|
Myc.Ast.Environment,
|
||||||
@@ -92,6 +93,9 @@ type
|
|||||||
CompilerStageBox: TComboBox;
|
CompilerStageBox: TComboBox;
|
||||||
SaveTestBtn: TButton;
|
SaveTestBtn: TButton;
|
||||||
SaveScriptButton: TButton;
|
SaveScriptButton: TButton;
|
||||||
|
Timer1: TTimer;
|
||||||
|
WatchLabel: TLabel;
|
||||||
|
procedure FormDestroy(Sender: TObject);
|
||||||
procedure InnerLambdaButtonClick(Sender: TObject);
|
procedure InnerLambdaButtonClick(Sender: TObject);
|
||||||
procedure ClearButtonClick(Sender: TObject);
|
procedure ClearButtonClick(Sender: TObject);
|
||||||
procedure CompilerStageBoxChange(Sender: TObject);
|
procedure CompilerStageBoxChange(Sender: TObject);
|
||||||
@@ -118,14 +122,17 @@ type
|
|||||||
procedure LoadUserLibButtonClick(Sender: TObject);
|
procedure LoadUserLibButtonClick(Sender: TObject);
|
||||||
procedure RTLListViewChange(Sender: TObject);
|
procedure RTLListViewChange(Sender: TObject);
|
||||||
procedure SaveScriptButtonClick(Sender: TObject);
|
procedure SaveScriptButtonClick(Sender: TObject);
|
||||||
|
procedure Timer1Timer(Sender: TObject);
|
||||||
private
|
private
|
||||||
FCurrUnboundAst: IAstNode;
|
FCurrUnboundAst: IAstNode;
|
||||||
FCurrCompiled: ILambdaExpressionNode;
|
FCurrLambda: ILambdaExpressionNode;
|
||||||
FCurrExec: TCompiledFunction;
|
FCurrCompiled: TCompiledFunction;
|
||||||
|
FCurrExec: TDataValue;
|
||||||
FEnvironment: TAstEnvironment;
|
FEnvironment: TAstEnvironment;
|
||||||
FAstEditor: TAstEditor; // Die Komponente (Facade)
|
FAstEditor: TAstEditor; // Die Komponente (Facade)
|
||||||
FScriptUpdate: Boolean;
|
FScriptUpdate: Boolean;
|
||||||
FTriggerTest: TAstEnvironment;
|
FTriggerTest: TAstEnvironment;
|
||||||
|
FSim: IFinanceSimulator;
|
||||||
|
|
||||||
procedure AstEditorMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure AstEditorMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
|
||||||
@@ -152,12 +159,18 @@ uses
|
|||||||
Myc.Trade.Broker,
|
Myc.Trade.Broker,
|
||||||
System.Diagnostics, // For TStopwatch
|
System.Diagnostics, // For TStopwatch
|
||||||
System.TimeSpan,
|
System.TimeSpan,
|
||||||
|
Myc.Data.Keyword,
|
||||||
Myc.Ast.Json, // For TAstJson serialization
|
Myc.Ast.Json, // For TAstJson serialization
|
||||||
System.IOUtils, // For TFile
|
System.IOUtils, // For TFile
|
||||||
Myc.Ast.Dumper; // Needed for DumpButtonClick
|
Myc.Ast.Dumper; // Needed for DumpButtonClick
|
||||||
|
|
||||||
{$R *.fmx}
|
{$R *.fmx}
|
||||||
|
|
||||||
|
procedure TForm1.FormDestroy(Sender: TObject);
|
||||||
|
begin
|
||||||
|
FSim := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TForm1.FormCreate(Sender: TObject);
|
procedure TForm1.FormCreate(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
// 1. Initialize Environment
|
// 1. Initialize Environment
|
||||||
@@ -262,6 +275,28 @@ begin
|
|||||||
end
|
end
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Scope.Define(
|
||||||
|
'watch',
|
||||||
|
function(const Args: TArray<TDataValue>): TDataValue
|
||||||
|
var
|
||||||
|
str: TStringBuilder;
|
||||||
|
begin
|
||||||
|
str := TStringBuilder.Create;
|
||||||
|
try
|
||||||
|
for var i := 0 to High(Args) do
|
||||||
|
begin
|
||||||
|
if Args[i].Kind = vkText then
|
||||||
|
str.Append(Args[i].AsText)
|
||||||
|
else
|
||||||
|
str.Append(Args[i].ToString);
|
||||||
|
end;
|
||||||
|
WatchLabel.Text := str.ToString;
|
||||||
|
finally
|
||||||
|
str.Free;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
);
|
||||||
|
|
||||||
Scope.Define(
|
Scope.Define(
|
||||||
'timestamp',
|
'timestamp',
|
||||||
function(const Args: TArray<TDataValue>): TDataValue
|
function(const Args: TArray<TDataValue>): TDataValue
|
||||||
@@ -291,13 +326,11 @@ begin
|
|||||||
CompilerStageBox.BringToFront;
|
CompilerStageBox.BringToFront;
|
||||||
ClearButtonClick(Self);
|
ClearButtonClick(Self);
|
||||||
|
|
||||||
|
FSim := TFinanceSimulator.Create;
|
||||||
|
FSim.CreateOHLCSeries(FEnvironment.RootScope, 'dax');
|
||||||
|
|
||||||
if FileExists(SrcName) then
|
if FileExists(SrcName) then
|
||||||
ScriptMemo.Lines.LoadFromFile(SrcName);
|
ScriptMemo.Lines.LoadFromFile(SrcName);
|
||||||
|
|
||||||
// Memo1.Lines.Add(TAstSchema.GenerateFullSystemPrompt(FEnvironment.RootScope));
|
|
||||||
|
|
||||||
// "Dieses JSON-Objekt betest du in das Feld response_schema (Gemini) oder json_schema (OpenAI) ein."
|
|
||||||
// Memo1.Lines.Add(TAstSchema.GenerateFullSchema.ToJSON);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.ShowVizualization;
|
procedure TForm1.ShowVizualization;
|
||||||
@@ -313,7 +346,7 @@ end;
|
|||||||
function TForm1.ExecuteAst(const ANode: IAstNode): TDataValue;
|
function TForm1.ExecuteAst(const ANode: IAstNode): TDataValue;
|
||||||
begin
|
begin
|
||||||
FCurrUnboundAst := ANode;
|
FCurrUnboundAst := ANode;
|
||||||
FCurrExec := Default(TCompiledFunction);
|
FCurrCompiled := Default(TCompiledFunction);
|
||||||
|
|
||||||
if DebugBox.IsChecked then
|
if DebugBox.IsChecked then
|
||||||
FEnvironment.SetDebugMode(Memo1.Lines, ShowScopeBox.IsChecked)
|
FEnvironment.SetDebugMode(Memo1.Lines, ShowScopeBox.IsChecked)
|
||||||
@@ -323,17 +356,18 @@ begin
|
|||||||
try
|
try
|
||||||
// Use Compile directly. Errors will be raised as ECompilationFailed.
|
// Use Compile directly. Errors will be raised as ECompilationFailed.
|
||||||
// We catch them to print to log, but allow flow to continue so UI can be updated.
|
// We catch them to print to log, but allow flow to continue so UI can be updated.
|
||||||
FCurrCompiled := FEnvironment.Compile(ANode);
|
FCurrLambda := FEnvironment.Compile(ANode);
|
||||||
FCurrExec := FEnvironment.Link(FCurrCompiled);
|
FCurrCompiled := FEnvironment.Link(FCurrLambda);
|
||||||
|
|
||||||
if Assigned(FCurrExec.Func) then
|
if Assigned(FCurrCompiled.Func) then
|
||||||
begin
|
begin
|
||||||
Memo1.Lines.Add(
|
Memo1.Lines.Add(
|
||||||
Format('Compiled. Signature: %s, IsPure=%s', [FCurrExec.StaticType.ToString, BoolToStr(FCurrExec.IsPure, true)])
|
Format('Compiled. Signature: %s, IsPure=%s', [FCurrCompiled.StaticType.ToString, BoolToStr(FCurrCompiled.IsPure, true)])
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := FCurrExec.Func([]);
|
FCurrExec := FCurrCompiled.Func([]);
|
||||||
|
Result := FCurrExec;
|
||||||
|
|
||||||
except
|
except
|
||||||
on E: ECompilationFailed do
|
on E: ECompilationFailed do
|
||||||
@@ -354,7 +388,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
FCurrExec.Func := nil;
|
FCurrCompiled.Func := nil;
|
||||||
Memo1.Lines.Add('--- RUNTIME ERROR ---');
|
Memo1.Lines.Add('--- RUNTIME ERROR ---');
|
||||||
Memo1.Lines.Add(E.ClassName + ': ' + E.Message);
|
Memo1.Lines.Add(E.ClassName + ': ' + E.Message);
|
||||||
Result := TDataValue.Void;
|
Result := TDataValue.Void;
|
||||||
@@ -617,8 +651,8 @@ begin
|
|||||||
|
|
||||||
Memo1.Lines.Add('--- Naive recursive fib with AST---');
|
Memo1.Lines.Add('--- Naive recursive fib with AST---');
|
||||||
|
|
||||||
FCurrCompiled := TestEnv.Compile(TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(25)]));
|
FCurrLambda := TestEnv.Compile(TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(25)]));
|
||||||
var fibExec := TestEnv.Link(FCurrCompiled).Func;
|
var fibExec := TestEnv.Link(FCurrLambda).Func;
|
||||||
|
|
||||||
sw := TStopwatch.StartNew;
|
sw := TStopwatch.StartNew;
|
||||||
result := fibExec([]);
|
result := fibExec([]);
|
||||||
@@ -781,8 +815,8 @@ begin
|
|||||||
|
|
||||||
// Die Pipeline nutzt den Environment-Helper (Managed Record)
|
// Die Pipeline nutzt den Environment-Helper (Managed Record)
|
||||||
// Compile akzeptiert IAstNode und erzeugt intern ein Lambda
|
// Compile akzeptiert IAstNode und erzeugt intern ein Lambda
|
||||||
FCurrCompiled := FEnvironment.Compile(FCurrUnboundAst);
|
FCurrLambda := FEnvironment.Compile(FCurrUnboundAst);
|
||||||
FCurrExec := FEnvironment.Link(FCurrCompiled);
|
FCurrCompiled := FEnvironment.Link(FCurrLambda);
|
||||||
|
|
||||||
Memo1.Lines.Clear;
|
Memo1.Lines.Clear;
|
||||||
Memo1.Lines.Add('AST deserialized and bound successfully from JSON.');
|
Memo1.Lines.Add('AST deserialized and bound successfully from JSON.');
|
||||||
@@ -1196,8 +1230,8 @@ end;
|
|||||||
procedure TForm1.Test1ButtonClick(Sender: TObject);
|
procedure TForm1.Test1ButtonClick(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
FCurrUnboundAst := TAstScript.Parse('(do (print txt))');
|
FCurrUnboundAst := TAstScript.Parse('(do (print txt))');
|
||||||
FCurrCompiled := FEnvironment.Compile(FCurrUnboundAst, [TAst.Identifier('txt')]);
|
FCurrLambda := FEnvironment.Compile(FCurrUnboundAst, [TAst.Identifier('txt')]);
|
||||||
var fn := FEnvironment.Link(FCurrCompiled).Func;
|
var fn := FEnvironment.Link(FCurrLambda).Func;
|
||||||
fn(['Hello World']);
|
fn(['Hello World']);
|
||||||
FEnvironment.RootScope.Define('fn', fn);
|
FEnvironment.RootScope.Define('fn', fn);
|
||||||
UpdateScript;
|
UpdateScript;
|
||||||
@@ -1280,14 +1314,14 @@ begin
|
|||||||
Memo1.Lines.Clear;
|
Memo1.Lines.Clear;
|
||||||
Memo1.Lines.Add('--- AST Dump ---');
|
Memo1.Lines.Add('--- AST Dump ---');
|
||||||
|
|
||||||
if FCurrCompiled = nil then
|
if FCurrLambda = nil then
|
||||||
begin
|
begin
|
||||||
Memo1.Lines.Add('No compiled AST available.');
|
Memo1.Lines.Add('No compiled AST available.');
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Dump the raw unbound AST first
|
// Dump the raw unbound AST first
|
||||||
TAstDumper.Dump(FCurrCompiled, Memo1.Lines);
|
TAstDumper.Dump(FCurrLambda, Memo1.Lines);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.SaveScriptButtonClick(Sender: TObject);
|
procedure TForm1.SaveScriptButtonClick(Sender: TObject);
|
||||||
@@ -1295,6 +1329,12 @@ begin
|
|||||||
ScriptMemo.Lines.SaveToFile(SrcName);
|
ScriptMemo.Lines.SaveToFile(SrcName);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TForm1.Timer1Timer(Sender: TObject);
|
||||||
|
begin
|
||||||
|
if Assigned(FSim) then
|
||||||
|
FSim.AddRandomCandle(FEnvironment.RootScope, 'dax');
|
||||||
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
TAst.RegisterLibrary(RegisterBroker);
|
TAst.RegisterLibrary(RegisterBroker);
|
||||||
|
|
||||||
|
|||||||
+27
-134
@@ -1,139 +1,32 @@
|
|||||||
(do
|
(do
|
||||||
;; ===========================================================================
|
(def med (pipe [[dax [:Close :Open]]]
|
||||||
;; 1. DEFINITIONEN
|
(fn [cs os]
|
||||||
;; ===========================================================================
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
;; Factory: create-wma
|
|
||||||
;; Berechnet WMA. Wartet, bis das Fenster voll ist.
|
|
||||||
;; Phase 1: Warten (< len)
|
|
||||||
;; Phase 2: Init (= len) -> Loop berechnung
|
|
||||||
;; Phase 3: Slide (> len) -> O(1) berechnung
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
(def create-wma
|
|
||||||
(fn [len]
|
|
||||||
(do
|
|
||||||
(def prev-w-sum 0.0)
|
|
||||||
(def prev-s-sum 0.0)
|
|
||||||
(def weight-div (/ (* len (+ len 1)) 2))
|
|
||||||
|
|
||||||
(fn [src]
|
|
||||||
(do
|
|
||||||
(def cnt (count src))
|
|
||||||
|
|
||||||
;; PHASE 1: Nicht genug Daten
|
|
||||||
(if (< cnt len)
|
|
||||||
NaN
|
|
||||||
|
|
||||||
;; Genug Daten vorhanden
|
|
||||||
(if (= cnt len)
|
|
||||||
;; PHASE 2: Initialisierung (Das Fenster ist gerade voll geworden)
|
|
||||||
;; Wir müssen einmalig die Basis-Summen berechnen.
|
|
||||||
(do
|
|
||||||
(def init-w 0.0)
|
|
||||||
(def init-s 0.0)
|
|
||||||
|
|
||||||
;; Loop über die Elemente 0 bis len-1
|
|
||||||
((fn [i]
|
|
||||||
(if (< i len)
|
|
||||||
(do
|
|
||||||
(def val (get src i))
|
|
||||||
;; Gewichtung: Index 0 ist das neueste -> Gewicht 'len'
|
|
||||||
(assign init-w (+ init-w (* (- len i) val)))
|
|
||||||
(assign init-s (+ init-s val))
|
|
||||||
(recur (+ i 1)))
|
|
||||||
)) 0)
|
|
||||||
|
|
||||||
;; State speichern
|
|
||||||
(assign prev-w-sum init-w)
|
|
||||||
(assign prev-s-sum init-s)
|
|
||||||
(/ init-w weight-div))
|
|
||||||
|
|
||||||
;; PHASE 3: Sliding Window (O(1))
|
|
||||||
;; Wir haben bereits einen gültigen State aus dem vorherigen Schritt.
|
|
||||||
(do
|
|
||||||
(def val-new (get src 0)) ; Neuster Wert
|
|
||||||
(def val-out (get src len)) ; Wert der rausfällt
|
|
||||||
|
|
||||||
;; Update Formeln
|
|
||||||
(def w-sum (+ prev-w-sum (- (* len val-new) prev-s-sum)))
|
|
||||||
(def s-sum (+ (- prev-s-sum val-out) val-new))
|
|
||||||
|
|
||||||
;; State speichern
|
|
||||||
(assign prev-w-sum w-sum)
|
|
||||||
(assign prev-s-sum s-sum)
|
|
||||||
(/ w-sum weight-div))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
))
|
|
||||||
)))
|
|
||||||
|
|
||||||
;; --- HMA Factory ---
|
|
||||||
(def create-hma
|
|
||||||
(fn [len]
|
|
||||||
(do
|
|
||||||
(def half-len (round (/ len 2)))
|
|
||||||
(def sqrt-len (round (sqrt len)))
|
|
||||||
|
|
||||||
(def wma-half (create-wma half-len))
|
|
||||||
(def wma-full (create-wma len))
|
|
||||||
(def wma-smooth (create-wma sqrt-len))
|
|
||||||
|
|
||||||
(def diff-series (new-series [[:val :Float]]))
|
|
||||||
|
|
||||||
(fn [src]
|
|
||||||
(do
|
|
||||||
(def v1 (wma-half src))
|
|
||||||
(def v2 (wma-full src))
|
|
||||||
|
|
||||||
;; WICHTIG: Wir dürfen erst weitermachen, wenn BEIDE WMAs gültig sind.
|
|
||||||
;; Da v2 das längere Fenster (len) braucht, diktiert es den Takt.
|
|
||||||
(def diff (- (* 2 v1) v2))
|
|
||||||
(if (not (is-NaN diff))
|
|
||||||
(add-item diff-series {:val diff}))
|
|
||||||
|
|
||||||
;; Glättung auf dem Resultat
|
|
||||||
(wma-smooth (.val diff-series))
|
|
||||||
))
|
|
||||||
)))
|
|
||||||
|
|
||||||
;; --- Repeat Macro ---
|
|
||||||
(defmacro repeat [n body]
|
|
||||||
`((fn [cnt]
|
|
||||||
(if (> cnt 0)
|
|
||||||
(do
|
|
||||||
~body
|
|
||||||
(recur (- cnt 1)))))
|
|
||||||
~n))
|
|
||||||
|
|
||||||
;; ===========================================================================
|
|
||||||
;; 2. SIMULATION
|
|
||||||
;; ===========================================================================
|
|
||||||
|
|
||||||
(def hma-length 14)
|
|
||||||
(def simulation-steps 500)
|
|
||||||
|
|
||||||
(def hma-calc (create-hma hma-length))
|
|
||||||
(def prices (new-series [[:close :Float]]))
|
|
||||||
(def current-price 100.0)
|
|
||||||
|
|
||||||
(print "Step | Price | HMA(14)")
|
|
||||||
(print "-----|----------|----------")
|
|
||||||
|
|
||||||
(repeat simulation-steps
|
|
||||||
(do
|
(do
|
||||||
;; Random Walk
|
(def o (get os 0))
|
||||||
(assign current-price (+ current-price (- (random) 0.5)))
|
(def c (get cs 0))
|
||||||
(add-item prices {:close current-price})
|
(def mm (* 0.5 (+ o c)))
|
||||||
|
{:m mm}
|
||||||
;; Berechnung
|
|
||||||
;; Nutzung von Member Access (.close), da es eine Record-Series ist
|
|
||||||
(def hma-val (hma-calc (.close prices)))
|
|
||||||
|
|
||||||
;; Ausgabe
|
|
||||||
(if (not (is-NaN hma-val))
|
|
||||||
(print (count prices) " |" current-price " | " hma-val)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
(def v 0)
|
||||||
|
|
||||||
|
(def xxx (pipe [[dax [:High :Low]] [med [:m]]]
|
||||||
|
(fn [hs ls ms]
|
||||||
|
(do
|
||||||
|
(def h (round (get hs 0)))
|
||||||
|
(def l (round (get ls 0)))
|
||||||
|
(def m (get ms 0))
|
||||||
|
(watch "h=" h " l=" l " m=" m)
|
||||||
|
(assign v (+ v h))
|
||||||
|
{:ah v}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
(pipe [[xxx [:ah]]]
|
||||||
|
(fn [a] (do (print (get a 0)) ...))
|
||||||
|
)
|
||||||
|
|
||||||
)
|
)
|
||||||
@@ -629,16 +629,36 @@ begin
|
|||||||
if Length(sig.ParamTypes) <> Length(argTypes) then
|
if Length(sig.ParamTypes) <> Length(argTypes) then
|
||||||
continue;
|
continue;
|
||||||
match := True;
|
match := True;
|
||||||
|
|
||||||
|
var sigIsStatic := sig.ReturnType.Kind <> stUnknown;
|
||||||
for j := 0 to High(argTypes) do
|
for j := 0 to High(argTypes) do
|
||||||
|
begin
|
||||||
if not TTypeRules.CanAssign(sig.ParamTypes[j], argTypes[j]) then
|
if not TTypeRules.CanAssign(sig.ParamTypes[j], argTypes[j]) then
|
||||||
begin
|
begin
|
||||||
match := False;
|
match := False;
|
||||||
break;
|
break;
|
||||||
end;
|
end;
|
||||||
|
if sig.ParamTypes[j].Kind = stUnknown then
|
||||||
|
sigIsStatic := false;
|
||||||
|
end;
|
||||||
|
|
||||||
if match then
|
if match then
|
||||||
begin
|
begin
|
||||||
bestSig := sig;
|
if not sigIsStatic then
|
||||||
break;
|
begin
|
||||||
|
if bestSig <> nil then
|
||||||
|
if Assigned(FLog) then
|
||||||
|
FLog.AddError(
|
||||||
|
Format('RTL contains ambiguous signatures for method call on %s', [calleeType.ToString]),
|
||||||
|
Node
|
||||||
|
);
|
||||||
|
bestSig := sig;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
bestSig := sig;
|
||||||
|
break;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -763,19 +783,21 @@ begin
|
|||||||
baseType := PrepareBaseType(newBase, isOpt);
|
baseType := PrepareBaseType(newBase, isOpt);
|
||||||
resType := TTypes.Unknown;
|
resType := TTypes.Unknown;
|
||||||
|
|
||||||
if (baseType.Kind = stRecord) or (baseType.Kind = stRecordSeries) then
|
case baseType.Kind of
|
||||||
begin
|
stRecord, stRecordSeries:
|
||||||
idx := baseType.AsRecord.Definition.IndexOf(M.Member.Value);
|
|
||||||
if idx >= 0 then
|
|
||||||
begin
|
begin
|
||||||
var fieldType := TTypes.FromScalarKind(baseType.AsRecord.Definition[idx]);
|
idx := baseType.AsRecord.Definition.IndexOf(M.Member.Value);
|
||||||
if baseType.Kind = stRecordSeries then
|
if idx >= 0 then
|
||||||
resType := TTypes.CreateSeries(fieldType)
|
begin
|
||||||
else
|
var fieldType := TTypes.FromScalarKind(baseType.AsRecord.Definition[idx]);
|
||||||
resType := fieldType;
|
if baseType.Kind = stRecordSeries then
|
||||||
end
|
resType := TTypes.CreateSeries(fieldType)
|
||||||
else if Assigned(FLog) then
|
else
|
||||||
FLog.AddError('Member not found', Node);
|
resType := fieldType;
|
||||||
|
end
|
||||||
|
else if Assigned(FLog) then
|
||||||
|
FLog.AddError('Member not found', Node);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
resType := ApplyOptionality(resType, isOpt);
|
resType := ApplyOptionality(resType, isOpt);
|
||||||
@@ -1081,7 +1103,8 @@ begin
|
|||||||
else
|
else
|
||||||
inferredType := TTypes.Ordinal;
|
inferredType := TTypes.Ordinal;
|
||||||
end;
|
end;
|
||||||
paramTypes.Add(inferredType);
|
|
||||||
|
paramTypes.Add(TTypes.CreateSeries(inferredType));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Reconstruct the typed Input Tuple [StreamIdent, [Selectors]]
|
// Reconstruct the typed Input Tuple [StreamIdent, [Selectors]]
|
||||||
@@ -1150,7 +1173,7 @@ begin
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
if (lambdaRetType.Kind <> stUnknown) and (lambdaRetType.Kind <> stVoid) then
|
if lambdaRetType.Kind <> stVoid then
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(
|
FLog.AddError(
|
||||||
@@ -1158,7 +1181,7 @@ begin
|
|||||||
lambda
|
lambda
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
pipeType := TTypes.Unknown;
|
pipeType := TTypes.Void;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Reconstruct the Pipe Node with typed Inputs tuple and typed Lambda
|
// Reconstruct the Pipe Node with typed Inputs tuple and typed Lambda
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ begin
|
|||||||
vkRecordSeries: Result := base.AsRecordSeries.Fields[N.Member.Value];
|
vkRecordSeries: Result := base.AsRecordSeries.Fields[N.Member.Value];
|
||||||
vkScalarRecord: Result := base.AsScalarRecord.Fields[N.Member.Value];
|
vkScalarRecord: Result := base.AsScalarRecord.Fields[N.Member.Value];
|
||||||
vkRecord: Result := base.AsRecord.Fields[N.Member.Value];
|
vkRecord: Result := base.AsRecord.Fields[N.Member.Value];
|
||||||
vkStream: Result := base.AsStream.Series.Fields[N.Member.Value];
|
// vkStream: Result := base.AsStream.Def.Fields[N.Member.Value];
|
||||||
else
|
else
|
||||||
raise EEvaluatorException.Create('Member error');
|
raise EEvaluatorException.Create('Member error');
|
||||||
end;
|
end;
|
||||||
@@ -549,7 +549,6 @@ var
|
|||||||
sources: TArray<IStream>;
|
sources: TArray<IStream>;
|
||||||
config: TPipeConfig;
|
config: TPipeConfig;
|
||||||
inputVal: TDataValue;
|
inputVal: TDataValue;
|
||||||
sourceSeries: IScalarRecordSeries;
|
|
||||||
lambdaFunc: TDataValue.TFunc;
|
lambdaFunc: TDataValue.TFunc;
|
||||||
|
|
||||||
inputsElements: TArray<IAstNode>;
|
inputsElements: TArray<IAstNode>;
|
||||||
@@ -581,8 +580,7 @@ begin
|
|||||||
raise EEvaluatorException.Create(Format('Variable "%s" is not a Stream.', [sourceId.Name]));
|
raise EEvaluatorException.Create(Format('Variable "%s" is not a Stream.', [sourceId.Name]));
|
||||||
|
|
||||||
sources[i] := inputVal.AsStream;
|
sources[i] := inputVal.AsStream;
|
||||||
sourceSeries := sources[i].Series;
|
var srcDef := sources[i].Def;
|
||||||
var srcDef := sourceSeries.Def;
|
|
||||||
|
|
||||||
// Build Selectors Config
|
// Build Selectors Config
|
||||||
SetLength(config[i], Length(selectorsElements));
|
SetLength(config[i], Length(selectorsElements));
|
||||||
@@ -601,11 +599,18 @@ begin
|
|||||||
// Compile the transformation function
|
// Compile the transformation function
|
||||||
lambdaFunc := Visit(N.Transformation).AsMethod();
|
lambdaFunc := Visit(N.Transformation).AsMethod();
|
||||||
|
|
||||||
// Allow both stRecord and stRecordSeries (as TypeChecker correctly assigns stRecordSeries for Pipe nodes)
|
var outputDef: IScalarRecordDefinition;
|
||||||
if (N.StaticType.Kind <> stRecordSeries) and (N.StaticType.Kind <> stRecord) then
|
|
||||||
raise EEvaluatorException.Create('Pipe requires Type Checking to determine output structure (RecordDefinition).');
|
|
||||||
|
|
||||||
var outputDef := N.StaticType.AsRecord.Definition;
|
case N.StaticType.Kind of
|
||||||
|
stVoid:
|
||||||
|
// This is an endpoint. The lambda returns nothing. So we produce "nothing".
|
||||||
|
outputDef := TScalarRecord.TRegistry.Empty;
|
||||||
|
stRecord, stRecordSeries:
|
||||||
|
// Allow both stRecord and stRecordSeries (as TypeChecker correctly assigns stRecordSeries for Pipe nodes)
|
||||||
|
outputDef := N.StaticType.AsRecord.Definition;
|
||||||
|
else
|
||||||
|
raise EEvaluatorException.Create('Pipe requires Type Checking to determine output structure (RecordDefinition).');
|
||||||
|
end;
|
||||||
|
|
||||||
var pipeAdapter: TPipeStream.TPipeLambda :=
|
var pipeAdapter: TPipeStream.TPipeLambda :=
|
||||||
function(const S: array of ISeries; out R: array of TScalar.TValue): Boolean
|
function(const S: array of ISeries; out R: array of TScalar.TValue): Boolean
|
||||||
@@ -630,7 +635,8 @@ begin
|
|||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TPipeStream.Create(config, outputDef, sources, pipeAdapter);
|
//TODO Lookback not evaluated in script!
|
||||||
|
Result := TPipeStream.Create(config, outputDef, sources, pipeAdapter, 1000);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -86,12 +86,14 @@ type
|
|||||||
class var
|
class var
|
||||||
FLock: TSpinLock;
|
FLock: TSpinLock;
|
||||||
FRegistry: TDictionary<TArray<Integer>, IKeywordMapping<T>>;
|
FRegistry: TDictionary<TArray<Integer>, IKeywordMapping<T>>;
|
||||||
|
FEmpty: IKeywordMapping<T>;
|
||||||
class constructor Create;
|
class constructor Create;
|
||||||
class destructor Destroy;
|
class destructor Destroy;
|
||||||
|
|
||||||
public
|
public
|
||||||
// Creates or gets the cached keyword mapping.
|
// Creates or gets the cached keyword mapping.
|
||||||
class function Intern(const AFields: TArray<TPair<IKeyword, T>>): IKeywordMapping<T>; static;
|
class function Intern(const AFields: TArray<TPair<IKeyword, T>>): IKeywordMapping<T>; static;
|
||||||
|
class property Empty: IKeywordMapping<T> read FEmpty;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TGenericRecord<T> = class(TInterfacedObject, IKeywordMapping<T>)
|
TGenericRecord<T> = class(TInterfacedObject, IKeywordMapping<T>)
|
||||||
@@ -270,6 +272,8 @@ class constructor TKeywordMappingRegistry<T>.Create;
|
|||||||
begin
|
begin
|
||||||
FRegistry := TDictionary<TArray<Integer>, IKeywordMapping<T>>.Create(TEqualityComparer<TArray<Integer>>.Default);
|
FRegistry := TDictionary<TArray<Integer>, IKeywordMapping<T>>.Create(TEqualityComparer<TArray<Integer>>.Default);
|
||||||
FLock := TSpinLock.Create(False);
|
FLock := TSpinLock.Create(False);
|
||||||
|
|
||||||
|
FEmpty := Intern([]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class destructor TKeywordMappingRegistry<T>.Destroy;
|
class destructor TKeywordMappingRegistry<T>.Destroy;
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ type
|
|||||||
class operator Implicit(const A: TScalar): Int64; overload; inline;
|
class operator Implicit(const A: TScalar): Int64; overload; inline;
|
||||||
class operator Implicit(const A: TScalar): Boolean; overload; inline;
|
class operator Implicit(const A: TScalar): Boolean; overload; inline;
|
||||||
class operator Implicit(const A: TScalar): TDateTime; overload; inline;
|
class operator Implicit(const A: TScalar): TDateTime; overload; inline;
|
||||||
|
class operator Implicit(const A: TScalar): IKeyword; overload; inline;
|
||||||
|
|
||||||
class function StringToKind(const AName: string): TKind; static;
|
class function StringToKind(const AName: string): TKind; static;
|
||||||
function ToString: String;
|
function ToString: String;
|
||||||
@@ -750,6 +751,15 @@ begin
|
|||||||
raise EArgumentException.Create('Operator "xor" requires Ordinal or Boolean arguments.');
|
raise EArgumentException.Create('Operator "xor" requires Ordinal or Boolean arguments.');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
class operator TScalar.Implicit(const A: TScalar): IKeyword;
|
||||||
|
begin
|
||||||
|
case A.Kind of
|
||||||
|
TKind.Keyword: Result := TKeywordRegistry.GetKeyword(A.Value.AsInt64);
|
||||||
|
else
|
||||||
|
raise EInvalidCast.CreateFmt('Cannot implicitly convert %s to Keyword', [A.Kind.ToString]);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
class operator TScalar.LeftShift(const A, B: TScalar): TScalar;
|
class operator TScalar.LeftShift(const A, B: TScalar): TScalar;
|
||||||
begin
|
begin
|
||||||
if (A.Kind <> TKind.Ordinal) or (B.Kind <> TKind.Ordinal) then
|
if (A.Kind <> TKind.Ordinal) or (B.Kind <> TKind.Ordinal) then
|
||||||
|
|||||||
+164
-100
@@ -14,128 +14,143 @@ uses
|
|||||||
type
|
type
|
||||||
TSignalKind = (skData, skHeartbeat);
|
TSignalKind = (skData, skHeartbeat);
|
||||||
|
|
||||||
|
// TStreamSignal represents the data packet traveling through the graph.
|
||||||
|
// It carries the CycleID for synchronization and the actual data payload.
|
||||||
TStreamSignal = record
|
TStreamSignal = record
|
||||||
private
|
strict private
|
||||||
FCycleID: Int64;
|
FCycleID: Int64;
|
||||||
FKind: TSignalKind;
|
FKind: TSignalKind;
|
||||||
|
FData: TArray<TScalar.TValue>;
|
||||||
public
|
public
|
||||||
constructor Create(AKind: TSignalKind; ACycleID: Int64);
|
constructor Create(AKind: TSignalKind; ACycleID: Int64; const AData: TArray<TScalar.TValue> = nil);
|
||||||
function ToString: string;
|
function ToString: string;
|
||||||
property CycleID: Int64 read FCycleID;
|
property CycleID: Int64 read FCycleID;
|
||||||
property Kind: TSignalKind read FKind;
|
property Kind: TSignalKind read FKind;
|
||||||
|
property Data: TArray<TScalar.TValue> read FData;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
IStreamObserver = interface
|
TSignalProc = reference to procedure(const Signal: TStreamSignal);
|
||||||
procedure OnSignal(const Signal: TStreamSignal);
|
|
||||||
end;
|
|
||||||
|
|
||||||
TSubscriptionTag = Pointer;
|
TSubscriptionTag = Pointer;
|
||||||
|
|
||||||
|
// IStream is now stateless. It defines WHAT is produced (Def), but not
|
||||||
|
// the history. History must be captured by an observer (Accumulator).
|
||||||
IStream = interface
|
IStream = interface
|
||||||
{$region 'private'}
|
{$region 'private'}
|
||||||
function GetSeries: IScalarRecordSeries;
|
function GetDef: IScalarRecordDefinition;
|
||||||
{$endregion}
|
{$endregion}
|
||||||
|
|
||||||
function Subscribe(const Observer: IStreamObserver): TSubscriptionTag;
|
function Subscribe(const Observer: TSignalProc): TSubscriptionTag;
|
||||||
procedure Unsubscribe(Tag: TSubscriptionTag);
|
procedure Unsubscribe(Tag: TSubscriptionTag);
|
||||||
|
|
||||||
// Returns the Series of the records this stream produces. Streams are scalar record series. Always!
|
{ Returns the definition (schema) of the records this stream produces. }
|
||||||
property Series: IScalarRecordSeries read GetSeries;
|
property Def: IScalarRecordDefinition read GetDef;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// =========================================================================
|
// Implements broadcasting logic. No internal storage of past values.
|
||||||
// BASE STREAM
|
|
||||||
// Implements storage (Series) and broadcasting (Observers).
|
|
||||||
// Does not define WHEN data is emitted (Policy).
|
|
||||||
// =========================================================================
|
|
||||||
TCustomDataStream = class(TInterfacedObject, IStream)
|
TCustomDataStream = class(TInterfacedObject, IStream)
|
||||||
strict private
|
strict private
|
||||||
FSeries: IWriteableScalarRecordSeries;
|
FDef: IScalarRecordDefinition;
|
||||||
FObservers: TMycNotifyList<IStreamObserver>;
|
FObservers: TMycNotifyList<TSignalProc>;
|
||||||
function GetSeries: IScalarRecordSeries;
|
function GetDef: IScalarRecordDefinition;
|
||||||
protected
|
protected
|
||||||
// Derived classes call this to write data and notify listeners.
|
{ Broadcasts data to all subscribers. Stateless. }
|
||||||
// ACycleID: The cycle this data belongs to.
|
|
||||||
procedure Emit(const Value: array of TScalar.TValue; ACycleID: Int64);
|
procedure Emit(const Value: array of TScalar.TValue; ACycleID: Int64);
|
||||||
public
|
public
|
||||||
constructor Create(const ADef: IScalarRecordDefinition);
|
constructor Create(const ADef: IScalarRecordDefinition);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
// IStream
|
function Subscribe(const Observer: TSignalProc): TSubscriptionTag;
|
||||||
function Subscribe(const Observer: IStreamObserver): TSubscriptionTag;
|
|
||||||
procedure Unsubscribe(Tag: TSubscriptionTag);
|
procedure Unsubscribe(Tag: TSubscriptionTag);
|
||||||
property Series: IScalarRecordSeries read GetSeries;
|
property Def: IScalarRecordDefinition read GetDef;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// =========================================================================
|
// The entry point for external data. Generates the global CycleID (Clock).
|
||||||
// ROOT STREAM (SOURCE)
|
|
||||||
// Acts as the clock source. Generates new CycleIDs.
|
|
||||||
// =========================================================================
|
|
||||||
TRootStream = class(TCustomDataStream)
|
TRootStream = class(TCustomDataStream)
|
||||||
private
|
strict private
|
||||||
FLastCycleID: Int64;
|
FLastCycleID: Int64;
|
||||||
public
|
public
|
||||||
constructor Create(const ADef: IScalarRecordDefinition);
|
constructor Create(const ADef: IScalarRecordDefinition);
|
||||||
// Public API to inject data from Delphi
|
{ Injects a new record into the stream system. }
|
||||||
procedure Push(const RowData: TArray<TScalar.TValue>);
|
procedure Push(const RowData: array of TScalar.TValue);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// =========================================================================
|
// Synchronizes multiple inputs via barrier and executes a transformation.
|
||||||
// PIPE STREAM (NODE)
|
TPipeStream = class;
|
||||||
// Reacts to upstream signals using barrier synchronization.
|
|
||||||
// =========================================================================
|
|
||||||
TPipeStream = class; // Forward
|
|
||||||
|
|
||||||
TPipeConfig = TArray<TArray<TScalarRecordField>>;
|
{ TPipeSource acts as a selective accumulator for a single field of a stream. }
|
||||||
|
TPipeSource = class(TInterfacedObject, ISeries)
|
||||||
TPipeSource = class(TContainedObject, IStreamObserver)
|
public
|
||||||
private
|
type
|
||||||
|
TSignalProc = reference to procedure(const Signal: TStreamSignal);
|
||||||
|
strict private
|
||||||
FSource: IStream;
|
FSource: IStream;
|
||||||
FTag: TSubscriptionTag;
|
FTag: TSubscriptionTag;
|
||||||
|
FOnSignal: TSignalProc;
|
||||||
|
FFieldIndex: Integer;
|
||||||
|
FData: IWriteableSeries;
|
||||||
FLastSeenCycle: Int64;
|
FLastSeenCycle: Int64;
|
||||||
procedure OnSignal(const Signal: TStreamSignal);
|
FLookback: Int64;
|
||||||
|
|
||||||
|
{ ISeries implementation }
|
||||||
|
function GetCount: Int64;
|
||||||
|
function GetItems(Idx: Integer): TScalar;
|
||||||
|
function GetTotalCount: Int64;
|
||||||
|
private
|
||||||
|
procedure HandleSignal(const Signal: TStreamSignal);
|
||||||
|
protected
|
||||||
|
procedure Detach;
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TPipeStream; ASource: IStream);
|
constructor Create(const ASource: IStream; const AField: IKeyword; ALookback: Int64; const AOnSignal: TSignalProc);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
function IsReady(RequiredLookback: Int64): Boolean;
|
||||||
property LastSeenCycle: Int64 read FLastSeenCycle;
|
property LastSeenCycle: Int64 read FLastSeenCycle;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TPipeConfig = TArray<TArray<TScalarRecordField>>;
|
||||||
|
|
||||||
TPipeStream = class(TCustomDataStream)
|
TPipeStream = class(TCustomDataStream)
|
||||||
public
|
public
|
||||||
type
|
type
|
||||||
TPipeLambda = reference to function(const Sources: array of ISeries; out Results: array of TScalar.TValue): Boolean;
|
TPipeLambda = reference to function(const Sources: array of ISeries; out Results: array of TScalar.TValue): Boolean;
|
||||||
private
|
strict private
|
||||||
FSources: TArray<TPipeSource>;
|
FSources: TArray<ISeries>;
|
||||||
FSourceSeries: TArray<ISeries>;
|
FResults: TArray<TScalar.TValue>;
|
||||||
FLastFiredCycleID: Int64;
|
FLastFiredCycleID: Int64;
|
||||||
|
FLookback: Int64;
|
||||||
FLambda: TPipeLambda;
|
FLambda: TPipeLambda;
|
||||||
|
|
||||||
|
private
|
||||||
procedure CheckBarrierAndFire(CurrentCycle: Int64);
|
procedure CheckBarrierAndFire(CurrentCycle: Int64);
|
||||||
|
|
||||||
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: TPipeLambda
|
const ALambda: TPipeLambda;
|
||||||
|
ALookback: Int64
|
||||||
);
|
);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
property Lookback: Int64 read FLookback;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
{ TStreamSignal }
|
||||||
Winapi.Windows;
|
|
||||||
|
|
||||||
constructor TStreamSignal.Create(AKind: TSignalKind; ACycleID: Int64);
|
constructor TStreamSignal.Create(AKind: TSignalKind; ACycleID: Int64; const AData: TArray<TScalar.TValue>);
|
||||||
begin
|
begin
|
||||||
FKind := AKind;
|
FKind := AKind;
|
||||||
FCycleID := ACycleID;
|
FCycleID := ACycleID;
|
||||||
|
FData := AData;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TStreamSignal.ToString: string;
|
function TStreamSignal.ToString: string;
|
||||||
begin
|
begin
|
||||||
if Kind = skData then
|
if Kind = skData then
|
||||||
Result := Format('Signal(Data, #%d)', [CycleID])
|
Result := Format('Signal(Data, #%d, Fields: %d)', [CycleID, Length(Data)])
|
||||||
else
|
else
|
||||||
Result := Format('Signal(Heartbeat, #%d)', [CycleID]);
|
Result := Format('Signal(Heartbeat, #%d)', [CycleID]);
|
||||||
end;
|
end;
|
||||||
@@ -145,7 +160,7 @@ end;
|
|||||||
constructor TCustomDataStream.Create(const ADef: IScalarRecordDefinition);
|
constructor TCustomDataStream.Create(const ADef: IScalarRecordDefinition);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FSeries := TScalarRecordSeries.Create(ADef);
|
FDef := ADef;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TCustomDataStream.Destroy;
|
destructor TCustomDataStream.Destroy;
|
||||||
@@ -154,12 +169,12 @@ begin
|
|||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomDataStream.GetSeries: IScalarRecordSeries;
|
function TCustomDataStream.GetDef: IScalarRecordDefinition;
|
||||||
begin
|
begin
|
||||||
Result := FSeries;
|
Result := FDef;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomDataStream.Subscribe(const Observer: IStreamObserver): TSubscriptionTag;
|
function TCustomDataStream.Subscribe(const Observer: TSignalProc): TSubscriptionTag;
|
||||||
begin
|
begin
|
||||||
FObservers.Lock;
|
FObservers.Lock;
|
||||||
try
|
try
|
||||||
@@ -171,6 +186,9 @@ end;
|
|||||||
|
|
||||||
procedure TCustomDataStream.Unsubscribe(Tag: TSubscriptionTag);
|
procedure TCustomDataStream.Unsubscribe(Tag: TSubscriptionTag);
|
||||||
begin
|
begin
|
||||||
|
if Tag = nil then
|
||||||
|
exit;
|
||||||
|
|
||||||
FObservers.Lock;
|
FObservers.Lock;
|
||||||
try
|
try
|
||||||
FObservers.Unadvise(Tag);
|
FObservers.Unadvise(Tag);
|
||||||
@@ -181,20 +199,23 @@ end;
|
|||||||
|
|
||||||
procedure TCustomDataStream.Emit(const Value: array of TScalar.TValue; ACycleID: Int64);
|
procedure TCustomDataStream.Emit(const Value: array of TScalar.TValue; ACycleID: Int64);
|
||||||
var
|
var
|
||||||
signal: TStreamSignal;
|
LSignal: TStreamSignal;
|
||||||
|
LData: TArray<TScalar.TValue>;
|
||||||
|
I: Integer;
|
||||||
begin
|
begin
|
||||||
|
// Convert open array to dynamic array for signal payload
|
||||||
|
SetLength(LData, Length(Value));
|
||||||
|
for I := 0 to High(Value) do
|
||||||
|
LData[I] := Value[I];
|
||||||
|
|
||||||
|
LSignal := TStreamSignal.Create(skData, ACycleID, LData);
|
||||||
|
|
||||||
FObservers.Lock;
|
FObservers.Lock;
|
||||||
try
|
try
|
||||||
// 1. Write Data
|
|
||||||
FSeries.Add(Value);
|
|
||||||
|
|
||||||
// 2. Broadcast Signal
|
|
||||||
signal := TStreamSignal.Create(skData, ACycleID);
|
|
||||||
|
|
||||||
FObservers.Notify(
|
FObservers.Notify(
|
||||||
function(const Obs: IStreamObserver): Boolean
|
function(const Obs: TSignalProc): Boolean
|
||||||
begin
|
begin
|
||||||
Obs.OnSignal(signal);
|
Obs(LSignal);
|
||||||
Result := True;
|
Result := True;
|
||||||
end
|
end
|
||||||
);
|
);
|
||||||
@@ -211,116 +232,159 @@ begin
|
|||||||
FLastCycleID := 0;
|
FLastCycleID := 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TRootStream.Push(const RowData: TArray<TScalar.TValue>);
|
procedure TRootStream.Push(const RowData: array of TScalar.TValue);
|
||||||
begin
|
begin
|
||||||
// Root streams increment the global clock
|
if Length(RowData) <> Def.Count then
|
||||||
|
raise EArgumentException.Create('Push: Data field count mismatch.');
|
||||||
|
|
||||||
Inc(FLastCycleID);
|
Inc(FLastCycleID);
|
||||||
Emit(RowData, FLastCycleID);
|
Emit(RowData, FLastCycleID);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TPipeSource }
|
{ TPipeSource }
|
||||||
|
|
||||||
constructor TPipeSource.Create(AOwner: TPipeStream; ASource: IStream);
|
constructor TPipeSource.Create(const ASource: IStream; const AField: IKeyword; ALookback: Int64; const AOnSignal: TSignalProc);
|
||||||
begin
|
begin
|
||||||
inherited Create(AOwner);
|
inherited Create;
|
||||||
FSource := ASource;
|
FSource := ASource;
|
||||||
|
FOnSignal := AOnSignal;
|
||||||
|
FLookback := ALookback;
|
||||||
FLastSeenCycle := -1;
|
FLastSeenCycle := -1;
|
||||||
FTag := FSource.Subscribe(Self);
|
|
||||||
|
FFieldIndex := ASource.Def.IndexOf(AField);
|
||||||
|
if (FFieldIndex < 0) then
|
||||||
|
raise EArgumentException.CreateFmt('Field %s not found in source stream.', [AField.Name]);
|
||||||
|
|
||||||
|
FData := TScalarSeries.Create(ASource.Def.Items[FFieldIndex]);
|
||||||
|
|
||||||
|
FTag := FSource.Subscribe(procedure(const Signal: TStreamSignal) begin HandleSignal(Signal); end);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TPipeSource.Destroy;
|
destructor TPipeSource.Destroy;
|
||||||
begin
|
begin
|
||||||
FSource.Unsubscribe(FTag);
|
Detach;
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPipeSource.OnSignal(const Signal: TStreamSignal);
|
procedure TPipeSource.Detach;
|
||||||
begin
|
begin
|
||||||
if Signal.Kind = skData then
|
FSource.Unsubscribe(FTag);
|
||||||
|
FTag := nil;
|
||||||
|
FOnSignal := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPipeSource.HandleSignal(const Signal: TStreamSignal);
|
||||||
|
begin
|
||||||
|
if (Signal.Kind = skData) then
|
||||||
begin
|
begin
|
||||||
FLastSeenCycle := Signal.CycleID;
|
FLastSeenCycle := Signal.CycleID;
|
||||||
(Controller as TPipeStream).CheckBarrierAndFire(FLastSeenCycle);
|
FData.Add(Signal.Data[FFieldIndex], FLookback);
|
||||||
|
if Assigned(FOnSignal) then
|
||||||
|
FOnSignal(Signal);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TPipeSource.IsReady(RequiredLookback: Int64): Boolean;
|
||||||
|
begin
|
||||||
|
Result := (FLastSeenCycle >= 0) and (FData.Count >= RequiredLookback);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPipeSource.GetCount: Int64;
|
||||||
|
begin
|
||||||
|
Result := FData.Count;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPipeSource.GetItems(Idx: Integer): TScalar;
|
||||||
|
begin
|
||||||
|
Result := FData.Items[Idx];
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPipeSource.GetTotalCount: Int64;
|
||||||
|
begin
|
||||||
|
Result := FData.TotalCount;
|
||||||
|
end;
|
||||||
|
|
||||||
{ TPipeStream }
|
{ TPipeStream }
|
||||||
|
|
||||||
constructor TPipeStream.Create(
|
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: TPipeLambda
|
const ALambda: TPipeLambda;
|
||||||
|
ALookback: Int64
|
||||||
);
|
);
|
||||||
var
|
var
|
||||||
i, j, n: Integer;
|
i, j, n: Integer;
|
||||||
begin
|
begin
|
||||||
// Pass Definition to base class
|
|
||||||
inherited Create(ADef);
|
inherited Create(ADef);
|
||||||
|
|
||||||
FLambda := ALambda;
|
FLambda := ALambda;
|
||||||
|
FLookback := ALookback;
|
||||||
FLastFiredCycleID := -1;
|
FLastFiredCycleID := -1;
|
||||||
|
|
||||||
SetLength(FSources, Length(ASources));
|
// Flatten sources from config
|
||||||
|
|
||||||
// Flatten Sources
|
|
||||||
n := 0;
|
n := 0;
|
||||||
for i := 0 to High(AConfig) do
|
for i := 0 to High(AConfig) do
|
||||||
inc(n, Length(AConfig[i]));
|
inc(n, Length(AConfig[i]));
|
||||||
SetLength(FSourceSeries, n);
|
|
||||||
|
SetLength(FSources, n);
|
||||||
|
SetLength(FResults, n);
|
||||||
|
|
||||||
n := 0;
|
n := 0;
|
||||||
for i := 0 to High(ASources) do
|
for i := 0 to High(ASources) do
|
||||||
begin
|
begin
|
||||||
FSources[i] := TPipeSource.Create(Self, ASources[i]);
|
|
||||||
for j := 0 to High(AConfig[i]) do
|
for j := 0 to High(AConfig[i]) do
|
||||||
begin
|
begin
|
||||||
// Extract the specific column series
|
// Each input is wrapped in an accumulator source
|
||||||
var m := ASources[i].Series.IndexOf(AConfig[i][j].Key);
|
FSources[n] :=
|
||||||
if m >= 0 then
|
TPipeSource.Create(
|
||||||
begin
|
ASources[i],
|
||||||
FSourceSeries[n] := ASources[i].Series[m];
|
AConfig[i][j].Key,
|
||||||
inc(n);
|
FLookback,
|
||||||
end;
|
procedure(const Signal: TStreamSignal) begin CheckBarrierAndFire(Signal.CycleID); end
|
||||||
|
);
|
||||||
|
inc(n);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TPipeStream.Destroy;
|
destructor TPipeStream.Destroy;
|
||||||
begin
|
begin
|
||||||
for var i := High(FSources) downto 0 do
|
for var i := 0 to High(FSources) do
|
||||||
FSources[i].Free;
|
(FSources[i] as TPipeSource).Detach;
|
||||||
FSources := nil;
|
|
||||||
FSourceSeries := nil;
|
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPipeStream.CheckBarrierAndFire(CurrentCycle: Int64);
|
procedure TPipeStream.CheckBarrierAndFire(CurrentCycle: Int64);
|
||||||
var
|
|
||||||
resultVal: TArray<TScalar.TValue>;
|
|
||||||
begin
|
begin
|
||||||
// We assume thread safety is handled by the caller
|
const requiredLookback = 10;
|
||||||
|
|
||||||
|
// 1. Check if all sources reached this cycle AND have enough history (Warm-up)
|
||||||
for var src in FSources do
|
for var src in FSources do
|
||||||
if src.LastSeenCycle < CurrentCycle then
|
begin
|
||||||
exit; // Barrier closed
|
if src.Count < requiredLookback then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
var LSource := src as TPipeSource;
|
||||||
|
if LSource.LastSeenCycle < CurrentCycle then
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
if FLastFiredCycleID >= CurrentCycle then
|
if FLastFiredCycleID >= CurrentCycle then
|
||||||
exit; // Already fired
|
Exit;
|
||||||
|
|
||||||
FLastFiredCycleID := CurrentCycle;
|
FLastFiredCycleID := CurrentCycle;
|
||||||
|
|
||||||
if Assigned(FLambda) then
|
if Assigned(FLambda) then
|
||||||
begin
|
begin
|
||||||
SetLength(resultVal, Series.Def.Count);
|
// Execute transformation on synchronized window
|
||||||
if FLambda(FSourceSeries, resultVal) then
|
if FLambda(FSources, FResults) then
|
||||||
begin
|
begin
|
||||||
// Pass through CycleID from upstream
|
Emit(FResults, FLastFiredCycleID);
|
||||||
Emit(resultVal, FLastFiredCycleID);
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
TMycNotifyList<IStreamObserver>.ReverseOnNotify := False;
|
TMycNotifyList<TSignalProc>.ReverseOnNotify := False;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ begin
|
|||||||
// Für den Compiler ist es eine RecordSeries (Typ), zur Laufzeit ein Stream (Wert)
|
// Für den Compiler ist es eine RecordSeries (Typ), zur Laufzeit ein Stream (Wert)
|
||||||
StaticType := TTypes.CreateRecordSeries(Def);
|
StaticType := TTypes.CreateRecordSeries(Def);
|
||||||
|
|
||||||
// UPDATED: Dokumentation für die Stream-Variable hinzufügen
|
|
||||||
Scope.Define(VarName, Stream, StaticType, 'Financial OHLC Data Stream (Time, Open, High, Low, Close, Vol).');
|
Scope.Define(VarName, Stream, StaticType, 'Financial OHLC Data Stream (Time, Open, High, Low, Close, Vol).');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -118,7 +117,7 @@ begin
|
|||||||
raise Exception.CreateFmt('Variable "%s" is a Stream, but not a TRootStream (cannot push manually).', [VarName]);
|
raise Exception.CreateFmt('Variable "%s" is a Stream, but not a TRootStream (cannot push manually).', [VarName]);
|
||||||
|
|
||||||
Stream := TRootStream(Val.AsStream);
|
Stream := TRootStream(Val.AsStream);
|
||||||
Def := Stream.Series.Def;
|
Def := Stream.Def;
|
||||||
|
|
||||||
// --- Simulation ---
|
// --- Simulation ---
|
||||||
FLastTime := IncMinute(FLastTime, 5);
|
FLastTime := IncMinute(FLastTime, 5);
|
||||||
|
|||||||
Reference in New Issue
Block a user