Financiamiento histórico MINERO PARA FTX.COM -- go campo con rest campo con postgresql campo con docker camp codereview Relacionados El problema

Historical Funding Rate Miner for ftx.com


4
vote

problema

Español

Recientemente comencé a echar un vistazo a la Go Programming Language y decidí escribir un pequeño proyecto sin uso práctico .

Los objetivos fueron:

  • Encuesta regularmente las últimas tasas de financiamiento desde https://ftx.com/api/funding_rates ( Los elegí, porque cada objeto JSON es bastante pequeño y porque las nuevas actualizaciones están disponibles por la hora)
  • Mantener una base de datos duplicada gratuita de todos dbRecords == jsonObjects visto

Configuración de la base de datos

DockerFile

  FROM postgres:13 COPY *.sql /docker-entrypoint-initdb.d/   

Consulta de inicio de la base de datos

  -- Create Database and mark it as active so that all -- consecutive commands are applied to this table CREATE DATABASE crypto_mining; connect crypto_mining;  CREATE TABLE funding_rates(     id SERIAL PRIMARY KEY,     exchange TEXT NOT NULL,     future TEXT NOT NULL,     time TIMESTAMP NOT NULL,     rate DOUBLE PRECISION DEFAULT 0.0 );   

Disco API Miner

DockerFile

  List.replaceAll()0  

Encuesta de la API

  List.replaceAll()1  

Ponerlo todo junto en Docker-Compose.yml

  List.replaceAll()2  

Puntos que encontré torpes durante la escritura del código IR o que estoy particularmente interesado en mejorar:

  • ¿Es la forma correcta de mantener todo lo lógico en un archivo?
  • es común que el código de IR se interrumpe permanentemente por pequeñas verificaciones de errores (especialmente como en List.replaceAll()3 )?
  • es común distinguir los tipos de errores por su mensaje de error (como List.replaceAll()4 en List.replaceAll()5 ?
Original en ingles

I recently started to take a look at the Go programming language and decided to write a small project without practical use.

The objectives were:

  • Regularly poll the latest funding rates from https://ftx.com/api/funding_rates (I chose them, because each JSON object is rather small and because new updates are available by the hour)
  • Maintain a duplicate free database of all dbRecords == jsonObjects seen

Database setup

Dockerfile

FROM postgres:13 COPY *.sql /docker-entrypoint-initdb.d/ 

Database init query

-- Create Database and mark it as active so that all -- consecutive commands are applied to this table CREATE DATABASE crypto_mining; \connect crypto_mining;  CREATE TABLE funding_rates(     id SERIAL PRIMARY KEY,     exchange TEXT NOT NULL,     future TEXT NOT NULL,     time TIMESTAMP NOT NULL,     rate DOUBLE PRECISION DEFAULT 0.0 ); 

REST Api Miner

Dockerfile

FROM golang WORKDIR /src  RUN go get github.com/lib/pq  COPY . . ENTRYPOINT [ "go", "run", "ftxminer.go" ] 

Polling the API

package main  import "fmt" import "os" import "time" import "sort" import "net/http" import "io/ioutil" import "encoding/json" import "database/sql" import _ "github.com/lib/pq"  type t_fundingRates struct {     Future string    `json:"future`     Rate   float64   `json:"rate"`     Time   time.Time `json:"time"` }  type t_fundingRates_apiResult struct {     Result  []t_fundingRates `json:"result"`     Success bool             `json:"success"` }  // implement logic to sort t_fundingRates by time type rateByTime []t_fundingRates  func (r rateByTime) Len() int {     return len(r) }  func (r rateByTime) Swap(i, j int) {     r[i], r[j] = r[j], r[i] }  func (r rateByTime) Less(i, j int) bool {     return r[i].Time.Before(r[j].Time) }  func getFundingRates(minedRates chan<- []t_fundingRates) {     ticker := time.NewTicker(30 * time.Second)     for {         <-ticker.C          // get response object with status code, result, etc         httpResponse, err := http.Get("https://ftx.com/api/funding_rates")         if nil != err {             fmt.Printf(err.Error())             continue         }          // extract application data         httpResponseText, err := ioutil.ReadAll(httpResponse.Body)         if nil != err {             fmt.Printf(err.Error())             continue         }          // parse application data         httpResponseJson := t_fundingRates_apiResult{}         if err := json.Unmarshal(httpResponseText, &httpResponseJson); err != nil {             fmt.Printf(err.Error())             continue         }          // if the api responds success         if httpResponseJson.Success {             // send data to consumer             minedRates <- httpResponseJson.Result         } else {             fmt.Printf("api did not send funding rates")             continue         }     } }  func getIndex(rates []t_fundingRates, future string) int {     for i, rate := range rates {         if future == rate.Future {             return i         }     }      return -1 }  func storeFundingRates(minedRates <-chan []t_fundingRates) {     psqlInfo := os.Getenv("DATABASE_URL")     knownRates := make([]t_fundingRates, 0)       // rates we have seen previously     updateCandidates := make([]t_fundingRates, 0) // rates we maybe need to compare to database and maybe update     updateRates := make([]t_fundingRates, 0)      // rates we need to update in database      for {         // wait for funding rates to arrive         rates := <-minedRates         fmt.Println("Successfully retrieved", len(rates), "funding rates from ftx.com")          // evaluate which rates we need to update         updateRates = make([]t_fundingRates, 0)      // clear updateRates and         updateCandidates = make([]t_fundingRates, 0) // updateCandidates first         for _, rate := range rates {             knownRatesIndex := getIndex(knownRates, rate.Future)              if -1 == knownRatesIndex {                 // if we have not seen the future before, then add it to collection                 updateCandidates = append(updateCandidates, rate) // mark as need to update in database             } else {                 // if we have seen the future earlier, then compare dates and decide whether we                 // need to update                 knownRate := knownRates[knownRatesIndex]                  if knownRate.Time.Before(rate.Time) {                     knownRates[knownRatesIndex] = rate                     updateRates = append(updateRates, rate)                 }             }         }          // only pass this point if there is data to be inserted into the database         if 0 == len(updateRates) && 0 == len(updateCandidates) {             fmt.Println("No outdated or new futures found")             continue         } else {             fmt.Println("Found", len(updateRates)+len(updateCandidates), "outdated or potentially new futures")         }          // prepare the database connection         db, err := sql.Open("postgres", psqlInfo)         if nil != err {             fmt.Println(err.Error())             continue         }         defer db.Close()          // establish the connection and send a ping         err = db.Ping()         if nil != err {             fmt.Println(err.Error())             continue         }         fmt.Println("Successfully connected")          // check for update candidates and potentially mark for inserting         sort.Sort(rateByTime(updateCandidates))         for _, candidate := range updateCandidates {              candidateRecord := t_fundingRates{}             err := db.QueryRow(                 "SELECT DISTINCT ON (future,time) future,time,rate FROM funding_rates WHERE exchange='ftx' AND future=$1 ORDER BY future,time DESC;",                 candidate.Future).Scan(                 &candidateRecord.Future,                 &candidateRecord.Time,                 &candidateRecord.Rate)             isCandidateRecordValid := true              // did we get the result? if not I am just going to assume that             if err != nil {                 if err.Error() == "sql: no rows in result set" {                     isCandidateRecordValid = false                       knownRates = append(knownRates, candidate)                     updateRates = append(updateRates, candidate)                 } else {                     fmt.Println(err.Error())                     continue                 }             }              // add the future to the list of known futures             if isCandidateRecordValid {                 if candidateRecord.Time.Before(candidate.Time) {                     updateRates = append(updateRates, candidate) // mark as definite update                      knownRatesIndex := getIndex(knownRates, candidate.Future) // things get a bit messy here due to duplicates in source data                     if -1 == knownRatesIndex {                         knownRates = append(knownRates, candidate)                     } else {                         knownRate := knownRates[knownRatesIndex]                          if knownRate.Time.Before(candidate.Time) {                             knownRates[knownRatesIndex] = candidate                         }                     }                 } else {                     knownRates = append(knownRates, candidateRecord)                 }             }         }          // insert guaranteed updates into database         for _, update := range updateRates {             _, err = db.Exec("INSERT INTO funding_rates (exchange , future , time , rate) VALUES ('ftx' , $1 , $2 , $3);", update.Future, update.Time, update.Rate)             if nil != err {                 fmt.Println(err.Error())                 continue             }         }     } }  func main() {     fmt.Println("Starting FTX Miner")      chan_fundingRates := make(chan []t_fundingRates)     go storeFundingRates(chan_fundingRates)     go getFundingRates(chan_fundingRates)     fmt.Println("... Funding Rates [OK]")      sleepForever := make(chan string)     <-sleepForever      os.Exit(0) }  

Putting it all together in docker-compose.yml

version: '2.0'  services:     database:         build:             context: ./database/             dockerfile: ./Dockerfile         environment:             POSTGRES_PASSWORD: test123         ports:             - 5432:5432         volumes:             - databasevol:/var/lib/postgresql/data         networks:             - net      ftxminer:         build:             context: ./ftx/             dockerfile: ./Dockerfile         depends_on:             - database         environment:             DATABASE_URL: host=database port=5432 user=postgres password=test123 dbname=crypto_mining sslmode=disable         networks:             - net  volumes:     databasevol:         driver: local  networks:     net:         driver: bridge 

Points that I found clumsy during writing the go code or that I am particularly interested in improving:

  • Is it the proper way to keep all go logic in one file?
  • Is it common that go code is permanently interrupted by small error checks (especially as in storeFundingRates)?
  • Is it common to distinguish error types by their error message (such as if err.Error() == "sql: no rows in result set" { in storeFundingRates?
           

Lista de respuestas

4
 
vote
vote
La mejor respuesta
 

El código debe ser correcto, mantable, robusto, razonablemente eficiente y, lo más importante, legible. El código debe ser útil y tener un valor real.

Encontré que tu código era difícil de leer. Su código no parece ser correcto.


En el principio escribimos:

  if err != nil {     // error handling     return // or continue, etc. } // normal code   

Confunde a todos, por ninguna razón obvia, escribiendo:

  if nil != err {     // error handling     return // or continue, etc. } // normal code   

¿Por qué?


Sus comentarios duplican en gran medida el código.


En Go, lea la documentación del paquete.

usted escribe:

  // implement logic to sort t_fundingRates by time type rateByTime []t_fundingRates  func (r rateByTime) Len() int {     return len(r) }  func (r rateByTime) Swap(i, j int) {     r[i], r[j] = r[j], r[i] }  func (r rateByTime) Less(i, j int) bool {     return r[i].Time.Before(r[j].Time) }  sort.Sort(rateByTime(updateCandidates))   

Es más legible escribir:

  func ratesByTime(r []t_fundingRates) {     sort.Slice(r, func(i, j int) bool {         return r[i].Time.After(r[j].Time)     }) }  ratesByTime(updateCandidates)   

Su código es más que un poco feo, por lo que se agregó la función 9988776655544338 en Go 1.8. Una ejemplo en el Paquete de clasificación La documentación ilustra claramente la diferencia:

Go Playground: https://play.golang.org/p/lzgzi-s8ix -

¿Por qué elegiste la versión compleja, difícil de leer?


En Go, lea la documentación del paquete.

la paquete neto / http Documentación estados:

El cliente debe cerrar el cuerpo de respuesta cuando haya terminado con él:

  resp, err := http.Get("http://example.com/") if err != nil {   // handle error } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) // ...   

Usted no transform(Arrays.asList(argv), a -> a.toLowerCase()); 0 transform(Arrays.asList(argv), a -> a.toLowerCase()); 1 . ¿Por qué?


En Go, lee la especificación del lenguaje de programación de Go .

En particular, diferencias de diferencias

Una declaración de "diferir" invoca una función cuya ejecución está aplazada a En el momento en que se devuelve la función circundante, ya sea porque la La función circundante ejecutó una declaración de devolución, llegó al final de su cuerpo de función, o porque la goroutina correspondiente es pánico.

Cada vez que se ejecuta una declaración de "diferenciación", el valor de la función y Los parámetros a la llamada se evalúan como de costumbre y se guardan de nuevo, pero el La función real no se invoca. En su lugar, las funciones diferidas son invocado inmediatamente antes de que se devuelva la función circundante, en el Orden inverso fueron diferidos.

usted escribe:

  transform(Arrays.asList(argv), a -> a.toLowerCase()); 2  

Parece que está en un bucle infinito y, por lo tanto, la función no termina. Los functiomas diferidos se acumulan pero nunca se ejecutan.


Aquí está un primer corte en la limpieza de su transform(Arrays.asList(argv), a -> a.toLowerCase()); 3 (Renombrado a transform(Arrays.asList(argv), a -> a.toLowerCase()); 4 ) Función.

  transform(Arrays.asList(argv), a -> a.toLowerCase()); 5  

Hay más, pero estoy fuera de tiempo.

 

Code should be correct, maintainable, robust, reasonably efficient, and, most importantly, readable. Code should be useful and have real value.

I found your code hard to read. Your code does not appear to be correct.


In Go we write:

if err != nil {     // error handling     return // or continue, etc. } // normal code 

You confuse everybody, for no obvious reason, by writing:

if nil != err {     // error handling     return // or continue, etc. } // normal code 

Why?


Your comments largely duplicate the code.


In Go, read the package documentation.

You write:

// implement logic to sort t_fundingRates by time type rateByTime []t_fundingRates  func (r rateByTime) Len() int {     return len(r) }  func (r rateByTime) Swap(i, j int) {     r[i], r[j] = r[j], r[i] }  func (r rateByTime) Less(i, j int) bool {     return r[i].Time.Before(r[j].Time) }  sort.Sort(rateByTime(updateCandidates)) 

It is more readable to write:

func ratesByTime(r []t_fundingRates) {     sort.Slice(r, func(i, j int) bool {         return r[i].Time.After(r[j].Time)     }) }  ratesByTime(updateCandidates) 

Your code is more than a little ugly, so the sort.Slice function was added in Go 1.8. An Example in the sort package documentation clearly illustrates the difference:

Go Playground: https://play.golang.org/p/LZgZi-s8IX-

Why did you choose the complex, hard-to-read version?


In Go, read the package documentation.

The net/http package documentation states:

The client must close the response body when finished with it:

resp, err := http.Get("http://example.com/") if err != nil {   // handle error } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) // ... 

You do not Close httpResponse.Body. Why?


In Go, read The Go Programming Language Specification.

In particular, Defer statements

A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.

Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.

You write:

func storeFundingRates(minedRates <-chan []t_fundingRates) {      for {          db, err := sql.Open("postgres", psqlInfo)         if nil != err {             fmt.Println(err.Error())             continue         }         defer db.Close()      }  }  go storeFundingRates(chan_fundingRates) 

You appear to be in an infinite loop and so the function does not end. The deferred functioms accumulate but are never executed.


Here's a first cut at cleaning up your getFundingRates (renamed to mineFundingRates) function.

func getFundingRates() ([]t_fundingRates, error) {     resp, err := http.Get("https://ftx.com/api/funding_rates")     if err != nil {         return nil, err     }     defer resp.Body.Close()     respBody, err := ioutil.ReadAll(resp.Body)     if err != nil {         return nil, err     }      var respJson t_fundingRates_apiResult     if err := json.Unmarshal(respBody, &respJson); err != nil {         return nil, err     }     if !respJson.Success {         err := fmt.Errorf("api did not send funding rates")         return nil, err     }      return respJson.Result, nil }  func mineFundingRates(minedRates chan<- []t_fundingRates) {     ticker := time.NewTicker(30 * time.Second)     for {         <-ticker.C          rates, err := getFundingRates()         if err != nil {             log.Printf(err.Error())             continue         }         minedRates <- rates     } }  go mineFundingRates(chan_fundingRates) 

There is more, but I'm out of time.

 
 
     
     

Relacionados problema

5  Docker-compone para API de matraz basado en tareas con Redis y RQ  ( Docker compose for task based flask api with redis and rq ) 
No soy un Gurú de Docker o Expert in Flask o Redis. Sin embargo, necesito aprovechar estas tecnologías. Me las arreglé para guijarme algo juntos que funciona ...

3  Revisión de los archivos DockerFile y Docker-Compose.Yml para un proyecto MySQL de NodoJS básico  ( Reviewing the dockerfile and docker compose yml files for a basic nodejs mysql p ) 
Soy nuevo en Docker y después de una gran investigación y Stufy He creado una aplicación de muestra y desea que se revise en caso de que me esté perdiendo una...

5  Batch Sube a los usuarios a SiguienteCloud en Docker con Bash CSV  ( Batch upload users to nextcloud on docker with bash csv ) 
Tengo este script de Bash que agrega a los usuarios a NextCloud desde un archivo CSV. Espera que se ejecute desde el mismo directorio que el archivo Docker-Co...

3  Despliegue la automatización para las compilaciones de Docker, las despliegos de Kubernetes y la inyección secreta  ( Deployment automatization for docker builds kubernetes deploys and secret injec ) 
He escrito un pequeño script que automatiza las tareas que anteriormente hice manualmente para implementar en nuestros grupos de Kubernetes y estoy bastante c...

3  Microservice para raspar imágenes con apio  ( Microservice for scraping images with celery ) 
Hice un proyecto, que raspa las imágenes de forma asíncrona y las guarda en un contenedor. Tengo acceso a ellos a través del volumen. Scrapony encuentra imáge...

3  Script PowerShell para ejecutar el mantenimiento de Docker, opcionalmente no interactivamente  ( Powershell script to execute docker maintenance optionally non interactively ) 
Problema Los desarrolladores en mi equipo son nuevos en Docker. Quería proporcionar una manera de realizar tareas generales de limpieza de Docker (como la p...

3  Función BOOL en PHP para verificar si ejecuta el script dentro de Docker  ( Bool function in php to check if you run the script inside docker ) 
Tuve que hacer un guión de CLI que hace algo, también tuve que agregar una validación que comprueba si se ejecuta dentro de la ventanavienta. Después de una i...

1  Combinando AÑADIR Y VOLUMEN EN UN FILE DE DOCHER NODEJS  ( Combining add and volume on a nodejs dockerfile ) 
Tengo un proyecto de nodo. My Scripts Sección de Package.json se parece a esto: "scripts": { "postinstall": "gulp", "start": "node index.js", "test":...

3  Lote Agregue usuarios a SiguienteCloud en Docker con CSV  ( Batch add users to nextcloud on docker with csv ) 
Tengo un script de trabajo que lote agregará a los usuarios a una instancia de SiguienteCloud ejecutándose en la parte superior de Docker. Esta versión es el ...

2  First DockerFile, apreciaría la crítica  ( First dockerfile i would appreciate critique ) 
Como dice el título, este es mi primer archivo de bloqueador. No se publicará en ningún registro, solo para mi uso interno en casa. Apreciaría cualquier críti...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos