TAChart: extend TOHLCSeries for display as candlesticks. Add demo "financial" with OHLCSeries in OHLC and candlestick mode.

git-svn-id: trunk@46244 -
This commit is contained in:
wp 2014-09-16 11:45:08 +00:00
parent 0a7975bb19
commit 9a2d8a270b
7 changed files with 521 additions and 13 deletions

5
.gitattributes vendored
View File

@ -3556,6 +3556,11 @@ components/tachart/demo/extent/extentdemo.lpi svneol=native#text/plain
components/tachart/demo/extent/extentdemo.lpr svneol=native#text/plain
components/tachart/demo/extent/main.lfm svneol=native#text/plain
components/tachart/demo/extent/main.pas svneol=native#text/plain
components/tachart/demo/financial/data.txt svneol=native#text/plain
components/tachart/demo/financial/financialdemo.lpi svneol=native#text/plain
components/tachart/demo/financial/financialdemo.lpr svneol=native#text/plain
components/tachart/demo/financial/main.lfm svneol=native#text/plain
components/tachart/demo/financial/main.pas svneol=native#text/plain
components/tachart/demo/fit/Main.lfm svneol=native#text/plain
components/tachart/demo/fit/Main.pas svneol=native#text/pascal
components/tachart/demo/fit/fitdemo.lpi svneol=native#text/plain

View File

@ -0,0 +1,23 @@
Date,open,high,low,close
2009-12-01,29.52,30.05,29.41,30.01
2009-12-02,29.90,29.99,29.65,29.78
2009-12-03,29.84,30.20,29.76,29.83
2009-12-04,30.05,30.37,29.83,29.98
2009-12-07,29.78,30.08,29.68,29.79
2009-12-08,29.52,29.74,29.38,29.57
2009-12-09,29.47,29.81,29.25,29.71
2009-12-10,29.71,29.96,29.66,29.87
2009-12-11,29.97,30.00,29.79,29.85
2009-12-14,29.91,30.16,29.90,30.11
2009-12-15,29.89,30.21,29.88,30.02
2009-12-16,30.07,30.41,30.04,30.10
2009-12-17,29.95,29.96,29.57,29.60
2009-12-18,29.84,30.45,29.80,30.36
2009-12-21,30.40,30.84,30.37,30.52
2009-12-22,30.60,30.93,30.54,30.82
2009-12-23,30.71,30.95,30.69,30.92
2009-12-24,30.88,31.00,30.76,31.00
2009-12-28,31.00,31.18,30.89,31.17
2009-12-29,31.35,31.50,31.23,31.39
2009-12-30,31.15,31.29,30.80,30.96
2009-12-31,30.98,30.99,30.48,30.48

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="9"/>
<PathDelim Value="\"/>
<General>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="financialdemo"/>
<ResourceType Value="res"/>
<UseXPManifest Value="True"/>
<Icon Value="0"/>
</General>
<i18n>
<EnableI18N LFM="False"/>
</i18n>
<VersionInfo>
<StringTable ProductVersion=""/>
</VersionInfo>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
<RunParams>
<local>
<FormatVersion Value="1"/>
</local>
</RunParams>
<RequiredPackages Count="2">
<Item1>
<PackageName Value="TAChartLazarusPkg"/>
</Item1>
<Item2>
<PackageName Value="LCL"/>
</Item2>
</RequiredPackages>
<Units Count="3">
<Unit0>
<Filename Value="financialdemo.lpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
<Unit1>
<Filename Value="main.pas"/>
<IsPartOfProject Value="True"/>
<ComponentName Value="MainForm"/>
<HasResources Value="True"/>
<ResourceBaseClass Value="Form"/>
<UnitName Value="main"/>
</Unit1>
<Unit2>
<Filename Value="data.txt"/>
<IsPartOfProject Value="True"/>
</Unit2>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<Target>
<Filename Value="financialdemo"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<Linking>
<Options>
<Win32>
<GraphicApplication Value="True"/>
</Win32>
</Options>
</Linking>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@ -0,0 +1,21 @@
program financialdemo;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset
Forms, main, tachartlazaruspkg
{ you can add units after this };
{$R *.res}
begin
RequireDerivedFormResource := True;
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.

View File

@ -0,0 +1,85 @@
object MainForm: TMainForm
Left = 313
Height = 438
Top = 177
Width = 758
Caption = 'MainForm'
ClientHeight = 438
ClientWidth = 758
OnCreate = FormCreate
LCLVersion = '1.3'
object TopPanel: TPanel
Left = 0
Height = 42
Top = 0
Width = 758
Align = alTop
BevelOuter = bvNone
ClientHeight = 42
ClientWidth = 758
TabOrder = 0
object cbSeriesType: TComboBox
Left = 8
Height = 28
Top = 8
Width = 242
ItemHeight = 20
ItemIndex = 0
Items.Strings = (
'OHLC series'
'Candle stick series'
)
OnChange = cbSeriesTypeChange
Style = csDropDownList
TabOrder = 0
Text = 'OHLC series'
end
end
object FinancialChart: TChart
Left = 0
Height = 396
Top = 42
Width = 758
AxisList = <
item
Grid.Color = clSilver
Minors = <>
Title.LabelFont.Orientation = 900
Title.LabelFont.Style = [fsBold]
Title.Visible = True
Title.Caption = 'Stock price'
end
item
Grid.Visible = False
Intervals.MinLength = 20
Alignment = calBottom
Marks.LabelFont.Orientation = 900
Marks.Format = '%2:s'
Marks.Style = smsLabel
Minors = <>
end>
BackColor = clWhite
Foot.Brush.Color = clBtnFace
Foot.Font.Color = clBlue
Margins.Left = 16
Margins.Top = 16
Margins.Right = 16
Margins.Bottom = 16
Title.Brush.Color = clBtnFace
Title.Font.Color = clBlue
Title.Text.Strings = (
'TAChart'
)
Align = alClient
ParentColor = False
object ohlcSeries: TOpenHighLowCloseSeries
CandlestickDownBrush.Color = clRed
CandlestickUpBrush.Color = clLime
DownLinePen.Color = clRed
DownLinePen.Width = 2
LinePen.Color = clLime
LinePen.Width = 2
Mode = mOHLC
end
end
end

View File

@ -0,0 +1,102 @@
unit main;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, TAGraph, TASources, TAMultiSeries, Forms,
Controls, Graphics, Dialogs, ExtCtrls, StdCtrls;
type
{ TMainForm }
TMainForm = class(TForm)
cbSeriesType: TComboBox;
FinancialChart: TChart;
ohlcSeries: TOpenHighLowCloseSeries;
TopPanel: TPanel;
procedure cbSeriesTypeChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.lfm}
uses
DateUtils;
const
DATA_FILE = 'data.txt';
{ TMainForm }
procedure TMainForm.FormCreate(Sender: TObject);
var
dataList: TStringList;
lines: TStringList;
fs: TFormatSettings;
i: Integer;
xDate: TDate;
yOpen, yClose, yHigh, yLow: Double;
begin
fs := DefaultFormatSettings;
fs.DateSeparator := '-';
fs.ShortDateFormat := 'd/mmm/yyyy';
fs.DecimalSeparator := '.';
fs.ShortMonthNames[1] := 'Jan';
fs.ShortMonthNames[2] := 'Feb';
fs.ShortMonthNames[3] := 'Mar';
fs.ShortMonthNames[4] := 'Apr';
fs.ShortMonthNames[5] := 'May';
fs.ShortMonthNames[6] := 'Jun';
fs.ShortMonthNames[7] := 'Jul';
fs.ShortMonthNames[8] := 'Aug';
fs.ShortMonthNames[9] := 'Sep';
fs.ShortMonthNames[10] := 'Oct';
fs.ShortMonthNames[11] := 'Nov';
fs.ShortMonthNames[12] := 'Dec';
dataList := TStringList.Create;
try
dataList.LoadFromFile(DATA_FILE);
lines := TStringList.Create;
try
for i:=1 to dataList.Count-1 do begin // skip header line
lines.CommaText := dataList[i];
xDate := ScanDateTime('yyyy-mm-dd', lines[0]);
yOpen := StrToFloat(lines[1], fs);
yHigh := StrToFloat(lines[2], fs);
yLow := StrToFloat(lines[3], fs);
yClose := StrToFloat(lines[4], fs);
// We don't use the date for x because we want to skip the weekends in the chart
// Therefore, we use the index and add the date as a label. Diplay of
// the data labels is activated by BottomAxis.Marks.Style = smsLabel.
ohlcSeries.AddXOHLC(i, yOpen, yHigh, yLow, yClose, DateToStr(xDate, fs));
end;
finally
lines.Free;
end;
finally
dataList.Free;
end;
FinancialChart.BottomAxis.Marks.Source := ohlcSeries.ListSource;
end;
procedure TMainForm.cbSeriesTypeChange(Sender: TObject);
begin
ohlcSeries.Mode := TOHLCMode(CbSeriesType.ItemIndex);
end;
end.

View File

@ -116,8 +116,13 @@ type
property Color default clTAColor;
end;
TOHLCMode = (mOHLC, mCandleStick);
TOpenHighLowCloseSeries = class(TBasicPointSeries)
private
FCandlestickDownBrush: TBrush;
FCandleStickLinePen: TPen;
FCandlestickUpBrush: TBrush;
FDownLinePen: TOHLCDownPen;
FLinePen: TPen;
FTickWidth: Cardinal;
@ -125,8 +130,13 @@ type
FYIndexHigh: Cardinal;
FYIndexLow: Cardinal;
FYIndexOpen: Cardinal;
FMode: TOHLCMode;
procedure SetCandlestickLinePen(AValue: TPen);
procedure SetCandlestickDownBrush(AValue: TBrush);
procedure SetCandlestickUpBrush(AValue: TBrush);
procedure SetDownLinePen(AValue: TOHLCDownPen);
procedure SetLinePen(AValue: TPen);
procedure SetOHLCMode(AValue: TOHLCMode);
procedure SetTickWidth(AValue: Cardinal);
procedure SetYIndexClose(AValue: Cardinal);
procedure SetYIndexHigh(AValue: Cardinal);
@ -146,8 +156,15 @@ type
procedure Draw(ADrawer: IChartDrawer); override;
function Extent: TDoubleRect; override;
published
property CandlestickDownBrush: TBrush
read FCandlestickDownBrush write SetCandlestickDownBrush;
property CandlestickLinePen: TPen
read FCandlestickLinePen write FCandleStickLinePen;
property CandlestickUpBrush: TBrush
read FCandlestickUpBrush write SetCandlestickUpBrush;
property DownLinePen: TOHLCDownPen read FDownLinePen write SetDownLinePen;
property LinePen: TPen read FLinePen write SetLinePen;
property Mode: TOHLCMode read FMode write SetOHLCMode;
property TickWidth: Cardinal
read FTickWidth write SetTickWidth default DEF_OHLC_TICK_WIDTH;
property YIndexClose: Cardinal
@ -163,7 +180,12 @@ type
property AxisIndexY;
property Source;
end;
(*
TCandleStickSeries = class(TOpenHighLowCloseSeries)
public
procedure Draw(ADrawer: IChartDrawer); override;
end;
*)
implementation
uses
@ -578,7 +600,8 @@ function TOpenHighLowCloseSeries.AddXOHLC(
AX, AOpen, AHigh, ALow, AClose: Double;
ALabel: String; AColor: TColor): Integer;
begin
Result := ListSource.Add(AX, 0, ALabel, AColor);
if ListSource.YCount < 4 then ListSource.YCount := 4;
Result := ListSource.Add(AX, NaN, ALabel, AColor);
with ListSource.Item[Result]^ do begin
SetY(YIndexOpen, AOpen);
SetY(YIndexHigh, AHigh);
@ -591,8 +614,12 @@ procedure TOpenHighLowCloseSeries.Assign(ASource: TPersistent);
begin
if ASource is TOpenHighLowCloseSeries then
with TOpenHighLowCloseSeries(ASource) do begin
Self.DownLinePen := FDownLinePen;
Self.LinePen := FLinePen;
Self.FCandlestickDownBrush := FCandlestickDownBrush;
Self.FCandlestickLinePen := FCandlestickLinePen;
Self.FCandlestickUpBrush := FCandlestickUpBrush;
Self.FDownLinePen := FDownLinePen;
Self.FLinePen := FLinePen;
Self.FMode := FMode;
Self.FTickWidth := FTickWidth;
Self.FYIndexClose := FYIndexClose;
Self.FYIndexHigh := FYIndexHigh;
@ -605,11 +632,29 @@ end;
constructor TOpenHighLowCloseSeries.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FCandlestickDownBrush := TBrush.Create;
with FCandlestickDownBrush do begin
Color := clRed;
OnChange := @StyleChanged;
end;
FCandlestickLinePen := TPen.Create;
with FCandlestickLinePen do begin
Color := clBlack;
OnChange := @StyleChanged;
end;
FCandlestickUpBrush := TBrush.Create;
with FCandlestickUpBrush do begin
Color := clLime;
OnChange := @StyleChanged;
end;
FDownLinePen := TOHLCDownPen.Create;
FDownLinePen.Color := clTAColor;
FDownLinePen.OnChange := @StyleChanged;
with FDownLinePen do begin
Color := clTAColor;
OnChange := @StyleChanged;
end;
FLinePen := TPen.Create;
FLinePen.OnChange := @StyleChanged;
with FLinePen do
OnChange := @StyleChanged;
FTickWidth := DEF_OHLC_TICK_WIDTH;
FYIndexClose := DEF_YINDEX_CLOSE;
FYIndexHigh := DEF_YINDEX_HIGH;
@ -619,6 +664,9 @@ end;
destructor TOpenHighLowCloseSeries.Destroy;
begin
FreeAndNil(FCandlestickDownBrush);
FreeAndNil(FCandlestickLinePen);
FreeAndNil(FCandlestickUpBrush);
FreeAndNil(FDownLinePen);
FreeAndNil(FLinePen);
inherited;
@ -638,6 +686,18 @@ procedure TOpenHighLowCloseSeries.Draw(ADrawer: IChartDrawer);
ADrawer.Line(MaybeRotate(AX1, AY1), MaybeRotate(AX2, AY2));
end;
procedure DoRect(AX1, AY1, AX2, AY2: Double);
var
r: TRect;
begin
with ParentChart do begin
r.TopLeft := MaybeRotate(AX1, AY1);
r.BottomRight := MaybeRotate(AX2, AY2);
end;
ADrawer.FillRect(r.Left, r.Top, r.Right, r.Bottom);
ADrawer.Rectangle(r);
end;
function GetGraphPointYIndex(AIndex, AYIndex: Integer): Double;
begin
if AYIndex = 0 then
@ -646,6 +706,20 @@ procedure TOpenHighLowCloseSeries.Draw(ADrawer: IChartDrawer);
Result := AxisToGraphY(Source[AIndex]^.YList[AYIndex - 1]);
end;
procedure DrawOHLC(x, yopen, yhigh, ylow, yclose, tw: Double);
begin
DoLine(x, yhigh, x, ylow);
DoLine(x - tw, yopen, x, yopen);
DoLine(x, yclose, x + tw, yclose);
end;
procedure DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw: Double);
begin
ADrawer.Pen := FCandlestickLinePen;
DoLine(x, yhigh, x, ylow);
DoRect(x - tw, yopen, x + tw, yclose);
end;
var
my: Cardinal;
ext2: TDoubleRect;
@ -670,18 +744,23 @@ begin
yclose := GetGraphPointYIndex(i, YIndexClose);
tw := GetXRange(x, i) * PERCENT * TickWidth;
if (DownLinePen.Color = clTAColor) or (yopen <= yclose) then
p := LinePen
else
if (yopen <= yclose) then begin
p := LinePen;
ADrawer.Brush := FCandleStickUpBrush;
end
else begin
p := DownLinePen;
ADrawer.Brush := FCandleStickDownBrush;
end;
ADrawer.Pen := p;
with Source[i]^ do
if Color <> clTAColor then
ADrawer.SetPenParams(p.Style, Color);
DoLine(x, yhigh, x, ylow);
DoLine(x - tw, yopen, x, yopen);
DoLine(x, yclose, x + tw, yclose);
case FMode of
mOHLC: DrawOHLC(x, yopen, yhigh, ylow, yclose, tw);
mCandleStick: DrawCandleStick(x, yopen, yhigh, ylow, yclose, tw);
end;
end;
end;
@ -700,6 +779,27 @@ begin
Result := LinePen.Color;
end;
procedure TOpenHighLowCloseSeries.SetCandlestickLinePen(AValue: TPen);
begin
if FCandleStickLinePen = AValue then exit;
FCandleStickLinePen.Assign(AValue);
UpdateParentChart;
end;
procedure TOpenHighLowCloseSeries.SetCandlestickDownBrush(AValue: TBrush);
begin
if FCandlestickDownBrush = AValue then exit;
FCandlestickDownBrush.Assign(AValue);
UpdateParentChart;
end;
procedure TOpenHighLowCloseSeries.SetCandlestickUpBrush(AValue: TBrush);
begin
if FCandlestickUpBrush = AValue then exit;
FCandlestickUpBrush.Assign(AValue);
UpdateParentChart;
end;
procedure TOpenHighLowCloseSeries.SetDownLinePen(AValue: TOHLCDownPen);
begin
if FDownLinePen = AValue then exit;
@ -714,6 +814,13 @@ begin
UpdateParentChart;
end;
procedure TOpenHighLowCloseSeries.SetOHLCMode(AValue: TOHLCMode);
begin
if FMode = AValue then exit;
FMode := AValue;
UpdateParentChart;
end;
procedure TOpenHighLowCloseSeries.SetTickWidth(AValue: Cardinal);
begin
if FTickWidth = AValue then exit;
@ -749,9 +856,85 @@ begin
UpdateParentChart;
end;
(*
{ TCandleStickSeries }
procedure TCandleStickChart.Draw(ADrawer: IChartDrawer);
function MaybeRotate(AX, AY: Double): TPoint;
begin
if IsRotated then
Exchange(AX, AY);
Result := ParentChart.GraphToImage(DoublePoint(AX, AY));
end;
procedure DoLine(AX1, AY1, AX2, AY2: Double);
begin
ADrawer.Line(MaybeRotate(AX1, AY1), MaybeRotate(AX2, AY2));
end;
function GetGraphPointYIndex(AIndex, AYIndex: Integer): Double;
begin
if AYIndex = 0 then
Result := GetGraphPointY(AIndex)
else
Result := AxisToGraphY(Source[AIndex]^.YList[AYIndex - 1]);
end;
procedure DoRect(AX1, AY1, AX2, AY2: Double);
var
r: TRect;
begin
with ParentChart do begin
r.TopLeft := MaybeRotate(AX1, AY1);
r.BottomRight := MaybeRotate(AX2, AY2);
end;
ADrawer.FillRect(r.Left, r.Top, r.Right, r.Bottom);
ADrawer.Rectangle(r);
end;
var
maxy: Cardinal;
ext2: TDoubleRect;
i: Integer;
x, tw, yopen, yhigh, ylow, yclose: Double;
p: TPen;
begin
maxy := MaxIntValue([YIndexOpen, YIndexHigh, YIndexLow, YIndexClose]);
if IsEmpty or (maxy >= Source.YCount) then exit;
ext2 := ParentChart.CurrentExtent;
ExpandRange(ext2.a.X, ext2.b.X, 1.0);
ExpandRange(ext2.a.Y, ext2.b.Y, 1.0);
PrepareGraphPoints(ext2, true);
for i := FLoBound to FUpBound do begin
x := GetGraphPointX(i);
yopen := GetGraphPointYIndex(i, YIndexOpen);
yhigh := GetGraphPointYIndex(i, YIndexHigh);
ylow := GetGraphPointYIndex(i, YIndexLow);
yclose := GetGraphPointYIndex(i, YIndexClose);
tw := GetXRange(x, i) * PERCENT * TickWidth;
if (DownLinePen.Color = clTAColor) or (yopen <= yclose) then
p := LinePen
else
p := DownLinePen;
ADrawer.BrushColor:= P.Color;
// set border black
ADrawer.SetPenParams(p.Style, clBlack);
DoLine(x, yhigh, x, ylow);
DoRect(x - tw, yopen, x + tw, yclose);
end;
end;
*)
initialization
RegisterSeriesClass(TBubbleSeries, 'Bubble series');
RegisterSeriesClass(TBoxAndWhiskerSeries, 'Box-and-whiskers series');
RegisterSeriesClass(TOpenHighLowCloseSeries, 'Open-high-low-close series');
// RegisterSeriesClass(TCandleStickSeries, 'Candle stick series');
end.