diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj
index b01b2ad..5c1bb79 100644
--- a/AuraTrader/AuraTrader.dproj
+++ b/AuraTrader/AuraTrader.dproj
@@ -4,7 +4,7 @@
20.3
FMX
True
- Debug
+ Release
Win64
AuraTrader
3
diff --git a/AuraTrader/FirstStrategy.pas b/AuraTrader/FirstStrategy.pas
index 16f8b47..35defbf 100644
--- a/AuraTrader/FirstStrategy.pas
+++ b/AuraTrader/FirstStrategy.pas
@@ -13,8 +13,6 @@ uses
Myc.Trade.DataArray;
type
- TTimeframe = (M1, M5, H1, D);
-
TMycGenericConverter = class(TMycConverter)
type
TConvertFunc = reference to function(const Value: S): T;
@@ -54,11 +52,11 @@ type
TGenericIndicator = class(TIndicator)
private
- FFunc: TFunc;
+ FFunc: TIndicatorFunc;
protected
function Calculate(const Value: S): T; override; final;
public
- constructor Create(const AFunc: TFunc);
+ constructor Create(const AFunc: TIndicatorFunc);
end;
implementation
@@ -76,14 +74,49 @@ begin
end;
function TTicksToTimeframe.GetBarStartTime(const TimeStamp: TDateTime; const Timeframe: TTimeframe): TDateTime;
+var
+ baseTime: TDateTime;
begin
// Align the time grid to UTC 0:00 using functions from System.DateUtils
+ baseTime := RecodeMilliSecond(TimeStamp, 0);
+
case Timeframe of
- M1: Result := RecodeSecond(RecodeMilliSecond(TimeStamp, 0), 0);
- M5: Result := RecodeMinute(RecodeSecond(RecodeMilliSecond(TimeStamp, 0), 0), MinuteOf(TimeStamp) - MinuteOf(TimeStamp) mod 5);
- H1: Result := RecodeMinute(RecodeSecond(RecodeMilliSecond(TimeStamp, 0), 0), 0);
+ S: Result := baseTime;
+ S5: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 5));
+ S15: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 15));
+ S30: Result := RecodeSecond(baseTime, SecondOf(TimeStamp) - (SecondOf(TimeStamp) mod 30));
+
+ M: Result := RecodeSecond(baseTime, 0);
+ M2: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 2));
+ M3: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 3));
+ M5: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 5));
+ M10: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 10));
+ M15: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 15));
+ M30: Result := RecodeMinute(RecodeSecond(baseTime, 0), MinuteOf(TimeStamp) - (MinuteOf(TimeStamp) mod 30));
+
+ H: Result := RecodeMinute(RecodeSecond(baseTime, 0), 0);
+ H2: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 2));
+ H3: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 3));
+ H4: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 4));
+ H8: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 8));
+ H12: Result := RecodeHour(RecodeMinute(RecodeSecond(baseTime, 0), 0), HourOf(TimeStamp) - (HourOf(TimeStamp) mod 12));
+
D: Result := StartOfTheDay(TimeStamp);
+ // D2, D3 are uncommon; this is a simple modulo-based approach relative to TDateTime's epoch.
+ D2: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 2);
+ D3: Result := Floor(TimeStamp) - (Floor(TimeStamp) mod 3);
+
+ W: Result := TimeStamp.StartOfTheWeek;
+
+ MN: Result := TimeStamp.StartOfTheMonth;
+ // Quarter alignment
+ MN3: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 3 * 3 + 1);
+ // Half-year alignment
+ MN6: Result := RecodeMonth(TimeStamp.StartOfTheMonth, (MonthOf(TimeStamp) - 1) div 6 * 6 + 1);
+
+ Y: Result := TimeStamp.StartOfTheYear;
else
+ // Fallback for any undefined timeframe
Result := 0;
end;
end;
@@ -122,7 +155,9 @@ begin
begin
// A new bar starts, so the previous one is now complete.
if (lastBarTime > 0) then
+ begin
states.Add(Broadcast(FCurrentBar));
+ end;
// Start a new bar, Volume is 1 because this is the first tick.
currentBar := TOhlcItem.Create(midPrice, midPrice, midPrice, midPrice, 1);
@@ -161,12 +196,16 @@ begin
Result := Broadcast(FFunc(Value));
end;
+{ TIndicator }
+
function TIndicator.ProcessData(const Value: S): TState;
begin
- Result := FQueue.Enqueue(function: TState begin Result := Broadcast(Calculate(Value)); end);
+ Result := Broadcast(Calculate(Value));
end;
-constructor TGenericIndicator.Create(const AFunc: TFunc);
+{ TGenericIndicator }
+
+constructor TGenericIndicator.Create(const AFunc: TIndicatorFunc);
begin
inherited Create;
FFunc := AFunc;
diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas
index 40b78f4..5409a9b 100644
--- a/AuraTrader/MainForm.pas
+++ b/AuraTrader/MainForm.pas
@@ -165,17 +165,25 @@ begin
/////
- var OhlcPoint: IMycConverter>, TDataPoint> := TTicksToTimeframe.Create(M1);
+ var timeframe := TTimeframe.S15;
+
+ var OhlcPoint: IMycConverter>, TDataPoint> := TTicksToTimeframe.Create(timeframe);
var Timestamps: IMycConverter, TDateTime> :=
TMycGenericConverter, TDateTime>
.Create(function(const Ohlc: TDataPoint): TDateTime begin Result := Ohlc.Time; end);
+ OhlcPoint.Sender.Link(TimeStamps);
+ chart.SetXAxisSeries(timeframe, Timestamps.Sender);
+
+ var Panel := chart.Panels[0];
+
var Ohlc: IMycConverter, TOhlcItem> :=
TMycGenericConverter, TOhlcItem>
.Create(function(const Ohlc: TDataPoint): TOhlcItem begin Result := Ohlc.Data; end);
OhlcPoint.Sender.Link(Ohlc);
+ Panel.AddOhlcSeries(Ohlc.Sender);
var Closes: IMycConverter :=
TMycGenericConverter.Create(function(const Ohlc: TOhlcItem): Double begin Result := Ohlc.Close; end);
@@ -184,17 +192,17 @@ begin
var Hull: IMycConverter := TGenericIndicator.Create(TIndicators.CreateHMA(150));
Closes.Sender.Link(Hull);
- chart.Panels[0].AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
+ Panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
// Add SMA (Simple Moving Average)
var Sma: IMycConverter := TGenericIndicator.Create(TIndicators.CreateSMA(50));
Closes.Sender.Link(Sma);
- chart.Panels[0].AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
+ Panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
// Add EMA (Exponential Moving Average)
var Ema: IMycConverter := TGenericIndicator.Create(TIndicators.CreateEMA(21));
Closes.Sender.Link(Ema);
- chart.Panels[0].AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
+ Panel.AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
// Add Bollinger Bands (20, 2.0)
var Boli: IMycConverter :=
@@ -205,59 +213,64 @@ begin
TMycGenericConverter
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.UpperBand; end);
Boli.Sender.Link(BoliUpper);
- chart.Panels[0].AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray);
+ Panel.AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray);
var BoliMiddle: IMycConverter :=
TMycGenericConverter
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.MiddleBand; end);
Boli.Sender.Link(BoliMiddle);
- chart.Panels[0].AddDoubleSeries(BoliMiddle.Sender, TAlphaColors.Darkgray, 1.0);
+ Panel.AddDoubleSeries(BoliMiddle.Sender, TAlphaColors.Darkgray, 1.0);
var BoliLower: IMycConverter :=
TMycGenericConverter
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.LowerBand; end);
Boli.Sender.Link(BoliLower);
- chart.Panels[0].AddDoubleSeries(BoliLower.Sender, TAlphaColors.Gray);
+ Panel.AddDoubleSeries(BoliLower.Sender, TAlphaColors.Gray);
- chart.AddPanel;
+ Panel := chart.AddPanel;
// Add RSI (Relative Strength Index)
var Rsi: IMycConverter := TGenericIndicator.Create(TIndicators.CreateRSI(14));
Closes.Sender.Link(Rsi);
- chart.Panels[1].AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
- {
+ Panel.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
+
// Add MACD (12, 26, 9)
var Macd: IMycConverter := TGenericIndicator.Create(TIndicators.CreateMACD(12, 26, 9));
Closes.Sender.Link(Macd);
- var MacdLine: IMycConverter := TMycGenericConverter.Create(function(const Item: TMacdResult): Double begin Result := Item.MacdLine; end);
+ Panel := chart.AddPanel;
+
+ var MacdLine: IMycConverter :=
+ TMycGenericConverter.Create(function(const Item: TMacdResult): Double begin Result := Item.MacdLine; end);
Macd.Sender.Link(MacdLine);
- chart.AddDoubleSeries(MacdLine.Sender, TAlphaColors.Orange);
+ Panel.AddDoubleSeries(MacdLine.Sender, TAlphaColors.Orange);
- var MacdSignal: IMycConverter := TMycGenericConverter.Create(function(const Item: TMacdResult): Double begin Result := Item.SignalLine; end);
+ var MacdSignal: IMycConverter :=
+ TMycGenericConverter.Create(function(const Item: TMacdResult): Double begin Result := Item.SignalLine; end);
Macd.Sender.Link(MacdSignal);
- chart.AddDoubleSeries(MacdSignal.Sender, TAlphaColors.Dodgerblue);
+ Panel.AddDoubleSeries(MacdSignal.Sender, TAlphaColors.Dodgerblue);
- var MacdHist: IMycConverter := TMycGenericConverter.Create(function(const Item: TMacdResult): Double begin Result := Item.Histogram; end);
+ var MacdHist: IMycConverter :=
+ TMycGenericConverter.Create(function(const Item: TMacdResult): Double begin Result := Item.Histogram; end);
Macd.Sender.Link(MacdHist);
- chart.AddDoubleSeries(MacdHist.Sender, TAlphaColors.Lightgreen, 1.0);
+ 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 := TGenericIndicator.Create(TIndicators.CreateStochastic(14, 3));
+ var Stoch: IMycConverter :=
+ TGenericIndicator.Create(TIndicators.CreateStochastic(14, 3));
Ohlc.Sender.Link(Stoch);
- var StochK: IMycConverter := TMycGenericConverter.Create(function(const Item: TStochasticResult): Double begin Result := Item.K; end);
+ var StochK: IMycConverter :=
+ TMycGenericConverter.Create(function(const Item: TStochasticResult): Double begin Result := Item.K; end);
Stoch.Sender.Link(StochK);
- chart.AddDoubleSeries(StochK.Sender, TAlphaColors.Green);
+ Panel.AddDoubleSeries(StochK.Sender, TAlphaColors.Green);
- var StochD: IMycConverter := TMycGenericConverter.Create(function(const Item: TStochasticResult): Double begin Result := Item.D; end);
+ var StochD: IMycConverter :=
+ TMycGenericConverter.Create(function(const Item: TStochasticResult): Double begin Result := Item.D; end);
Stoch.Sender.Link(StochD);
- chart.AddDoubleSeries(StochD.Sender, TAlphaColors.Red);
-}
- OhlcPoint.Sender.Link(TimeStamps);
-
- chart.SetXAxisSeries(Timestamps.Sender);
- chart.Panels[0].AddOhlcSeries(Ohlc.Sender);
+ Panel.AddDoubleSeries(StochD.Sender, TAlphaColors.Red);
var done := ExecuteStrategy(Symbol, OhlcPoint);
diff --git a/Src/Myc.Core.Notifier.pas b/Src/Myc.Core.Notifier.pas
index 4401406..789ca37 100644
--- a/Src/Myc.Core.Notifier.pas
+++ b/Src/Myc.Core.Notifier.pas
@@ -130,11 +130,56 @@ end;
procedure TMycNotifyList.Notify(const Func: TNotifyProc);
var
Item: PItem;
- Last: PItem;
- tmp: PItem;
begin
Assert(IsLocked);
+ var rev: PItem := nil;
+ var tmp: PItem := nil;
+
+ while (FList <> nil) and Assigned(FList.Receiver) do
+ begin
+ Item := FList;
+ FList := Item.Next;
+
+ if Func(Item.Receiver) then
+ begin
+ Item.Next := rev;
+ rev := Item;
+ end
+ else
+ begin
+ // release the receiver
+ Item.Receiver := nil;
+
+ Item.Next := tmp;
+ tmp := Item;
+ end;
+ end;
+
+ while tmp <> nil do
+ begin
+ Item := tmp;
+ tmp := Item.Next;
+
+ Item.Prev := nil;
+ Item.Next := FList;
+ FList := Item;
+ if Item.Next <> nil then
+ Item.Next.Prev := Item;
+ end;
+
+ while rev <> nil do
+ begin
+ Item := rev;
+ rev := Item.Next;
+
+ Item.Prev := nil;
+ Item.Next := FList;
+ FList := Item;
+ if Item.Next <> nil then
+ Item.Next.Prev := Item;
+ end;
+ {
tmp := nil;
Last := nil;
Item := FList;
@@ -189,6 +234,7 @@ begin
if Item.Next <> nil then
Item.Next.Prev := Item;
end;
+ }
end;
procedure TMycNotifyList.Release;
diff --git a/Src/Myc.Fmx.Chart.Series.pas b/Src/Myc.Fmx.Chart.Series.pas
index 3dbfe24..3f97bba 100644
--- a/Src/Myc.Fmx.Chart.Series.pas
+++ b/Src/Myc.Fmx.Chart.Series.pas
@@ -1,4 +1,4 @@
-unit Myc.FMX.Chart.Series;
+unit Myc.Fmx.Chart.Series;
interface
@@ -58,14 +58,14 @@ type
end;
{ TChartCustomLayer }
- TChartCustomLayer = class(TMycChart.TLayer)
+ TChartCustomLayer = class abstract(TMycChart.TDataLayer)
private
FSeries: TChartSeriesProcessor;
protected
function GetSeries: TMycChart.TSeries; override;
- function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
+ function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override; abstract;
procedure Update; override;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
+ procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override; abstract;
public
constructor Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider);
destructor Destroy; override;
@@ -104,6 +104,30 @@ type
);
end;
+ { TChartXAxisLayer }
+ TChartXAxisLayer = class(TMycChart.TXAxisLayer)
+ private
+ FSeries: TChartSeriesProcessor;
+ protected
+ function GetSeries: TMycChart.TSeries; override;
+ procedure Update; override;
+ function GetCaption(Idx: Int64): String; override; abstract;
+ public
+ constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
+ destructor Destroy; override;
+ end;
+
+ { TChartXAxisTimestampLayer }
+ TChartXAxisTimestampLayer = class(TChartXAxisLayer)
+ private
+ FTimeframe: TTimeframe;
+ protected
+ // Provide a formatted timestamp string for a given data index.
+ function GetCaption(Idx: Int64): String; override; final;
+ public
+ constructor Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: IMycDataProvider);
+ end;
+
implementation
uses
@@ -320,19 +344,61 @@ begin
Result := FSeries;
end;
-function TChartCustomLayer.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
-begin
- Result := false;
-end;
-
-procedure TChartCustomLayer.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc);
-begin
- // nope
-end;
-
procedure TChartCustomLayer.Update;
begin
FSeries.Update;
end;
+{ TChartXAxisLayer }
+
+constructor TChartXAxisLayer.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
+begin
+ inherited Create(AOwner);
+ FSeries := TChartSeriesProcessor.Create(ADataProvider, AOwner.Lookback.AsMutable);
+end;
+
+destructor TChartXAxisLayer.Destroy;
+begin
+ FSeries.Free;
+ inherited;
+end;
+
+function TChartXAxisLayer.GetSeries: TMycChart.TSeries;
+begin
+ Result := FSeries;
+end;
+
+procedure TChartXAxisLayer.Update;
+begin
+ FSeries.Update;
+end;
+
+constructor TChartXAxisTimestampLayer.Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: IMycDataProvider);
+begin
+ inherited Create(AOwner, ADataProvider);
+ FTimeframe := ATimeframe;
+end;
+
+function TChartXAxisTimestampLayer.GetCaption(Idx: Int64): String;
+var
+ dt: TDateTime;
+ formatStr: string;
+begin
+ Result := '';
+ if (Idx >= 0) and (Idx < FSeries.Count) then
+ begin
+ dt := FSeries.Data[Idx];
+
+ // Choose format based on the series timeframe.
+ if FTimeframe >= D then // Daily, Weekly, Monthly, Yearly
+ formatStr := 'dd.mm.yyyy'
+ else if FTimeframe >= M then // Hourly + Minutes
+ formatStr := 'dd.mm.yyyy hh:nn'
+ else // Seconds
+ formatStr := 'dd.mm.yyyy hh:nn:ss';
+
+ Result := FormatDateTime(formatStr, dt);
+ end;
+end;
+
end.
diff --git a/Src/Myc.Fmx.Chart.pas b/Src/Myc.Fmx.Chart.pas
index 30d432f..35c1350 100644
--- a/Src/Myc.Fmx.Chart.pas
+++ b/Src/Myc.Fmx.Chart.pas
@@ -38,29 +38,66 @@ type
// Abstract base for a drawable layer in a panel.
TLayer = class abstract(TObject)
- private
- FParent: TPanel;
- function GetOwner: TMycChart;
protected
+ function GetOwner: TMycChart; virtual; abstract;
function GetSeries: TSeries; virtual; abstract;
procedure Update; virtual; abstract;
- function GetValueRange(First, Last: Int64; out Min, Max: Double): Boolean; virtual; abstract;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); virtual; abstract;
public
- constructor Create(AOwner: TPanel);
procedure Repaint;
property Owner: TMycChart read GetOwner;
property Series: TSeries read GetSeries;
end;
+ TDataLayer = class abstract(TLayer)
+ private
+ FParent: TPanel;
+ protected
+ function GetOwner: TMycChart; override; final;
+ function GetValueRange(First, Last: Int64; out Min, Max: Double): Boolean; virtual; abstract;
+ procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); virtual; abstract;
+ public
+ constructor Create(AParent: TPanel);
+ end;
+
+ TAxisLayer = class(TLayer)
+ private
+ FOwner: TMycChart;
+ protected
+ function GetOwner: TMycChart; override; final;
+ // Paint crosshair and other axis related overlays
+ procedure Paint(
+ const Canvas: TCanvas;
+ const Viewport: TRectF;
+ ViewStartIndex, ViewCount: Int64;
+ const MousePos: TPointF
+ ); virtual;
+ public
+ constructor Create(AOwner: TMycChart);
+ end;
+
+ TXAxisLayer = class(TAxisLayer)
+ protected
+ // Paint crosshair and time caption
+ procedure Paint(
+ const Canvas: TCanvas;
+ const Viewport: TRectF;
+ ViewStartIndex, ViewCount: Int64;
+ const MousePos: TPointF
+ ); override;
+ // This function delivers the text for the caption.
+ function GetCaption(Idx: Int64): String; virtual; abstract;
+ end;
+
// A panel is a rectangular area in the chart with its own Y-axis, which can contain multiple layers.
TPanel = class(TObject)
private
FOwner: TMycChart;
- FSeriesList: TObjectList;
+ FSeriesList: TObjectList;
+ FWeight: Single;
+ procedure SetWeight(const Value: Single);
protected
procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; MasterTotalCount: Int64; ViewStartIndex, ViewCount: Int64);
- procedure UpdateAndCheckChanges(MasterTotalCount: Int64; ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
+ procedure Update(MasterTotalCount: Int64; ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
public
constructor Create(AOwner: TMycChart);
@@ -70,35 +107,42 @@ type
const DataProvider: IMycDataProvider;
const AUpColor: TAlphaColor = TAlphaColors.Green;
const ADownColor: TAlphaColor = TAlphaColors.Red
- ): TMycChart.TLayer;
+ ): TMycChart.TDataLayer;
function AddDoubleSeries(
const DataProvider: IMycDataProvider;
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
const ALineWidth: Single = 1.5
- ): TMycChart.TLayer;
+ ): TMycChart.TDataLayer;
property Owner: TMycChart read FOwner;
+ property Weight: Single read FWeight write SetWeight;
end;
private
FPanelList: TObjectList;
- FXAxisSeries: TMycChart.TLayer;
+ FXAxisSeries: TMycChart.TXAxisLayer;
FLookback: TWriteable;
FIdleSubscrId: TMessageSubscriptionId;
FViewStartIndex: Int64;
FViewCount: Int64;
FIsDragging: Boolean;
FDragStartPoint: TPointF;
+ FMousePos: TPointF;
+ FIsMouseInControl: Boolean;
FJumpButtonRect: TRectF;
FJumpButtonHot: Boolean;
FJumpButtonPressed: Boolean;
FNeedRepaint: TFlag;
+ FIsResizing: Boolean;
+ FResizePanelIndex: Integer;
+ FHotResizePanelIndex: Integer;
function GetPanel(Index: Integer): TPanel;
function GetPanelCount: Integer;
protected
procedure Paint; override;
procedure DoIdle;
+ procedure DoMouseLeave; override;
procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
procedure MouseMove(Shift: TShiftState; X, Y: Single); override;
@@ -110,13 +154,15 @@ type
// Adds a new, empty panel to the bottom of the chart.
function AddPanel: TPanel;
- // Sets the master series that defines the time scale (X-axis). This series is not drawn.
- function SetXAxisSeries(const DataProvider: IMycDataProvider): TMycChart.TLayer;
+ // Sets the master series that defines the time scale (X-axis).
+ function SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: IMycDataProvider): TMycChart.TXAxisLayer;
property Lookback: TWriteable read FLookback write FLookback;
property NeedRepaint: TFlag read FNeedRepaint;
property PanelCount: Integer read GetPanelCount;
property Panels[Index: Integer]: TPanel read GetPanel; default;
+ // The rectangle occupied by the jump-to-latest button, used for hit testing.
+ property JumpButtonRect: TRectF read FJumpButtonRect;
end;
implementation
@@ -125,6 +171,11 @@ uses
System.Math,
Myc.FMX.Chart.Series;
+const
+ RESIZE_HOT_ZONE = 4;
+ MIN_PANEL_HEIGHT = 30;
+ MIN_PANEL_WEIGHT = 0.05;
+
{ TMycChart }
constructor TMycChart.Create(AOwner: TComponent);
@@ -136,7 +187,14 @@ begin
FLookback := TWriteable.CreateWriteable(1000);
FViewStartIndex := 0;
FViewCount := 100;
+
FIsDragging := false;
+ FIsResizing := false;
+ FResizePanelIndex := -1;
+ FHotResizePanelIndex := -1;
+ FIsMouseInControl := false;
+
+ FNeedRepaint := TFlag.CreateFlag();
FIdleSubscrId :=
TMessageManager
@@ -144,7 +202,7 @@ begin
.SubscribeToMessage(TIdleMessage, procedure(const Sender: TObject; const M: TMessage) begin DoIdle; end);
// Create the default panel.
- AddPanel;
+ AddPanel.Weight := 1.0;
end;
destructor TMycChart.Destroy;
@@ -173,16 +231,6 @@ begin
Result := FPanelList.Count;
end;
-function TMycChart.SetXAxisSeries(const DataProvider: IMycDataProvider): TMycChart.TLayer;
-begin
- FXAxisSeries.Free;
- // Note: TChartCustomLayer needs its owner to be a TPanel. We use the first panel.
- var counter := TChartSeriesCounter.Create;
- DataProvider.Link(counter);
- FXAxisSeries := TChartCustomLayer.Create(Self.Panels[0], counter.Sender);
- Result := FXAxisSeries;
-end;
-
procedure TMycChart.DoIdle;
var
prevTotalCount, newTotalCount: Int64;
@@ -202,11 +250,12 @@ begin
for var panel in FPanelList do
begin
var panelChanged, panelRepaint: Boolean;
- panel.UpdateAndCheckChanges(prevTotalCount, FViewStartIndex, panelChanged, panelRepaint);
+ panel.Update(prevTotalCount, FViewStartIndex, panelChanged, panelRepaint);
seriesChanged := seriesChanged or panelChanged;
seriesRepaint := seriesRepaint or panelRepaint;
end;
+ // When we are not displaying live data, adjust start index accordingly
if (FViewStartIndex > 0) then
begin
countDelta := newTotalCount - prevTotalCount;
@@ -224,6 +273,16 @@ begin
Repaint;
end;
+procedure TMycChart.DoMouseLeave;
+begin
+ inherited;
+ if FIsMouseInControl then
+ begin
+ FIsMouseInControl := false;
+ Repaint;
+ end;
+end;
+
procedure TMycChart.Paint;
var
rect, panelRect: TRectF;
@@ -231,7 +290,7 @@ var
buttonColor: TAlphaColor;
path: TPathData;
masterTotalCount: Int64;
- panelHeight: Single;
+ panelHeight, totalWeight, currentY: Single;
begin
inherited;
rect := Self.LocalRect;
@@ -244,12 +303,20 @@ begin
end;
masterTotalCount := FXAxisSeries.Series.TotalCount;
- panelHeight := rect.Height / PanelCount;
+ // Calculate total weight for proportional panel height
+ totalWeight := 0;
+ for var panel in FPanelList do
+ totalWeight := totalWeight + panel.Weight;
+ if (totalWeight <= 0) then
+ totalWeight := PanelCount; // Fallback to equal distribution
+
+ currentY := rect.Top;
for var i := 0 to PanelCount - 1 do
begin
var panel := Panels[i];
- panelRect := TRectF.Create(rect.Left, rect.Top + (i * panelHeight), rect.Right, rect.Top + ((i + 1) * panelHeight));
+ panelHeight := (panel.Weight / totalWeight) * rect.Height;
+ panelRect := TRectF.Create(rect.Left, currentY, rect.Right, currentY + panelHeight);
// Draw panel content
panel.Paint(Self.Canvas, panelRect, masterTotalCount, FViewStartIndex, FViewCount);
@@ -261,8 +328,14 @@ begin
Canvas.Stroke.Thickness := 1;
Canvas.DrawLine(PointF(panelRect.Left, panelRect.Top), PointF(panelRect.Right, panelRect.Top), 1);
end;
+
+ currentY := currentY + panelHeight;
end;
+ // Draw crosshair if mouse is in control
+ if FIsMouseInControl and Assigned(FXAxisSeries) and not FIsDragging and not FIsResizing then
+ FXAxisSeries.Paint(Self.Canvas, rect, FViewStartIndex, FViewCount, FMousePos);
+
// --- Draw the "jump to latest" button (code unchanged) ---
isButtonVisible := (FViewStartIndex > 0);
@@ -312,7 +385,17 @@ begin
begin
FJumpButtonPressed := true;
Repaint;
- exit; // Prevent chart dragging
+ exit;
+ end;
+
+ // Check for starting a resize
+ if (Button = TMouseButton.mbLeft) and (FHotResizePanelIndex >= 0) then
+ begin
+ FIsResizing := true;
+ FResizePanelIndex := FHotResizePanelIndex;
+ FDragStartPoint := TPointF.Create(X, Y);
+ Capture;
+ exit;
end;
inherited MouseDown(Button, Shift, X, Y);
@@ -320,61 +403,111 @@ begin
begin
FIsDragging := true;
FDragStartPoint := TPointF.Create(X, Y);
+ Capture;
end;
end;
procedure TMycChart.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
- // Check if a button press was active
- if FJumpButtonPressed then
- begin
- FJumpButtonPressed := false;
- // If mouse is released over the button, trigger the action
- if (FViewStartIndex > 0) and FJumpButtonRect.Contains(PointF(X, Y)) then
- begin
- FViewStartIndex := 0; // Jump to the latest candle
- end;
- Repaint;
- exit;
- end;
-
inherited MouseUp(Button, Shift, X, Y);
+
if (Button = TMouseButton.mbLeft) then
begin
- FIsDragging := false;
+ // Stop resizing
+ if FIsResizing then
+ begin
+ FIsResizing := false;
+ FResizePanelIndex := -1;
+ // Force cursor update on next move by resetting hot index
+ if (FHotResizePanelIndex <> -1) then
+ begin
+ FHotResizePanelIndex := -1;
+ Self.Cursor := crDefault;
+ end;
+ end;
+
+ // Stop panning
+ if FIsDragging then
+ begin
+ FIsDragging := false;
+ end;
+
+ // Stop button press
+ if FJumpButtonPressed then
+ begin
+ FJumpButtonPressed := false;
+ // If mouse is released over the button, trigger the action
+ if (FViewStartIndex > 0) and FJumpButtonRect.Contains(PointF(X, Y)) then
+ begin
+ FViewStartIndex := 0; // Jump to the latest candle
+ end;
+ Repaint;
+ end;
+
+ // Release mouse capture if it was held by this control
+ ReleaseCapture;
end;
end;
procedure TMycChart.MouseMove(Shift: TShiftState; X, Y: Single);
var
- isHot: Boolean;
- dx: Single;
+ dx, dy: Single;
indexDelta: Int64;
maxIndex: Int64;
begin
- // Update button hot state
- if (FViewStartIndex > 0) then
- begin
- isHot := FJumpButtonRect.Contains(PointF(X, Y));
- if (isHot <> FJumpButtonHot) then
- begin
- FJumpButtonHot := isHot;
- Repaint;
- end;
- end
- else
- begin
- // Ensure hot state is off when button is not visible
- if FJumpButtonHot then
- begin
- FJumpButtonHot := false;
- Repaint;
- end;
- end;
-
- // Panning logic
inherited MouseMove(Shift, X, Y);
+ if not FIsMouseInControl then
+ FIsMouseInControl := true;
+ FMousePos := TPointF.Create(X, Y);
+
+ // --- Panel Resizing Logic ---
+ if FIsResizing then
+ begin
+ dy := Y - FDragStartPoint.Y;
+
+ var panel1 := Panels[FResizePanelIndex];
+ var panel2 := Panels[FResizePanelIndex + 1];
+
+ // Get total weight and height of the two panels being resized
+ var totalWeight := 0.0;
+ for var panel in FPanelList do
+ totalWeight := totalWeight + panel.Weight;
+ if (totalWeight <= 0) then
+ totalWeight := PanelCount;
+
+ var h1 := (panel1.Weight / totalWeight) * Self.Height;
+ var h2 := (panel2.Weight / totalWeight) * Self.Height;
+ var twoPanelWeight := panel1.Weight + panel2.Weight;
+
+ var newH1 := h1 + dy;
+ var newH2 := h2 - dy;
+
+ // Clamp to minimum height
+ if (newH1 < MIN_PANEL_HEIGHT) then
+ begin
+ newH2 := newH2 + (newH1 - MIN_PANEL_HEIGHT);
+ newH1 := MIN_PANEL_HEIGHT;
+ end;
+ if (newH2 < MIN_PANEL_HEIGHT) then
+ begin
+ newH1 := newH1 + (newH2 - MIN_PANEL_HEIGHT);
+ newH2 := MIN_PANEL_HEIGHT;
+ end;
+
+ // Recalculate weights based on new clamped heights
+ if (newH1 + newH2 > 0) then
+ begin
+ panel1.Weight := (newH1 / (newH1 + newH2)) * twoPanelWeight;
+ panel2.Weight := (newH2 / (newH1 + newH2)) * twoPanelWeight;
+ end;
+
+ FDragStartPoint.Y := Y; // Update start point for next move
+ Repaint;
+ exit; // Do not continue with other mouse move logic
+ end;
+
+ // --- Chart Panning Logic ---
if FIsDragging then
begin
dx := X - FDragStartPoint.X;
@@ -383,24 +516,16 @@ begin
exit;
end;
- // An X-axis series is required for panning
if not Assigned(FXAxisSeries) or (FViewCount <= 0) then
- begin
exit;
- end;
indexDelta := Round(dx / (Self.Width / FViewCount));
-
- // To move content left (natural), view must shift to newer data (lower index).
- // Drag left (dx < 0) -> FViewStartIndex must decrease.
- // The formula for that is FViewStartIndex := FViewStartIndex + indexDelta; -> StartIndex + (negative) = decrease.
FViewStartIndex := FViewStartIndex + indexDelta;
// Clamp values
maxIndex := FXAxisSeries.Series.Count - FViewCount;
if (maxIndex < 0) then
maxIndex := 0;
-
if (FViewStartIndex < 0) then
FViewStartIndex := 0;
if (FViewStartIndex > maxIndex) then
@@ -408,9 +533,43 @@ begin
FDragStartPoint.X := X;
FDragStartPoint.Y := Y;
-
Repaint;
+ exit;
end;
+
+ // --- Separator Hit-Testing and Cursor Change ---
+ var currentHotIndex := -1;
+ if (PanelCount > 1) then
+ begin
+ var totalWeight := 0.0;
+ for var panel in FPanelList do
+ totalWeight := totalWeight + panel.Weight;
+ if (totalWeight <= 0) then
+ totalWeight := PanelCount;
+
+ var currentY: Single := 0;
+ for var i := 0 to PanelCount - 2 do
+ begin
+ currentY := currentY + (Panels[i].Weight / totalWeight) * Self.Height;
+ if (Abs(Y - currentY) < RESIZE_HOT_ZONE) then
+ begin
+ currentHotIndex := i;
+ break;
+ end;
+ end;
+ end;
+
+ if (currentHotIndex <> FHotResizePanelIndex) then
+ begin
+ FHotResizePanelIndex := currentHotIndex;
+ if (FHotResizePanelIndex <> -1) then
+ Self.Cursor := crVSplit
+ else
+ Self.Cursor := crDefault;
+ end;
+
+ // --- Jump Button Hot State Logic and final repaint for crosshair ---
+ Repaint;
end;
procedure TMycChart.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
@@ -471,22 +630,17 @@ begin
Repaint;
end;
-{ TMycChart.TLayer }
-
-constructor TMycChart.TLayer.Create(AOwner: TPanel);
+function TMycChart.SetXAxisSeries(Timeframe: TTimeframe; const DataProvider: IMycDataProvider): TMycChart.TXAxisLayer;
begin
- inherited Create;
- FParent := AOwner;
-end;
+ FXAxisSeries.Free;
-function TMycChart.TLayer.GetOwner: TMycChart;
-begin
- Result := FParent.Owner;
+ FXAxisSeries := TChartXAxisTimestampLayer.Create(Self, Timeframe, DataProvider);
+ Result := FXAxisSeries;
end;
procedure TMycChart.TLayer.Repaint;
begin
- FParent.Owner.Repaint;
+ Owner.Repaint;
end;
{ TMycChart.TPanel }
@@ -495,7 +649,8 @@ constructor TMycChart.TPanel.Create(AOwner: TMycChart);
begin
inherited Create;
FOwner := AOwner;
- FSeriesList := TObjectList.Create(true);
+ FSeriesList := TObjectList.Create(true);
+ FWeight := 0.2;
end;
destructor TMycChart.TPanel.Destroy;
@@ -506,9 +661,9 @@ end;
function TMycChart.TPanel.AddDoubleSeries(
const DataProvider: IMycDataProvider;
- const ALineColor: TAlphaColor;
- const ALineWidth: Single
-): TMycChart.TLayer;
+ const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
+ const ALineWidth: Single = 1.5
+): TMycChart.TDataLayer;
begin
Result := TChartLineLayer.Create(Self, DataProvider, ALineColor, ALineWidth);
FSeriesList.Add(Result);
@@ -516,8 +671,9 @@ end;
function TMycChart.TPanel.AddOhlcSeries(
const DataProvider: IMycDataProvider;
- const AUpColor, ADownColor: TAlphaColor
-): TMycChart.TLayer;
+ const AUpColor: TAlphaColor = TAlphaColors.Green;
+ const ADownColor: TAlphaColor = TAlphaColors.Red
+): TMycChart.TDataLayer;
begin
Result :=
TChartOhlcLayer.Create(Self, DataProvider, TMutable.Constant(AUpColor), TMutable.Constant(ADownColor));
@@ -603,7 +759,20 @@ begin
end;
end;
-procedure TMycChart.TPanel.UpdateAndCheckChanges(MasterTotalCount, ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
+procedure TMycChart.TPanel.SetWeight(const Value: Single);
+begin
+ // Ensure weight does not become too small or negative
+ var clampedValue := Max(MIN_PANEL_WEIGHT, Value);
+ if FWeight <> clampedValue then
+ begin
+ FWeight := clampedValue;
+ // Do not call Repaint here, it will be handled by the mouse move logic
+ if Assigned(FOwner) and (not FOwner.FIsResizing) then
+ FOwner.Repaint;
+ end;
+end;
+
+procedure TMycChart.TPanel.Update(MasterTotalCount: Int64; ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
var
seriesCount: Int64;
begin
@@ -623,4 +792,121 @@ begin
end;
end;
+{ TMycChart.TDataLayer }
+
+constructor TMycChart.TDataLayer.Create(AParent: TPanel);
+begin
+ inherited Create;
+ FParent := AParent;
+end;
+
+function TMycChart.TDataLayer.GetOwner: TMycChart;
+begin
+ Result := FParent.Owner;
+end;
+
+{ TMycChart.TAxisLayer }
+
+constructor TMycChart.TAxisLayer.Create(AOwner: TMycChart);
+begin
+ inherited Create;
+ FOwner := AOwner;
+end;
+
+function TMycChart.TAxisLayer.GetOwner: TMycChart;
+begin
+ Result := FOwner;
+end;
+
+procedure TMycChart.TAxisLayer.Paint(
+ const Canvas: TCanvas;
+ const Viewport: TRectF;
+ ViewStartIndex, ViewCount: Int64;
+ const MousePos: TPointF
+);
+begin
+ // Do nothing in base class
+end;
+
+{ TMycChart.TXAxisLayer }
+
+procedure TMycChart.TXAxisLayer.Paint(
+ const Canvas: TCanvas;
+ const Viewport: TRectF;
+ ViewStartIndex, ViewCount: Int64;
+ const MousePos: TPointF
+);
+var
+ idx: Int64;
+ caption: string;
+ captionRect: TRectF;
+ padding: Single;
+ indexFromMouse: Double;
+ candleX: Single;
+ textSize: TRectF;
+begin
+ // Do not draw crosshair if mouse is over the jump button
+ if Owner.JumpButtonRect.Contains(MousePos) then
+ exit;
+
+ // Do not draw if no data or view is invalid
+ if (not Assigned(Series)) or (Series.Count = 0) or (ViewCount <= 1) then
+ exit;
+
+ // 1. Calculate the data index from the mouse X position
+ indexFromMouse := ViewStartIndex + (Viewport.Right - MousePos.X) * (ViewCount - 1) / Viewport.Width;
+ idx := Round(indexFromMouse);
+
+ // Check if the calculated index is valid for the series
+ if (idx < 0) or (idx >= Series.Count) then
+ exit;
+
+ // Snap X to the candle's center
+ candleX := Viewport.Right - ((idx - ViewStartIndex) * (ViewCount - 1)) / (ViewCount - 1) * (Viewport.Width / (ViewCount - 1));
+
+ // 2. Draw the vertical line at the snapped position
+ Canvas.Stroke.Kind := TBrushKind.Solid;
+ Canvas.Stroke.Color := TAlphaColors.Gray;
+ Canvas.Stroke.Thickness := 1;
+ Canvas.DrawLine(PointF(candleX, Viewport.Top), PointF(candleX, Viewport.Bottom), 1.0);
+
+ // 3. Get the caption for the index
+ caption := GetCaption(idx);
+ if caption.IsEmpty then
+ exit;
+
+ // 4. Draw the caption with a background
+ padding := 1;
+ Canvas.Font.Size := 9;
+
+ // Measure text size
+ textSize := TRectF.Create(0, 0, 10000, 10000);
+ Canvas.MeasureText(textSize, caption, False, [], TTextAlign.Leading);
+
+ // Position the caption box, centered on the snapped candle X
+ captionRect.Left := candleX - (textSize.Width / 2) - padding;
+ captionRect.Width := textSize.Width + (padding * 2);
+ captionRect.Top := Viewport.Bottom - textSize.Height - (padding * 2);
+ captionRect.Height := textSize.Height + (padding * 2);
+
+ // Adjust position to not go outside the viewport
+ if captionRect.Left < Viewport.Left then
+ captionRect.SetLocation(Viewport.Left, captionRect.Top);
+ if captionRect.Right > Viewport.Right then
+ captionRect.SetLocation(Viewport.Right - captionRect.Width, captionRect.Top);
+
+ // Draw the styled box
+ Canvas.Fill.Color := TAlphaColors.White;
+ Canvas.Fill.Kind := TBrushKind.Solid;
+ Canvas.Stroke.Kind := TBrushKind.Solid;
+ Canvas.Stroke.Color := TAlphaColors.Gray;
+ Canvas.Stroke.Thickness := 1;
+ Canvas.FillRect(captionRect, 2, 2, AllCorners, 1.0);
+ Canvas.DrawRect(captionRect, 2, 2, AllCorners, 1.0);
+
+ // Draw the text
+ Canvas.Fill.Color := TAlphaColors.Black;
+ Canvas.FillText(captionRect, caption, false, 1.0, [], TTextAlign.Center, TTextAlign.Center);
+end;
+
end.
diff --git a/Src/Myc.Trade.Indicators.pas b/Src/Myc.Trade.Indicators.pas
index 7c0c342..71f67ae 100644
--- a/Src/Myc.Trade.Indicators.pas
+++ b/Src/Myc.Trade.Indicators.pas
@@ -36,19 +36,19 @@ type
class function CalculateWMA(const Series: TMycDataArray; const Period: Integer): Double; static;
public
// Simple Moving Average
- class function CreateSMA(Period: Integer): TFunc; static;
+ class function CreateSMA(Period: Integer): TIndicatorFunc; static;
// Exponential Moving Average
- class function CreateEMA(Period: Integer): TFunc; static;
+ class function CreateEMA(Period: Integer): TIndicatorFunc; static;
// Hull Moving Average
- class function CreateHMA(Period: Integer): TFunc; static;
+ class function CreateHMA(Period: Integer): TIndicatorFunc; static;
// Relative Strength Index
- class function CreateRSI(Period: Integer): TFunc; static;
+ class function CreateRSI(Period: Integer): TIndicatorFunc; static;
// Moving Average Convergence Divergence
- class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TFunc; static;
+ class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TIndicatorFunc; static;
// Stochastic Oscillator
- class function CreateStochastic(KPeriod, DPeriod: Integer): TFunc; static;
+ class function CreateStochastic(KPeriod, DPeriod: Integer): TIndicatorFunc; static;
// Bollinger Bands
- class function CreateBollingerBands(Period: Integer; Multiplier: Double): TFunc; static;
+ class function CreateBollingerBands(Period: Integer; Multiplier: Double): TIndicatorFunc; static;
end;
implementation
@@ -112,11 +112,11 @@ begin
Result := numerator / denominator;
end;
-class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TFunc;
+class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TIndicatorFunc;
begin
var sourceData := TMycDataArray.CreateEmpty;
Result :=
- function(Value: Double): TBollingerBandsResult
+ function(const Value: Double): TBollingerBandsResult
var
stdDev: Double;
begin
@@ -135,14 +135,14 @@ begin
end;
end;
-class function TIndicators.CreateEMA(Period: Integer): TFunc;
+class function TIndicators.CreateEMA(Period: Integer): TIndicatorFunc;
begin
var lastEma: Double := Double.NaN;
var sourceData := TMycDataArray.CreateEmpty;
var multiplier := 2 / (Period + 1);
Result :=
- function(Value: Double): Double
+ function(const Value: Double): Double
begin
sourceData := sourceData.Add(Value, Period);
@@ -166,7 +166,7 @@ begin
end;
end;
-class function TIndicators.CreateHMA(Period: Integer): TFunc;
+class function TIndicators.CreateHMA(Period: Integer): TIndicatorFunc;
begin
var periodHalf := Period div 2;
var periodSqrt := Round(Sqrt(Period));
@@ -174,7 +174,7 @@ begin
var diffSeries := TMycDataArray.CreateEmpty;
Result :=
- function(Value: Double): Double
+ function(const Value: Double): Double
var
price: Double;
wmaHalf, wmaFull, diff: Double;
@@ -208,14 +208,14 @@ begin
end;
end;
-class function TIndicators.CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TFunc;
+class function TIndicators.CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TIndicatorFunc;
begin
var emaFast := CreateEMA(FastPeriod);
var emaSlow := CreateEMA(SlowPeriod);
var emaSignal := CreateEMA(SignalPeriod);
Result :=
- function(Value: Double): TMacdResult
+ function(const Value: Double): TMacdResult
var
fastVal, slowVal: Double;
begin
@@ -240,14 +240,14 @@ begin
end;
end;
-class function TIndicators.CreateRSI(Period: Integer): TFunc;
+class function TIndicators.CreateRSI(Period: Integer): TIndicatorFunc;
begin
var avgGain: Double := Double.NaN;
var avgLoss: Double := Double.NaN;
var sourceData := TMycDataArray.CreateEmpty;
Result :=
- function(Value: Double): Double
+ function(const Value: Double): Double
var
change, gain, loss, rs: Double;
gainSum, lossSum: Double;
@@ -299,11 +299,11 @@ begin
end;
end;
-class function TIndicators.CreateSMA(Period: Integer): TFunc;
+class function TIndicators.CreateSMA(Period: Integer): TIndicatorFunc;
begin
var sourceData := TMycDataArray.CreateEmpty;
Result :=
- function(Value: Double): Double
+ function(const Value: Double): Double
begin
sourceData := sourceData.Add(Value, Period);
if (sourceData.Count >= Period) then
@@ -313,13 +313,13 @@ begin
end;
end;
-class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TFunc;
+class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TIndicatorFunc;
begin
var sourceData := TMycDataArray.CreateEmpty;
var smaD := CreateSMA(DPeriod);
Result :=
- function(Value: TOhlcItem): TStochasticResult
+ function(const Value: TOhlcItem): TStochasticResult
var
i: Integer;
highestHigh, lowestLow: Double;
diff --git a/Src/Myc.Trade.Types.pas b/Src/Myc.Trade.Types.pas
index e123c7d..a7a4874 100644
--- a/Src/Myc.Trade.Types.pas
+++ b/Src/Myc.Trade.Types.pas
@@ -3,6 +3,10 @@ unit Myc.Trade.Types;
interface
type
+ TTimeframe = (S, S5, S15, S30, M, M2, M3, M5, M10, M15, M30, H, H2, H3, H4, H8, H12, D, D2, D3, W, MN, MN3, MN6, Y);
+
+ TIndicatorFunc = reference to function(const Value: S): T;
+
// A data record for an Ask/Bid price pair.
TAskBidItem = packed record
Ask: Double;