【Grasshopper基础5】在GH里看基金? —— 简单电池项目实战
经过前面【Grasshopper基础1~4】的介绍,相信读者已经了解了如何在Visual Studio里创建电池、如何获取数据、如何传出数据。
那么在了解这些原理之后,就让我们来一起实现一个小的项目,来看看一个电池从头到尾到底如何制作吧。
正如标题中所述的,我们今天就来做一个 “在GH里看基金净值” 的小电池,大致就是实现一个“给定基金代码,通过网络API调用获取基金净值信息,并输出该基金的所有净值信息”。要实现这个功能,首先我们需要首先进行需求分析,然后确定实现的逻辑,最后进行代码编写和简单的测试。
需求分析
“给定基金代码”
一般而言,基金代码都是整型数值,但是考虑到日常使用的时候,基金代码前可能会包含数字0,故认为它会是整型或者是字符串。
不过,我们在前文(【Grasshopper基础3】)中已经了解到了,由于GH里字符串(GH_String
)和整型(GH_Integer
)会自动进行隐式转换,其实不需要担心到底会是整形还是字符串,只需确定我们代码最终接受的类型,GH会在输入参数中自动帮我们进行类型转换。
“输出该基金的所有净值信息”
基金净值是浮点型,但是由于需要获取所有净值信息,包含历史上基金存在时所有的值,所以对应每一个基金代码应该会附带一个列表的信息。净值是浮点型。
经过上述分析,
- 电池的输入端需要一个参数,可以是整型或字符串,这个项目我们就直接采用整型;
- 输出端需要一个参数,是浮点型;
- 输入和输出直接的对应关系是一个整型输入对应一个浮点型列表,确定输入端的参数的
Access
属性为item,输出端的参数的Access
属性为list。
此时可以开一个Visual Studio,新建一个Grasshopper电池项目,然后将我们的输入、输出端的参数相关代码放入了。
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{pManager.AddIntegerParameter("Code", "C", "", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{pManager.AddNumber("DayValues", "v", "", GH_ParamAccess.list);
}
电池内部实现逻辑
这部分是整个电池项目中最耗时的部分,也就是在电池类的 SolveInstance
中处理输入的基金代码信息,并给出最终的净值浮点数列表。
首先获取基金代码:
protected override void SolveInstance(IGH_DataAccess DA)
{// 获取基金代码,检测获取是否成功,若不成功直接退出电池执行int code = int.MinValue;bool Success = DA.GetData(0, ref code);if(!Success) return;
}
然后就是对基金净值API的调用,这里调用的是 “小熊同学”网站的基金API。这个API的返回值是一个json字符串,里面包含基金的名称、代码、净值等信息,详细的API文档可在网站中找到。
现在的问题就聚焦到两个部分了,一个是如何发送网络请求,另一个是如何在返回的json字符串中提取我们需要的信息。
网络请求的发送
由于我们是在Grasshopper中实现该逻辑,所以运行环境是 .NET Framework 4.5。在网络上找资料时也需要时刻注意运行环境。目前.NET生态已经布局到全平台,很多相关网络请求的资料找到的都是 .NET Core 或者 .NET 的,而 .NET Framework 与它们略有不同,依赖项可能会不一样。
(具体 .NET / .NET Core / .NET Framework 三者有什么区别和联系读者们也可以自行探索一下,相关历史介绍还是挺多的)
在 .NET Framework 框架下,网络请求的发送可以使用 System.Net
命名空间里的 WebRequest
类实现。
protected override void SolveInstance(IGH_DataAccess DA)
{// 获取基金代码,检测获取是否成功,若不成功直接退出电池执行// .... ... .. . 略// 建立请求var req = WebRequest.Create(@"https://api.doctorxiong.club/v1/fund/detail?code=" + code.ToString());req.Method = "GET";req.Headers.Add(HttpRequestHeader.AcceptCharset, "utf-8");// 发起请求,将请求返回保存至res中var res = req.GetResponse();
}
返回的Json字符串解析
接下来,就需要对返回的json字符串进行解析了。考虑到现在网络编程的便利性,json字符串几乎已经成为网络编程中数据往来的标准格式,所以json字符串的解析就不需要手写了,.NET 框架已经自带了json字符串解析的功能(System.Text.Json
),可以自动将json字符串封装成一个 C# 的object
提供原生访问,十分方便。
但是,这个 System.Text.Json
在 .NET Framework 中是没有的!所以我们需要用到它的前生 Newtonsoft.Json
。不过这个dll需要手动添加,具体添加方法为
- 在Visual Studio的项目管理器中找到引用(References)并右键单击,选择添加引用
- 弹出窗口中,左上角选择 Assemblies -> Extensions
- 找到并选择 Json.NET,单击确定添加,此时引用中出现
Newtonsoft.Json
引用添加成功后我们就可以在代码中加入命名空间
using Newtonsoft.Json;
此时我们离解析成功仅差一步之遥了。
前面提到用使用Newtonsoft.Json
可以自动把json字符串转换为一个可以操作的对象实例,其前提是,这个对象实例我们需要有这个对象的数据模型。
想象json是一堆货物,Newtonsoft.Json
可以帮忙装货,但是它需要知道每种货物需要被装在什么位置,我们需要指明数据会被装在什么对象的什么属性中。
数据模型其实就是一个类,我们需要依据json的格式去构建这个类的属性,下面就直接给出笔者建立的数据模型。笔者在这里使用的是struct
结构体,读者也可以自行换成class
关键字。
// 数据模型中每一个属性都对应着json字符串中的标签
// 数据模型可以不包含所有的json字符串标签,不包含的数据会被忽略掉
public struct ResponseData
{public HttpStatusCode Code { get; set; }public string Message { get; set; }public Fund Data { get; set; }public string Meta { get; set; }public struct Fund{public string Code { get; set; }public string Name { get; set; }public string Type { get; set; }public float NetWorth { get; set; }public float ExpectWorth { get; set; }public float TotalWorth { get; set; }public string ExpectGrowth { get; set; }public string DayGrowth { get; set; }public string LastWeekGrowth { get; set; }public string LastMonthGrowth { get; set; }public string LastThreeMonthsGrowth { get; set; }public string LastSixMonthsGrowth { get; set; }public string LastYearGrowth { get; set; }public string BuyMin { get; set; }public string BuySourceRate { get; set; }public string BuyRate { get; set; }public string Manager { get; set; }public string FundScale { get; set; }public string NetWorthDate { get; set; }public string ExpectWorthDate { get; set; }public string[][] NetWorthData { get; set; }public string[][] TotalNetWorthData { get; set; }}
}
有了这个数据模型,我们就可以直接对返回的json字符串进行解析,代码如下。MemoryStream
类需要用到命名空间System.IO
,Encoding
类需要用命名空间System.Text
。
protected override void SolveInstance(IGH_DataAccess DA)
{// 获取基金代码,检测获取是否成功,若不成功直接退出电池执行// 建立请求// 发起请求,将请求返回保存至res中// 准备解析string jsonString;using (var responseStream = res.GetResponseStream()) // 获取返回流{// 在内存中创建临时存储用来存储返回流数据using (var ms = new MemoryStream()) {// 返回流数据复制至内存中responseStream.CopyTo(ms); // 用UTF8解码返回流数据jsonString = Encoding.UTF8.GetString(ms.ToArray()); // 解析json字符串var resData = JsonConvert.DeserializeObject<ResponseData>(jsonString);}}
}
最终我们获得了变量 resData 就是最终我们解析得到的数据,它是一个 ResponseData
结构体的实例,我们可以在后续传出数据时方便地通过该结构体获得数据。
电池数据传出
下面的事情就又变得简单了起来,由于我们的输出是对应一个数据列表,我们仅需从 resData 中提取到我们想要的基金净值数据,存储在一个 List<double>
中,然后借由 DA.SetDataList()
方法传出即可。
通过基金API可知,基金净值信息包含在 NetWorthData
中,每个NetWorthData
包含4个数据,分别是 “日期、净值、日涨跌、额外信息”。我们所需要的净值在该列表的第二项。
又因为所有的数据在数据模型中是以字符串形式给出,我们需要使用double.Parse来进行格式转 …… 等等……GH可以自动隐式转换,我们不如直接输出字符串试试看?
protected override void SolveInstance(IGH_DataAccess DA)
{// 获取基金代码,检测获取是否成功,若不成功直接退出电池执行// 建立请求// 发起请求,将请求返回保存至res中// 准备解析string jsonString;using (var responseStream = res.GetResponseStream()) // 获取返回流{// 在内存中创建临时存储用来存储返回流数据using (var ms = new MemoryStream()) {// 返回流数据复制至内存中// 用UTF8解码返回流数据// 解析json字符串// 提取每天的净值信息,放入列表中List<string> dayValues = new List<string>();foreach (var item in resData.Data.NetWorthData)dayValues.Add(item[1]);// 传出数据DA.SetDataList(0, dayValues);}}
}
这样我们的电池就完工了!下面赶紧让我们来试试看把。(文章最后有完整cs文件的代码可供参考)
代码测试
首先要做的是在Rhino中使用 GrasshopperDevelopSetting 命令加入我们电池的输出目录。具体细节可见【Grasshopper基础1】,本文在此不再复述。设置完成后关闭Rhino。
点击运行,让我们测试一下自己的电池吧!
但是作为测试内容,我们不能总是期待会给出正确的基金代码,换一个思路,当我们瞎输入一个实际不存在的基金代码时……
报错了!
Error converting value {null} to type ******
解读一下发现,原来是我们在做解析的时候并没有做空值判断。因为如果基金代码不存在的话,API返回的值肯定是带有错误信息的,其中的Data
值肯定是空,我们忽略了这一点,所以自然就报错啦。
简单处理一下,把这段放在try-catch
中,并添加一个错误信息,直接返回
protected override void SolveInstance(IGH_DataAccess DA)
{// 获取基金代码,检测获取是否成功,若不成功直接退出电池执行// 建立请求// 发起请求,将请求返回保存至res中// 准备解析string jsonString;using (var responseStream = res.GetResponseStream()) // 获取返回流{// 在内存中创建临时存储用来存储返回流数据using (var ms = new MemoryStream()) {// 返回流数据复制至内存中// 用UTF8解码返回流数据// 解析json字符串ResponseData resData = default(ResponseData);try{resData = JsonConvert.DeserializeObject<ResponseData>(jsonString);}catch (Exception e){AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);return;}// 提取每天的净值信息,放入列表中// 传出数据}}
}
这回就对错误有了一些简单的错误处理逻辑了。当然,通过查阅API,还可以在返回的数据结构中针对不同种类的错误给出不同的提示,这里限于篇幅就不展开了。
总结
通过这个小电池,我们将前面1~4的内容进行了一个串联,从如何收集数据、实现自己的逻辑,到最后传出数据,进行简单的测试和Debug。涉及到的点大致有下面几个内容
- 输入/输出参数的确定,以及它们的类型,相关的GH特有的隐式类型转换
- 对 WebAPI 发起一个网络请求
- 对返回的json字符串进行解析
- 简单的测试
其中,json字符串解析部分本文并没有详细展开,因为它本身就能做一个超级大的内容了,这里只给出了一个具体的应用。想要详细了解的读者可以在CSDN上直接搜索(点击右侧) Newtonsoft.Json 就可以出来更多的资料了。
另外,网络请求相关的内容,读者在搜索额外资料时请注意适用环境,.NET / .NET Core 是一类环境,.NET Framework 与它们是有一些差别的。
最后,部分代码涉及到了 流(Stream) 的概念,这是有关于I/O相关的内容。初步理解的话就是可以认为“流”是一系列函数,最后的数据会在所有的中间的流中过一遍,也就是实现了对整个数据流的依次处理。读者感兴趣的话可以自行继续深入学习。
电池cs文件的全部代码
using Grasshopper.Kernel;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;namespace DigitalCrab.Grasshopper
{public struct ResponseData{public HttpStatusCode Code { get; set; }public string Message { get; set; }public Fund Data { get; set; }public string Meta { get; set; }public struct Fund{public string Code { get; set; }public string Name { get; set; }public string Type { get; set; }public float NetWorth { get; set; }public float ExpectWorth { get; set; }public float TotalWorth { get; set; }public string ExpectGrowth { get; set; }public string DayGrowth { get; set; }public string LastWeekGrowth { get; set; }public string LastMonthGrowth { get; set; }public string LastThreeMonthsGrowth { get; set; }public string LastSixMonthsGrowth { get; set; }public string LastYearGrowth { get; set; }public string BuyMin { get; set; }public string BuySourceRate { get; set; }public string BuyRate { get; set; }public string Manager { get; set; }public string FundScale { get; set; }public string NetWorthDate { get; set; }public string ExpectWorthDate { get; set; }public string[][] NetWorthData { get; set; }public string[][] TotalNetWorthData { get; set; }}}public class GetStock : GH_Component{public GetStock(): base("GetStock", "GS","Description","Params", "DigitalCrab"){}protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager){pManager.AddIntegerParameter("Code", "C", "", GH_ParamAccess.item);}protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager){pManager.AddNumberParameter("DayValues", "v", "", GH_ParamAccess.list);}protected override void SolveInstance(IGH_DataAccess DA){int code = int.MinValue;bool Success = DA.GetData(0, ref code);if(!Success) return;var req = WebRequest.Create(@"https://api.doctorxiong.club/v1/fund/detail?code=" + code.ToString());req.Method = "GET";req.Headers.Add(HttpRequestHeader.AcceptCharset, "utf-8");var res = req.GetResponse();string jsonString;using (var responseStream = res.GetResponseStream()){using (var ms = new MemoryStream()){responseStream.CopyTo(ms);jsonString = Encoding.UTF8.GetString(ms.ToArray());ResponseData resData = default(ResponseData);try {resData = JsonConvert.DeserializeObject<ResponseData>(jsonString);}catch (Exception e) {AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);return;}List<string> dayValues = new List<string>();foreach (var item in resData.Data.NetWorthData) dayValues.Add((item[1]));DA.SetDataList(0, dayValues);}}}protected override System.Drawing.Bitmap Icon => null;public override Guid ComponentGuid => throw new NotImplementedException("请自行申请GUID并替换");}
}
【Grasshopper基础5】在GH里看基金? —— 简单电池项目实战相关推荐
- 【Grasshopper基础6】输入/输出参数可变的电池 / 如何让电池支持参数增加和减少
相信大家一定在Grasshopper中见过输入或者输出参数可以自由变化的电池,例如,笔者常用的电池"Entwine"就可以在电池的输入端添加一个参数或者减少参数,用来支持更多的电池 ...
- 零基础入行软件测试全套学习资料汇总,项目实战源码+视频教程应有尽有
目录 一.了解软件测试的基本概念 二.软件测试的前景 三.学习软件测试的基础知识 四.参加软件测试培训班 五.积累实践经验 六.寻找实习机会 七.积极提升自己的综合能力 八.建立自己的社交网络 九.制 ...
- 疫情下的5.20给女朋友写的一份信:哈哈感动了女友,一下午也值了(一份静态网站,基础入门的也可以看懂+简单部署)
首先@
- 代码里无图片地址_项目实战:爬高清图片
↑ 关注 + 星标 ,后台回复[大礼包]送你2TPython自学资料 好消息:Python学习交流群,已经建立,猛戳加入 之前我发过一些爬虫的文章,不过一直没发过爬取图片的,今天就给大家分享一篇吧! ...
- 上海c语言做游戏培训,0基础C语言游戏逆向课程,培训视频+项目实战
第一部分 1.Visual Studio IDE的安装和基本使用 1.Visual Studio IDE的安装和基本使用 .docx 1.Visual Studio IDE的安装和基本使用 .mp4 ...
- 超详细!“看图说话”(Image Caption)项目实战
超详细!基于pytorch的"看图说话"(Image Caption)项目实战 0.简介 1.运行环境 1.1 我的环境 1.2 建立环境 2.理论介绍 3.运行项目 3.1 项目 ...
- Python编程:从入门到实践+爬虫开发与项目实战+网络编程基础+项目开发实战
给还在苦苦自学Python的小伙伴们分享一波学习教程~有了它们,至少能节省50%的时间,少走一半的弯路. 书不在多,而在于精~ <Python编程:从入门到实践>豆瓣评分9.2 本书是针对 ...
- 【Grasshopper基础11】如何在GH电池上增加一个自己的按钮
作者:"咕咕咕?下一篇马上就写好了" 通过上一篇[基础10]的文章,大家已经了解到一个GH电池在画布上的样式是由其背后的 GH_Attribute 类实例来决定的,而大部分的GH电 ...
- 【Grasshopper基础13】创建可在画布上自由传递的自定义类型数据(上)—— IGH_Goo接口的重要性及其实现
接下来的两章,我们来介绍一下在之前章节尚未介绍到的,但却在Grasshopper中占据极其重要地位的另一批我们早就虎视眈眈但却还没想到理由要去触碰的电池们(左侧红色框指示): 是的,就是这一些带黑底的 ...
最新文章
- CMAKE_CURRENT_BINARY_DIR
- python中的简单while循环及逻辑运算符
- lua 访问oracle,lua语言数据库访问 - Lua教程
- Qt智能指针--QSharedPointer
- 南山中学2021级2班高考成绩查询,绵阳南山中学双语学校2021年排名
- c语言分组求和函数,R语言 实现data.frame 分组计数、求和等
- ObjectTive C语言语法,[译]理解 Objective-C 运行时(下篇)
- Maven 打包war文件
- 《图像超分》一些论文走读(SRCNN ,ESPCN ,VDSR ,SRGAN)
- ubuntu保存_Arch与Ubuntu安装软件对比
- 深入理解 gRPC 协议--理解protobuf/.proto/http2
- Windows下本地安装SVN客户端
- OpenGL实现B样条曲线
- mysql5.7卸载教程_MySQL 5.7.19 简易安装、卸载教程
- html过滤检索类似excel,利用jQuery实现仿Excel表格排序筛选代码
- 天下会 - Google系列之谷歌搜索引擎高级用法:使用搜索语法精确搜索
- 喜迎B+轮融资,ThingJS母公司优锘科技成为新基建的一匹黑马
- 基础命令和脚本练习初识
- 17 岁成为 iOS 越狱之父,25 岁造出无人车,黑客传奇!
- 2022年,短视频直播现状与发展趋势