d9a82365d3
Bugfix in Chart -> StrokeJoint set to Bevel
484 lines
16 KiB
ObjectPascal
484 lines
16 KiB
ObjectPascal
unit MainForm;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Types,
|
|
System.UITypes,
|
|
System.Classes,
|
|
System.Variants,
|
|
System.DateUtils,
|
|
System.Generics.Collections,
|
|
System.Rtti,
|
|
FMX.Types,
|
|
FMX.Controls,
|
|
FMX.Forms,
|
|
FMX.Graphics,
|
|
FMX.Dialogs,
|
|
FMX.Controls.Presentation,
|
|
FMX.StdCtrls,
|
|
FMX.ListView.Types,
|
|
FMX.ListView.Appearances,
|
|
FMX.ListView.Adapters.Base,
|
|
FMX.ListView,
|
|
FMX.Memo.Types,
|
|
FMX.ScrollBox,
|
|
FMX.Memo,
|
|
FMX.Objects,
|
|
Myc.Futures,
|
|
Myc.Trade.Types,
|
|
Myc.Trade.DataStream,
|
|
Myc.Trade.DataPoint,
|
|
Myc.Trade.DataProvider,
|
|
Myc.Signals,
|
|
Myc.Mutable,
|
|
Myc.Signals.FMX,
|
|
Myc.TaskManager,
|
|
Myc.Aura.Module,
|
|
FMX.ListBox,
|
|
FMX.Layouts,
|
|
FMX.TreeView,
|
|
FMX.TabControl,
|
|
FMX.Menus,
|
|
System.ImageList,
|
|
FMX.ImgList,
|
|
System.Actions,
|
|
FMX.ActnList,
|
|
DynamicFMXControl,
|
|
FirstStrategy,
|
|
Myc.FMX.Chart;
|
|
|
|
type
|
|
TForm1 = class(TForm)
|
|
LogMemo: TMemo;
|
|
MainPanel: TPanel;
|
|
SymbolsComboBox: TComboBox;
|
|
RandomBox: TCheckBox;
|
|
LoadButton: TButton;
|
|
RandomButton: TButton;
|
|
ChartButton: TButton;
|
|
StopButton: TButton;
|
|
Splitter1: TSplitter;
|
|
ActionList: TActionList;
|
|
AddWorkspaceAction: TAction;
|
|
WorkspacePanel: TPanel;
|
|
TabControl: TTabControl;
|
|
ToolBar: TToolBar;
|
|
AddWorkspaceButton: TSpeedButton;
|
|
ObjectsPanel: TPanel;
|
|
ObjectsTabControl: TTabControl;
|
|
ModulesTabItem: TTabItem;
|
|
TreeView: TTreeView;
|
|
TestButton: TSpeedButton;
|
|
TestAction: TAction;
|
|
TestPopup: TPopup;
|
|
FlowLayout: TFlowLayout;
|
|
StrategyButton: TSpeedButton;
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure FormDestroy(Sender: TObject);
|
|
procedure StopButtonClick(Sender: TObject);
|
|
procedure TreeViewDblClick(Sender: TObject);
|
|
procedure AddWorkspaceActionExecute(Sender: TObject);
|
|
procedure TestActionExecute(Sender: TObject);
|
|
procedure StrategyButtonClick(Sender: TObject);
|
|
private
|
|
FOnEvent: TNotifyEvent;
|
|
{ Private declarations }
|
|
FServer: IDataServer<TAuraAskBidFileItem>;
|
|
FSymbols: TFuture<TArray<String>>;
|
|
FTerminate: TEvent;
|
|
FProcessDone: TState;
|
|
FApplication: IAuraApplication;
|
|
FModulesItem: TTreeViewItem;
|
|
function SelectedSymbol: String;
|
|
function ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor<TArray<TDataPoint<TAskBidItem>>>): TState;
|
|
|
|
public
|
|
procedure NewWorkspace;
|
|
function CurrLayout<T: TControl>: T;
|
|
procedure AlignControl(Control: TControl);
|
|
published
|
|
property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
|
|
end;
|
|
|
|
var
|
|
Form1: TForm1;
|
|
|
|
implementation
|
|
|
|
uses
|
|
TestModule,
|
|
Myc.Trade.Indicators;
|
|
|
|
{$R *.fmx}
|
|
|
|
procedure TForm1.NewWorkspace;
|
|
begin
|
|
var ws: IAuraWorkspace := TMycAuraWorkspace.Create('New workspace', tmTesting);
|
|
FApplication.Workspaces.Insert(-1, ws);
|
|
|
|
var tab := TabControl.Add;
|
|
tab.Text := ws.Caption;
|
|
tab.Tag := NativeInt(ws);
|
|
|
|
var scrollbox := TVertScrollBox.Create(Self);
|
|
scrollbox.Parent := tab;
|
|
scrollbox.Align := TAlignLayout.Client;
|
|
|
|
tab.ProcessSignal(ws.Name.Changed, procedure begin tab.Text := ws.Caption; end);
|
|
end;
|
|
|
|
procedure TForm1.StopButtonClick(Sender: TObject);
|
|
begin
|
|
FTerminate.Notify;
|
|
end;
|
|
|
|
procedure TForm1.TreeViewDblClick(Sender: TObject);
|
|
begin
|
|
var sel := TreeView.Selected as TTreeViewItem;
|
|
|
|
var parent := sel.ParentItem;
|
|
if parent = nil then
|
|
exit;
|
|
|
|
if parent = FModulesItem then
|
|
begin
|
|
if (TabControl.ActiveTab <> nil) and (TabControl.ActiveTab.Tag <> 0) then
|
|
begin
|
|
var ws := IAuraWorkspace(TabControl.ActiveTab.Tag);
|
|
var module := FApplication.Modules[sel.TagString];
|
|
if (ws <> nil) and (module <> nil) then
|
|
module.SetupWorkspace(ws);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.FormCreate(Sender: TObject);
|
|
begin
|
|
FApplication := TMycAuraApplication.Create;
|
|
|
|
FModulesItem := TTreeViewItem.Create(Self);
|
|
FModulesItem.Text := 'Modules';
|
|
|
|
const modName = 'Test_Module';
|
|
|
|
FApplication.RegisterModule(modName, TTestModule.Create('Test-Module', 1));
|
|
|
|
TreeView.AddObject(FModulesItem);
|
|
FModulesItem.ProcessSignal(
|
|
FApplication.ModuleNames.Changed,
|
|
procedure
|
|
begin
|
|
FModulesItem.BeginUpdate;
|
|
try
|
|
while FModulesItem.Count > 0 do
|
|
FModulesItem[0].Free;
|
|
var mods := FApplication.ModuleNames.Value;
|
|
for var i := 0 to High(mods) do
|
|
begin
|
|
var ModItem := TTreeViewItem.Create(Self);
|
|
ModItem.Text := mods[i];
|
|
ModItem.DragMode := TDragMode.dmAutomatic;
|
|
ModItem.Text := 'Module-' + mods[i];
|
|
ModItem.TagString := mods[i];
|
|
FModulesItem.AddObject(ModItem);
|
|
end;
|
|
FModulesItem.ExpandAll;
|
|
finally
|
|
FModulesItem.EndUpdate;
|
|
end;
|
|
end
|
|
);
|
|
|
|
FTerminate := TEvent.CreateEvent;
|
|
|
|
// Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364]
|
|
FServer := TAuraTABFileServer.Create('\\COFFEE\TickData\Pepperstone');
|
|
|
|
SymbolsComboBox.Enabled := false;
|
|
ChartButton.Enabled := false;
|
|
LoadButton.Enabled := false;
|
|
|
|
FSymbols := FServer.EnumerateSymbols;
|
|
|
|
SymbolsComboBox.ProcessSignal(
|
|
FSymbols.Done.Signal,
|
|
procedure
|
|
begin
|
|
SymbolsComboBox.BeginUpdate;
|
|
try
|
|
SymbolsComboBox.Items.Clear;
|
|
SymbolsComboBox.Items.AddStrings(FSymbols.WaitFor);
|
|
if SymbolsComboBox.Items.Count > 0 then
|
|
begin
|
|
if SymbolsComboBox.ItemIndex < 0 then
|
|
SymbolsComboBox.ItemIndex := SymbolsComboBox.Items.IndexOf('GER40');
|
|
SymbolsComboBox.Enabled := true;
|
|
ChartButton.Enabled := true;
|
|
LoadButton.Enabled := true;
|
|
end;
|
|
finally
|
|
SymbolsComboBox.EndUpdate;
|
|
end;
|
|
end
|
|
);
|
|
|
|
NewWorkspace;
|
|
end;
|
|
|
|
procedure TForm1.FormDestroy(Sender: TObject);
|
|
begin
|
|
FSymbols.WaitFor;
|
|
FTerminate.Notify;
|
|
TaskManager.WaitFor(FProcessDone);
|
|
end;
|
|
|
|
procedure TForm1.TestActionExecute(Sender: TObject);
|
|
begin
|
|
TestPopup.IsOpen := TestAction.Checked;
|
|
end;
|
|
|
|
procedure TForm1.AddWorkspaceActionExecute(Sender: TObject);
|
|
begin
|
|
NewWorkspace;
|
|
end;
|
|
|
|
procedure TForm1.AlignControl(Control: TControl);
|
|
begin
|
|
var Layout := CurrLayout<TVertScrollBox>;
|
|
if Layout = nil then
|
|
exit;
|
|
|
|
Control.Parent := Layout;
|
|
Control.Width := Layout.Width;
|
|
Control.Position.Y := Layout.ChildrenRect.Bottom + 1;
|
|
Control.Anchors := [TAnchorKind.akLeft, TAnchorKind.akRight];
|
|
Control.Align := TAlignLayout.Top;
|
|
end;
|
|
|
|
function TForm1.CurrLayout<T>: T;
|
|
begin
|
|
if TabControl.ActiveTab = nil then
|
|
exit(nil);
|
|
|
|
var Res: T := nil;
|
|
TabControl.ActiveTab.EnumControls(
|
|
function(Control: TControl): TEnumControlsResult
|
|
begin
|
|
Result := TEnumControlsResult.Continue;
|
|
if Control is T then
|
|
begin
|
|
Res := Control as T;
|
|
Result := TEnumControlsResult.Stop;
|
|
end;
|
|
end
|
|
);
|
|
|
|
Result := Res;
|
|
end;
|
|
|
|
function TForm1.ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor<TArray<TDataPoint<TAskBidItem>>>): TState;
|
|
var
|
|
dataProvider: IMycConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>;
|
|
begin
|
|
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
|
|
|
|
dataProvider :=
|
|
TMycGenericConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>.Create(
|
|
function(const Values: TArray<TDataPoint<TAuraAskBidFileItem>>): TArray<TDataPoint<TAskBidItem>>
|
|
begin
|
|
SetLength(Result, Length(Values));
|
|
for var i := 0 to High(Result) do
|
|
begin
|
|
Result[i].Time := Values[i].Time;
|
|
Result[i].Data.Ask := Values[i].Data.Ask;
|
|
Result[i].Data.Bid := Values[i].Data.Bid;
|
|
end;
|
|
end
|
|
);
|
|
|
|
dataProvider.Sender.Link(Processor);
|
|
|
|
Result := FServer.ProcessData(Symbol, terminated, dataProvider);
|
|
end;
|
|
|
|
function TForm1.SelectedSymbol: String;
|
|
begin
|
|
Result := '';
|
|
if RandomBox.IsChecked then
|
|
Result := FSymbols.WaitFor[Random(Length(FSymbols.WaitFor))]
|
|
else if SymbolsComboBox.ItemIndex >= 0 then
|
|
Result := FSymbols.WaitFor[SymbolsComboBox.ItemIndex];
|
|
end;
|
|
|
|
procedure TForm1.StrategyButtonClick(Sender: TObject);
|
|
begin
|
|
var Layout := CurrLayout<TVertScrollBox>;
|
|
if Layout = nil then
|
|
exit;
|
|
|
|
var Symbol := SelectedSymbol;
|
|
if Symbol = '' then
|
|
exit;
|
|
|
|
var chart := TMycChart.Create(Self);
|
|
AlignControl(chart);
|
|
chart.Height := Layout.ChildrenRect.Width * 9 / 16;
|
|
chart.Lookback.Value := 50000;
|
|
|
|
/////
|
|
|
|
var timeframe := TTimeframe.S15;
|
|
|
|
var ticker := TTicker<TDataPoint<TAskBidItem>>.Create;
|
|
|
|
var OhlcPoint := TTicksToBars.Create(timeframe);
|
|
ticker.Sender.Link(OhlcPoint);
|
|
|
|
var Timestamps: IMycConverter<TDataPoint<TOhlcItem>, TDateTime> :=
|
|
TMycGenericConverter<TDataPoint<TOhlcItem>, TDateTime>
|
|
.Create(function(const Ohlc: TDataPoint<TOhlcItem>): TDateTime begin Result := Ohlc.Time; end);
|
|
|
|
OhlcPoint.Sender.Link(TimeStamps);
|
|
chart.SetXAxisSeries(timeframe, Timestamps.Sender);
|
|
|
|
var Panel := chart.AddPanel;
|
|
|
|
var Ohlc: IMycConverter<TDataPoint<TOhlcItem>, TOhlcItem> :=
|
|
TMycGenericConverter<TDataPoint<TOhlcItem>, TOhlcItem>
|
|
.Create(function(const Ohlc: TDataPoint<TOhlcItem>): TOhlcItem begin Result := Ohlc.Data; end);
|
|
|
|
OhlcPoint.Sender.Link(Ohlc);
|
|
Panel.AddOhlcSeries(Ohlc.Sender);
|
|
|
|
var Closes: IMycConverter<TOhlcItem, Double> :=
|
|
TMycGenericConverter<TOhlcItem, Double>.Create(function(const Ohlc: TOhlcItem): Double begin Result := Ohlc.Close; end);
|
|
|
|
Ohlc.Sender.Link(Closes);
|
|
|
|
var Hull: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateHMA(150));
|
|
Closes.Sender.Link(Hull);
|
|
Panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
|
|
|
|
// Add SMA (Simple Moving Average)
|
|
var Sma: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateSMA(50));
|
|
Closes.Sender.Link(Sma);
|
|
Panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
|
|
|
|
// Add EMA (Exponential Moving Average)
|
|
var Ema: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateEMA(21));
|
|
Closes.Sender.Link(Ema);
|
|
Panel.AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
|
|
|
|
// Add Bollinger Bands (20, 2.0)
|
|
var Boli: IMycConverter<Double, TBollingerBandsResult> :=
|
|
TGenericIndicator<Double, TBollingerBandsResult>.Create(TIndicators.CreateBollingerBands(20, 2.0));
|
|
Closes.Sender.Link(Boli);
|
|
|
|
var BoliUpper: IMycConverter<TBollingerBandsResult, Double> :=
|
|
TMycGenericConverter<TBollingerBandsResult, Double>
|
|
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.UpperBand; end);
|
|
Boli.Sender.Link(BoliUpper);
|
|
Panel.AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray);
|
|
|
|
var BoliMiddle: IMycConverter<TBollingerBandsResult, Double> :=
|
|
TMycGenericConverter<TBollingerBandsResult, Double>
|
|
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.MiddleBand; end);
|
|
Boli.Sender.Link(BoliMiddle);
|
|
Panel.AddDoubleSeries(BoliMiddle.Sender, TAlphaColors.Darkgray, 1.0);
|
|
|
|
var BoliLower: IMycConverter<TBollingerBandsResult, Double> :=
|
|
TMycGenericConverter<TBollingerBandsResult, Double>
|
|
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.LowerBand; end);
|
|
Boli.Sender.Link(BoliLower);
|
|
Panel.AddDoubleSeries(BoliLower.Sender, TAlphaColors.Gray);
|
|
|
|
Panel := chart.AddPanel;
|
|
|
|
// Add RSI (Relative Strength Index)
|
|
var Rsi: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateRSI(14));
|
|
Closes.Sender.Link(Rsi);
|
|
Panel.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
|
|
|
|
// Add MACD (12, 26, 9)
|
|
var Macd: IMycConverter<Double, TMacdResult> := TGenericIndicator<Double, TMacdResult>.Create(TIndicators.CreateMACD(12, 26, 9));
|
|
Closes.Sender.Link(Macd);
|
|
|
|
Panel := chart.AddPanel;
|
|
|
|
var MacdLine: IMycConverter<TMacdResult, Double> :=
|
|
TMycGenericConverter<TMacdResult, Double>.Create(function(const Item: TMacdResult): Double begin Result := Item.MacdLine; end);
|
|
Macd.Sender.Link(MacdLine);
|
|
Panel.AddDoubleSeries(MacdLine.Sender, TAlphaColors.Orange);
|
|
|
|
var MacdSignal: IMycConverter<TMacdResult, Double> :=
|
|
TMycGenericConverter<TMacdResult, Double>.Create(function(const Item: TMacdResult): Double begin Result := Item.SignalLine; end);
|
|
Macd.Sender.Link(MacdSignal);
|
|
Panel.AddDoubleSeries(MacdSignal.Sender, TAlphaColors.Dodgerblue);
|
|
|
|
var MacdHist: IMycConverter<TMacdResult, Double> :=
|
|
TMycGenericConverter<TMacdResult, Double>.Create(function(const Item: TMacdResult): Double begin Result := Item.Histogram; end);
|
|
Macd.Sender.Link(MacdHist);
|
|
Panel.AddDoubleSeries(MacdHist.Sender, TAlphaColors.Lightgreen, 1.0);
|
|
|
|
Panel := chart.AddPanel;
|
|
|
|
// Add Stochastic Oscillator (14, 3) - This needs OHLC data, not just Close prices.
|
|
var Stoch: IMycConverter<TOhlcItem, TStochasticResult> :=
|
|
TGenericIndicator<TOhlcItem, TStochasticResult>.Create(TIndicators.CreateStochastic(14, 3));
|
|
Ohlc.Sender.Link(Stoch);
|
|
|
|
var StochK: IMycConverter<TStochasticResult, Double> :=
|
|
TMycGenericConverter<TStochasticResult, Double>.Create(function(const Item: TStochasticResult): Double begin Result := Item.K; end);
|
|
Stoch.Sender.Link(StochK);
|
|
Panel.AddDoubleSeries(StochK.Sender, TAlphaColors.Green);
|
|
|
|
var StochD: IMycConverter<TStochasticResult, Double> :=
|
|
TMycGenericConverter<TStochasticResult, Double>.Create(function(const Item: TStochasticResult): Double begin Result := Item.D; end);
|
|
Stoch.Sender.Link(StochD);
|
|
Panel.AddDoubleSeries(StochD.Sender, TAlphaColors.Red);
|
|
|
|
/////
|
|
|
|
var tickChart := TMycChart.Create(Self);
|
|
tickChart.Height := Layout.ChildrenRect.Width * 9 / 16;
|
|
AlignControl(tickChart);
|
|
tickChart.Lookback.Value := 1000000;
|
|
|
|
var TickTime: IMycConverter<TDataPoint<TAskBidItem>, TDateTime> :=
|
|
TMycGenericConverter<TDataPoint<TAskBidItem>, TDateTime>
|
|
.Create(function(const Tick: TDataPoint<TAskBidItem>): TDateTime begin Result := Tick.Time; end);
|
|
|
|
var TickAsk: IMycConverter<TDataPoint<TAskBidItem>, Double> :=
|
|
TMycGenericConverter<TDataPoint<TAskBidItem>, Double>
|
|
.Create(function(const Tick: TDataPoint<TAskBidItem>): Double begin Result := Tick.Data.Ask; end);
|
|
|
|
var TickBid: IMycConverter<TDataPoint<TAskBidItem>, Double> :=
|
|
TMycGenericConverter<TDataPoint<TAskBidItem>, Double>
|
|
.Create(function(const Tick: TDataPoint<TAskBidItem>): Double begin Result := Tick.Data.Bid; end);
|
|
|
|
var TickSpread: IMycConverter<TDataPoint<TAskBidItem>, Double> :=
|
|
TMycGenericConverter<TDataPoint<TAskBidItem>, Double>
|
|
.Create(function(const Tick: TDataPoint<TAskBidItem>): Double begin Result := Tick.Data.Bid-Tick.Data.Ask; end);
|
|
|
|
ticker.Sender.Link( TickTime );
|
|
ticker.Sender.Link( TickAsk );
|
|
ticker.Sender.Link( TickBid );
|
|
ticker.Sender.Link( TickSpread );
|
|
|
|
tickChart.SetXAxisSeries( TTimeframe.S, TickTime.Sender );
|
|
panel := tickChart.AddPanel;
|
|
panel.AddDoubleSeries(TickAsk.Sender, TAlphaColors.Blue);
|
|
panel.AddDoubleSeries(TickBid.Sender, TAlphaColors.Red);
|
|
panel := tickChart.AddPanel;
|
|
panel.AddDoubleSeries(TickSpread.Sender);
|
|
|
|
/////
|
|
|
|
var done := ExecuteStrategy(Symbol, ticker);
|
|
FProcessDone := TState.All([FProcessDone, done]);
|
|
end;
|
|
|
|
end.
|