当前位置: 首页 > 编程日记 > 正文

技术图文:如何进行代码的重构?以封装 BigOne API 为例

背景

自从把“量化交易”作为自己精进的技术方向之后,我做了一些准备工作。

比如:

1. 爬取交易所的公告,根据公告的信息来研判数字货币的短期走势

这里面有一个“流动性溢价”的概念,等后面我会结合一些例子跟大家聊聊这块的发现。

  • 如何利用 C# 爬取Gate.io交易所的公告
  • 如何利用 C# 爬取BigOne交易所的公告

2. 爬取平台币 One 的数据,根据数据来估计该数字货币的价值

  • 如何利用 C# 爬取 One 持有者返利数据
  • 如何利用 C# 爬取 ONE 的交易数据
  • One的投资价值分析

3. 封装 BigOne 交易所的 API,改进“网格交易法”做了一款自动化交易系统

  • 如何利用BigOne的API制作自动化交易系统 – 身份验证
  • 如何利用BigOne的API制作自动化交易系统 – 获取账户资产
  • 如何利用BigOne的API制作自动化交易系统 – 订单系统

技术分析

写程序的码农们所追求的一定是自己写的代码是 可复用、可扩展、易维护、灵活性好 的。所以,这两天我用 Layers软件体系结构风格 对自己的交易系统进行了重构。

什么是 Layers软件体系结构风格 呢?

层次系统组织成一个层次结构,每一层为上一层提供服务,并作为下一层的客户。

Layers软件体系结构风格

这种风格的优点是:

  • 支持基于可增加抽象的层的设计。这样允许将一个复杂问题分解成一个增量步骤序列的实现。
  • 支持系统改进。和管道过滤器风格一样,因为每一层最多只与上下相邻两层交互,所以改变每一层的功能最多只影响两层。
  • 支持软件复用。和抽象数据类型风格一样,只要给相邻层提供相同的接口,允许每一层用不同的方法实现。这使得在标准的接口上构建不同的实现成为可能。

基于这种风格,我把 BigOne API 封装为三层:

  • 通讯层,该层的作用是:封装 GetSet 请求,与 BigOne 服务器进行通讯,为中间层提供支撑。
  • 中间层,该层的作用是:调用通讯层,获取 BigOne 服务器返回的数据,该数据不进行处理。
  • 应用层,该层的作用是:为客户端提供对象群体,通过对象之间的协作辅助客户端实现用户需求。

代码实现

Step1 封装最底层 – 通讯层

internal class HttpUtilManagerBigOne
{private const string ApiKey = "";//您的API Keyprivate const string ApiSecret = "";//您的API Secretprivate static readonly HttpUtilManagerBigOne Instance = new HttpUtilManagerBigOne();//单例模式private HttpUtilManagerBigOne(){}public static HttpUtilManagerBigOne GetInstance(){return Instance;}public string GetToken(){DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));long timestamp = Convert.ToInt64(((long) (DateTime.Now - startTime).TotalMilliseconds).ToString(CultureInfo.InvariantCulture).PadRight(19, '0'));IDictionary<string, object> payload = new Dictionary<string, object>{{"type", "OpenAPI"},{"sub", ApiKey},{"nonce", timestamp}};byte[] secretKey = Encoding.Default.GetBytes(ApiSecret);string token = JWT.Encode(payload, secretKey, JwsAlgorithm.HS256);return token;}public string RequestHttpGet(string url, string param, bool withToken = false){if (string.IsNullOrEmpty(url))throw new ArgumentNullException();if (string.IsNullOrEmpty(param)==false){if (url.EndsWith("?")){url = url + param;}else{url = url + "?" + param;}}HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;if (request == null)return string.Empty;request.ProtocolVersion = HttpVersion.Version10;request.Method = "GET";request.Timeout = 30000;if (withToken){string token = "Bearer " + GetToken();request.Headers.Add("authorization", token);}ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;ServicePointManager.Expect100Continue = true; try{HttpWebResponse response = (HttpWebResponse) request.GetResponse();Stream stream = response.GetResponseStream();if (stream == null)return string.Empty;StreamReader reader = new StreamReader(stream, Encoding.UTF8);return reader.ReadToEnd();}catch{return string.Empty;}}public string RequestHttpPost(string url, Dictionary<string, string> arguments){int i = 0;StringBuilder builder = new StringBuilder();foreach (KeyValuePair<string, string> item in arguments){if (i > 0){builder.Append("&");}builder.AppendFormat("{0}={1}", item.Key, item.Value);i++;}HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);request.Method = "POST";request.ContentType = "application/x-www-form-urlencoded";string token = "Bearer " + GetToken();request.Headers.Add("authorization", token);ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;ServicePointManager.Expect100Continue = true; byte[] data = Encoding.UTF8.GetBytes(builder.ToString());request.ContentLength = data.Length;using (Stream reqStream = request.GetRequestStream()){reqStream.Write(data, 0, data.Length);reqStream.Close();}try{HttpWebResponse resp = (HttpWebResponse) request.GetResponse();Stream stream = resp.GetResponseStream();if (stream == null)return string.Empty;StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);return streamReader.ReadToEnd();}catch{return string.Empty;}}
}

Step2 封装中间层

internal class RestApiBigOne
{public string ApiEntryPoint = @"https://big.one/api/v2/";public string GetPing(){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "ping";string param = "";string result = httpUtil.RequestHttpGet(url, param);return result;}public string GetTickers(){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "tickers";string param = "";string result = httpUtil.RequestHttpGet(url, param);return result;}public string GetTicker(string marketId){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "markets/" + marketId + "/ticker";string param = "market_id=" + marketId;string result = httpUtil.RequestHttpGet(url, param);return result;}public string GetOrderBook(string marketId){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "markets/" + marketId + "/depth";string param = "market_id=" + marketId;string result = httpUtil.RequestHttpGet(url, param);return result;}public string GetMarketTrade(string marketId){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "markets/" + marketId + "/trades";string param = "market_id=" + marketId;string result = httpUtil.RequestHttpGet(url, param);return result;}public string GetMarket(){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "markets";string param = "";string result = httpUtil.RequestHttpGet(url, param);return result;}public string GetAccount(){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "viewer/accounts";string param = "";string result = httpUtil.RequestHttpGet(url, param, true);return result;}public string GetOrders(){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "viewer/orders";string param = "";string result = httpUtil.RequestHttpGet(url, param, true);return result;}public string GetOrder(string orderId){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "viewer/orders/" + orderId;string param = "order_id=" + orderId;string result = httpUtil.RequestHttpGet(url, param, true);return result;}public string CreateOrder(string marketId, string side, string price, string amount){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "viewer/orders";Dictionary<string, string> param = new Dictionary<string, string>();param.Add("market_id", marketId);param.Add("side", side);param.Add("price", price);param.Add("amount", amount);string result = httpUtil.RequestHttpPost(url, param);return result;}public string CancelOrder(string orderId){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "viewer/orders/" + orderId + "/cancel";Dictionary<string, string> param = new Dictionary<string, string>();param.Add("order_id", orderId);string result = httpUtil.RequestHttpPost(url, param);return result;}public string CancelAllOrders(string marketId){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "viewer/orders/cancel_all";Dictionary<string, string> param = new Dictionary<string, string>();param.Add("market_id", marketId);string result = httpUtil.RequestHttpPost(url, param);return result;}public string GetWithdrawal(){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "viewer/withdrawals";string param = "";string result = httpUtil.RequestHttpGet(url, param, true);return result;}public string GetDeposit(){HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();string url = ApiEntryPoint + "viewer/deposits";string param = "";string result = httpUtil.RequestHttpGet(url, param, true);return result;}
}

Step3 封装应用层

以获取某交易对 marketId 市场挂单 买单Bid,卖单Ask 为例进行说明。

public class BigOneUtility
{private static readonly RestApiBigOne BigOneApi = new RestApiBigOne();public static void GetOrderBook(string marketId, out List<Ask> ask, out List<Bid> bid){ask = default(List<Ask>);bid = default(List<Bid>);string json = BigOneApi.GetOrderBook(marketId);if (string.IsNullOrEmpty(json) == false){StringReader sr = new StringReader(json);JsonTextReader jsonReader = new JsonTextReader(sr);JsonSerializer serializer = new JsonSerializer();GetOrderBookJson orderBook = serializer.Deserialize<GetOrderBookJson>(jsonReader);if (orderBook.data.asks != null){ask = orderBook.data.asks.OrderBy(a => a.price).ToList();}if (orderBook.data.bids != null){bid = orderBook.data.bids.OrderByDescending(a => a.price).ToList();}}}
}

买单结构

public class Bid
{/// <summary>/// bid price/// </summary>public string price { get; set; }/// <summary>/// bid amount/// </summary>public string amount { get; set; }
}

卖单结构

public class Ask
{/// <summary>/// ask price/// </summary>public string price { get; set; }/// <summary>/// ask amount/// </summary>public string amount { get; set; }
}

总结

到此为止,向大家介绍了自己重构 BigOne API 的整体思路,即利用分层的思想把不同功能的对象放在不同的层中,来为上一层提供服务,减少对象之间的耦合。

这是自动运行四个交易对 PRS-USDTBTM-USDTEOS-USDTONE-USDT 的截图,在这个市场里存在很多的套利机会,只要我们肯学习,再懂一些编程的技术,就会拥有大量的机会。

自动化交易

好的,今天就到这里吧! See You!

对了,到目前为止已经有9名同学 通过解码 Huffman Code 得到团队的报名方式。

-如何加入 LSGO 软件技术团队?

团队的招新仍在进行中,对我们感兴趣的同学欢迎与我联系,我会亲自带着大家学习,一起成长!


相关图文

  • 如何利用 C# 实现 K 最邻近算法?
  • 如何利用 C# 实现 K-D Tree 结构?
  • 如何利用 C# + KDTree 实现 K 最邻近算法?
  • 如何利用 C# 对神经网络模型进行抽象?
  • 如何利用 C# 实现神经网络的感知器模型?
  • 如何利用 C# 实现 Delta 学习规则?
  • 如何利用 C# 爬取带 Token 验证的网站数据?
  • 如何利用 C# 向 Access 数据库插入大量数据?
  • 如何利用 C# 开发「桌面版百度翻译」软件!
  • 如何利用 C# 开发「股票数据分析软件」(上)
  • 如何利用 C# 开发「股票数据分析软件」(中)
  • 如何利用 C# 开发「股票数据分析软件」(下)
  • 如何利用 C# 爬取「财报说」中的股票数据?
  • 如何利用 C# 爬取 One 持有者返利数据!
  • 如何利用 C# 爬取Gate.io交易所的公告!
  • 如何利用 C# 爬取BigOne交易所的公告!
  • 如何利用 C# 爬取 ONE 的交易数据?
  • 如何利用 C# 爬取「猫眼电影:热映口碑榜」及对应影片信息!
  • 如何利用 C# 爬取「猫眼电影专业版:票房」数据!
  • 如何利用 C# 爬取「猫眼电影:最受期待榜」及对应影片信息!
  • 如何利用 C# 爬取「猫眼电影:国内票房榜」及对应影片信息!
  • 如何利用 C# + Python 破解猫眼电影的反爬虫机制?
  • 如何利用BigOne的API制作自动化交易系统 – 身份验证
  • 如何利用BigOne的API制作自动化交易系统 – 获取账户资产
  • 如何利用BigOne的API制作自动化交易系统 – 订单系统

相关文章:

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一3.7.5 使用StAX写出XML文档

3.7.5 使用StAX写出XML文档 在前一节中&#xff0c;你看到了如何通过写出DOM树的方法来产生XML文件。如果这个DOM树没有其他任何用途&#xff0c;那么这种方式就不是很高效。StAX API使我们可以直接将XML树写出&#xff0c;这需要从某个OutputStream中构建一个XMLStreamWriter…

大数据就业前景怎么样?需要学会哪些技术?

智能时代的来临&#xff0c;我们日常生活中的很多技术都可以用大数据来实现&#xff0c;大数据开发行业做为IT行业中的一类更是前景无限。所以很多人想转行做大数据开发。那么现在大数据就业前景怎么样?需要学会哪些技术? 大数据就业前景怎么样?需要学会哪些技术?大数据行业…

技术图文:如何利用 C# 实现 误差反向传播 学习规则?

背景 我们在 如何利用 C# 对神经网络模型进行抽象&#xff1f; 中完成了神经网络的抽象结构&#xff1a; 三个接口&#xff1a;激活函数、有监督学习、无监督学习 三个抽象类&#xff1a;神经元、网络层、网络拓扑 我们在 如何利用 C# 实现神经网络的感知器模型&#xff1f; …

SQL注入漏洞全接触--入门篇

随着B/S模式应用开发的发展&#xff0c;使用这种模式编写应用程序的程序员也越来越多。但是由于这个行业的入门门槛不高&#xff0c;程序员的水平及经验也参差不齐&#xff0c;相当大一部分程序员在编写代码的时候&#xff0c;没有对用户输入数据的合法性进行判断&#xff0c;使…

WannaCry 不相信眼泪 它需要你的安全防御与响应能力

在过去的几天里&#xff0c;WannaCry恶意软件及其变体影响了全球数百家组织与机构。 尽管每个组织都会因各种各样的原因没能及时对存在漏洞的系统做更新保护&#xff0c;或者担心更新实时系统的风险&#xff0c;两个月对于任何组织来用于采取措施保证系统安全也并不算太短的时间…

小白阶段如何学习Web前端知识

学会了UI设计技术&#xff0c;接下来的计划就是要找工作了&#xff0c;UI设计在面试环节的自我介绍很重要&#xff0c;有时候一分钟的自我介绍已经足够让HR判断出你适不适合他们公司&#xff0c;那做为一名UI设计师面试时如何自我介绍呢?来看看下面的详细介绍。 UI设计师面试时…

康泰瑞影推高性能3D/4D超声可视化方案

本文讲的是康泰瑞影推高性能3D/4D超声可视化方案,康泰瑞影(ContextVision)推出的业界首款超声实时3D立体图像增强产品已经配备全新的影像可视化功能。所推出的产品REALiCE?将提供逼真的3D超声影像&#xff0c;提高了诊断质量。 REALiCE软件将GOPiCE?自适应3D/4D立体图像增强产…

New Video Game Controlled By Kissing

unassimilatible writes "Artist Hye Yeon Nam has put her video game where her mouth is — literally — with the creation of a new bowling game thats controlled only by passionate (and awkward) French kissing. The Kiss Controller, as its called, has two…

资料分享:数学建模资料分享 -- 图论部分

背景 今天上午&#xff0c;在教六第一阶梯教室为数学建模俱乐部的同学们分享了有关图论的基本知识和应用。 课后&#xff0c;为同学们留了一个算法实现的小练习&#xff0c;大家可以先做一下。在本图文的末尾处&#xff0c;我把上课的资料以及代码分享出来&#xff0c;供大家…

免费学习编程的10个好工具

互联网时代的快速发展&#xff0c;很多人都在学习编程技术&#xff0c;小编今天为大家推荐的就是学习编程技术会用到的一些编程工具&#xff0c;免费学习编程的10个好工具!希望能够帮助到正在学习的小伙伴们。 免费学习编程的10个好工具&#xff1a; 1. Code/Racer Code/Racer是…

【ZT】我家宝宝不会哭----分享在美国养孩子的妈妈经(必看)

我在美国生了两个孩子&#xff0c;生育前后都有培训班&#xff0c;家庭医生每次洗脑让我受益匪浅&#xff0c;我的两个宝宝在婴儿时期乖巧得好像家里没有小婴儿&#xff0c;我甚至疑心她们会不会哭&#xff1f;如今外婆常拿这句傻话笑我。回国后看到朋友或邻居们被小祖宗折磨得…

资料分享:数学建模资料分享 -- 神经网络部分

背景 周日的时候&#xff0c;为数学建模俱乐部的同学们进行了一场有关人工神经网络方面的分享。虽然在这个方面有一些积累&#xff0c;但过于零散&#xff0c;所以拿了一堆文件拼凑成整体的内容。 幸亏自己有分享的习惯&#xff0c;学会一些知识就写下来&#xff0c;这次就是…

配置文件的简单使用

常见的配置文件格式:1.properties里面内容的格式 keyvalue2.xml后期详细解释若我们的配置文件为properties,并且放在src目录下.我们可以通过 ResourceBundle工具快速获取里面的配置信息使用步骤:1.获取ResourceBundle 对象:static ResourceBundle getBundle("文件名称不带…

软件测试工程师如何提升自己?

在软件测试行业&#xff0c;尤其是今年疫情的发生&#xff0c;想要在这个行业站稳脚跟&#xff0c;有着扎实的技术是非常重要的&#xff0c;今天小编要为大家介绍的内容就是软件测试工程师如何提升自己?希望能够给大家带来帮助。 软件测试工程师如何提升自己?熟练掌握IT核心技…

一些简单的SQL语句

1.创建数据库&#xff1a;create database database-name 2.删除数据库&#xff1a;delete database database-name 3.选择:select * from table where ... 4.插入&#xff1a;insert into table(field1,field2) values(value1,value2) 5.更新&#xff1a;update table set fie…

技术图文:进一步完善自动化交易系统 - 01

背景 最初&#xff0c;我们介绍了 如何利用网格交易法对数字资产进行交易。只要价格存在波动&#xff0c;通过这种方法就能赚取利润。 接着&#xff0c;我们介绍了 如何通过三角套利来交易数字资产。只要三种数字资产&#xff0c;两两存在交易对&#xff0c;就可以建立套利的…

String、StringBuffer与StringBuilder之间区别 (转载)

最近学习到StringBuffer&#xff0c;心中有好些疑问&#xff0c;搜索了一些关于String&#xff0c;StringBuffer&#xff0c;StringBuilder的东西&#xff0c;现在整理一下。 关于这三个类在字符串处理中的位置不言而喻&#xff0c;那么他们到底有什么优缺点&#xff0c;到底什…

女生可以学习Web前端吗?

随着时代的不断发展&#xff0c;女性独立意识越来越高&#xff0c;在职场这方面&#xff0c;很多岗位都有了女性的存在&#xff0c;尤其是IT互联网行业&#xff0c;广阔的发展前景和优厚的福利待遇吸引了很多女性小伙伴想要学习Web前端开发&#xff0c;那么女生可以学习Web前端…

adobe就不敢把融合做得更好一点?

体验了一把flash bulider4 和 flash cs5的结合。 之前说得挺牛B的协同开发也不过如此。还以为会给开发者更多的便利。。。 其也就是让建立一个有外链类的mc的时候&#xff0c;能自动为你在bulider里面提示新建一个类而已。 在flash bulider(flex bulider)中依然不认识flash中的…

apache开启虚拟主机 并进行配置

sudo vi /etc/apache2/httpd.conf 进入 apache 配置文件 在配置文件中搜索 Virtual hosts 如图 把前面的#删掉 #为注释 sudo vi /etc/apache2/extra/httpd-vhosts.conf 打开虚拟主机配置文件对下面的一下代码进行复制并粘贴在下方并更改里面的一些内容 DocumentRoot …

技术图文:进一步完善自动化交易系统 - 02

背景 自己的自动化交易系统起初只有网格交易法一个版本&#xff1a;如何利用网格交易法对数字资产进行交易。后面&#xff0c;加入了ONE、USDT、EOS的三角套利版本&#xff1a;如何通过三角套利来交易数字资产。接着&#xff0c;发现三角套利的个别订单在未全部成交的情况下被…

女士做软件测试的利弊有哪些?

社会在不断的进步&#xff0c;在如今的智能时代&#xff0c;女性独立意识越来越高&#xff0c;很多女性都把重心放在职场上&#xff0c;IT行业出现了越来越多的女性职业者&#xff0c;尤其是软件测试这一块&#xff0c;那么女士做软件测试的利弊有哪些呢?来看看下面的详细介绍…

刻意练习:Python基础 -- Task01. 变量、运算符与数据类型

背景 我们准备利用17天时间&#xff0c;将 Python 基础的刻意练习分为如下任务&#xff1a; Task01&#xff1a;变量、运算符与数据类型&#xff08;1day&#xff09;Task02&#xff1a;条件与循环&#xff08;1day&#xff09;Task03&#xff1a;列表与元组&#xff08;2day…

linux ramdisk与tmpfs的深入分析

一)ramdisk 1)概念 ramdisk就是指使用一部分内存空间来模拟硬盘分区,也就是说ramdisk是一个块设备,要用mkfs格式化,才能真正使用它. .ramdisk在内核2.0/2.2版本就已经支持. .ramdisk设备是它不允许重新声明它的内存空间,所以ramdisk块通常会一直占用空间的内存直到系统重启. .r…

Unique Paths II

注意一个容易犯的错误&#xff1a;判断obstacleGrid是否为1时&#xff0c;else那部分不能少。因为如果不加&#xff0c;就会默认把那些值设置为0。 class Solution { public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int height ob…

零基础java培训如何规划学习路线

学习java的同学越来越多&#xff0c;很多小伙伴都想知道零基础如何学习java?如何安排学习路线&#xff0c;零基础java培训如何规划学习路线?那么针对以上问题&#xff0c;今天小编特意分享这篇Java学习指南&#xff0c;希望对那些想学Java&#xff0c;想零基础快速入门Java有…

CCIE-MPLS基础篇-实验手册

又一部前期JUSTECH&#xff08;南京捷式泰&#xff09;工程师职业发展系列丛书完整拷贝。 MPLS&#xff08;Multi-Protocol Label Switching&#xff09; 目录 1&#xff1a;MPLS 基础实验.... 3 1.1实验拓扑... 3 1.2实验需求&#xff1a;... 3 1.3实验步骤... 3 1.4校验…

[转载]Oracle 11g新特征之形式料理(2)

不可见的索引 您经常感应利诱&#xff0c;索引能否真的有利于用户的盘考&#xff1f;它也许有利于一个盘考&#xff0c;但会影响 10 个其他盘考。索引肯定会对 INSERT 语句形成负面影响&#xff0c;也会执行潜伏的删除和更新操纵&#xff0c;这取决于 WHERE 条件能否在索引中包…

技术图文:进一步完善自动化交易系统 - 03

背景 在 进一步完善自动化交易系统 - 02 这篇图文中&#xff0c;向大家详细介绍了&#xff0c;满足以下两个条件下&#xff1a; 拥有数字资产&#xff1a;BTC、EOS、USDT、ONEBigOne 提供交易对&#xff1a;ONE-USDT、ONE-EOS、ONE-BTC、EOS-BTC、EOS-USDT、BTC-USDT 我们可…

HTML5培训后好就业吗

HTML5技术在移动互联网行业的发展前景是非常可观的&#xff0c;以至于很多人都非常看好HTML5行业&#xff0c;想要学习HTML5技术的人越来越多&#xff0c;但大家都比较关心HTML5培训后好就业吗?就这个问题我们来看看下面的详细介绍吧。 HTML5培训后好就业吗?首先&#xff0c;…