[C#][群益 Api]計算 1 分 K 線與產生 KD 技術指標
股市的 K 線圖是所有技術指標計算的基礎,我在此篇教學文章的重點將會放在串接群益 Api 報價之後,取得某商品即時價格,然後計算 1 分 K 線,再產生技術指標。
技術指標有非常多種類,而我這次先以最常見的 KD 指標來做範例教學。
我在之前的文章已經有講解如何連線登入群益 Api 取得報價,可參考:
官方範例下載與安裝環境 #CH1
帳號登入、取得下單帳號教學 #CH2
取得商品報價、Tick、最佳 5 檔教學 #CH3
這裡我額外寫了一個範例,已經將連線 Api 與報價完成,接著就計算技術指標。
下圖紅框處是今天分享的重點。
此範例是由 Visual Studio 2022 開發的 Windows Form .Net Core 6 專案。
文章會講解重點程式碼,若需要完整範例程式碼,可至文末下載 (需付費)。
搜集報價資料
當訂閱報價之後,群益會透過事件方法返回報價資料,我註冊的事件名稱是 OnNotifyQuoteLONG
事件方法是 m_SKQuoteLib_OnNotifyQuoteLONG
1 2 |
// 註冊國內報價回傳事件 m_SKQuoteLib.OnNotifyQuoteLONG += new _ISKQuoteLibEvents_OnNotifyQuoteLONGEventHandler(m_SKQuoteLib_OnNotifyQuoteLONG); |
以下是 m_SKQuoteLib_OnNotifyQuoteLONG 接收報價的語法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
/// <summary> /// 國內報價回應事件 /// </summary> /// <param name="sMarketNo"></param> /// <param name="nStockIdx"></param> void m_SKQuoteLib_OnNotifyQuoteLONG(short sMarketNo, int nStockIdx) { // 報價資訊物件 SKSTOCKLONG pSKStockLONG = new SKSTOCKLONG(); // 取得最新報價寫入報價資訊物件 m_SKQuoteLib.SKQuoteLib_GetStockByIndexLONG(sMarketNo, nStockIdx, ref pSKStockLONG); // 將報價資訊物件輸出在 DataGridView onUpdateQuote(pSKStockLONG); } /// <summary> /// 更新最新報價 /// </summary> private void onUpdateQuote(SKSTOCKLONG pSKStockLONG) { if (dtQuote == null) { // 報價物件寫入 Datatable dtQuote = new DataTable(); dtQuote.Columns.Add("QuoteName"); dtQuote.Columns.Add("QuoteValue"); DataRow drNew = dtQuote.NewRow(); drNew["QuoteName"] = "代碼"; drNew["QuoteValue"] = pSKStockLONG.bstrStockNo; dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "名稱"; drNew["QuoteValue"] = pSKStockLONG.bstrStockName; dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "開盤價"; drNew["QuoteValue"] = pSKStockLONG.nOpen / (Math.Pow(10, pSKStockLONG.sDecimal)); dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "最高價"; drNew["QuoteValue"] = pSKStockLONG.nHigh / (Math.Pow(10, pSKStockLONG.sDecimal)); dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "最低價"; drNew["QuoteValue"] = pSKStockLONG.nLow / (Math.Pow(10, pSKStockLONG.sDecimal)); dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "成交價"; drNew["QuoteValue"] = pSKStockLONG.nClose / (Math.Pow(10, pSKStockLONG.sDecimal)); dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "單量"; drNew["QuoteValue"] = pSKStockLONG.nTickQty; dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "昨收價"; drNew["QuoteValue"] = pSKStockLONG.nRef / (Math.Pow(10, pSKStockLONG.sDecimal)); dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "買價"; drNew["QuoteValue"] = (pSKStockLONG.nBid / (Math.Pow(10, pSKStockLONG.sDecimal))).ToString(); dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "買量"; drNew["QuoteValue"] = pSKStockLONG.nBc; dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "賣價"; drNew["QuoteValue"] = (pSKStockLONG.nAsk / (Math.Pow(10, pSKStockLONG.sDecimal))).ToString(); dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "賣量"; drNew["QuoteValue"] = pSKStockLONG.nAc; dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "總量"; drNew["QuoteValue"] = pSKStockLONG.nTQty; dtQuote.Rows.Add(drNew); drNew = dtQuote.NewRow(); drNew["QuoteName"] = "時間"; drNew["QuoteValue"] = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); dtQuote.Rows.Add(drNew); //輸出 GridView gvQuote.DataSource = dtQuote; gvQuote.Refresh(); } else { // 報價物件更新 Datatable DataRow dr = dtQuote.Select("QuoteName='開盤價'")[0]; dr["QuoteValue"] = pSKStockLONG.nOpen / (Math.Pow(10, pSKStockLONG.sDecimal)); dr = dtQuote.Select("QuoteName='最高價'")[0]; dr["QuoteValue"] = pSKStockLONG.nHigh / (Math.Pow(10, pSKStockLONG.sDecimal)); dr = dtQuote.Select("QuoteName='最低價'")[0]; dr["QuoteValue"] = pSKStockLONG.nLow / (Math.Pow(10, pSKStockLONG.sDecimal)); dr = dtQuote.Select("QuoteName='成交價'")[0]; dr["QuoteValue"] = pSKStockLONG.nClose / (Math.Pow(10, pSKStockLONG.sDecimal)); dr = dtQuote.Select("QuoteName='單量'")[0]; dr["QuoteValue"] = pSKStockLONG.nTickQty; dr = dtQuote.Select("QuoteName='昨收價'")[0]; dr["QuoteValue"] = pSKStockLONG.nRef / (Math.Pow(10, pSKStockLONG.sDecimal)); dr = dtQuote.Select("QuoteName='買價'")[0]; dr["QuoteValue"] = (pSKStockLONG.nBid / (Math.Pow(10, pSKStockLONG.sDecimal))).ToString(); dr = dtQuote.Select("QuoteName='買量'")[0]; dr["QuoteValue"] = pSKStockLONG.nBc; dr = dtQuote.Select("QuoteName='賣價'")[0]; dr["QuoteValue"] = (pSKStockLONG.nAsk / (Math.Pow(10, pSKStockLONG.sDecimal))).ToString(); dr = dtQuote.Select("QuoteName='賣量'")[0]; dr["QuoteValue"] = pSKStockLONG.nAc; dr = dtQuote.Select("QuoteName='總量'")[0]; dr["QuoteValue"] = pSKStockLONG.nTQty; dr = dtQuote.Select("QuoteName='時間'")[0]; dr["QuoteValue"] = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); } } |
這裡只是將報價呈現在畫面上而已。
建立價格列表
首先先建立價格物件 ClosePriceModel
1 2 3 4 5 6 |
public class ClosePriceModel { public DateTime Datetime { get; set; } public double Price { get; set; } public int Qty { get; set; } } |
然後在 Form1.cs 建立價格物件列表
1 |
List<ClosePriceModel> listPriceCollect = new List<ClosePriceModel>(); //搜集收盤價 |
在 onUpdateQuote
方法最後面加上這段:
1 2 3 4 5 6 7 |
// 搜集報價 listPriceCollect.Add(new ClosePriceModel { Datetime = DateTime.Now, Price = pSKStockLONG.nClose / (Math.Pow(10, pSKStockLONG.sDecimal)), Qty = pSKStockLONG.nTickQty }); |
將即時的價格搜集起來。
計算 1 分 K 線
我們先建立一個 K 線物件:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class KlineModel { public int idx { get; set; } public DateTime KlineTime { get; set; } public double Open { get; set; } public double High { get; set; } public double Low { get; set; } public double Close { get; set; } public int Qty { get; set; } public double? Kd_K { get; set; } public double? Kd_D { get; set; } } |
然後在 Form1.cs 新增 K 線物件列表:
1 |
List<KlineModel> listKline = new List<KlineModel>(); //搜集k線 |
宣告一個 timer 設定 1 秒觸發一次,然後判斷秒數等於 0 秒時,計算 K 線。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/// <summary> /// 定時 1 秒事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer1_Tick(object sender, EventArgs e) { if (DateTime.Now.ToString("ss") == "00") { // 計算上一分鐘K線 DateTime lastMinute = DateTime.Now.AddMinutes(-1); List<ClosePriceModel> items = listPriceCollect.Where(m => m.Datetime >= lastMinute).ToList(); if (items.Count() > 0) { // 搜集k線 listKline.Add(new KlineModel() { idx = listKline.Count, KlineTime = lastMinute, Open = items.First().Price, High = items.Max(m => m.Price), Low = items.Min(m => m.Price), Close = items.Last().Price, Qty = items.Sum(m => m.Qty) }); // 輸出至 DataGridView gvKline.DataSource = listKline.ToList(); } } } |
執行過後,每隔 1 分鐘就會顯示一筆資料。
計算 KD 指標
我們在建立 K 線物件時,已經預留了 KD 指標欄位:
而計算 KD 指標只要將 listKline 這個列表帶入公式就可以算出來了。
這裡我提供兩個計算 K 值和 D 值的公式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
/// <summary> /// 計算 KD 的 K 值 /// </summary> /// <param name="ary"></param> /// <param name="rownum"></param> /// <returns></returns> private void CalcKD_K(List<KlineModel> listKline, int days) { if (listKline.Count < days) { return; } /* * 第n天收盤價-最近n天內最低價 RSV =────────────────×100 最近n天內最高價-最近n天內最低價 計算出RSV之後,再來計算K值與D值。 當日K值(%K)= 2/3 前一日 K值 + 1/3 RSV */ for (int i = 0; i < listKline.Count; i++) { if ((i + 1) - days < 0) { continue; } double val = 0; double beforeK = 0; // 取得前一天K值 if (listKline[i - 1].Kd_K == null) { beforeK = 50; } else { beforeK = Convert.ToDouble(listKline[i - 1].Kd_K); } // 取當天收盤價 double nowClose = listKline[i].Close; //取最近n天內最低價 double minPrice = listKline.Where(w => w.idx > i - days && w.idx <= i).Min(s => s.Low); if (minPrice == 0) { continue; } //取最近n天內最高價 double maxPrice = listKline.Where(w => w.idx > i - days && w.idx <= i).Max(s => s.High); double RSV = ((nowClose - minPrice) / (maxPrice - minPrice) * 100); if (double.IsNaN(RSV) == false) { val = ((beforeK * 2 / 3) + (RSV / 3)); val = Math.Round(val, 2); } else { val = beforeK; } if (val > 0) { listKline[i].Kd_K = val; } } } /// <summary> /// 計算 KD 的 D 值 /// </summary> private void CalcKD_D(List<KlineModel> listKline, int days) { if (listKline.Count < days) { return; } /* 當日D值(%D)= 2/3 前一日 D值+ 1/3 當日K值 */ double val = 0; double beforeD = 0; for (int i = 0; i < listKline.Count; i++) { if (i + 1 < days) { continue; } // 取得前一天D值 if (listKline[i - 1].Kd_D == null) { beforeD = 50; } else { beforeD = (double)listKline[i - 1].Kd_D; } // 取當日K值 if (listKline[i].Kd_K == null) { continue; } double K9 = (double)listKline[i].Kd_K; val = (beforeD * 2 / 3) + (K9 / 3); if (val > 0) { val = Math.Round(val, 2); listKline[i].Kd_D = val; } } } |
使用方法就是呼叫並帶入參數就可以了:
1 2 3 |
CalcKD_K(listKline, Convert.ToInt32(txtKdParam.Text)); CalcKD_D(listKline, Convert.ToInt32(txtKdParam.Text)); gvKlineKD.DataSource = listKline.ToList(); |
完成之後,執行程式,經過幾分鐘計算 K 線就可以顯示以下的結果。
範例下載
實際網頁專案開發範例
此連結是我實際應用在網頁上呈現的範例: Winvest 雲投資
相關學習文章
- 【C# 群益 API 開發教學】期貨委託下單測試 #CH5
- [C#][Console] 群益 API 串接報價 + Socket Server 教學 #CH1
- 【C# 群益 API 開發教學】取得商品報價、Tick、最佳 5 檔教學 #CH3
如果你在學習上有不懂的地方,需要諮詢服務,可以參考站長服務,我想辨法解決你的問題
如果文章內容有過時、不適用或錯誤的地方,幫我在下方留言通知我一下,謝謝