Estamos escribiendo una aplicación de aprendizaje en Go y Javascript para evaluar el rendimiento real de las acciones. Parte 1 - backend

Intentemos escribir un pequeño entrenamiento, pero un sistema de información bastante completo que consiste en una parte del servidor en Go y una aplicación web de cliente en Javascript + Vue JS.

Primero, algunas palabras sobre qué es esta aplicación y para qué sirve. Hace algún tiempo, surgió la pregunta frente a mí sobre cómo ahorrar una cierta cantidad de dinero que había formado. Incluso para mí, una persona lejos del mundo de las finanzas, era obvio que tener dinero en efectivo era malo por al menos dos razones:

  • El dinero come la inflación (riesgo inflacionario)
  • El rublo puede depreciarse (riesgo de tipo de cambio)

Se decidió estudiar el tema y elegir la herramienta de inversión adecuada. Los criterios principales fueron la fiabilidad y la protección del ahorro de los riesgos anteriores.
Estudié el tema y como resultado llegué a la conclusión de que la única herramienta de inversión adecuada para un ruso son los fondos cotizados en bolsa (ETF) y los que se negocian en la Bolsa de Moscú.

Por lo tanto, propongo escribir una aplicación de capacitación que muestre la rentabilidad de todos los ETF que se presentan en la Bolsa de Moscú.

Puede decir que esta rentabilidad se puede ver en el sitio web de intercambio en sí, y la aplicación, al menos para capacitación, debería ser algo útil. Estoy de acuerdo, por lo tanto, trataremos de mostrar un rendimiento condicional de stock real. Con este rendimiento real condicional, me refiero a la tasa de rendimiento ajustada por la inflación en Rusia.
En la primera parte del artículo analizaremos la parte del servidor de la aplicación. Nuestro backend está escrito en Go y durante el desarrollo intentaremos aplicar características del lenguaje como la ejecución de código paralelo, interfaces, pruebas y más.

Requisitos de conocimientos tradicionales:

  1. La parte del servidor de la aplicación debe proporcionar, previa solicitud, datos sobre cotizaciones para todos los ETF de Exchange de Moscú y datos de inflación para todos los meses de negociación de cada valor
  2. La parte del servidor de la aplicación debe admitir varios proveedores de almacenamiento de datos, el cambio entre proveedores no debe requerir cambios de código
  3. La parte del servidor de la aplicación debe proporcionar la API a través del protocolo http para recibir datos del almacenamiento

Entonces, diseñemos la arquitectura de software de la parte del servidor de nuestro sistema.

Primero , presentaremos la estructura del paquete de la aplicación. Según la declaración de trabajo, la aplicación consistirá en un servidor web que proporcionará la API REST y proporcionará los archivos de nuestra aplicación web (más adelante escribiremos SPA en Vue). Además, de acuerdo con los términos de referencia, debemos hacer varios paquetes para los proveedores de almacenamiento de datos.

En este punto, debemos insistir en más detalles. ¿Cómo puedo proporcionar la capacidad de cambiar entre proveedores de alguna funcionalidad en Go? Respuesta: usando interfaces. Por lo tanto, necesitaremos desarrollar una interfaz (contrato) para paquetes, cada uno de los cuales ejecutará un contrato para su propio tipo de almacenamiento. En el artículo, consideraremos almacenar datos en RAM, pero por analogía, puede agregar fácilmente cualquier DBMS. La estructura final del paquete será la siguiente:



En segundo lugar , decidamos los tipos de datos en los que almacenaremos la información recibida y el contrato para los proveedores de almacenamiento.

Necesitaremos tipos de datos para cotizaciones de acciones e inflación. Tomaremos cotizaciones e inflación por meses, esta escala es bastante adecuada para un instrumento tan no especulativo como el ETF.

El contrato requerirá la disponibilidad de métodos para llenar el almacenamiento con datos del servidor Mosbirzha (inicialización) y proporcionar datos de cotización a pedido. Todo es extremadamente simple.

Como resultado, colocamos los tipos para almacenar cotizaciones y la interfaz en el módulo de almacenamiento:

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

Para simplificar, codificamos los datos de inflación en el módulo del servidor:

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

Tercero , describamos los puntos finales de nuestra API. Solo habrá dos: para cotizaciones e inflación. Solo el método HTTP GET.

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

En realidad, la recepción y el procesamiento de datos del sitio web de Mosbirzi se lleva a cabo en el método de inicialización. Tomamos los datos de acuerdo con la ayuda en la API de intercambio .
A lo que debe prestar atención: nos vemos obligados a utilizar una solicitud por separado para cada seguridad (y ya hay un par de docenas de ellas). Ejecutar la inicialización de datos secuencialmente, en un solo hilo, llevaría mucho tiempo. Por lo tanto, utilizaremos el orgullo de Go - goroutines. Presta atención al siguiente código:

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

En la función de inicialización de datos, paralelizamos solicitudes al servidor. En la práctica, este análisis del sitio tiene varios problemas:

  • Puede conducir al bloqueo automático de solicitudes debido a sospechas de DoS
  • Debe usar el módulo de contexto o el canal de control para forzar el final de goroutin.
  • Debe usar el canal para devolver errores de goroutine

Para simplificar, se omiten todos estos puntos.

Para los fines del programa de capacitación, el enrutador de solicitud HTTP incorporado es suficiente para nosotros. En sistemas más complejos, probablemente quiera usar otro. Personalmente, uso el enrutador del proyecto Gorilla, pero en general hay muchos de ellos.

Posteriormente, agregaremos un punto para cargar archivos de nuestra aplicación web. Mirando hacia el futuro, diré que para esto simplemente debe usar la devolución del contenido del archivo.

Entonces, escriba nuestro servidor:

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

No proporcionaré el código de implementación de almacenamiento en la memoria aquí, todo está disponible en Github .

Para probar nuestra API:

inflación
citas

Esto completa la primera parte del artículo. En la segunda parte, escribiremos pruebas y mediciones de rendimiento para nuestros paquetes. En la tercera parte, desarrollaremos una aplicación web.

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


All Articles