ろうそく足チャート - peace098beat/windows_applicaciton GitHub Wiki
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace PloniexApi.Net.FormDemo
{
/// <summary>
/// ろうそく足チャート
/// </summary>
class CandleChart : Panel
{
/// <summary>
/// チャートデータ
/// </summary>
public List<MyMarketChartData> markets = null;
// マウスドラッグ
private bool fDrag = false;
/*************************************************************************
*
* 描画関連メンバ変数
*
*************************************************************************/
#region
//ろうそく足の本数
const int NDATA = 500;
// サイズ
static Padding mrgn = new Padding(50, 30, 50, 80); // Chart1のグラフマージン
// サイズ
const int HEIGHT2 = 45; // Chart2の高さ(リサイズにより変わらない)
const int MARGIN2 = 20; // Chart1とChart2のグラフの隙間
// マージン
int horzMrgn = mrgn.Left + mrgn.Right;
int vertMrgn = mrgn.Top + mrgn.Bottom;
// テキスト描画のプロパティ
TextFormatFlags mid = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter; // センター寄オプション
TextFormatFlags left = TextFormatFlags.Left | TextFormatFlags.VerticalCenter; // センター寄オプション
// フォント
Font font = new Font("Arial", 9);
Font font2 = new Font("Arial", 7);
// ペン
Pen penRed = new Pen(Color.Red, 2);
Pen penBlue = new Pen(Color.Blue, 2);
Pen penGrayDot = new Pen(Color.Gray, 1) { DashStyle = DashStyle.Dot }; // 罫線点線
int nBgn; // 表示する先頭レコード番号 [0, data.Length-NDATA]
int xOffset = 0;
double vMax, vMin;
double[] Tick = {1e-8, 5e-8, 1e-7, 5e-7, 1e-6, 5e-6, 1e-5, 5e-5,
1e-4, 5e-4, 1e-3, 5e-3, 1e-2, 5e-2, 1e-1, 5e-1, 1, 2, 5, 10, 20, 50, 100, 200, 500, 10000, 20000, 50000 };
Rectangle rectGraph; // Chart1とChart2をあわせたRectangle
#endregion
/*************************************************************************
*
* 座標変換
*
*************************************************************************/
/// <summary> グラフ座標からウィンドウ座標へ変換 </summary>
/// <param name="x">グラフ座標上の正規化された位置(0~1)</param>
/// <returns></returns>
int nx(double x)
{
return (int)(rectGraph.Left + x * rectGraph.Width);
}
/// <summary>グラフ座標からウィンドウ座標へ変換</summary>
/// <param name="y">グラフ座標上の正規化された位置(0~1)</param>
/// <returns></returns>
int ny(double y)
{
return (int)(rectGraph.Top + (1.0 - y) * rectGraph.Height);
}
/// <summary>グラフ座標からウィンドウ座標へ変換</summary>
/// <param name="y">グラフ座標上の正規化された位置(0~1)</param>
/// <returns></returns>
int ny2(double y)
{
return ny(0) + MARGIN2 + (int)((1.0 - y) * HEIGHT2);
}
// *************************************************************************//
/// <summary>
/// コンストラクタ
/// </summary>
public CandleChart()
{
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.DoubleBuffered = true; // ダブルバッファリング: ちらつき防止
this.Size = new Size(600, 400);
this.MouseMove += new MouseEventHandler(mouseMove);
this.MouseDown += new MouseEventHandler(mouseDown);
this.MouseUp += new MouseEventHandler(mouseUp);
}
/// <summary>
/// チャートデータのセット
/// </summary>
/// <param name="_markets"></param>
public void SetMarketChartData(List<MyMarketChartData> _markets)
{
this.markets = _markets;
// 最大値,最小値検索
foreach (var chartData in markets)
{
this.vMax = Math.Max(vMax, chartData.Close);
this.vMin = Math.Min(vMin, chartData.Close);
}
// 先頭番号(描画するNDATA分ずらしておく)
nBgn = markets.Count - NDATA;
}
/*************************************************************************
*
* マウスイベント
*
*************************************************************************/
private void mouseMove(object sender, MouseEventArgs e)
{
// nBgnの更新
if (fDrag && (Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
{
// マウスの位置からnBgnを決める
this.nBgn = (int)((double)(e.X - rectGraph.Left - xOffset) / rectGraph.Width * markets.Count);
// Clilp : 0 =< nBgn <= markets.Counet-NDATAに収める
this.nBgn = Math.Min(nBgn, markets.Count - NDATA);
this.nBgn = Math.Max(0, nBgn);
this.Refresh();
}
// nBgnの更新
if ((Control.MouseButtons & MouseButtons.Right) == MouseButtons.Right)
{
if (ny2(1) < e.Y && e.Y < ny2(0) && nx(0) < e.X && e.X < nx(1))
{
// マウスの位置からnBgnを決める
this.nBgn = (int)((double)(e.X - rectGraph.Left) / rectGraph.Width * markets.Count);
// Clilp : 0 =< nBgn <= markets.Counet-NDATAに収める
this.nBgn = Math.Min(nBgn, markets.Count - NDATA);
this.nBgn = Math.Max(0, nBgn);
this.Refresh();
}
}
}
private void mouseDown(object sender, MouseEventArgs e)
{
// 選択されている開始・終了位置
double xBgn = (double)nBgn / markets.Count; // 正規化
double xEnd = (double)(nBgn + NDATA) / markets.Count; // 正規化
// マウス位置がChart2の選択範囲に入っているか?
if (ny2(1) < e.Y && e.Y < ny2(0) && nx(xBgn) < e.X && e.X < nx(xEnd))
{
fDrag = true; // ドラッグ開始
xOffset = e.X - nx(xBgn); // ドラッグ開始時の選択範囲内でのマウス位置
this.Refresh();
}
// nBgnの更新
if ((Control.MouseButtons & MouseButtons.Right) == MouseButtons.Right)
{
if (ny2(1) < e.Y && e.Y < ny2(0) && nx(0) < e.X && e.X < nx(1))
{
// マウスの位置からnBgnを決める
this.nBgn = (int)((double)(e.X - rectGraph.Left) / rectGraph.Width * markets.Count);
// Clilp : 0 =< nBgn <= markets.Counet-NDATAに収める
this.nBgn = Math.Min(nBgn, markets.Count - NDATA);
this.nBgn = Math.Max(0, nBgn);
this.Refresh();
}
}
}
private void mouseUp(object sender, MouseEventArgs e)
{
if (fDrag)
{
fDrag = false;
this.Refresh();
}
}
/*************************************************************************
*
* 再描画メソッド
*
*************************************************************************/
// 再描画メソッド
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// もしデータが無い場合は処理無し
if (this.markets == null) return;
// グラフィックス
Graphics g = e.Graphics;
// 定数
double max = double.MinValue, min = double.MaxValue; // 現在の表示区間での最大最小
// 開始点?
// グラフ(2つあわせた)サイズ ※リサイズのたびに作り直される
rectGraph = new Rectangle(mrgn.Left, mrgn.Top, ClientSize.Width - horzMrgn, ClientSize.Height - vertMrgn);
// -- タイトル用Rectangle
Rectangle RectTitle = new Rectangle(nx(0), ny(1) - 20, nx(1) - nx(0), 14);
string title = "fichart";
TextRenderer.DrawText(g, title, font, RectTitle, Color.Black, mid);
// 現在の表示区間での最大最小
for (int n = 0; n < NDATA; n++)
{
max = Math.Max(max, markets[nBgn + n].High);
min = Math.Min(min, markets[nBgn + n].Low);
}
// チャート1の外枠を表示
Rectangle RCChart1 = new Rectangle(nx(0), ny(1), nx(1) - nx(0), ny(0) - ny(1));
g.DrawRectangle(Pens.Black, RCChart1);
Console.WriteLine($"nBgn:{nBgn},nEnd:{nBgn + NDATA}, Count:{markets.Count}, ");
#region Chart1 X軸罫線
// -- x軸罫線,目盛り --
int XTickWidth = 100;
int XTickOffset = XTickWidth / 2;
int XTickHeight = 18;
int XTickNum = 5;
int XTickInterval = NDATA / XTickNum;
for (int n = 0; n < NDATA; n++)
{
double x = (n + 0.5) / NDATA; // 区間の中間に目盛り
if ((n % XTickInterval) == 0 || n == NDATA - 1)
{
if (n > 0 && n < NDATA - 1)
{
// 縦目盛り軸
g.DrawLine(penGrayDot, nx(x), ny(0), nx(x), ny(1));
}
Rectangle RC_DateText = new Rectangle(nx(x) - XTickOffset, ny(0), XTickWidth, XTickHeight);
string dateString = markets[nBgn + n].Time.ToShortDateString();
TextRenderer.DrawText(g, dateString, font, RC_DateText, Color.Black, mid);
}
}
#endregion
// -- Y軸罫線,目盛り --
// -- Ticks --
double tick = 0; // 一目盛りの大きさ
//for (int i = 0; Tick[i] < (max - min); i++)
//{
// tick = Tick[i];
//}
for (int i = 0; i < Tick.Length; i++)
{
double _tick = Tick[i];
if (7 * _tick < (max - min))
{
tick = _tick;
}
else
{
continue;
}
}
// Tick単位の大きさに変更
max = Math.Ceiling(max / tick) * tick; // 大きい側を使用
min = Math.Floor(min / tick) * tick; // 小さい側を使用
// Y軸の目盛りの数
int numTicks = (int)((max - min) / tick);
#region Y軸罫線
// -- Y軸罫線,目盛り --
int nChars = (int)Math.Log10(max) + 1; // 桁数 (あやしい)
for (int n = 0; n <= numTicks; n++)
{
double y = (double)n / numTicks; // 正規化
if (n > 0 && n <= numTicks)
{
//Y軸の罫線
g.DrawLine(penGrayDot, nx(0), ny(y), nx(1), ny(y));
}
// Y軸の目盛り文字
RectTitle = new Rectangle(nx(1), ny(y) - (n > 0 ? 10 : 14), 50, 20);
string num = (min + n * tick).ToString();
//string num = (min + n * tick).ToString().PadLeft(nChars);
TextRenderer.DrawText(g, num, font, RectTitle, Color.Black, left);
}
#endregion
#region ろうそく足
// -- ろうそく足
for (int n = 1; n < NDATA; n++)
{
double xCenter = (n + 0.5) / NDATA; // 区間の中央
double yLow = (markets[n + nBgn].Low - min) / (max - min);
double yHigh = (markets[n + nBgn].High - min) / (max - min);
double yOpen = (markets[n + nBgn].Open - min) / (max - min);
double yClose = (markets[n + nBgn].Close - min) / (max - min);
int top = ny(Math.Max(yOpen, yClose)); // 高値
int bottom = ny(Math.Min(yOpen, yClose)); // 低値
// もし同じの場合は1pixずらしておく.
if (bottom == top) bottom++;
// ろうそくの色を選択
Brush brush = (yClose > yOpen) ? Brushes.Red : Brushes.Blue;
Pen pen = (yClose > yOpen) ? Pens.Red : Pens.Blue;
// ボックス
int BoxWidth = 4;
int BoxHarfWidth = 2;
int BoxHeight = bottom - top;
// ボックス表示
g.FillRectangle(brush, nx(xCenter) - BoxHarfWidth, top, BoxWidth, BoxHeight);
// 足
g.DrawLine(pen, nx(xCenter), ny(yLow), nx(xCenter), ny(yHigh));
}
#endregion
#region 移動平均
Pen penGreenMA = new Pen(Color.Green, 2);
for (int n = 1; n < NDATA; n++)
{
int index = n + nBgn;
double y1 = (markets[index - 1].WeightedAverage - min) / (max - min);
double y2 = (markets[index].WeightedAverage - min) / (max - min);
double x1 = (double)(n - 1) / NDATA;
double x2 = (double)n / NDATA;
Point pt1 = new Point(nx(x1), ny(y1));
Point pt2 = new Point(nx(x2), ny(y2));
g.DrawLine(penGreenMA, pt1, pt2);
}
#endregion
guide(g);
}
void fillPolygon(Graphics g, int bgn, int end, Brush brush)
{
int n = bgn;
double x = 0, y;
double xBgn = (double)bgn / markets.Count;
Point[] pt = new Point[end - bgn + 3];
pt[0] = new Point(nx(xBgn) + 1, ny2(0)); // 原点
while (n < end)
{
x = (n + 0.5) / markets.Count;
y = markets[n].Close / vMax;
pt[++n - bgn] = new Point(nx(x) + 1, ny2(y)); // 折れ線グラフに相当
}
pt[1 + n - bgn] = new Point(nx(x) + 1, ny2(0));
pt[2 + n - bgn] = new Point(nx(xBgn) + 1, ny2(0));
g.FillPolygon(brush, pt);
}
// 画面下のガイド
void guide(Graphics g)
{
// -- 画面下のガイド用
Rectangle RCChart2 = new Rectangle(nx(0), ny2(1), nx(1) - nx(0), ny2(0) - ny2(1));
g.DrawRectangle(Pens.Black, RCChart2);
fillPolygon(g, 0, markets.Count, Brushes.Gray);
fillPolygon(g, nBgn, nBgn + NDATA, Brushes.Pink);
// 目盛り文字
string month = null;
for (int n = 0; n < markets.Count; n++)
{
double x = (double)n / markets.Count;
if (markets[n].Time.Month.ToString() != month)
{
month = markets[n].Time.Month.ToString();
if (int.Parse(month) % 3 == 0) // 間引き
{
int text_width = 80;
int text_height = 18;
int offset_x = text_width / 2;
int offset_y = -0;
Rectangle RCChart2Text = new Rectangle(nx(x) - offset_x, ny2(0) - offset_y, text_width, text_height);
string text = $"{markets[n].Time.Year}.{markets[n].Time.Month}";
TextRenderer.DrawText(g, text, font2, RCChart2Text, Color.Black, Color.White, mid);
}
}
}
}
}
/// <summary>
/// チャートデータ
/// </summary>
class MyMarketChartData
{
public DateTime Time;
public double Open;
public double Close;
public double High;
public double Low;
public double VolumeBase;
public double VolumeQuote;
public double WeightedAverage;
public MyMarketChartData(DateTime time,
double open, double close, double high, double low,
double volumebase, double volumequote, double weitedaverage)
{
this.Time = time;
this.Open = open;
this.Close = close;
this.High = high;
this.Low = low;
this.VolumeBase = volumebase;
this.VolumeQuote = volumequote;
this.WeightedAverage = weitedaverage;
}
}
}
#コントロールフォーム例
using Jojatekok.PoloniexAPI;
using Jojatekok.PoloniexAPI.MarketTools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace PloniexApi.Net.FormDemo
{
public partial class Form4 : Form
{
private PoloniexClient PloniexClient { get; set; }
public Form4()
{
InitializeComponent();
PloniexClient = new PoloniexClient(ApiKeys.PublicKey, ApiKeys.PrivateKey);
string pair1 = "BTC";
string pair2 = "ETH";
CurrencyPair pair = new CurrencyPair(pair1, pair2);
string backuppath = $"CharData_{pair.ToString()}.bit";
if (System.IO.File.Exists(backuppath) == false)
{
var task = LoadMarketSummaryAsync();
}
var markets = (IList<IMarketChartData>)FiBinaryWriter.LoadFromBinaryFile(backuppath);
List < MyMarketChartData > chartData = new List<MyMarketChartData>();
for (int i = 0; i < markets.Count; i++)
{
var _ChartData = new MyMarketChartData(
markets[i].Time,
markets[i].Open,
markets[i].Close,
markets[i].High,
markets[i].Low,
markets[i].VolumeBase,
markets[i].VolumeQuote,
markets[i].WeightedAverage);
chartData.Add(_ChartData);
}
candleChart1.SetMarketChartData(chartData);
}
/// <summary>
/// WebAPIにてチャートデータ取得
/// </summary>
/// <returns></returns>
private async Task LoadMarketSummaryAsync()
{
string pair1 = "BTC";
string pair2 = "ETH";
CurrencyPair pair = new CurrencyPair(pair1, pair2);
var markets = await PloniexClient.Markets.GetChartDataAsync(
currencyPair: pair,
period: MarketPeriod.Minutes15,
startTime: new DateTime(2010, 5, 12, 20, 30, 15, 123),
endTime: new DateTime(2017, 5, 31, 20, 30, 15, 123)
);
markets.RemoveAt(0);
string backuppath = $"CharData_{pair.ToString()}.bit";
FiBinaryWriter.SaveToBinaryFile(markets, backuppath);
}
}
}