unit Demo.Finance;

interface

uses
    System.SysUtils,
    System.Math,
    System.DateUtils,
    // Framework
    Myc.Data.Value,
    Myc.Data.Scalar,
    Myc.Data.Keyword,
    Myc.Data.Stream, // IStream
    Myc.Data.Stream.Pipes, // TRootStream
    Myc.Ast.Types,
    Myc.Ast.Scope,
    Myc.Ast;

type
    IFinanceSimulator = interface
        procedure CreateOHLCSeries(const Scope: IExecutionScope; const VarName: string);
        procedure AddRandomCandle(const Scope: IExecutionScope; const VarName: string);
    end;

    TFinanceSimulator = class(TInterfacedObject, IFinanceSimulator)
    private
        FLastClose: Double;
        FLastTime: TDateTime;

        kTime, kOpen, kHigh, kLow, kClose, kVol: IKeyword;

        procedure InitKeywords;
    public
        constructor Create;
        procedure CreateOHLCSeries(const Scope: IExecutionScope; const VarName: string);
        procedure AddRandomCandle(const Scope: IExecutionScope; const VarName: string);
    end;

implementation

{ TFinanceSimulator }

constructor TFinanceSimulator.Create;
begin
    inherited;
    FLastClose := 150.00;
    FLastTime := Now;
end;

procedure TFinanceSimulator.InitKeywords;
begin
    if kTime = nil then
    begin
        kTime := TKeywordRegistry.Intern('Time');
        kOpen := TKeywordRegistry.Intern('Open');
        kHigh := TKeywordRegistry.Intern('High');
        kLow := TKeywordRegistry.Intern('Low');
        kClose := TKeywordRegistry.Intern('Close');
        kVol := TKeywordRegistry.Intern('Vol');
    end;
end;

procedure TFinanceSimulator.CreateOHLCSeries(const Scope: IExecutionScope; const VarName: string);
var
    Fields: TArray<TScalarRecordField>;
    Def: IScalarRecordDefinition;
    Stream: TRootStream;
    StaticType: IStaticType;
begin
    InitKeywords;

    SetLength(Fields, 6);
    Fields[0] := TScalarRecordField.Create(kTime, TScalar.TKind.DateTime);
    Fields[1] := TScalarRecordField.Create(kOpen, TScalar.TKind.Float);
    Fields[2] := TScalarRecordField.Create(kHigh, TScalar.TKind.Float);
    Fields[3] := TScalarRecordField.Create(kLow, TScalar.TKind.Float);
    Fields[4] := TScalarRecordField.Create(kClose, TScalar.TKind.Float);
    Fields[5] := TScalarRecordField.Create(kVol, TScalar.TKind.Ordinal);

    Def := TKeywordMappingRegistry<TScalar.TKind>.Intern(Fields);

    // Wir erzeugen einen aktiven RootStream
    Stream := TRootStream.Create(Def);

    // Für den Compiler ist es eine RecordSeries (Typ), zur Laufzeit ein Stream (Wert)
    StaticType := TTypes.CreateRecordSeries(Def);

    Scope.Define(VarName, TDataValue.FromStream(Stream), StaticType);
end;

procedure TFinanceSimulator.AddRandomCandle(const Scope: IExecutionScope; const VarName: string);
var
    Addr: TResolvedAddress;
    Val: TDataValue;
    Stream: TRootStream;
    Def: IScalarRecordDefinition;
    RowData: TArray<TScalar.TValue>;
    i: Integer;
    Key: IKeyword;

    O, H, L, C: Double;
    V: Int64;
begin
    InitKeywords;

    Addr := Scope.Resolve(VarName);
    if Addr.Kind = akUnresolved then
        raise Exception.CreateFmt('Variable "%s" not found.', [VarName]);

    Val := Scope[Addr];

    // Wir benötigen zwingend einen Stream für sim-tick, um Events zu feuern
    if Val.Kind <> vkStream then
        raise Exception.CreateFmt('Variable "%s" is not a Stream.', [VarName]);

    // Sicherer Cast auf TRootStream
    if not (Val.AsStream is TRootStream) then
        raise Exception.CreateFmt('Variable "%s" is a Stream, but not a TRootStream (cannot push manually).', [VarName]);

    Stream := TRootStream(Val.AsStream);
    Def := Stream.Series.Def;

    // --- Simulation ---
    FLastTime := IncMinute(FLastTime, 5);
    O := FLastClose;
    C := O + (Random - 0.5) * 2.0;
    H := Max(O, C) + Random * 0.5;
    L := Min(O, C) - Random * 0.5;
    V := Random(1000) + 100;
    FLastClose := C;
    // ------------------

    SetLength(RowData, Def.Count);
    for i := 0 to Def.Count - 1 do
    begin
        Key := Def.Items[i].Key;
        if Key = kTime then
            RowData[i] := FLastTime
        else if Key = kOpen then
            RowData[i] := O
        else if Key = kHigh then
            RowData[i] := H
        else if Key = kLow then
            RowData[i] := L
        else if Key = kClose then
            RowData[i] := C
        else if Key = kVol then
            RowData[i] := V;
    end;

    // Push triggert das Notify-Event für alle angeschlossenen Pipes
    Stream.Push(RowData);
end;

procedure RegisterFinanceLibrary(const Scope: IExecutionScope);
var
    Sim: IFinanceSimulator;
    Sig: IStaticType;
begin
    Sim := TFinanceSimulator.Create;
    Sig := TTypes.CreateMethod([TTypes.Text], TTypes.Void);

    Scope.Define(
        'sim-create',
        function(const Args: TArray<TDataValue>): TDataValue
        begin
            if Length(Args) <> 1 then
                raise Exception.Create('sim-create(Name) expects 1 argument');
            Sim.CreateOHLCSeries(Scope, Args[0].AsText);
            Result := TDataValue.Void;
        end,
        Sig
    );

    Scope.Define(
        'sim-tick',
        function(const Args: TArray<TDataValue>): TDataValue
        begin
            if Length(Args) <> 1 then
                raise Exception.Create('sim-tick(Name) expects 1 argument');
            Sim.AddRandomCandle(Scope, Args[0].AsText);
            Result := TDataValue.Void;
        end,
        Sig
    );

    // Some Demo-Stream
    Sim.CreateOHLCSeries(Scope, 'btc');
end;

initialization
    TAst.RegisterLibrary(RegisterFinanceLibrary);

end.
