[C#] 取得证交所台股价格的 3 种实用方法(附范例下载)

想要在网路上取得台股最新的股价有许多种方式,其中一种免费的方式就是直接向证交所网站取得股价资料,这次我会分享 3 种用 C# 向证交所取得股价资料的实用方法。

  1. 取得即时的价格
  2. 取得每日收盘行情的 K 线资料
  3. 取得单一股票在当月各日成交资讯

这 3 种方式分别可以应用在不同的实务上,当了解基本取得资料的方式,就可以写程序定期取得最新或是历史的股价资料。

先看一次我这次范例的操作画面。

范例建置环境
後端架构: C# ASP.Net MVC .Net Framework
前端架构: Vue.js, jQuery, Bootstrap

我网页上只讲解重要的程序码部份,完整范例可至文末下载。

范例1 取得即时股价

范例画面输入要查询的股票代码,可输入单一股票或是用 , (逗号)分隔输入代码後按查询。

按下查询後,程序会组合查询网址,例如

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 来取得即时报价的,可参考这篇文章

Html 网页版面

<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>

Javascript 程序码

只向後端执行呼叫动作

// 即时股价
, 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();
			}
		}
	});
}

C# Controller 程序

利用程序组出要查询的网址,接着使用 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;
}

程序码上有简单的注解说明,若需要实际操作可至下方下载完整范例。

范例2 取得每日收盘行情

在范例画面上输入要查询的日期,查询後就会列出当日所有股价的 K 线价格。

当查询之後,程序会组合查询网址:

https://www.twse.com.tw/exchangeReport/MI_INDEX?response=csv&date=20210507&type=ALL

替换参数 「20210507」 後就可以改成需要查询的日期
此查询结果会回传 csv 格式的内容,内容包含所有上市交易的清单,以及类股指数的价格,

此档案比较大有 2 万多笔资料回传。
回传资料分 2 部份,上半部是类股价格而下半部是个股价格,会在同一份档案中。
我自己是用栏位数量来分辨类股或是个股的。
那接下来就来看程序码的部份

Html 网页版面

<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>

Javascript 程序码

只向後端执行呼叫动作

// 每日收盘行情
, 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();
			}
		}
	});
}

C# Controller 程序

利用程序组出要查询的网址,接着使用 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;
}

范例3 取得当月各日成交资讯

在范例画面上输入股票代码及查询日期,就可以查询该股票当月的所有日期价格。

当查询之後,程序会组合查询网址:

http://www.twse.com.tw/exchangeReport/STOCK_DAY?response=csv&date=20210510&stockNo=2330

替换参数 「20210510」及「2330」 後就可以改变回传内容。
此查询结果会回传 csv 格式的内容,内容包含股票当月各日成交资讯。

资料栏位包含日期/成交股数/成交金额/开盘价/最高价/最低价/收盘价/涨跌价差/成交笔数

接下来我就示范如何用程序取出栏位

Html 网页版面

<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>

Javascript 程序码

只向後端执行呼叫动作

// 当月各日成交资讯
, 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();
			}
		}
	});
}

C# Controller 程序

利用程序组出要查询的网址,接着使用 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 种向证交所取得股价的实用方式,网页上的程序码中有些方法写在共用方法区内,没有展示出来,若需要了解更多可以下载完整范例。

重点整理

  1. 取得证交所即时价格是每 5 秒更新一次
  2. 要取得逐笔交易的 Tick 价格可串接券商 API 取得证交所即时价格是每
  3. 知道证交所查询网址即可自写程序查询
  4. 一天更新一次价格可利用查询证交所每日收盘行情
  5. 当月各日成交资讯可回溯非常久的历史资料

范例下载

付费後可下载此篇文章教学程序码

相关学习文章
[C#] 取得证交所上市及上柜的股票及ETF清单(附范例)
[C#] 取得公开资讯观测站股票基本资料(上市、上柜、兴柜、公开发行) (附范例下载)
【C# 群益 API 开发教学】取得商品报价、Tick、最佳 5 档教学 (附范例下载)

相关网站
WinTog 云投资


<<:  第42天~

>>:  样本指纹与模型库中的模板匹配(The sample fingerprint matches the template in the model repository)

DAY14 呼叫功能列表样板

@csrf_exempt def callback(request): if request.met...

工程、生命周期阶段和过程(Engineering, Life Cycle Stages, and Processes)

-NIST SP 800-160 V1 和 ISO 15288 工程(Engineering) ....

Day 2 - Using Google reCAPTCHA with ASP.NET Web Forms C#「我不是机器人」验证

=x= 🌵 CONTACT Page 寄信页的「我不是机器人」验证功能,後端实作。 Google r...

Unity自主学习(二十六):物件移动(5)

那昨天之後,我就把整个物件移动的程序都弄好了。弄完之後大概是这样的: using System.Co...

DAY 29 第二十九章 风险类别-财务风险-感知层:8.4 实体攻击、8.5 维护设备成本、 8.6 设备失窃

本章是最後一个章节,进入财务风险的感知层了,明天就是完赛日了,笔者会做个汇整给大家参考,谢谢大家。 ...