【C#】Chartコントロールでローソク足チャートを表示させてみる

C#で「ローソク足チャートを表示させたい」とお悩みではありませんか?
この記事では、C#エンジニア歴10年の筆者が、C#のChartコントロールを活用してローソク足チャートを作成する方法を徹底解説します。この技術を習得することで、金融データの可視化ツールやトレードアプリの開発に役立てることができます。

記事を読むことで得られる3つのポイント

  1. C#のChartコントロールの基本的な使い方
    ローソク足チャートを表示するためのコントロール配置やプロパティ設定の基礎がわかります。
  2. データベース連携を通じたローソク足データの取得方法
    Pan Active Marketライブラリを活用したデータの取得方法を具体的に学べます。
  3. ローソク足チャートに移動平均を追加する実践テクニック
    FinancialFormulaメソッドを使用し、チャートをさらに見やすく、分析しやすい形に仕上げる方法を習得できます。

記事を読み終えるころには、ローソク足チャート作成の基礎から応用までを理解し、自分のアプリケーションに実装できるスキルが身についているはずです。

では、早速始めましょう!

動作確認環境

  • Windows10
  • Visual studio Community 2017 
  • ターゲットフレームワーク .NET Framework 4.6.1

コントロールの配置とプロパティ

コントロールプロパティ
chart1AnchorTop,Bottom,Left, right
txtCodeText“1308”
lblNameText“銘柄名”
btnViewText“表示”
numericMAMaximum25
Minimum 5

参照設定

Pan Active Market DataBaseの参照設定は下記を参考にして下さい。

PanActiveDBAc.cs:Pan Active MarketDataBaseのラッパー

using System;
using System.Data;
using System.Collections.Generic;

namespace PanActiveMarket
{
    class Prices
    {
        private ActiveMarket.Prices prices = new ActiveMarket.Prices();
        /// <summary>
        /// Readメソッドで読み出すデータの日付位置の最小値
        /// </summary>
        public int ReadBegin
        {
            set
            {
                prices.ReadBegin = value;
            }
            get
            {
                return (prices.ReadBegin);
            }

        }
        /// <summary>
        /// Readメソッドで読み出すデータの日付位置の最大値
        /// </summary>
        public int ReadEnd
        {
            set
            {
                prices.ReadEnd = value;
            }
            get
            {
                return (prices.ReadEnd);
            }

        }
        /// <summary>
        ///  四本値と出来高をオブジェクト内に読み込む
        /// </summary>
        public void Read(string code)
        {

            try
            {
                prices.Read(code);
            }
            catch
            {
                
            }
        }
        /// <summary>
        /// 日付位置の最大値を返す
        /// </summary>
        public int End()
        {
            return (prices.End());
        }
        /// <summary>
        /// 日付位置の最小値を返す
        /// </summary>
        public int Begin()
        {
            return (prices.Begin());
        }
        /// <summary>
        /// 指定した日付位置が休場日かどうかを返す
        /// </summary>
        public bool IsClosed(int DatePos)
        {
            // ヘルプでは、boolを返すのに、intで定義されている。
            bool result = (prices.IsClosed(DatePos) == 0) ? false : true;
            return result;

        }
        /// <summary>
        /// 指定した日付位置の始値を返す
        /// </summary>
        public double Open(int DatePos)
        {
            return (prices.Open(DatePos));
        }
        /// <summary>
        /// 指定した日付位置の高値を返す
        /// </summary>
        public double High(int DatePos)
        {
            return (prices.High(DatePos));
        }
        /// <summary>
        /// 指定した日付位置の安値を返す
        /// </summary>
        public double Low(int DatePos)
        {
            return (prices.Low(DatePos));
        }
        /// <summary>
        /// 指定した日付位置の終値を返す
        /// </summary>
        public double Close(int DatePos)
        {
            return (prices.Close(DatePos));
        }
        /// <summary>
        /// 指定した日付位置の出来高を返す
        /// </summary>
        public double Volume(int DatePos)
        {
            return (prices.Volume(DatePos));
        }
        /// <summary>
        /// 指定した日付位置の権利落ち乗数を返す
        /// </summary>
        public double ExRights(int DatePos)
        {
            return (prices.ExRights(DatePos));
        }
        /// <summary>
        /// true を設定すると権利落ち修正した価格を読み込みます
        /// </summary>
        public bool AdjustExRights
        {
            set
            {
                // AdjustExRightsはヘルプでみるとboolなのに、なぜかintで定義されている
                if (value == false)
                {
                    prices.AdjustExRights = 0;
                }
                else
                {
                    prices.AdjustExRights = 1;
                }
            }
        }
        /// <summary>
        /// 読み込んでいるいる銘柄名を返す
        /// </summary>
        public string Name()
        {
            return (prices.Name());
        }

        /// <summary>
        /// Panデータから4本値のDataTableを返す
        /// </summary>
        /// <param name="code">銘柄コード</param>
        /// <param name="beginDay">最初の日付</param>
        /// <param name="endDay">最終の日付</param>
        /// <returns>DataTable型の4本値 </returns>
        public DataTable GetDataTable(string code, DateTime beginDay, DateTime endDay, bool nan = false)
        {
            DataTable dtable = new DataTable();
            Calendar calendar = new Calendar();
            dtable.Columns.Add("date");
            dtable.Columns.Add("high_v", Type.GetType("System.Double"));
            dtable.Columns.Add("low_v", Type.GetType("System.Double"));
            dtable.Columns.Add("open_v", Type.GetType("System.Double"));
            dtable.Columns.Add("close_v", Type.GetType("System.Double"));
            dtable.Columns.Add("volume_v", Type.GetType("System.Double"));
            dtable.Columns.Add("exrights_v", Type.GetType("System.Double"));

            int begin = 0;
            int end = 0;
            try
            {
                // 四本値と出来高を読み込む
                prices.Read(code);
                // 日付位置の最小値と最大値を読み込む
                end = prices.End();
                begin = prices.Begin();
            }
            catch (Exception a)
            {
                // 例外処理
                //イミディエイトウィンドウにエラー表示
                Console.WriteLine(a.Message);
                dtable = null;
                return dtable;
            }
            int dataPosBegin = calendar.DatePosition(beginDay, -1);    // 最初の日付位置を取得
            int dataPosEnd = calendar.DatePosition(endDay, -1);        // 最終の日付位置を取得

            // 最初の日付と最終日付を補正
            if (dataPosBegin <= end && dataPosEnd >= begin)
            {
                if (dataPosBegin >= begin)
                {
                    begin = dataPosBegin;
                }
                if(dataPosEnd < end)
                {
                    end = dataPosEnd;
                }
            } 
            else
            {
                // 指定した期間にデータが存在しない
                dtable = null;
                return dtable;
            }
            for (int i = begin; i <= end; i++)
            {
                if (prices.IsClosed(i) == 0)
                {
                    DataRow dr = dtable.NewRow();
                    try
                    {
                        dr["date"] = calendar.Date(i);
                        dr["high_v"] = prices.High(i);
                        dr["low_v"] = prices.Low(i);
                        dr["open_v"] = prices.Open(i);
                        dr["close_v"] = prices.Close(i);
                        dr["volume_v"] = prices.Volume(i);
                    }
                    catch (Exception a)
                    {
                        //イミディエイトウィンドウにエラー表示
                        Console.WriteLine(a.Message);
                        continue;

                    }
                    dtable.Rows.Add(dr);
                }
                else
                {
                    
                    Console.WriteLine("休場日");
                    if (nan == true) {
                        // 休場日には、非数を代入する。
                        DataRow dr = dtable.NewRow();
                        dr["date"] = calendar.Date(i);
                        dr["high_v"] = Double.NaN;
                        dr["low_v"] = Double.NaN;
                        dr["open_v"] = Double.NaN;
                        dr["close_v"] = Double.NaN;
                        dr["volume_v"] = Double.NaN;
                        dtable.Rows.Add(dr);
                    }

                }
            }
            return dtable;
        }

    }
  
    class Calendar
    {
        private ActiveMarket.Calendar calendar = new ActiveMarket.Calendar();

        /// <summary>
        /// DatePosで指定した日付位置に対応する日付を返す
        /// </summary>
        /// <param name="Datepos">日付位置</param>
        /// <returns>日付</returns>
        public DateTime Date(int Datepos)
        {
            return (calendar.Date(Datepos));

        }
        // 
        /// <summary>
        /// dateで指定した日付に対応する日付位置を返す
        /// </summary>
        /// <param name="date">日付</param>
        /// <param name="direction">検索方向 -1:過去へ、1:未来へ</param>
        /// <returns>日付位置</returns>
        public int DatePosition(DateTime date, int direction = 0)
        {
            return (calendar.DatePosition(date, direction));
        }
    }

    class AllCodeNames
    {
        private ActiveMarket.Names names = new ActiveMarket.Names();
        private Array codeAr;
        private Array nameAr;
        public Dictionary<string, string> dict = new Dictionary<string, string>();
        // 銘柄コードと銘柄名の一覧を取得します
        public void AllNames()
        {
            names.AllNames(ActiveMarket._KindFlag.AM_KINDFLAG_SPOTS, out codeAr, out nameAr);
        }
        // 銘柄コードと銘柄名の一覧を取得します
        // dictへ格納
        public AllCodeNames()
        {
            AllNames();

            for (int i = 0; i < codeAr.Length; i++)
            {

                dict.Add(Convert.ToString(codeAr.GetValue(i + 1)), Convert.ToString(nameAr.GetValue(i + 1)));
            }

        }

    }
}

Form1.cs

表示期間の設定

「cmbPeriod」コントロールを使用して、ローソク足チャートの表示期間を設定します。
サンプルでは、次のような表示期間を動的に追加しています。

  • 1か月
  • 2か月
  • 6か月
  • 1年
  • 期間指定(日付を手動で設定)

期間指定を選択する場合は、「dateTPbegin」や「dateTPend」で日付を指定する必要があります。一方で、その他の選択肢を選んだ場合は、「期間(月数)」を自動計算して開始日が設定されます。

例えば、2か月を選択すると、現在の終了日(dateTPend.Value)から2か月前を開始日(dateTPbegin.Value)として設定する仕組みです。

この機能により、動的に異なる表示期間を簡単に切り替えることができ、柔軟なチャート表示が可能になります。


            cmbPeriod.DataSource = dataPeriod;
            cmbPeriod.DisplayMember = "表示期間";
            cmbPeriod.ValueMember = "データ列";

移動平均

移動平均には、FinancialFormulaメソッドを使用し、数式の種別を設定すると自動的に計算されます。サンプルでは、移動平均FinancialFormula.MovingAverageを指定しています。

数式の種類はこちら

移動平均の計算と適用

chart1.DataManipulator.FinancialFormula(FinancialFormula.MovingAverage, numericMA.Value.ToString(), legend1 + ":Y4", legend2);
  • numericMA.Value で移動平均の計算期間を指定する(例:5日、10日など)
  • legend1 はローソク足のデータ系列名
  • legend2 は移動平均のデータ系列名

移動平均線のプロパティ設定

chart1.Series[legend2].ChartType = SeriesChartType.Line;
chart1.Series[legend2].Legend = chart1.Series[legend1].Legend;
chart1.Series[legend2].LegendText = "単純移動平均";
chart1.Series[legend2].ToolTip = "日付:#VALX\n移動平均値:#VALY";
  • ChartType で表示形式を指定(ラインチャートを選択)
  • LegendText で凡例に移動平均の説明を追加
  • ToolTip でデータポイントにカーソルを合わせた際の情報を設定

系列間のデータ整合性の補完

移動平均の計算結果がローソク足データのポイント数と一致しない場合、データを補完する必要があります。以下のコードでデータ系列間の整合性を保ちます。

for (int i = dataCount - chart1.Series[legend2].Points.Count - 1; i >= 0; i--)
{
    DataPoint newDataPoint = new DataPoint();
    newDataPoint.XValue = chart1.Series[legend1].Points[i].XValue;
    newDataPoint.IsEmpty = true; // 空のデータとして補完
    chart1.Series[legend2].Points.Insert(0, newDataPoint);
}

以下は、ローソク足と移動平均を表示するサンプルプログラムです。ローソク足は全て日足で、週足、月足の設定はできません。コードを入力し、表示期間と移動平均計算日数を設定して、「検索」ボタンを押下します。表示期間が「期間指定」の場合は、あらかじめ日付を設定しておきます。

プログラムコード

using System;
using System.Data;
using System.Windows.Forms;

using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;
using PanActiveMarket;
namespace CandleChart
{
    public partial class Form1 : Form
    {
        private Prices prices = new Prices();
        private Calendar calendar = new Calendar();
        private AllCodeNames allCodeNames = new AllCodeNames();
        private DataTable dataPeriod = new DataTable();

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            dataPeriod.Columns.Add("表示期間");
            dataPeriod.Columns.Add("データ列");
            DataRow row = dataPeriod.NewRow();
            row["表示期間"] = "1か月";
            row["データ列"] = 1;
            dataPeriod.Rows.Add(row);

            row = dataPeriod.NewRow();
            row["表示期間"] = "2か月";
            row["データ列"] = 2;
            dataPeriod.Rows.Add(row);

            row = dataPeriod.NewRow();
            row["表示期間"] = "6か月";
            row["データ列"] = 6;
            dataPeriod.Rows.Add(row);

            row = dataPeriod.NewRow();
            row["表示期間"] = "1年";
            row["データ列"] = 12;
            dataPeriod.Rows.Add(row);

            row = dataPeriod.NewRow();
            row["表示期間"] = "期間指定";
            row["データ列"] = -1;
            dataPeriod.Rows.Add(row);
            cmbPeriod.DataSource = dataPeriod;
            cmbPeriod.DisplayMember = "表示期間";
            cmbPeriod.ValueMember = "データ列";
            int period = 2;
            cmbPeriod.SelectedIndex = period;
            cmbPeriod.DropDownStyle = ComboBoxStyle.DropDownList; // キーボードからの入力を禁止する
            int value = Convert.ToInt32(cmbPeriod.SelectedValue);
            dateTPbegin.Value = dateTPend.Value.AddMonths(0 - value); // 期間設定
        }

        private void btnView_Click(object sender, EventArgs e)
        {
            prices.AdjustExRights = true;    // 権利落ち修正した価格を読み込む。

            string legend1 = txtCode.Text;  // ローソク足系列
            string legend2 = "SMA";         // 移動平均系列
            int value = Convert.ToInt32(cmbPeriod.SelectedValue);
            DataTable PanStock;
            if ( value < 0)
            {
                // 期間指定
                PanStock = prices.GetDataTable(legend1, dateTPbegin.Value, dateTPend.Value);
            }
            else
            {
                // 月数指定
                DateTime endDate = dateTPend.Value;
                DateTime beginDate = endDate.AddMonths(0 - value); 
                PanStock = prices.GetDataTable(legend1, beginDate, endDate);
            }
            

            if (allCodeNames.dict.ContainsKey(legend1) == false)
            {
                // 存在しないコード
                return;
            }

            if (PanStock is null)
            {
                // オブジェクトが存在しない。
                return;
            }
            int dataCount = PanStock.Rows.Count;
            if (dataCount == 0)
            {
                // データなし
                return;
            }

            string strName = allCodeNames.dict[txtCode.Text];  // 銘柄名を取得する
            chart1.Titles.Clear();
            chart1.Titles.Add(strName);
            chart1.ChartAreas["ChartArea1"].AxisX.ScaleView.Size = dataCount;

            chart1.ChartAreas["ChartArea1"].AxisX.IsMarginVisible = true;   // マージンあり
            chart1.ChartAreas["ChartArea1"].AxisY.IsStartedFromZero = false; // 軸の最小値を0に設定しない

            chart1.Series.Clear(); //グラフ初期化(系列を全て削除)

            chart1.Series.Add(legend1); //データ系列追加
            chart1.Series.Add(legend2); //データ系列追加
            chart1.Series[legend1].ChartArea = "ChartArea1";    // ChartArea1と紐づけする

            chart1.Series[legend1].ChartType = SeriesChartType.Candlestick;
            chart1.Series[legend1].LegendText = "株価"; //凡例に表示するテキストを指定
            chart1.Series[legend1].IsXValueIndexed = true;
            chart1.Series[legend1]["PriceUpColor"] = Color.Red.Name;
            chart1.Series[legend1]["PriceDownColor"] = Color.Blue.Name;
            chart1.Series[legend1].ToolTip = "日付:#VALX\n高値:#VALY\n安値:#VALY2\n始値" +
                                            ":#VALY3\n終値:#VALY4";
            object date;
            double high;
            double low;
            double open;
            double end;

            // ローソク足の表示
            for (int i = 0, j = 0 ; i < dataCount; i++, j++)
            {
                date = PanStock.Rows[j]["date"];
                high = Convert.ToDouble(PanStock.Rows[j]["high_v"]);
                low = Convert.ToDouble(PanStock.Rows[j]["low_v"]);
                open = Convert.ToDouble(PanStock.Rows[j]["open_v"]);
                end = Convert.ToDouble(PanStock.Rows[j]["close_v"]);

                // データポイントの値の登録
                chart1.Series[legend1].Points.AddXY(DateTime.Parse(date.ToString()), high);
                chart1.Series[legend1].Points[i].YValues[1] = low;
                chart1.Series[legend1].Points[i].YValues[2] = open;
                chart1.Series[legend1].Points[i].YValues[3] = end;
            }
            value = chart1.Series[legend1].Points.Count;
            chart1.DataManipulator.FinancialFormula(FinancialFormula.MovingAverage, numericMA.Value.ToString(), legend1 + ":Y4", legend2);
            chart1.Series[legend2].ChartType = SeriesChartType.Line;
            chart1.Series[legend2].ChartArea = "ChartArea1";    // ChartArea1と紐づけする
            chart1.Series[legend2].Legend = chart1.Series[legend1].Legend;
            chart1.Series[legend2].IsXValueIndexed = true;
            chart1.Series[legend2].LegendText = "単純移動平均";
            chart1.Series[legend2].ToolTip = "日付:#VALX\n移動平均値:#VALY";
            // 系列間のデータ数を合わせるためにデータを補完する
            for (int i = dataCount - chart1.Series[legend2].Points.Count - 1; i >= 0; i--)
            {
                DataPoint newDataPoint = new DataPoint();
                newDataPoint.XValue = chart1.Series[legend1].Points[i].XValue;
                newDataPoint.IsEmpty = true;    // 空のデータを設定する
                chart1.Series[legend2].Points.Insert(0, newDataPoint);
            }
        }

        private void cmbPeriod_SelectionChangeCommitted(object sender, EventArgs e)
        {
            // 期間が変更された時は、dateTimePicker2を起点に再計算されて設定されます。
            int value = Convert.ToInt32(cmbPeriod.SelectedValue);
            if (value > 0)
            {
                dateTPbegin.Value = dateTPend.Value.AddMonths(0 - value); // 期間設定
            }

        }

    }
}

実行結果

まとめ

「C#のChartコントロールを使ってローソク足チャートを表示させる」方法について解説してきました。今回の記事で学んだポイントを振り返りながら、要点をまとめます。この記事で学んだポイントをまとめ、習得した内容を振り返りましょう。

  1. Chartコントロールを使った基本的なローソク足チャートを作成できる
    Chartコントロールのプロパティ設定やデータの紐づけ方法を理解することで、ローソク足チャートを簡単に描画できるようになります。
  2. Pan Active Marketを活用した四本値データの取得と加工ができる
    Pan Active Marketライブラリを活用し、正確な四本値データを取得する方法を学びました。これにより、データの可視化が効率的に行えます。
  3. 移動平均を取り入れたチャートをカスタマイズできる
    FinancialFormulaメソッドを使った移動平均線の追加によって、より実用的で見やすいチャートを作成するスキルを習得しました。

C#のChartコントロールは柔軟性が高く、多機能であるため、今回紹介した内容をベースにさらに高度なチャート作成に挑戦することもできます。

金融データの可視化やトレードツールの作成を考えているなら、ぜひこの記事を参考にして実践してみて下さい。

DMM FX 外国為替証拠金取引の外為オンライン
タイトルとURLをコピーしました