想要在网路上取得台股最新的股价有许多种方式,其中一种免费的方式就是直接向证交所网站取得股价资料,这次我会分享 3 种用 C# 向证交所取得股价资料的实用方法。
这 3 种方式分别可以应用在不同的实务上,当了解基本取得资料的方式,就可以写程序定期取得最新或是历史的股价资料。
先看一次我这次范例的操作画面。
范例建置环境
後端架构: C# ASP.Net MVC .Net Framework
前端架构: Vue.js, jQuery, Bootstrap
我网页上只讲解重要的程序码部份,完整范例可至文末下载。
范例画面输入要查询的股票代码,可输入单一股票或是用 , (逗号)分隔输入代码後按查询。
按下查询後,程序会组合查询网址,例如
https://mis.twse.com.tw/stock/api/getStockInfo.jsp?json=1&delay=0&ex_ch=tse_2330.tw|tse_0050.tw|
网址中的 tse_2330.tw|tse_0050.tw| 就是要查询的股票名称,大家可以换成自己需要的股票。
当此网址在浏览器查询时,即会回传 Json 格式的价格资料
{"msgArray":[{"tv":"-","ps":"-","pz":"-","a":"592.0000_593.0000_594.0000_595.0000_596.0000_","b":"591.0000_590.0000_589.0000_588.0000_587.0000_","c":"2330","d":"20210510","ch":"2330.tw","tlong":"1620614180000","f":"332_721_423_419_366_","ip":"0","g":"586_1044_261_577_263_","mt":"866821","h":"597.0000","i":"24","it":"12","l":"591.0000","n":"台积电","o":"596.0000","p":"0","ex":"tse","s":"-","t":"10:36:20","u":"658.0000","v":"8071","w":"540.0000","nf":"台湾积体电路制造股份有限公司","y":"599.0000","z":"-","ts":"0"},{"tv":"-","ps":"-","nu":"http://www.yuantaetfs.com/#/RtNav/Index","pz":"-","a":"138.7000_138.7500_138.8000_138.8500_138.9000_","b":"138.6500_138.6000_138.5500_138.5000_138.4500_","c":"0050","d":"20210510","ch":"0050.tw","tlong":"1620614179000","f":"13_5_8_120_100_","ip":"0","g":"5_97_4_25_104_","mt":"995922","h":"139.5000","it":"02","l":"138.1500","n":"元大台湾50","o":"139.5000","p":"0","ex":"tse","s":"-","t":"10:36:19","u":"153.3000","v":"2229","w":"125.5000","nf":"元大台湾卓越50证券投资信托基金","y":"139.4000","z":"-","ts":"0"}],"referer":"","userDelay":5000,"rtcode":"0000","queryTime":{"sysDate":"20210510","stockInfoItem":1201,"stockInfo":629093,"sessionStr":"UserSession","sysTime":"10:36:25","showChart":false,"sessionFromTime":-1,"sessionLatestTime":-1},"rtmessage":"OK"}
接下来就可以用程序解析 Json 来取得价格资料。
我列出 Json 内容中价格资讯常用的栏位
在此范例中我只显示「股票代号/当盘成交价/最低委卖价/最高委买价」做示范,
大家可以依自己的需求调整要显示的资料。
在呼叫证交所的即时价格时,如果是盘中呼叫,"偶尔" 会发生没有当盘成交价的问题。
而这问题在逐笔交易上线(2020-3-23)以前,是不会发生的,当时是每 5 秒撮合一次,一定有成交价。
但在逐笔交易上线後,从证交所呼叫即时价格,其实是得到每 5 秒行情快照的结果,也就是 5 秒才会更新一个价格。
可能在快照那当下 0.1 秒时,就是没有人成交,所以也就没有看到当盘成交价。
如果没有当盘成交价的话,只能从委买 1 或委卖 1 来推算可能的成交价了。
如果需要非常即时的逐笔交易价格,我是透过群益 API 来取得即时报价的,可参考这篇文章。
<div class="panel panel-default">
<div class="panel-heading">范例1 即时股价</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label col-sm-2">股票:</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="form.Q_SYMBOL_1.value">
</div>
</div>
<button type="button" class="btn btn-primary" v-on:click="GetRealtimePrice()">查询</button>
</div>
<div class="panel-footer">
<span v-html="realPrice"></span>
</div>
</div>
只向後端执行呼叫动作
// 即时股价
, GetRealtimePrice: function () {
var self = this;
var postData = self._GetPostData(self.form, "Q_");
showProcess();
$.DoAjax({
url: '@Url.Content("~/Home/GetRealtimePrice")',
data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
success: function (datas) {
if (self._CheckReturnErrorMsg(datas)) {
self.realPrice = datas.realPrice;
hideProcess();
}
}
});
}
利用程序组出要查询的网址,接着使用 WebClient 取得回传资料,取得回传的 Json 资料後就依栏位取出资料。
/// <summary>
/// 即时股价
/// </summary>
/// <param name="inModel"></param>
/// <returns></returns>
[ValidateAntiForgeryToken]
public ActionResult GetRealtimePrice(FormIn inModel)
{
GetRealtimePriceOut outModel = new GetRealtimePriceOut();
outModel.ErrorMsg = "";
StringBuilder ExCode = new StringBuilder();
string[] symbols = inModel.Q_SYMBOL_1.Split(',');
foreach (string symbol in symbols)
{
ExCode.Append("tse_" + symbol + ".tw|");
}
// 呼叫网址
string url = "https://mis.twse.com.tw/stock/api/getStockInfo.jsp";
url += "?json=1&delay=0&ex_ch=" + ExCode;
string downloadedData = "";
using (WebClient wClient = new WebClient())
{
// 取得网页资料
wClient.Encoding = Encoding.UTF8;
downloadedData = wClient.DownloadString(url);
}
TwsePriceSchema jsonPrice = null;
if (downloadedData.Trim().Length > 0)
{
jsonPrice = JsonConvert.DeserializeObject<TwsePriceSchema>(downloadedData);
if (jsonPrice.rtcode != "0000")
{
throw new Exception("取商品价格失败: " + jsonPrice.rtmessage);
}
}
StringBuilder sbRealPrice = new StringBuilder();
for (int i = 0; i < jsonPrice.msgArray.Count; i++)
{
// 代码
string code = jsonPrice.msgArray[i].c;
// z = 收盘价
string close = jsonPrice.msgArray[i].z;
// a = 最低委卖价
string ask = "";
if (jsonPrice.msgArray[i].a.IndexOf("_") > -1)
{
ask = jsonPrice.msgArray[i].a.Split('_')[0];
}
// b = 最高委买价
string bid = "";
if (jsonPrice.msgArray[i].b.IndexOf("_") > -1)
{
bid = jsonPrice.msgArray[i].b.Split('_')[0];
}
sbRealPrice.Append("代码: " + code + " 收盘价: " + close + " 最低委卖价: " + ask + " 最高委买价: " + bid + "<br>");
}
outModel.realPrice = sbRealPrice.ToString();
// 输出json
ContentResult result = new ContentResult();
result.ContentType = "application/json";
result.Content = JsonConvert.SerializeObject(outModel);
return result;
}
程序码上有简单的注解说明,若需要实际操作可至下方下载完整范例。
在范例画面上输入要查询的日期,查询後就会列出当日所有股价的 K 线价格。
当查询之後,程序会组合查询网址:
https://www.twse.com.tw/exchangeReport/MI_INDEX?response=csv&date=20210507&type=ALL
替换参数 「20210507」 後就可以改成需要查询的日期
此查询结果会回传 csv 格式的内容,内容包含所有上市交易的清单,以及类股指数的价格,
此档案比较大有 2 万多笔资料回传。
回传资料分 2 部份,上半部是类股价格而下半部是个股价格,会在同一份档案中。
我自己是用栏位数量来分辨类股或是个股的。
那接下来就来看程序码的部份
<div class="panel panel-default">
<div class="panel-heading">范例2 每日收盘行情</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label col-sm-2">日期:</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="form.Q_DATE_2.value">
</div>
</div>
<button type="button" class="btn btn-primary" v-on:click="GetDayPrice()">查询</button>
</div>
<div class="panel-footer">
<div class="table-responsive" style="overflow-y:auto;height:400px;">
<table class="table">
<tr>
<th>代码</th>
<th>名称</th>
<th>开盘价</th>
<th>最高价</th>
<th>最低价</th>
<th>收盘价</th>
<th>成交量</th>
</tr>
<tr v-for="(item, index) in gridDay.datas">
<td>
{{item.symbolCode}}
</td>
<td>
{{item.symbolName}}
</td>
<td>
{{item.open}}
</td>
<td>{{item.high}}</td>
<td>{{item.low}}</td>
<td>{{item.close}}</td>
<td>{{item.volume}}</td>
</tr>
</table>
</div>
</div>
</div>
只向後端执行呼叫动作
// 每日收盘行情
, GetDayPrice: function () {
var self = this;
var postData = self._GetPostData(self.form, "Q_");
showProcess();
$.DoAjax({
url: '@Url.Content("~/Home/GetDayPrice")',
data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
success: function (datas) {
if (self._CheckReturnErrorMsg(datas)) {
self.gridDay.datas = [];
self._RowDataBound(self.gridDay.datas, datas.gridList);
hideProcess();
}
}
});
}
利用程序组出要查询的网址,接着使用 WebClient 取得回传资料,取得资料是 CSV 格式,将 CSV 解析成string[] 格式资料,就可以依索引位置来取得指定的资料。
/// <summary>
/// 每日收盘行情
/// </summary>
/// <param name="inModel"></param>
/// <returns></returns>
[ValidateAntiForgeryToken]
public ActionResult GetDayPrice(FormIn inModel)
{
GetDayPriceOut outModel = new GetDayPriceOut();
outModel.ErrorMsg = "";
// 呼叫网址
string twseUrl = "https://www.twse.com.tw/exchangeReport/MI_INDEX";
string download_url = twseUrl + "?response=csv&date=" + inModel.Q_DATE_2 + "&type=ALL";
string downloadedData = "";
using (WebClient wClient = new WebClient())
{
// 网页回传
downloadedData = wClient.DownloadString(download_url);
}
if (downloadedData.Trim().Length > 0)
{
// 回传前端的资料集
outModel.gridList = new List<StockPriceRow>();
string[] lineStrs = downloadedData.Split('\n');
for (int i = 0; i < lineStrs.Length; i++)
{
string strline = lineStrs[i];
if (strline.Trim().Length == 0)
{
continue;
}
// 排除非价格部份
if (strline.IndexOf("证券代号") > -1 || strline.IndexOf("(元,股)") > -1)
{
continue;
}
if (strline.Substring(0, 1) == "=")
{
strline = strline.TrimStart('=');
}
ArrayList resultLine = new ArrayList();
// 解析资料
this.ParseCSVData(resultLine, strline);
string[] datas = (string[])resultLine.ToArray(typeof(string));
//检查资料内容
if (datas.Length != 17)
{
continue;
}
// 股票代码
string symbolCode = datas[0];
if (symbolCode.Length == 4)
{
// 输出资料
StockPriceRow row = new StockPriceRow();
row.symbolCode = symbolCode; //股票代码
row.symbolName = datas[1]; //股票名称
row.open = datas[5]; //开盘价
row.high = datas[6]; //最高价
row.low = datas[7]; //最低价
row.close = datas[8]; //收盘价
row.volume = datas[2]; //成交量
outModel.gridList.Add(row);
}
}
}
// 输出json
ContentResult result = new ContentResult();
result.ContentType = "application/json";
result.Content = JsonConvert.SerializeObject(outModel); ;
return result;
}
在范例画面上输入股票代码及查询日期,就可以查询该股票当月的所有日期价格。
当查询之後,程序会组合查询网址:
http://www.twse.com.tw/exchangeReport/STOCK_DAY?response=csv&date=20210510&stockNo=2330
替换参数 「20210510」及「2330」 後就可以改变回传内容。
此查询结果会回传 csv 格式的内容,内容包含股票当月各日成交资讯。
资料栏位包含日期/成交股数/成交金额/开盘价/最高价/最低价/收盘价/涨跌价差/成交笔数
接下来我就示范如何用程序取出栏位
<div class="panel panel-default">
<div class="panel-heading">范例3 当月各日成交资讯</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label col-sm-2">股票:</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="form.Q_SYMBOL_3.value">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">日期:</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="form.Q_MONTH_3.value">
</div>
</div>
<button type="button" class="btn btn-primary" v-on:click="GetMonthPrice()">查询</button>
</div>
<div class="panel-footer">
<div class="table-responsive">
<table class="table">
<tr>
<th>日期</th>
<th>开盘价</th>
<th>最高价</th>
<th>最低价</th>
<th>收盘价</th>
<th>成交量</th>
</tr>
<tr v-for="(item, index) in gridMonthPirce.datas">
<td>{{item.date}}</td>
<td>{{item.open}}</td>
<td>{{item.high}}</td>
<td>{{item.low}}</td>
<td>{{item.close}}</td>
<td>{{item.volume}}</td>
</tr>
</table>
</div>
</div>
</div>
只向後端执行呼叫动作
// 当月各日成交资讯
, GetMonthPrice: function () {
var self = this;
var postData = self._GetPostData(self.form, "Q_");
showProcess();
$.DoAjax({
url: '@Url.Content("~/Home/GetMonthPrice")',
data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
success: function (datas) {
if (self._CheckReturnErrorMsg(datas)) {
self.gridMonthPirce.datas = [];
self._RowDataBound(self.gridMonthPirce.datas, datas.gridList);
hideProcess();
}
}
});
}
利用程序组出要查询的网址,接着使用 WebClient 取得回传资料,取得资料是 CSV 格式,将 CSV 解析成 string[] 格式资料,就可以依索引位置来取得指定的资料。
/// <summary>
/// 当月各日成交资讯
/// </summary>
/// <param name="inModel"></param>
/// <returns></returns>
[ValidateAntiForgeryToken]
public ActionResult GetMonthPrice(FormIn inModel)
{
GetMonthPriceOut outModel = new GetMonthPriceOut();
outModel.ErrorMsg = "";
// 呼叫网址
string download_url = "http://www.twse.com.tw/exchangeReport/STOCK_DAY?response=csv&date=" + inModel.Q_MONTH_3 + "&stockNo=" + inModel.Q_SYMBOL_3;
string downloadedData = "";
using (WebClient wClient = new WebClient())
{
// 网页回传
downloadedData = wClient.DownloadString(download_url);
}
if (downloadedData.Trim().Length > 0)
{
outModel.gridList = new List<StockPriceRow>();
string[] lineStrs = downloadedData.Split('\n');
for (int i = 0; i < lineStrs.Length; i++)
{
string strline = lineStrs[i];
if (i == 0 || i == 1 || strline.Trim().Length == 0)
{
continue;
}
// 排除非价格部份
if (strline.IndexOf("说明") > -1 || strline.IndexOf("符号") > -1 || strline.IndexOf("统计") > -1 || strline.IndexOf("ETF") > -1)
{
continue;
}
ArrayList resultLine = new ArrayList();
// 解析资料
this.ParseCSVData(resultLine, strline);
string[] datas = (string[])resultLine.ToArray(typeof(string));
//检查资料内容
if (Convert.ToInt32(datas[1].Replace(",", "")) == 0 || datas[3] == "--" || datas[4] == "--" || datas[5] == "--" || datas[6] == "--")
{
continue;
}
// 输出资料
StockPriceRow row = new StockPriceRow();
row.date = datas[0]; //日期
row.open = datas[3]; //开盘价
row.high = datas[4]; //最高价
row.low = datas[5]; //最低价
row.close = datas[6]; //收盘价
row.volume = datas[1]; //成交量
outModel.gridList.Add(row);
}
}
// 输出json
ContentResult resultJson = new ContentResult();
resultJson.ContentType = "application/json";
resultJson.Content = JsonConvert.SerializeObject(outModel); ;
return resultJson;
}
以上就是 3 种向证交所取得股价的实用方式,网页上的程序码中有些方法写在共用方法区内,没有展示出来,若需要了解更多可以下载完整范例。
付费後可下载此篇文章教学程序码。
相关学习文章
[C#] 取得证交所上市及上柜的股票及ETF清单(附范例)
[C#] 取得公开资讯观测站股票基本资料(上市、上柜、兴柜、公开发行) (附范例下载)
【C# 群益 API 开发教学】取得商品报价、Tick、最佳 5 档教学 (附范例下载)
相关网站
WinTog 云投资
>>: 样本指纹与模型库中的模板匹配(The sample fingerprint matches the template in the model repository)
@csrf_exempt def callback(request): if request.met...
-NIST SP 800-160 V1 和 ISO 15288 工程(Engineering) ....
=x= 🌵 CONTACT Page 寄信页的「我不是机器人」验证功能,後端实作。 Google r...
那昨天之後,我就把整个物件移动的程序都弄好了。弄完之後大概是这样的: using System.Co...
本章是最後一个章节,进入财务风险的感知层了,明天就是完赛日了,笔者会做个汇整给大家参考,谢谢大家。 ...