diff --git a/AuraTrader/AuraTrader.dpr b/AuraTrader/AuraTrader.dpr
index a55b017..7cf3b33 100644
--- a/AuraTrader/AuraTrader.dpr
+++ b/AuraTrader/AuraTrader.dpr
@@ -12,7 +12,8 @@ uses
DynamicFMXControl in 'DynamicFMXControl.pas',
FirstStrategy in 'FirstStrategy.pas',
Myc.Fmx.Chart in 'Myc.Fmx.Chart.pas',
- Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas';
+ Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas',
+ Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas';
{$R *.res}
diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj
index ecdfe00..236e6a0 100644
--- a/AuraTrader/AuraTrader.dproj
+++ b/AuraTrader/AuraTrader.dproj
@@ -141,6 +141,7 @@
+
Base
diff --git a/AuraTrader/FirstStrategy.pas b/AuraTrader/FirstStrategy.pas
index a9a546f..405cc0d 100644
--- a/AuraTrader/FirstStrategy.pas
+++ b/AuraTrader/FirstStrategy.pas
@@ -12,25 +12,6 @@ uses
type
TTimeframe = (M1, M5, H1, D);
- IMycConverter = interface(IMycProcessor)
- function GetSender: IMycDataProvider;
- property Sender: IMycDataProvider read GetSender;
- end;
-
- TMycConverter = class abstract(TMycProcessor, IMycConverter)
- private
- FSender: TMycDataProvider;
- function GetSender: IMycDataProvider;
- protected
- // Broadcasts the given data to all linked processors.
- procedure Broadcast(const Value: T);
-
- public
- constructor Create;
- destructor Destroy; override;
- property Sender: IMycDataProvider read GetSender;
- end;
-
TMycGenericConverter = class(TMycConverter)
type
TConvertFunc = reference to function(const Value: S): T;
@@ -255,29 +236,4 @@ begin
Broadcast(FFunc(Value));
end;
-{ TMycConverter }
-
-constructor TMycConverter.Create;
-begin
- inherited Create;
- FSender := TMycDataProvider.Create(Self);
-end;
-
-destructor TMycConverter.Destroy;
-begin
- FSender.Free;
- inherited Destroy;
-end;
-
-procedure TMycConverter.Broadcast(const Value: T);
-begin
- var cValue := Value;
- FSender.Notify(function(const Processor: IMycProcessor): Boolean begin Result := Processor.ProcessData(cValue) end);
-end;
-
-function TMycConverter.GetSender: IMycDataProvider;
-begin
- Result := FSender;
-end;
-
end.
diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas
index b1815fb..210c2d5 100644
--- a/AuraTrader/MainForm.pas
+++ b/AuraTrader/MainForm.pas
@@ -165,6 +165,10 @@ begin
var OhlcPoint: IMycConverter>, TDataPoint> := TTicksToTimeframe.Create(M1);
+ var Timestamps: IMycConverter, TDateTime> :=
+ TMycGenericConverter, TDateTime>
+ .Create(function(const Ohlc: TDataPoint): TDateTime begin Result := Ohlc.Time; end);
+
var Ohlc: IMycConverter, TOhlcItem> :=
TMycGenericConverter, TOhlcItem>
.Create(function(const Ohlc: TDataPoint): TOhlcItem begin Result := Ohlc.Data; end);
@@ -189,13 +193,14 @@ begin
//
// chart.AddDoubleSeries(Hull.Sender);
+ chart.SetXAxisSeries(OHLC.Sender);
+ chart.AddOhlcSeries(Ohlc.Sender);
+
var done := ExecuteStrategy(Symbol, OhlcPoint);
/////
FProcessDone := TState.All([FProcessDone, done]);
-
- chart.AddOhlcSeries(OhlcPoint.Sender);
end;
procedure TForm1.TreeViewDblClick(Sender: TObject);
diff --git a/Src/Myc.Fmx.Chart.Series.pas b/Src/Myc.Fmx.Chart.Series.pas
new file mode 100644
index 0000000..23cf722
--- /dev/null
+++ b/Src/Myc.Fmx.Chart.Series.pas
@@ -0,0 +1,282 @@
+unit Myc.FMX.Chart.Series;
+
+interface
+
+uses
+ System.SysUtils,
+ System.SyncObjs,
+ System.UITypes,
+ FMX.Graphics,
+ Myc.Trade.DataArray,
+ Myc.Trade.DataPoint,
+ Myc.Lazy,
+ Myc.Fmx.Chart;
+
+type
+ TChartSeriesCounter = class(TMycConverter)
+ private
+ FCount: Int64;
+ protected
+ function ProcessData(const Value: T): Boolean; override;
+ public
+ constructor Create;
+ end;
+
+ TChartSeriesReceiver = class(TMycProcessor)
+ strict private
+ FCurrData: TMycDataArray;
+ FLookback: TWriteable;
+ private
+ FData: TWriteable>;
+ protected
+ function ProcessData(const Value: T): Boolean; override;
+ public
+ constructor Create(const ALookback: TWriteable);
+ property Data: TWriteable> read FData;
+ end;
+
+ TChartSeriesProcessor = class(TMycChart.TSeries)
+ strict private
+ FDataSeries: TMycDataArray;
+ FLock: TSpinLock;
+ private
+ FData: TMycDataArray;
+ FDataProvider: IMycDataProvider;
+ FReceiver: TChartSeriesReceiver;
+ FReceiverTag: TTag;
+ protected
+ function GetCount: Int64; override;
+ function GetTotalCount: Int64; override;
+ procedure Update; override;
+
+ public
+ constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
+ destructor Destroy; override;
+ property Data: TMycDataArray read FData;
+ end;
+
+ { TChartOhlcSeries }
+ TChartOhlcSeries = class(TChartSeriesProcessor)
+ private
+ FUpColor: TAlphaColor;
+ FDownColor: TAlphaColor;
+ protected
+ function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
+ procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
+ public
+ constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider; const AUpColor, ADownColor: TAlphaColor);
+ end;
+
+ { TChartLineSeries }
+ TChartLineSeries = class(TChartSeriesProcessor)
+ private
+ FLineColor: TAlphaColor;
+ FLineWidth: Single;
+ protected
+ function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
+ procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
+ public
+ constructor Create(
+ AOwner: TMycChart;
+ const ADataProvider: IMycDataProvider;
+ const ALineColor: TAlphaColor;
+ ALineWidth: Single
+ );
+ end;
+
+implementation
+
+uses
+ System.Types,
+ System.Math,
+ FMX.Types;
+
+constructor TChartSeriesCounter.Create;
+begin
+ inherited Create;
+ FCount := 0;
+end;
+
+function TChartSeriesCounter.ProcessData(const Value: T): Boolean;
+begin
+ Result := true;
+ Broadcast(FCount);
+ inc(FCount);
+end;
+
+{ TChartSeriesReceiver }
+
+constructor TChartSeriesReceiver.Create(const ALookback: TWriteable);
+begin
+ inherited Create;
+ FLookback := ALookback;
+ FCurrData := TMycDataArray.CreateEmpty;
+ FData := TWriteable>.CreateWriteable(FCurrData).Protect;
+end;
+
+function TChartSeriesReceiver.ProcessData(const Value: T): Boolean;
+begin
+ Result := true;
+ FCurrData := FCurrData.Add(Value, FLookback.Value);
+ FData.Value := FCurrData;
+end;
+
+{ TChartSeriesProcessor }
+
+constructor TChartSeriesProcessor.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
+begin
+ inherited Create(AOwner);
+ FLock := TSpinLock.Create(false);
+ FDataSeries := TMycDataArray.CreateEmpty;
+ FData := FDataSeries;
+ FDataProvider := ADataProvider;
+ FReceiver := TChartSeriesReceiver.Create(AOwner.Lookback);
+ FReceiverTag := FDataProvider.Link(FReceiver);
+end;
+
+destructor TChartSeriesProcessor.Destroy;
+begin
+ FDataProvider.Unlink(FReceiverTag);
+ inherited;
+end;
+
+function TChartSeriesProcessor.GetCount: Int64;
+begin
+ Result := FData.Count;
+end;
+
+function TChartSeriesProcessor.GetTotalCount: Int64;
+begin
+ Result := FData.TotalCount;
+end;
+
+procedure TChartSeriesProcessor.Update;
+begin
+ FData := FReceiver.Data.Value;
+end;
+
+{ TChartLineSeries }
+
+constructor TChartLineSeries.Create(
+ AOwner: TMycChart;
+ const ADataProvider: IMycDataProvider;
+ const ALineColor: TAlphaColor;
+ ALineWidth: Single
+);
+begin
+ inherited Create(AOwner, ADataProvider);
+ FLineColor := ALineColor;
+ FLineWidth := ALineWidth;
+end;
+
+function TChartLineSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
+var
+ i: Int64;
+begin
+ MinValue := MaxDouble;
+ MaxValue := -MaxDouble;
+ for i := First to Last do
+ begin
+ var v := Data.Items[i];
+ if not IsNaN(v) then
+ begin
+ MinValue := Min(MinValue, v);
+ MaxValue := Max(MaxValue, v);
+ end;
+ end;
+ Result := (MinValue <> MaxDouble);
+end;
+
+procedure TChartLineSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc);
+var
+ points: TPathData;
+ i, n: Int64;
+begin
+ points := TPathData.Create;
+ try
+ // Skip warmup data
+ n := First;
+ while (n <= Last) and (IsNaN(Data[n])) do
+ inc(n);
+ if (n > Last) then
+ exit;
+
+ points.MoveTo(TPointF.Create(XForm(n), YForm(Data[n])));
+ for i := n + 1 to Last do
+ if not IsNaN(Data[i]) then
+ points.LineTo(TPointF.Create(XForm(i), YForm(Data[i])));
+
+ Canvas.Stroke.Color := FLineColor;
+ Canvas.Stroke.Thickness := FLineWidth;
+ Canvas.DrawPath(points, 1);
+ finally
+ points.Free;
+ end;
+end;
+
+{ TChartOhlcSeries }
+
+constructor TChartOhlcSeries.Create(
+ AOwner: TMycChart;
+ const ADataProvider: IMycDataProvider;
+ const AUpColor, ADownColor: TAlphaColor
+);
+begin
+ inherited Create(AOwner, ADataProvider);
+ FUpColor := AUpColor;
+ FDownColor := ADownColor;
+end;
+
+function TChartOhlcSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
+var
+ i: Int64;
+begin
+ MinValue := MaxDouble;
+ MaxValue := -MaxDouble;
+
+ for i := First to Last do
+ begin
+ var item := Data.Items[i];
+ MinValue := Min(MinValue, item.Low);
+ MaxValue := Max(MaxValue, item.High);
+ end;
+ Result := (MinValue <> MaxDouble);
+end;
+
+procedure TChartOhlcSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc);
+var
+ i: Int64;
+ x, candleWidth: Single;
+ yOpen, yHigh, yLow, yClose: Single;
+ item: TOhlcItem;
+ isUp: boolean;
+begin
+ candleWidth := Max(2, 0.8 * Abs((XForm(First + 1) - XForm(First))));
+
+ for i := First to Last do
+ begin
+ item := Data.Items[i];
+ x := XForm(i);
+ yOpen := YForm(item.Open);
+ yClose := YForm(item.Close);
+ yHigh := YForm(item.High);
+ yLow := YForm(item.Low);
+ isUp := item.Close >= item.Open;
+
+ if isUp then
+ Canvas.Stroke.Color := FUpColor
+ else
+ Canvas.Stroke.Color := FDownColor;
+ Canvas.Stroke.Thickness := 1.0;
+
+ Canvas.DrawLine(TPointF.Create(x, yHigh), TPointF.Create(x, yLow), 1);
+
+ Canvas.Fill.Color := Canvas.Stroke.Color;
+ if isUp then
+ Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yClose, x + candleWidth / 2, yOpen), 0, 0, AllCorners, 1)
+ else
+ Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yOpen, x + candleWidth / 2, yClose), 0, 0, AllCorners, 1);
+ end;
+end;
+
+end.
diff --git a/AuraTrader/Myc.Fmx.Chart.pas b/Src/Myc.Fmx.Chart.pas
similarity index 61%
rename from AuraTrader/Myc.Fmx.Chart.pas
rename to Src/Myc.Fmx.Chart.pas
index 046f36f..83d041b 100644
--- a/AuraTrader/Myc.Fmx.Chart.pas
+++ b/Src/Myc.Fmx.Chart.pas
@@ -16,7 +16,6 @@ uses
FMX.Graphics,
FMX.Forms,
Myc.Trade.DataPoint,
- Myc.Signals,
Myc.Lazy;
type
@@ -30,10 +29,10 @@ type
protected
function GetCount: Int64; virtual; abstract;
function GetTotalCount: Int64; virtual; abstract;
- function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual; abstract;
procedure Update; virtual; abstract;
+ function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual;
// The signature is changed to support viewport painting with an index offset
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); virtual; abstract;
+ procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); virtual;
property MainSeries: TSeries read GetMainSeries;
public
constructor Create(AOwner: TMycChart);
@@ -44,6 +43,7 @@ type
private
FSeriesList: TList;
+ FXAxisSeries: TSeries;
FLookback: TWriteable;
FIdleSubscrId: TMessageSubscriptionId;
FViewStartIndex: Int64;
@@ -64,9 +64,14 @@ type
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
- // Creates an OHLC candlestick/bar series and returns it
+ // Sets the master series that defines the time scale (X-axis). This series is not drawn.
+ // The chart will not render any data until the X-axis series is set.
+ function SetXAxisSeries(const DataProvider: IMycDataProvider): TSeries;
+
+ // Creates an OHLC candlestick/bar series and returns it.
+ // The data provider directly supplies OHLC items, as timestamps are managed by the data series internally.
function AddOhlcSeries(
- const DataProvider: IMycDataProvider>;
+ const DataProvider: IMycDataProvider;
const AUpColor: TAlphaColor = TAlphaColors.Green;
const ADownColor: TAlphaColor = TAlphaColors.Red
): TSeries;
@@ -85,78 +90,7 @@ implementation
uses
System.Math,
- System.SyncObjs,
- WinApi.Windows,
- Myc.Trade.DataArray;
-
-type
- TChartSeriesReceiver = class(TMycProcessor)
- strict private
- FCurrData: TMycDataArray;
- FLookback: TWriteable;
- private
- FData: TWriteable>;
- protected
- function ProcessData(const Value: T): Boolean; override;
- public
- constructor Create(const ALookback: TWriteable);
- property Data: TWriteable> read FData;
- end;
-
- TChartSeriesProcessor = class(TMycChart.TSeries)
- strict private
- FDataSeries: TMycDataArray;
- FLock: TSpinLock;
- private
- FData: TMycDataArray;
- FDataProvider: IMycDataProvider;
- FReceiver: TChartSeriesReceiver;
- FReceiverTag: TTag;
- protected
- function GetCount: Int64; override;
- function GetTotalCount: Int64; override;
- procedure Update; override;
-
- public
- constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
- destructor Destroy; override;
- property Data: TMycDataArray read FData;
- end;
-
- { TChartOhlcSeries }
- TChartOhlcSeries = class(TChartSeriesProcessor>)
- private
- FUpColor: TAlphaColor;
- FDownColor: TAlphaColor;
- protected
- function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
- public
- constructor Create(
- AOwner: TMycChart;
- const ADataProvider: IMycDataProvider>;
- const AUpColor, ADownColor: TAlphaColor
- );
- end;
-
- { TChartLineSeries }
- TChartLineSeries = class(TChartSeriesProcessor)
- private
- FLineColor: TAlphaColor;
- FLineWidth: Single;
- protected
- function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override;
- procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
- public
- constructor Create(
- AOwner: TMycChart;
- const ADataProvider: IMycDataProvider;
- const ALineColor: TAlphaColor;
- ALineWidth: Single
- );
- end;
-
-{ TMycChart.TSeries }
+ Myc.FMX.Chart.Series;
constructor TMycChart.TSeries.Create(AOwner: TMycChart);
begin
@@ -166,9 +100,17 @@ end;
function TMycChart.TSeries.GetMainSeries: TSeries;
begin
- Result := nil;
- if (Owner.FSeriesList.Count > 0) then
- Result := Owner.FSeriesList[0];
+ Result := Owner.FXAxisSeries;
+end;
+
+function TMycChart.TSeries.GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean;
+begin
+ Result := false;
+end;
+
+procedure TMycChart.TSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc);
+begin
+ // nothing
end;
{ TMycChart }
@@ -177,6 +119,7 @@ constructor TMycChart.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FSeriesList := TObjectList.Create(true);
+ FXAxisSeries := nil;
FLookback := TWriteable.CreateWriteable(1000); // Load more data for panning
FViewStartIndex := 0;
@@ -193,10 +136,24 @@ destructor TMycChart.Destroy;
begin
TMessageManager.DefaultManager.Unsubscribe(TIdleMessage, FIdleSubscrId);
+ FXAxisSeries.Free;
FSeriesList.Free;
inherited;
end;
+function TMycChart.SetXAxisSeries(const DataProvider: IMycDataProvider): TSeries;
+begin
+ // Free the old one if it exists, and create the new master series
+ FXAxisSeries.Free;
+
+ var counter := TChartSeriesCounter.Create;
+
+ DataProvider.Link(counter);
+
+ FXAxisSeries := TChartSeriesProcessor.Create(Self, counter.Sender);
+ Result := FXAxisSeries;
+end;
+
function TMycChart.AddDoubleSeries(
const DataProvider: IMycDataProvider;
const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
@@ -208,7 +165,7 @@ begin
end;
function TMycChart.AddOhlcSeries(
- const DataProvider: IMycDataProvider>;
+ const DataProvider: IMycDataProvider;
const AUpColor: TAlphaColor = TAlphaColors.Green;
const ADownColor: TAlphaColor = TAlphaColors.Red
): TSeries;
@@ -219,18 +176,26 @@ end;
procedure TMycChart.DoIdle;
begin
- if (FSeriesList.Count = 0) then
+ // An X-axis series is required for any operation
+ if not Assigned(FXAxisSeries) then
exit;
- var prevTotalCount := FSeriesList[0].TotalCount;
+ // First update the master series
+ var prevTotalCount := FXAxisSeries.TotalCount;
+ FXAxisSeries.Update;
+
+ // Check if the master series itself has changed
+ var newTotalCount := FXAxisSeries.TotalCount;
+ var seriesChanged := prevTotalCount <> newTotalCount;
+
+ // Repaint if the last bar is visible, so we display live data
+ var seriesRepaint := FViewStartIndex = 0;
- var seriesRepaint := false;
- var seriesChanged := false;
for var series in FSeriesList do
begin
var seriesCount := series.TotalCount;
- // Check if this series may need a repaint
+ // Check if this series may need a repaint (if it lags behind the master series and these lags are currently visible)
if FViewStartIndex <= prevTotalCount - seriesCount then
seriesRepaint := true;
@@ -241,16 +206,15 @@ begin
seriesChanged := true;
end;
- // Don't move with live data, if the last bar isn't visible
+ // Don't move chart, if the last bar isn't visible
if FViewStartIndex > 0 then
begin
- var newTotalCount := FSeriesList[0].TotalCount;
var countDelta := newTotalCount - prevTotalCount;
-
if countDelta > 0 then
begin
// Adjust the start index based on the absolute change in total data points
FViewStartIndex := FViewStartIndex + countDelta;
+ // Clamp to beginning of dataset
if FViewStartIndex + FViewCount > newTotalCount then
FViewStartIndex := newTotalCount - FViewCount;
end;
@@ -339,7 +303,8 @@ begin
exit;
end;
- if (FSeriesList.Count = 0) or (FViewCount <= 0) then
+ // An X-axis series is required for panning
+ if not Assigned(FXAxisSeries) or (FViewCount <= 0) then
begin
exit;
end;
@@ -352,7 +317,7 @@ begin
FViewStartIndex := FViewStartIndex + indexDelta;
// Clamp values
- mainSeries := FSeriesList[0];
+ mainSeries := FXAxisSeries;
maxIndex := mainSeries.Count - FViewCount;
if (maxIndex < 0) then
maxIndex := 0;
@@ -380,9 +345,11 @@ var
begin
Handled := true;
- if (FSeriesList.Count = 0) then
+ // An X-axis series is required for zooming
+ if not Assigned(FXAxisSeries) then
exit;
- mainSeries := FSeriesList[0];
+
+ mainSeries := FXAxisSeries;
if (mainSeries.Count = 0) or (Width <= 0) then
exit;
@@ -456,7 +423,8 @@ begin
inherited;
rect := Self.LocalRect;
- if (FSeriesList.Count = 0) or (FViewCount <= 1) then
+ // The chart requires an X-axis series to be set before it can draw anything.
+ if not Assigned(FXAxisSeries) or (FXAxisSeries.Count <= 1) or (FViewCount <= 1) then
begin
Canvas.Fill.Color := TAlphaColors.Gray;
Canvas.FillText(rect, 'No Data', false, 1, [], TTextAlign.Center, TTextAlign.Center);
@@ -464,7 +432,7 @@ begin
end;
rangeInitialized := false;
- mainSeries := FSeriesList[0];
+ mainSeries := FXAxisSeries;
masterTotalCount := mainSeries.TotalCount;
for series in FSeriesList do
@@ -566,179 +534,4 @@ begin
end;
end;
-{ TChartSeriesReceiver }
-
-constructor TChartSeriesReceiver.Create(const ALookback: TWriteable);
-begin
- inherited Create;
- FLookback := ALookback;
- FCurrData := TMycDataArray.CreateEmpty;
- FData := TWriteable>.CreateWriteable(FCurrData).Protect;
-end;
-
-function TChartSeriesReceiver.ProcessData(const Value: T): Boolean;
-begin
- Result := true;
- FCurrData := FCurrData.Add(Value, FLookback.Value);
- FData.Value := FCurrData;
-end;
-
-{ TChartSeriesProcessor }
-
-constructor TChartSeriesProcessor.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
-begin
- inherited Create(AOwner);
- FLock := TSpinLock.Create(false);
- FDataSeries := TMycDataArray.CreateEmpty;
- FData := FDataSeries;
- FDataProvider := ADataProvider;
- FReceiver := TChartSeriesReceiver.Create(AOwner.FLookback);
- FReceiverTag := FDataProvider.Link(FReceiver);
-end;
-
-destructor TChartSeriesProcessor.Destroy;
-begin
- FDataProvider.Unlink(FReceiverTag);
- inherited;
-end;
-
-function TChartSeriesProcessor.GetCount: Int64;
-begin
- Result := FData.Count;
-end;
-
-function TChartSeriesProcessor.GetTotalCount: Int64;
-begin
- Result := FData.TotalCount;
-end;
-
-procedure TChartSeriesProcessor.Update;
-begin
- FData := FReceiver.Data.Value;
-end;
-
-{ TChartOhlcSeries }
-
-constructor TChartOhlcSeries.Create(
- AOwner: TMycChart;
- const ADataProvider: IMycDataProvider>;
- const AUpColor, ADownColor: TAlphaColor
-);
-begin
- inherited Create(AOwner, ADataProvider);
- FUpColor := AUpColor;
- FDownColor := ADownColor;
-end;
-
-function TChartOhlcSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
-var
- i: Int64;
-begin
- MinValue := MaxDouble;
- MaxValue := -MaxDouble;
-
- for i := First to Last do
- begin
- var dp := Data.Items[i];
- MinValue := Min(MinValue, dp.Data.Low);
- MaxValue := Max(MaxValue, dp.Data.High);
- end;
- Result := (MinValue <> MaxDouble);
-end;
-
-procedure TChartOhlcSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc);
-var
- i: Int64;
- x, candleWidth: Single;
- yOpen, yHigh, yLow, yClose: Single;
- item: TDataPoint;
- isUp: boolean;
-begin
- candleWidth := Max(2, 0.8 * Abs((XForm(First + 1) - XForm(First))));
-
- for i := First to Last do
- begin
- item := Data.Items[i];
- x := XForm(i);
- yOpen := YForm(item.Data.Open);
- yClose := YForm(item.Data.Close);
- yHigh := YForm(item.Data.High);
- yLow := YForm(item.Data.Low);
- isUp := item.Data.Close >= item.Data.Open;
-
- if isUp then
- Canvas.Stroke.Color := FUpColor
- else
- Canvas.Stroke.Color := FDownColor;
- Canvas.Stroke.Thickness := 1.0;
-
- Canvas.DrawLine(TPointF.Create(x, yHigh), TPointF.Create(x, yLow), 1);
-
- Canvas.Fill.Color := Canvas.Stroke.Color;
- if isUp then
- Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yClose, x + candleWidth / 2, yOpen), 0, 0, AllCorners, 1)
- else
- Canvas.FillRect(TRectF.Create(x - candleWidth / 2, yOpen, x + candleWidth / 2, yClose), 0, 0, AllCorners, 1);
- end;
-end;
-
-{ TChartLineSeries }
-
-constructor TChartLineSeries.Create(
- AOwner: TMycChart;
- const ADataProvider: IMycDataProvider;
- const ALineColor: TAlphaColor;
- ALineWidth: Single
-);
-begin
- inherited Create(AOwner, ADataProvider);
- FLineColor := ALineColor;
- FLineWidth := ALineWidth;
-end;
-
-function TChartLineSeries.GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean;
-var
- i: Int64;
-begin
- MinValue := MaxDouble;
- MaxValue := -MaxDouble;
- for i := First to Last do
- begin
- var v := Data.Items[i];
- if not IsNaN(v) then
- begin
- MinValue := Min(MinValue, v);
- MaxValue := Max(MaxValue, v);
- end;
- end;
- Result := (MinValue <> MaxDouble);
-end;
-
-procedure TChartLineSeries.Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc);
-var
- points: TPathData;
- i, n: Int64;
-begin
- points := TPathData.Create;
- try
- // Skip warmup data
- n := First;
- while (n <= Last) and (IsNaN(Data[n])) do
- inc(n);
- if (n > Last) then
- exit;
-
- points.MoveTo(TPointF.Create(XForm(n), YForm(Data[n])));
- for i := n + 1 to Last do
- if not IsNaN(Data[i]) then
- points.LineTo(TPointF.Create(XForm(i), YForm(Data[i])));
-
- Canvas.Stroke.Color := FLineColor;
- Canvas.Stroke.Thickness := FLineWidth;
- Canvas.DrawPath(points, 1);
- finally
- points.Free;
- end;
-end;
-
end.
diff --git a/Src/Myc.Trade.DataPoint.pas b/Src/Myc.Trade.DataPoint.pas
index 5673b76..40b1bf4 100644
--- a/Src/Myc.Trade.DataPoint.pas
+++ b/Src/Myc.Trade.DataPoint.pas
@@ -66,7 +66,7 @@ type
private
FProc: TProc;
protected
- function ProcessData(const Value: T): Boolean; override;
+ function ProcessData(const Value: T): Boolean; override; final;
public
constructor Create(const AProc: TProc);
end;
@@ -81,6 +81,27 @@ type
constructor Create(const Controller: IInterface; const AProc: TProc);
end;
+ IMycConverter = interface(IMycProcessor)
+ function GetSender: IMycDataProvider;
+ property Sender: IMycDataProvider read GetSender;
+ end;
+
+ TMycConverter = class abstract(TMycProcessor, IMycConverter)
+ private
+ FSender: TMycDataProvider;
+ function GetSender: IMycDataProvider;
+ protected
+ function ProcessData(const Value: S): Boolean; override; abstract;
+
+ // Broadcasts the given data to all linked processors.
+ procedure Broadcast(const Value: T);
+
+ public
+ constructor Create;
+ destructor Destroy; override;
+ property Sender: IMycDataProvider read GetSender;
+ end;
+
// An immutable time-ordered series of data points.
// The most recent element has the logical index 0.
IDataSeries = interface
@@ -508,4 +529,29 @@ begin
end;
end;
+{ TMycConverter }
+
+constructor TMycConverter.Create;
+begin
+ inherited Create;
+ FSender := TMycDataProvider.Create(Self);
+end;
+
+destructor TMycConverter.Destroy;
+begin
+ FSender.Free;
+ inherited Destroy;
+end;
+
+procedure TMycConverter.Broadcast(const Value: T);
+begin
+ var cValue := Value;
+ FSender.Notify(function(const Processor: IMycProcessor): Boolean begin Result := Processor.ProcessData(cValue) end);
+end;
+
+function TMycConverter.GetSender: IMycDataProvider;
+begin
+ Result := FSender;
+end;
+
end.