我们正在用Go和Javascript编写一个学习应用程序,以评估实际的股票收益。 第1部分-后端

让我们尝试编写一个简短的培训,但是相当完整的信息系统,该系统由Go上的服务器部分和Javascript + Vue JS上的客户端Web应用程序组成。

首先,简要介绍一下此应用程序的用途及其用途。 前一段时间,我想到了一个问题,即如何节省我已经结出的一定数量的钱。 甚至对我来说,一个远离金融界的人,也很明显,持有现金是不好的,原因至少有两个:

  • 金钱吞噬了通货膨胀(通货膨胀风险)
  • 卢布可能贬值(汇率风险)

已决定研究该问题并选择适当的投资工具。 主要标准是可靠性和保护上述风险的储蓄。
我研究了这个问题,得出的结论是,俄罗斯人唯一合适的投资工具是股票交易所交易基金(ETF),以及在莫斯科交易所交易的那些。

因此,我建议编写一个培训应用程序,以显示在莫斯科交易所展示的所有ETF的盈利能力。

您可以说可以在交易所网站本身上查看这种获利能力,并且该应用程序(至少用于培训)应该会有所帮助。 我同意,因此,我们将尝试显示一些有条件的实际库存收益。 所谓有条件的实际回报率,是指根据俄罗斯通胀率调整后的回报率。
在本文的第一部分,我们将分析应用程序的服务器部分。 我们的后端是用Go编写的,在开发过程中,我们将尝试应用这种语言的功能,例如并行代码执行,接口,测试等。

传统知识要求:

  1. 应要求,应用程序的服务器部分应提供所有Moscow Exchange ETF的报价数据以及每种证券交易所有月的通货膨胀数据。
  2. 应用程序的服务器部分必须支持多个数据仓库提供程序,在提供程序之间进行切换不应要求更改代码
  3. 应用程序的服务器部分必须通过http协议提供API,以从存储中接收数据

因此,让我们设计系统服务器部分的软件体系结构。

首先 ,我们将提出应用程序的程序包结构。 根据工作说明,该应用程序将由一个Web服务器组成,该Web服务器将提供REST API并提供该Web应用程序的文件(此后,我们将在Vue上编写SPA)。 此外,根据ToR,我们必须为数据仓库提供者制作几个软件包。

在这一点上,我们应该更详细地介绍。 如何提供在Go中某些功能的提供程序之间切换的功能? 答:使用接口。 因此,我们将需要为包开发一个接口(合同),每个包都将针对其自己的存储类型执行合同。 在本文中,我们将考虑将数据存储在RAM中,但以此类推,您可以轻松添加任何DBMS。 最终的包装结构如下:



其次 ,让我们决定存储接收到的信息的数据类型以及存储提供商的合同。

我们将需要用于股票报价和通货膨胀的数据类型。 我们将按月计算报价和通货膨胀,这种规模非常适合ETF这样的非投机性工具。

该合同将要求使用Mosbirzha服务器中的数据填充存储(初始化)并根据请求提供报价数据的方法。 一切都非常简单。

结果,我们将用于存储报价的类型和接口放在存储模块中:

// Package storage           package storage // Security -   type Security struct { ID string // ticker Name string //    IssueDate int64 //     Quotes []Quote //  } // Quote -    ( 'close') type Quote struct { SecurityID string // ticker Num int //   ( ) TimeStamp int64 //     Unix Time Price float64 //   } // Interface -      type Interface interface { InitData() error //       Securities() ([]Security, error) //      } 

为简单起见,我们在服务器模块中编写通货膨胀数据:

 var inflation = []struct { Year int Values [12]float64 }{ { Year: 2013, Values: [12]float64{0.97, 0.56, 0.34, 0.51, 0.66, 0.42, 0.82, 0.14, 0.21, 0.57, 0.56, 0.51}, }, { Year: 2014, Values: [12]float64{0.59, 0.70, 1.02, 0.90, 0.90, 0.62, 0.49, 0.24, 0.65, 0.82, 1.28, 2.62}, }, { Year: 2015, Values: [12]float64{3.85, 2.22, 1.21, 0.46, 0.35, 0.19, 0.80, 0.35, 0.57, 0.74, 0.75, 0.77}, }, { Year: 2016, Values: [12]float64{0.96, 0.63, 0.46, 0.44, 0.41, 0.36, 0.54, 0.01, 0.17, 0.43, 0.44, 0.40}, }, { Year: 2017, Values: [12]float64{0.62, 0.22, 0.13, 0.33, 0.37, 0.61, 0.07, -0.54, -0.15, 0.20, 0.22, 0.42}, }, { Year: 2018, Values: [12]float64{0.31, 0.21, 0.29, 0.38, 0.38, 0.49, 0.27, 0.01, 0.16, 0.35, 0.50, 0.84}, }, } 

第三 ,让我们描述API的端点。 只有两个:报价和通货膨胀。 仅HTTP GET方法。

  // API   http.HandleFunc("/api/v1/securities", securitiesHandler) //     http.HandleFunc("/api/v1/inflation", inflationHandler) //    

实际上,从莫斯科交易所网站接收和处理数据是通过初始化方法进行的。 我们根据交换API帮助获取数据。
您应该注意的是:我们被迫对每个安全性使用单独的请求(并且已经有几十个请求了)。 在一个线程中顺序执行数据初始化将花费大量时间。 因此,我们将使用Go的骄傲-goroutines。 请注意以下代码:

 // InitData       func (s *Storage) InitData() (err error) { securities, err := getSecurities() if err != nil { return err } //    var wg sync.WaitGroup //        wg.Add(len(securities)) for _, security := range securities { go func(item storage.Security) { //      defer wg.Done() var quotes []storage.Quote quotes, err = getSecurityQuotes(item) if err != nil { fmt.Println(item, err) return } item.Quotes = quotes err = s.Add(item) if err != nil { return } }(security) } //     wg.Wait() return err } 

在数据初始化功能中,我们将请求并行化到服务器。 实际上,这种站点解析有很多问题:

  • 由于怀疑有DoS可能导致自动阻止请求
  • 您必须使用上下文模块或控制通道来强制goroutin结束。
  • 您需要使用通道从goroutine返回错误

为简单起见,所有这些要点均被省略。

就课程而言,内置的HTTP请求路由器对我们来说足够了。 在更复杂的系统上,您可能希望使用其他一些系统。 就个人而言,我使用的是大猩猩项目中的路由器,但总的来说,它们很多。

随后,我们将添加一个点以上传我们的Web应用程序的文件。 展望未来,我要说的是,您应该只使用返回文件内容。

因此,让我们编写服务器:

 // Package main  -  moex-etf package main import ( "encoding/json" "fmt" "log" "moex_etf/server/storage" "moex_etf/server/storage/inmemory" "net/http" ) var db storage.Interface func main() { //   , ,        //    .   db = inmemory.New() fmt.Println("Inititalizing data") //    err := db.InitData() if err != nil { log.Fatal(err) } // API   http.HandleFunc("/api/v1/securities", securitiesHandler) //     http.HandleFunc("/api/v1/inflation", inflationHandler) //    //      8080 const addr = ":8080" fmt.Println("Starting web server at", addr) log.Fatal(http.ListenAndServe(addr, nil)) } //    func securitiesHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET") w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") if r.Method != http.MethodGet { return } securities, err := db.Securities() if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } err = json.NewEncoder(w).Encode(securities) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } } //    func inflationHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET") w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") if r.Method != http.MethodGet { return } err := json.NewEncoder(w).Encode(inflation) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } } //      var inflation = []struct { Year int Values [12]float64 }{ { Year: 2013, Values: [12]float64{0.97, 0.56, 0.34, 0.51, 0.66, 0.42, 0.82, 0.14, 0.21, 0.57, 0.56, 0.51}, }, { Year: 2014, Values: [12]float64{0.59, 0.70, 1.02, 0.90, 0.90, 0.62, 0.49, 0.24, 0.65, 0.82, 1.28, 2.62}, }, { Year: 2015, Values: [12]float64{3.85, 2.22, 1.21, 0.46, 0.35, 0.19, 0.80, 0.35, 0.57, 0.74, 0.75, 0.77}, }, { Year: 2016, Values: [12]float64{0.96, 0.63, 0.46, 0.44, 0.41, 0.36, 0.54, 0.01, 0.17, 0.43, 0.44, 0.40}, }, { Year: 2017, Values: [12]float64{0.62, 0.22, 0.13, 0.33, 0.37, 0.61, 0.07, -0.54, -0.15, 0.20, 0.22, 0.42}, }, { Year: 2018, Values: [12]float64{0.31, 0.21, 0.29, 0.38, 0.38, 0.49, 0.27, 0.01, 0.16, 0.35, 0.50, 0.84}, }, } 

我将不在此处提供内存中的存储实现代码, Github上的所有内容均可用。

要测试我们的API:

通货膨胀
引号

这样就完成了本文的第一部分。 在第二部分中,我们将为包装编写测试和性能度量。 在第三部分中,我们将开发一个Web应用程序。

Source: https://habr.com/ru/post/zh-CN440518/


All Articles