Wir schreiben eine Lernanwendung in Go und Javascript, um die tatsächlichen Aktienrenditen zu bewerten. Teil 1 - Backend

Versuchen wir, ein kleines, aber recht vollständiges Schulungssystem zu schreiben, das aus einem Serverteil auf Go und einer Client-Webanwendung auf Javascript + Vue JS besteht.

Zunächst ein paar Worte darüber, was diese Anwendung ist und wofür sie gedacht ist. Vor einiger Zeit stellte sich vor mir die Frage, wie ich einen bestimmten Geldbetrag sparen kann, den ich gebildet hatte. Selbst für mich, eine Person weit weg von der Finanzwelt, war es offensichtlich, dass es aus mindestens zwei Gründen schlecht war, Geld in bar zu halten:

  • Geld frisst die Inflation auf (Inflationsrisiko)
  • Der Rubel kann abwerten (Wechselkursrisiko)

Es wurde beschlossen, das Problem zu untersuchen und das geeignete Anlageinstrument auszuwählen. Die Hauptkriterien waren Zuverlässigkeit und Schutz der Einsparungen vor den oben genannten Risiken.
Ich habe das Problem untersucht und bin zu dem Schluss gekommen, dass das einzig angemessene Anlageinstrument für einen Russen börsengehandelte Fonds (ETF) und solche sind, die an der Moskauer Börse gehandelt werden.

Daher schlage ich vor, einen Schulungsantrag zu schreiben, der die Rentabilität aller an der Moskauer Börse präsentierten ETFs zeigt.

Sie können sagen, dass diese Rentabilität auf der Exchange-Website selbst angezeigt werden kann und die Anwendung, zumindest für Schulungen, etwas nützlich sein sollte. Ich bin damit einverstanden, daher werden wir versuchen, eine bedingte reale Aktienrendite anzuzeigen. Mit dieser bedingten realen Rendite meine ich die inflationsbereinigte Rendite in Russland.
Im ersten Teil des Artikels werden wir den Serverteil der Anwendung analysieren. Unser Backend ist in Go geschrieben und während der Entwicklung werden wir versuchen, Funktionen der Sprache wie parallele Codeausführung, Schnittstellen, Tests und mehr anzuwenden.

TK-Anforderungen:

  1. Der Serverteil der Anwendung sollte auf Anfrage Daten zu Notierungen für alle Moskauer Börsen-ETFs und Inflationsdaten für alle Handelsmonate für jedes Wertpapier bereitstellen
  2. Der Serverteil der Anwendung muss mehrere Data Warehouse-Anbieter unterstützen. Für den Wechsel zwischen Anbietern sollten keine Codeänderungen erforderlich sein
  3. Der Serverteil der Anwendung muss die API über das http-Protokoll bereitstellen, um Daten vom Speicher zu empfangen

Entwerfen wir also die Softwarearchitektur des Serverteils unseres Systems.

Zunächst werden wir die Paketstruktur der Anwendung erstellen. Gemäß der Arbeitserklärung besteht die Anwendung aus einem Webserver, der die REST-API bereitstellt und die Dateien unserer Webanwendung bereitstellt (später werden wir SPA on Vue schreiben). Darüber hinaus müssen wir laut ToR mehrere Pakete für Data-Warehouse-Anbieter erstellen.

An dieser Stelle sollten wir näher darauf eingehen. Wie kann ich die Möglichkeit bieten, in Go zwischen Anbietern bestimmter Funktionen zu wechseln? Antwort: Verwenden von Schnittstellen. Daher müssen wir eine Schnittstelle (Vertrag) für Pakete entwickeln, von denen jedes einen Vertrag für seinen eigenen Speichertyp ausführt. In diesem Artikel werden wir das Speichern von Daten im RAM in Betracht ziehen. Analog können Sie jedoch problemlos jedes DBMS hinzufügen. Die endgültige Paketstruktur lautet wie folgt:



Zweitens entscheiden wir uns für die Datentypen, in denen wir die empfangenen Informationen speichern, und für den Vertrag für Speicheranbieter.

Wir benötigen Datentypen für Börsenkurse und Inflation. Wir werden die Quotes und die Inflation monatelang annehmen. Diese Skala eignet sich gut für ein nicht spekulatives Instrument wie den ETF.

Der Vertrag erfordert die Verfügbarkeit von Methoden, um den Speicher mit Daten vom Mosbirzha-Server zu füllen (Initialisierung) und auf Anfrage Angebotsdaten bereitzustellen. Alles ist sehr einfach.

Als Ergebnis setzen wir die Typen zum Speichern von Anführungszeichen und die Schnittstelle in das Speichermodul:

// 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) //      } 

Der Einfachheit halber codieren wir die Inflationsdaten im Servermodul:

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

Drittens beschreiben wir die Endpunkte unserer API. Es wird nur zwei geben: für Kurse und Inflation. Nur HTTP GET-Methode.

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

Tatsächlich erfolgt der Empfang und die Verarbeitung von Daten von der Mosbirzi-Website in der Initialisierungsmethode. Wir nehmen die Daten gemäß der Hilfe auf der Exchange-API .
Worauf Sie achten sollten: Wir sind gezwungen, für jede Sicherheit eine separate Anfrage zu verwenden (und es gibt bereits ein paar Dutzend davon). Das sequentielle Ausführen der Dateninitialisierung in einem einzelnen Thread würde viel Zeit in Anspruch nehmen. Deshalb werden wir den Stolz der Go-Goroutinen nutzen. Beachten Sie den folgenden Code:

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

In der Dateninitialisierungsfunktion haben wir Anforderungen an den Server parallelisiert. In der Praxis weist eine solche Site-Analyse eine Reihe von Problemen auf:

  • Kann aufgrund des Verdachts auf DoS zur automatischen Blockierung von Anfragen führen
  • Sie müssen das Kontextmodul oder den Steuerkanal verwenden, um das Beenden von Goroutin zu erzwingen.
  • Sie müssen den Kanal verwenden, um Fehler von Goroutine zurückzugeben

Der Einfachheit halber sind alle diese Punkte weggelassen.

Für die Zwecke des Lehrplans reicht uns der eingebaute HTTP-Anforderungsrouter. Auf komplexeren Systemen möchten Sie wahrscheinlich andere verwenden. Persönlich benutze ich den Router aus dem Gorilla-Projekt, aber im Allgemeinen gibt es viele davon.

Anschließend fügen wir einen Punkt zum Hochladen von Dateien unserer Webanwendung hinzu. Mit Blick auf die Zukunft möchte ich sagen, dass Sie hierfür einfach die Rückgabe von Dateiinhalten verwenden sollten.

Schreiben wir also unseren Server:

 // 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}, }, } 

Ich werde den Speicherimplementierungscode hier nicht im Speicher bereitstellen, alles ist auf Github verfügbar.

So testen Sie unsere API:

Inflation
Zitate

Damit ist der erste Teil des Artikels abgeschlossen. Im zweiten Teil werden wir Tests und Leistungsmessungen für unsere Pakete schreiben. Im dritten Teil werden wir eine Webanwendung entwickeln.

Source: https://habr.com/ru/post/de440518/


All Articles