github.com/diadata-org/diadata@v1.4.593/pkg/model/trades.go (about) 1 package models 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "sort" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/diadata-org/diadata/pkg/dia" 13 clientInfluxdb "github.com/influxdata/influxdb1-client/v2" 14 ) 15 16 // SaveTradeInflux stores a trade in influx. Flushed when more than maxPoints in batch. 17 // Wrapper around SaveTradeInfluxToTable. 18 func (datastore *DB) SaveTradeInflux(t *dia.Trade) error { 19 return datastore.SaveTradeInfluxToTable(t, influxDbTradesTable) 20 } 21 22 // SaveTradeInfluxToTable stores a trade in influx into @table. 23 // Flushed when more than maxPoints in batch. 24 func (datastore *DB) SaveTradeInfluxToTable(t *dia.Trade, table string) error { 25 26 // Create a point and add to batch 27 tags := map[string]string{ 28 "symbol": EscapeReplacer.Replace(t.Symbol), 29 "pair": t.Pair, 30 "exchange": t.Source, 31 "verified": strconv.FormatBool(t.VerifiedPair), 32 "quotetokenaddress": t.QuoteToken.Address, 33 "basetokenaddress": t.BaseToken.Address, 34 "quotetokenblockchain": t.QuoteToken.Blockchain, 35 "basetokenblockchain": t.BaseToken.Blockchain, 36 "pooladdress": t.PoolAddress, 37 } 38 fields := map[string]interface{}{ 39 "price": t.Price, 40 "volume": t.Volume, 41 "estimatedUSDPrice": t.EstimatedUSDPrice, 42 "foreignTradeID": t.ForeignTradeID, 43 } 44 45 pt, err := clientInfluxdb.NewPoint(table, tags, fields, t.Time) 46 if err != nil { 47 log.Errorln("NewTradeInflux:", err) 48 } else { 49 datastore.addPoint(pt) 50 } 51 52 return err 53 } 54 55 // GetTradeInflux returns the latest trade of @asset on @exchange before @timestamp in the time-range [endtime-window, endtime]. 56 func (datastore *DB) GetTradeInflux(asset dia.Asset, exchange string, endtime time.Time, window time.Duration) (*dia.Trade, error) { 57 starttime := endtime.Add(-window) 58 retval := dia.Trade{} 59 var q string 60 if exchange != "" { 61 queryString := "SELECT estimatedUSDPrice,\"exchange\",foreignTradeID,\"pair\",price,\"symbol\",volume FROM %s WHERE quotetokenaddress='%s' AND quotetokenblockchain='%s' AND exchange='%s' AND time >= %d AND time < %d ORDER BY DESC LIMIT 1" 62 q = fmt.Sprintf(queryString, influxDbTradesTable, asset.Address, asset.Blockchain, exchange, starttime.UnixNano(), endtime.UnixNano()) 63 } else { 64 queryString := "SELECT estimatedUSDPrice,\"exchange\",foreignTradeID,\"pair\",price,\"symbol\",volume FROM %s WHERE quotetokenaddress='%s' AND quotetokenblockchain='%s' AND time >= %d AND time < %d ORDER BY DESC LIMIT 1" 65 q = fmt.Sprintf(queryString, influxDbTradesTable, asset.Address, asset.Blockchain, starttime.UnixNano(), endtime.UnixNano()) 66 } 67 68 /// TODO 69 res, err := queryInfluxDB(datastore.influxClient, q) 70 if err != nil { 71 return &retval, err 72 } 73 if len(res) > 0 && len(res[0].Series) > 0 { 74 for i := 0; i < len(res[0].Series[0].Values); i++ { 75 retval.Time, err = time.Parse(time.RFC3339, res[0].Series[0].Values[i][0].(string)) 76 if err != nil { 77 return &retval, err 78 } 79 retval.EstimatedUSDPrice, err = res[0].Series[0].Values[i][1].(json.Number).Float64() 80 if err != nil { 81 return &retval, err 82 } 83 retval.Source = res[0].Series[0].Values[i][2].(string) 84 retval.ForeignTradeID = res[0].Series[0].Values[i][3].(string) 85 retval.Pair = res[0].Series[0].Values[i][4].(string) 86 retval.Price, err = res[0].Series[0].Values[i][5].(json.Number).Float64() 87 if err != nil { 88 return &retval, err 89 } 90 retval.Symbol = res[0].Series[0].Values[i][6].(string) 91 retval.Volume, err = res[0].Series[0].Values[i][7].(json.Number).Float64() 92 if err != nil { 93 return &retval, err 94 } 95 } 96 } else { 97 return &retval, errors.New("parsing trade from database") 98 } 99 return &retval, nil 100 } 101 102 // GetOldTradesFromInflux returns all recorded trades from @table done on @exchange between @timeInit and @timeFinal 103 // where the time interval is closed on the left and open on the right side. 104 // If @exchange is empty, trades across all exchanges are returned. 105 // If @verified is true, address and blockchain are also parsed for both assets. 106 func (datastore *DB) GetOldTradesFromInflux(table string, exchange string, verified bool, timeInit, timeFinal time.Time) ([]dia.Trade, error) { 107 allTrades := []dia.Trade{} 108 var queryString, query, addQueryString string 109 if verified { 110 addQueryString = ",\"quotetokenaddress\",\"basetokenaddress\",\"quotetokenblockchain\",\"basetokenblockchain\",\"verified\"" 111 } 112 if exchange == "" { 113 queryString = "SELECT estimatedUSDPrice,\"exchange\",foreignTradeID,\"pair\",price,\"symbol\",volume" + 114 addQueryString + 115 " FROM %s WHERE time>=%d and time<%d order by asc" 116 query = fmt.Sprintf(queryString, table, timeInit.UnixNano(), timeFinal.UnixNano()) 117 } else { 118 queryString = "SELECT estimatedUSDPrice,\"exchange\",foreignTradeID,\"pair\",price,\"symbol\",volume" + 119 addQueryString + 120 " FROM %s WHERE exchange='%s' and time>=%d and time<%d order by asc" 121 query = fmt.Sprintf(queryString, table, exchange, timeInit.UnixNano(), timeFinal.UnixNano()) 122 } 123 res, err := queryInfluxDB(datastore.influxClient, query) 124 if err != nil { 125 log.Error("influx query: ", err) 126 return allTrades, err 127 } 128 129 log.Info("query: ", query) 130 131 if len(res) > 0 && len(res[0].Series) > 0 { 132 for i := 0; i < len(res[0].Series[0].Values); i++ { 133 var trade dia.Trade 134 trade.Time, err = time.Parse(time.RFC3339, res[0].Series[0].Values[i][0].(string)) 135 if err != nil { 136 return allTrades, err 137 } 138 trade.EstimatedUSDPrice, err = res[0].Series[0].Values[i][1].(json.Number).Float64() 139 if err != nil { 140 return allTrades, err 141 } 142 if res[0].Series[0].Values[i][2] != nil { 143 trade.Source = res[0].Series[0].Values[i][2].(string) 144 } 145 if res[0].Series[0].Values[i][3] != nil { 146 trade.ForeignTradeID = res[0].Series[0].Values[i][3].(string) 147 } 148 if res[0].Series[0].Values[i][4] != nil { 149 trade.Pair = res[0].Series[0].Values[i][4].(string) 150 } 151 trade.Price, err = res[0].Series[0].Values[i][5].(json.Number).Float64() 152 if err != nil { 153 return allTrades, err 154 } 155 if res[0].Series[0].Values[i][6] == nil { 156 continue 157 } 158 if res[0].Series[0].Values[i][6] != nil { 159 trade.Symbol = res[0].Series[0].Values[i][6].(string) 160 } 161 trade.Volume, err = res[0].Series[0].Values[i][7].(json.Number).Float64() 162 if err != nil { 163 return allTrades, err 164 } 165 if verified { 166 if res[0].Series[0].Values[i][8] != nil { 167 trade.QuoteToken.Address = res[0].Series[0].Values[i][8].(string) 168 } 169 if res[0].Series[0].Values[i][9] != nil { 170 trade.BaseToken.Address = res[0].Series[0].Values[i][9].(string) 171 } 172 if res[0].Series[0].Values[i][10] != nil { 173 trade.QuoteToken.Blockchain = res[0].Series[0].Values[i][10].(string) 174 } 175 if res[0].Series[0].Values[i][11] != nil { 176 trade.BaseToken.Blockchain = res[0].Series[0].Values[i][11].(string) 177 } 178 verifiedPair, ok := res[0].Series[0].Values[i][12].(string) 179 if ok { 180 trade.VerifiedPair, err = strconv.ParseBool(verifiedPair) 181 if err != nil { 182 log.Error("parse verified pair boolean: ", err) 183 } 184 } 185 } 186 allTrades = append(allTrades, trade) 187 } 188 } else { 189 return allTrades, errors.New("no trades in time range") 190 } 191 return allTrades, nil 192 } 193 194 // parseFullTrade parses a trade as retreived from influx. If fullAsset=true blockchain and address of 195 // the corresponding asset is returned as well. 196 func parseFullTrade(row []interface{}) *dia.Trade { 197 if len(row) > 13 { 198 t, err := time.Parse(time.RFC3339, row[0].(string)) 199 if err == nil { 200 201 var estimatedUSDPrice float64 202 v, o := row[1].(json.Number) 203 if o { 204 estimatedUSDPrice, _ = v.Float64() 205 } else { 206 log.Errorln("error on parsing row 1", row) 207 } 208 209 source, o := row[2].(string) 210 if !o { 211 log.Errorln("error on parsing row 2", row) 212 } 213 214 foreignTradeID, o := row[3].(string) 215 if !o { 216 log.Errorln("error on parsing row 3", row) 217 } 218 219 pair, o := row[4].(string) 220 if !o { 221 log.Errorln("error on parsing row 4", row) 222 } 223 224 var price float64 225 v, o = row[5].(json.Number) 226 if o { 227 price, _ = v.Float64() 228 } else { 229 log.Errorln("error on parsing row 5", row) 230 } 231 232 symbol, o := row[6].(string) 233 if !o { 234 log.Errorln("error on parsing row 6", row) 235 } 236 237 var volume float64 238 v, o = row[7].(json.Number) 239 if o { 240 volume, _ = v.Float64() 241 } else { 242 log.Errorln("error on parsing row 7", row) 243 } 244 var verified bool 245 ver, ok := row[8].(string) 246 if ok { 247 if ver == "true" { 248 verified = true 249 } 250 } 251 basetokenblockchain, o := row[9].(string) 252 if !o { 253 log.Errorln("error on parsing row 9", row) 254 } 255 basetokenaddress, o := row[10].(string) 256 if !o { 257 log.Errorln("error on parsing row 10", row) 258 } 259 quotetokenblockchain, o := row[11].(string) 260 if !o { 261 log.Errorln("error on parsing row 11", row) 262 } 263 quotetokenaddress, o := row[12].(string) 264 if !o { 265 log.Errorln("error on parsing row 12", row) 266 } 267 pooladdress, _ := row[13].(string) 268 269 trade := dia.Trade{ 270 Symbol: symbol, 271 Pair: pair, 272 QuoteToken: dia.Asset{Address: quotetokenaddress, Blockchain: quotetokenblockchain}, 273 BaseToken: dia.Asset{Address: basetokenaddress, Blockchain: basetokenblockchain}, 274 PoolAddress: pooladdress, 275 Time: t, 276 Source: source, 277 EstimatedUSDPrice: estimatedUSDPrice, 278 Price: price, 279 Volume: volume, 280 ForeignTradeID: foreignTradeID, 281 VerifiedPair: verified, 282 } 283 284 return &trade 285 } 286 287 } 288 return nil 289 } 290 291 // parseTrade parses a trade as retreived from influx. If fullAsset=true blockchain and address of 292 // the corresponding asset is returned as well. 293 func parseTrade(row []interface{}, fullBasetoken bool) *dia.Trade { 294 if len(row) > 10 { 295 t, err := time.Parse(time.RFC3339, row[0].(string)) 296 if err == nil { 297 298 var estimatedUSDPrice float64 299 v, o := row[1].(json.Number) 300 if o { 301 estimatedUSDPrice, _ = v.Float64() 302 } else { 303 log.Errorln("error on parsing row 1", row) 304 } 305 306 source, o := row[2].(string) 307 if !o { 308 log.Errorln("error on parsing row 2", row) 309 } 310 311 foreignTradeID, o := row[3].(string) 312 if !o { 313 log.Errorln("error on parsing row 3", row) 314 } 315 316 pair, o := row[4].(string) 317 if !o { 318 log.Errorln("error on parsing row 4", row) 319 } 320 321 var price float64 322 v, o = row[5].(json.Number) 323 if o { 324 price, _ = v.Float64() 325 } else { 326 log.Errorln("error on parsing row 5", row) 327 } 328 329 symbol, o := row[6].(string) 330 if !o { 331 log.Errorln("error on parsing row 6", row) 332 } 333 334 var volume float64 335 v, o = row[7].(json.Number) 336 if o { 337 volume, _ = v.Float64() 338 } else { 339 log.Errorln("error on parsing row 7", row) 340 } 341 var verified bool 342 ver, ok := row[8].(string) 343 if ok { 344 if ver == "true" { 345 verified = true 346 } 347 } 348 trade := dia.Trade{ 349 Symbol: symbol, 350 Pair: pair, 351 Time: t, 352 Source: source, 353 EstimatedUSDPrice: estimatedUSDPrice, 354 Price: price, 355 Volume: volume, 356 ForeignTradeID: foreignTradeID, 357 VerifiedPair: verified, 358 } 359 360 if fullBasetoken { 361 basetokenblockchain, o := row[9].(string) 362 if !o { 363 log.Errorln("error on parsing row 9", row) 364 } 365 basetokenaddress, o := row[10].(string) 366 if !o { 367 log.Errorln("error on parsing row 10", row) 368 } 369 trade.BaseToken.Blockchain = basetokenblockchain 370 trade.BaseToken.Address = basetokenaddress 371 } 372 373 return &trade 374 } 375 376 } 377 return nil 378 } 379 380 func (datastore *DB) GetTradesByExchangesAndBaseAssets(asset dia.Asset, baseassets []dia.Asset, exchanges []string, startTime, endTime time.Time, maxTrades int) ([]dia.Trade, error) { 381 return datastore.GetTradesByExchangesFull(asset, baseassets, exchanges, false, startTime, endTime, maxTrades) 382 } 383 384 func (datastore *DB) GetTradesByExchangesFull( 385 asset dia.Asset, 386 baseassets []dia.Asset, 387 exchanges []string, 388 returnBasetoken bool, 389 startTime time.Time, 390 endTime time.Time, 391 maxTrades int, 392 ) ([]dia.Trade, error) { 393 var r []dia.Trade 394 subQuery := "" 395 subQueryBase := "" 396 if len(exchanges) > 0 { 397 for _, exchange := range exchanges { 398 subQuery = subQuery + fmt.Sprintf("%s|", exchange) 399 } 400 subQuery = "AND exchange =~ /" + strings.TrimRight(subQuery, "|") + "/" 401 402 if len(baseassets) > 0 { 403 for i, baseasset := range baseassets { 404 if i == 0 { 405 subQueryBase = subQueryBase + fmt.Sprintf(` AND ((basetokenaddress='%s' AND basetokenblockchain='%s')`, baseasset.Address, baseasset.Blockchain) 406 407 } else { 408 subQueryBase = subQueryBase + fmt.Sprintf(` OR (basetokenaddress='%s' AND basetokenblockchain='%s')`, baseasset.Address, baseasset.Blockchain) 409 } 410 } 411 subQueryBase = subQueryBase + ") " 412 } 413 } 414 query := fmt.Sprintf(` 415 SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress 416 FROM %s 417 WHERE (quotetokenaddress='%s' and quotetokenblockchain='%s') %s %s 418 AND estimatedUSDPrice > 0 419 AND time > %d AND time <= %d `, 420 influxDbTradesTable, asset.Address, asset.Blockchain, subQuery, subQueryBase, startTime.UnixNano(), endTime.UnixNano()) 421 if maxTrades > 0 { 422 query += fmt.Sprintf("ORDER BY DESC LIMIT %d ", maxTrades) 423 } 424 log.Info("query: ", query) 425 res, err := queryInfluxDB(datastore.influxClient, query) 426 if err != nil { 427 return r, err 428 } 429 430 if len(res) > 0 && len(res[0].Series) > 0 { 431 for _, row := range res[0].Series[0].Values { 432 t := parseTrade(row, returnBasetoken) 433 if t != nil { 434 r = append(r, *t) 435 } 436 } 437 } else { 438 return nil, fmt.Errorf("no trades found") 439 } 440 return r, nil 441 } 442 443 // GetTradesByExchangesBatched executes multiple select queries on the trades table in one batch. 444 // The time ranges of the queries are given by the intervals [startTimes[i], endTimes[i]]. 445 func (datastore *DB) GetTradesByExchangesBatched( 446 quoteasset dia.Asset, 447 baseassets []dia.Asset, 448 exchanges []string, 449 startTimes []time.Time, 450 endTimes []time.Time, 451 maxTrades int, 452 ) ([]dia.Trade, error) { 453 return datastore.GetTradesByExchangesBatchedFull(quoteasset, baseassets, exchanges, false, startTimes, endTimes, maxTrades) 454 } 455 456 // GetTradesByExchangesBatchedFull executes multiple select queries on the trades table in one batch. 457 // The time ranges of the queries are given by the intervals [startTimes[i], endTimes[i]]. 458 func (datastore *DB) GetTradesByExchangesBatchedFull( 459 quoteasset dia.Asset, 460 baseassets []dia.Asset, 461 exchanges []string, 462 returnBasetoken bool, 463 startTimes []time.Time, 464 endTimes []time.Time, 465 maxTrades int, 466 ) ([]dia.Trade, error) { 467 468 var r []dia.Trade 469 if len(startTimes) != len(endTimes) { 470 return []dia.Trade{}, errors.New("number of start times must equal number of end times.") 471 } 472 var query string 473 for i := range startTimes { 474 subQuery := "" 475 subQueryBase := "" 476 if len(exchanges) > 0 { 477 for _, exchange := range exchanges { 478 subQuery = subQuery + fmt.Sprintf("%s|", exchange) 479 } 480 subQuery = "and exchange =~ /" + strings.TrimRight(subQuery, "|") + "/" 481 } 482 483 if len(baseassets) > 0 { 484 for i, baseasset := range baseassets { 485 if i == 0 { 486 subQueryBase = subQueryBase + fmt.Sprintf(` and ((basetokenaddress='%s' and basetokenblockchain='%s')`, baseasset.Address, baseasset.Blockchain) 487 488 } else { 489 subQueryBase = subQueryBase + fmt.Sprintf(` or (basetokenaddress='%s' and basetokenblockchain='%s')`, baseasset.Address, baseasset.Blockchain) 490 } 491 492 } 493 subQueryBase = subQueryBase + ") " 494 495 } 496 query = query + fmt.Sprintf(` 497 SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress 498 FROM %s 499 WHERE (quotetokenaddress='%s' AND quotetokenblockchain='%s') %s %s 500 AND estimatedUSDPrice > 0 501 AND time > %d AND time <= %d ; `, 502 influxDbTradesTable, quoteasset.Address, quoteasset.Blockchain, subQuery, subQueryBase, startTimes[i].UnixNano(), endTimes[i].UnixNano()) 503 } 504 log.Info("query: ", query) 505 res, err := queryInfluxDB(datastore.influxClient, query) 506 if err != nil { 507 return r, err 508 } 509 510 if len(res) > 0 { 511 for i := range res { 512 if len(res[i].Series) > 0 { 513 log.Infof("parse %v trades...", len(res[i].Series[0].Values)) 514 for _, row := range res[i].Series[0].Values { 515 t := parseTrade(row, returnBasetoken) 516 if t != nil { 517 r = append(r, *t) 518 } 519 } 520 log.Info("...done parsing.") 521 } 522 } 523 } else { 524 log.Errorf("Empty response GetTradesByExchangesBatched for %s \n", quoteasset.Symbol) 525 return nil, fmt.Errorf("no trades found") 526 } 527 528 return r, nil 529 } 530 531 // GetxcTradesByExchangesBatched executes multiple select queries on the trades table in one batch. 532 // The time ranges of the queries are given by the intervals [startTimes[i], endTimes[i]]. 533 func (datastore *DB) GetxcTradesByExchangesBatched( 534 quoteassets []dia.Asset, 535 exchanges []string, 536 startTimes []time.Time, 537 endTimes []time.Time, 538 ) ([]dia.Trade, error) { 539 540 var r []dia.Trade 541 if len(startTimes) != len(endTimes) { 542 return []dia.Trade{}, errors.New("number of start times must equal number of end times.") 543 } 544 var query string 545 for i := range startTimes { 546 subQueryExchanges := "" 547 subQueryAssets := "" 548 if len(exchanges) > 0 { 549 for _, exchange := range exchanges { 550 subQueryExchanges = subQueryExchanges + fmt.Sprintf("%s|", exchange) 551 } 552 subQueryExchanges = "AND exchange =~ /" + strings.TrimRight(subQueryExchanges, "|") + "/" 553 } 554 555 if len(quoteassets) > 0 { 556 for i, quoteasset := range quoteassets { 557 if i == 0 { 558 subQueryAssets = subQueryAssets + fmt.Sprintf(` AND ((quotetokenaddress='%s' AND quotetokenblockchain='%s')`, quoteasset.Address, quoteasset.Blockchain) 559 560 } else { 561 subQueryAssets = subQueryAssets + fmt.Sprintf(` OR (quotetokenaddress='%s' AND quotetokenblockchain='%s')`, quoteasset.Address, quoteasset.Blockchain) 562 } 563 564 } 565 subQueryAssets = subQueryAssets + ") " 566 567 } 568 query = query + fmt.Sprintf(` 569 SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress 570 FROM %s 571 WHERE estimatedUSDPrice > 0 572 AND time > %d AND time <= %d 573 %s %s ;`, 574 influxDbTradesTable, startTimes[i].UnixNano(), endTimes[i].UnixNano(), subQueryExchanges, subQueryAssets) 575 } 576 res, err := queryInfluxDB(datastore.influxClient, query) 577 if err != nil { 578 return r, err 579 } 580 581 if len(res) > 0 { 582 for i := range res { 583 if len(res[i].Series) > 0 { 584 log.Infof("parse %v trades...", len(res[i].Series[0].Values)) 585 for _, row := range res[i].Series[0].Values { 586 t := parseTrade(row, false) 587 if t != nil { 588 r = append(r, *t) 589 } 590 } 591 log.Info("...done parsing.") 592 } 593 } 594 } else { 595 log.Error("Empty response GetxcTradesByExchangesBatched") 596 return nil, fmt.Errorf("no trades found") 597 } 598 599 return r, nil 600 } 601 602 // GetTradesByFeedSelection returns all trades with restrictions given by the struct @feedselection. 603 func (datastore *DB) GetTradesByFeedSelection( 604 feedselection []dia.FeedSelection, 605 starttimes []time.Time, 606 endtimes []time.Time, 607 limit int, 608 ) ([]dia.Trade, error) { 609 var ( 610 query string 611 r []dia.Trade 612 ) 613 614 if len(starttimes) != len(endtimes) { 615 return []dia.Trade{}, errors.New("number of start times must equal number of end times.") 616 } 617 618 for i := range starttimes { 619 query += fmt.Sprintf(` 620 SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress,quotetokenblockchain,quotetokenaddress,pooladdress 621 FROM %s 622 WHERE ( `, 623 influxDbTradesTable, 624 ) 625 626 // --------------------- Iterate over assets. --------------------- 627 for i, item := range feedselection { 628 if i > 0 { 629 query += " OR " 630 } 631 // ---------------------Iterate over exchanges. --------------------- 632 var exchangeQuery string 633 for j, exchangepairs := range item.Exchangepairs { 634 if j == 0 { 635 exchangeQuery += " AND (" 636 } else { 637 exchangeQuery += " OR " 638 } 639 640 // --------------------- Iterate over pairs/pools. --------------------- 641 var pairsQuery string 642 if exchangepairs.Exchange.Centralized { 643 for k, pair := range exchangepairs.Pairs { 644 if k == 0 { 645 pairsQuery += " AND (" 646 } else { 647 pairsQuery += " OR " 648 } 649 pairsQuery += fmt.Sprintf(` 650 ( quotetokenaddress='%s' AND quotetokenblockchain='%s' AND basetokenaddress='%s' and basetokenblockchain='%s') 651 `, 652 pair.QuoteToken.Address, 653 pair.QuoteToken.Blockchain, 654 pair.BaseToken.Address, 655 pair.BaseToken.Blockchain, 656 ) 657 } 658 } else { 659 for k, pool := range exchangepairs.Pools { 660 if k == 0 { 661 pairsQuery += " AND (" 662 } else { 663 pairsQuery += " OR " 664 } 665 pairsQuery += fmt.Sprintf(" pooladdress='%s' ", pool.Address) 666 } 667 } 668 if len(exchangepairs.Pairs) > 0 || len(exchangepairs.Pools) > 0 { 669 pairsQuery += " ) " 670 } 671 672 if exchangepairs.Exchange.Name != "" { 673 exchangeQuery += fmt.Sprintf(`(exchange='%s' %s)`, exchangepairs.Exchange.Name, pairsQuery) 674 } else { 675 // Take into account trades on all exchanges. 676 exchangeQuery += fmt.Sprintf(`exchange=~/./ %s`, pairsQuery) 677 } 678 } 679 if len(item.Exchangepairs) > 0 { 680 exchangeQuery += " ) " 681 } 682 683 // Main query for trades by asset. 684 query += fmt.Sprintf(` 685 ( (quotetokenaddress='%s' AND quotetokenblockchain='%s') %s ) 686 `, 687 item.Asset.Address, 688 item.Asset.Blockchain, 689 exchangeQuery, 690 ) 691 } 692 693 // The bracket closes the main statement from the first WHERE clause. 694 var limitQuery string 695 if len(starttimes) == 1 && limit > 0 { 696 limitQuery = fmt.Sprintf(" ORDER BY DESC LIMIT %v", limit) 697 } 698 query += fmt.Sprintf(` 699 ) 700 AND estimatedUSDPrice > 0 701 AND time > %d 702 AND time < %d %s;`, 703 starttimes[i].UnixNano(), 704 endtimes[i].UnixNano(), 705 limitQuery, 706 ) 707 } 708 709 res, err := queryInfluxDB(datastore.influxClient, query) 710 if err != nil { 711 return r, err 712 } 713 714 if len(res) > 0 { 715 for i := range res { 716 if len(res[i].Series) > 0 { 717 log.Infof("parse %v trades...", len(res[i].Series[0].Values)) 718 for _, row := range res[i].Series[0].Values { 719 t := parseFullTrade(row) 720 if t != nil { 721 r = append(r, *t) 722 } 723 } 724 log.Info("...done parsing.") 725 } 726 } 727 } else { 728 return nil, fmt.Errorf("No trades found.") 729 } 730 731 return r, nil 732 } 733 734 // GetAggregatedFeedSelection returns aggregated quantities with restrictions given by the struct @feedselection. 735 func (datastore *DB) GetAggregatedFeedSelection( 736 feedselection []dia.FeedSelection, 737 starttime time.Time, 738 endtime time.Time, 739 tradeVolumeThreshold float64, 740 ) ([]dia.FeedSelectionAggregated, error) { 741 var ( 742 query string 743 feedSelectionAggregated []dia.FeedSelectionAggregated 744 ) 745 746 if starttime.After(endtime) { 747 return feedSelectionAggregated, errors.New("starttime is after endtime.") 748 } 749 750 query += fmt.Sprintf(` 751 SELECT SUM(abs),COUNT(multiplication),LAST(multiplication) 752 FROM ( 753 SELECT ABS(estimatedUSDPrice*volume),estimatedUSDPrice AS multiplication 754 FROM %s 755 WHERE ( `, 756 influxDbTradesTable, 757 ) 758 759 // --------------------- Iterate over assets. --------------------- 760 for i, item := range feedselection { 761 if i > 0 { 762 query += " OR " 763 } 764 // ---------------------Iterate over exchanges. --------------------- 765 var exchangeQuery string 766 for j, exchangepairs := range item.Exchangepairs { 767 if j == 0 { 768 exchangeQuery += " AND (" 769 } else { 770 exchangeQuery += " OR " 771 } 772 773 // --------------------- Iterate over pairs/pools. --------------------- 774 var pairsQuery string 775 if exchangepairs.Exchange.Centralized { 776 for k, pair := range exchangepairs.Pairs { 777 if k == 0 { 778 pairsQuery += " AND (" 779 } else { 780 pairsQuery += " OR " 781 } 782 pairsQuery += fmt.Sprintf(` 783 ( quotetokenaddress='%s' AND quotetokenblockchain='%s' AND basetokenaddress='%s' and basetokenblockchain='%s') 784 `, 785 pair.QuoteToken.Address, 786 pair.QuoteToken.Blockchain, 787 pair.BaseToken.Address, 788 pair.BaseToken.Blockchain, 789 ) 790 } 791 } else { 792 for k, pool := range exchangepairs.Pools { 793 if k == 0 { 794 pairsQuery += " AND (" 795 } else { 796 pairsQuery += " OR " 797 } 798 pairsQuery += fmt.Sprintf(" pooladdress='%s' ", pool.Address) 799 } 800 } 801 if len(exchangepairs.Pairs) > 0 || len(exchangepairs.Pools) > 0 { 802 pairsQuery += " ) " 803 } 804 805 if exchangepairs.Exchange.Name != "" { 806 exchangeQuery += fmt.Sprintf(`(exchange='%s' %s)`, exchangepairs.Exchange.Name, pairsQuery) 807 } else { 808 // Take into account trades on all exchanges. 809 exchangeQuery += fmt.Sprintf(`exchange=~/./ %s`, pairsQuery) 810 } 811 } 812 if len(item.Exchangepairs) > 0 { 813 exchangeQuery += " ) " 814 } 815 816 // Main query for trades by asset. 817 query += fmt.Sprintf(` 818 ( (quotetokenaddress='%s' AND quotetokenblockchain='%s') %s ) 819 `, 820 item.Asset.Address, 821 item.Asset.Blockchain, 822 exchangeQuery, 823 ) 824 } 825 826 query += fmt.Sprintf(` 827 ) 828 AND estimatedUSDPrice > 0 829 AND time >= %d 830 AND time < %d) 831 WHERE multiplication>%v `, 832 starttime.UnixNano(), 833 endtime.UnixNano(), 834 tradeVolumeThreshold, 835 ) 836 query += ` GROUP BY "exchange","quotetokenaddress","quotetokenblockchain","basetokenaddress","basetokenblockchain","pooladdress","symbol","pair"` 837 838 res, err := queryInfluxDB(datastore.influxClient, query) 839 if err != nil { 840 return feedSelectionAggregated, err 841 } 842 843 if len(res) > 0 && len(res[0].Series) > 0 { 844 for _, row := range res[0].Series { 845 if len(row.Values[0]) > 1 { 846 var fsa dia.FeedSelectionAggregated 847 fsa.Exchange = row.Tags["exchange"] 848 fsa.Quotetoken.Address = row.Tags["quotetokenaddress"] 849 fsa.Quotetoken.Blockchain = row.Tags["quotetokenblockchain"] 850 fsa.Basetoken.Address = row.Tags["basetokenaddress"] 851 fsa.Basetoken.Blockchain = row.Tags["basetokenblockchain"] 852 // Parse symbol of basetoken 853 pairSymbols, err := dia.GetPairSymbols(dia.ExchangePair{ 854 Symbol: row.Tags["symbol"], 855 ForeignName: row.Tags["pair"], 856 Exchange: row.Tags["exchange"], 857 }) 858 if err != nil { 859 log.Error("Get pair symbols: ", err) 860 } else if len(pairSymbols) > 1 { 861 fsa.Quotetoken.Symbol = strings.ToUpper(pairSymbols[0]) 862 fsa.Basetoken.Symbol = strings.ToUpper(pairSymbols[1]) 863 } 864 fsa.Pooladdress = row.Tags["pooladdress"] 865 fsa.Volume, err = row.Values[0][1].(json.Number).Float64() 866 if err != nil { 867 log.Error("cast float64: ", err) 868 } 869 tradescount, err := row.Values[0][2].(json.Number).Int64() 870 if err != nil { 871 log.Error("cast int64: ", err) 872 } 873 fsa.TradesCount = int32(tradescount) 874 fsa.LastPrice, err = row.Values[0][3].(json.Number).Float64() 875 if err != nil { 876 log.Error("cast float64: ", err) 877 } 878 fsa.Starttime = starttime 879 fsa.Endtime = endtime 880 feedSelectionAggregated = append(feedSelectionAggregated, fsa) 881 } 882 } 883 } else { 884 return feedSelectionAggregated, fmt.Errorf("No trades found.") 885 } 886 887 // Sort response by volume. 888 sort.Slice(feedSelectionAggregated, func(m, n int) bool { 889 return feedSelectionAggregated[m].Volume > feedSelectionAggregated[n].Volume 890 }) 891 892 return feedSelectionAggregated, nil 893 } 894 895 // GetTradesByExchangepairs returns all trades where either of the following is fulfilled. 896 // 1. The exchange is a key of @exchangepairMap AND the pair is in the corresponding slice @[]dia.Pair. 897 // 2. The exchange is a key of @exchangepoolMap AND the pool is in the corresponding slice @[]string. 898 func (datastore *DB) GetTradesByExchangepairs(exchangepairMap map[string][]dia.Pair, exchangepoolMap map[string][]string, starttime time.Time, endtime time.Time) ([]dia.Trade, error) { 899 var ( 900 query string 901 r []dia.Trade 902 ) 903 904 query = fmt.Sprintf(` 905 SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress,quotetokenblockchain,quotetokenaddress 906 FROM %s 907 WHERE ( `, 908 influxDbTradesTable, 909 ) 910 911 // Iterate over centralized exchanges. 912 var CEXCount int 913 for exchange := range exchangepairMap { 914 if CEXCount != 0 { 915 query += " OR " 916 } 917 918 // If, in addition to exchanges, pairs are also given, make pairs subquery for each exchange. 919 var pairsQuery string 920 if len(exchangepairMap) > 0 { 921 pairsQuery += " AND ( " 922 for i, pair := range exchangepairMap[exchange] { 923 if i != 0 { 924 pairsQuery += " OR " 925 } 926 pairsQuery += fmt.Sprintf(` 927 ( quotetokenaddress='%s' AND quotetokenblockchain='%s' AND basetokenaddress='%s' and basetokenblockchain='%s') 928 `, 929 pair.QuoteToken.Address, 930 pair.QuoteToken.Blockchain, 931 pair.BaseToken.Address, 932 pair.BaseToken.Blockchain, 933 ) 934 } 935 pairsQuery += " ) " 936 } 937 938 // Main query for trades by exchange. 939 query += fmt.Sprintf(" ( exchange='%s' %s ) ", exchange, pairsQuery) 940 CEXCount++ 941 } 942 943 // Iterate over decentralized exchanges. 944 var DEXCount int 945 for exchange := range exchangepoolMap { 946 if DEXCount != 0 || len(exchangepairMap) > 0 { 947 query += " OR " 948 } 949 950 // If, in addition to exchanges, pools are also given, make pool subquery for each exchange. 951 var poolsQuery string 952 if len(exchangepairMap) > 0 { 953 poolsQuery += " AND ( " 954 for i, pooladdress := range exchangepoolMap[exchange] { 955 if i != 0 { 956 poolsQuery += " OR " 957 } 958 poolsQuery += fmt.Sprintf("( pooladdress='%s' )", pooladdress) 959 } 960 poolsQuery += " ) " 961 } 962 963 // Main query for trades by exchange. 964 query += fmt.Sprintf(" ( exchange='%s' %s ) ", exchange, poolsQuery) 965 DEXCount++ 966 } 967 968 // The bracket closes the main statement from the first WHERE clause. 969 query += fmt.Sprintf(` 970 ) 971 AND estimatedUSDPrice > 0 972 AND time > %d 973 AND time < %d`, 974 starttime.UnixNano(), 975 endtime.UnixNano(), 976 ) 977 978 log.Info("query: ", query) 979 res, err := queryInfluxDB(datastore.influxClient, query) 980 if err != nil { 981 return r, err 982 } 983 984 if len(res) > 0 { 985 for i := range res { 986 if len(res[i].Series) > 0 { 987 log.Infof("parse %v trades...", len(res[i].Series[0].Values)) 988 for _, row := range res[i].Series[0].Values { 989 t := parseFullTrade(row) 990 if t != nil { 991 r = append(r, *t) 992 } 993 } 994 log.Info("...done parsing.") 995 } 996 } 997 } else { 998 log.Error("Empty response GetxcTradesByExchangesBatched") 999 return nil, fmt.Errorf("no trades found") 1000 } 1001 1002 return r, nil 1003 } 1004 1005 // GetAllTrades returns at most @maxTrades trades from influx with timestamp > @t. Only used by replayInflux option. 1006 func (datastore *DB) GetAllTrades(t time.Time, maxTrades int) ([]dia.Trade, error) { 1007 var r []dia.Trade 1008 // TO DO: Substitute select * with precise statment select estimatedUSDPrice, source,... 1009 q := fmt.Sprintf("SELECT time, estimatedUSDPrice, exchange, foreignTradeID, pair, price,symbol, volume,verified,basetokenblockchain,basetokenaddress FROM %s WHERE time > %d LIMIT %d", influxDbTradesTable, t.Unix()*1000000000, maxTrades) 1010 log.Debug(q) 1011 res, err := queryInfluxDB(datastore.influxClient, q) 1012 if err != nil { 1013 log.Errorln("GetAllTrades", err) 1014 return r, err 1015 } 1016 if len(res) > 0 && len(res[0].Series) > 0 { 1017 for _, row := range res[0].Series[0].Values { 1018 t := parseTrade(row, false) 1019 log.Errorln("row trade parseTrade", row) 1020 if t != nil { 1021 r = append(r, *t) 1022 } 1023 } 1024 } else { 1025 log.Error("Empty response GetAllTrades") 1026 } 1027 return r, nil 1028 } 1029 1030 // GetLastTrades returns the last @maxTrades of @asset on @exchange before @timestamp. 1031 // If exchange is empty string it returns trades from all exchanges. 1032 // If fullAsset=true, blockchain and address of both involved assets is returned as well 1033 func (datastore *DB) GetLastTrades(asset dia.Asset, exchange string, timestamp time.Time, maxTrades int, fullAsset bool) ([]dia.Trade, error) { 1034 var ( 1035 r []dia.Trade 1036 queryString string 1037 q string 1038 ) 1039 1040 if exchange == "" { 1041 queryString = ` 1042 SELECT estimatedUSDPrice,"exchange",foreignTradeID,"pair",price,"symbol",volume,"verified","basetokenblockchain","basetokenaddress" 1043 FROM %s 1044 WHERE time<%d 1045 AND time>%d-10d 1046 AND quotetokenaddress='%s' 1047 AND quotetokenblockchain='%s' 1048 AND estimatedUSDPrice>0 1049 ORDER BY DESC LIMIT %d 1050 ` 1051 q = fmt.Sprintf(queryString, influxDbTradesTable, timestamp.UnixNano(), timestamp.UnixNano(), asset.Address, asset.Blockchain, maxTrades) 1052 } else if (dia.Asset{}) == asset { 1053 queryString = ` 1054 SELECT estimatedUSDPrice,"exchange",foreignTradeID,"pair",price,"symbol",volume,"verified","basetokenblockchain","basetokenaddress" 1055 FROM %s 1056 WHERE time<%d 1057 AND time>%d-10d 1058 AND exchange='%s' 1059 AND estimatedUSDPrice>0 1060 ORDER BY DESC LIMIT %d 1061 ` 1062 q = fmt.Sprintf(queryString, influxDbTradesTable, timestamp.UnixNano(), timestamp.UnixNano(), exchange, maxTrades) 1063 } else { 1064 queryString = ` 1065 SELECT estimatedUSDPrice,"exchange",foreignTradeID,"pair",price,"symbol",volume,"verified","basetokenblockchain","basetokenaddress" 1066 FROM %s 1067 WHERE time<%d 1068 AND time>%d-10d 1069 AND exchange='%s' 1070 AND quotetokenaddress='%s' 1071 AND quotetokenblockchain='%s' 1072 AND estimatedUSDPrice>0 1073 ORDER BY DESC LIMIT %d 1074 ` 1075 q = fmt.Sprintf(queryString, influxDbTradesTable, timestamp.UnixNano(), timestamp.UnixNano(), exchange, asset.Address, asset.Blockchain, maxTrades) 1076 } 1077 1078 res, err := queryInfluxDB(datastore.influxClient, q) 1079 if err != nil { 1080 log.Errorln("GetLastTrades", err) 1081 return r, err 1082 } 1083 1084 if len(res) > 0 && len(res[0].Series) > 0 { 1085 for _, row := range res[0].Series[0].Values { 1086 t := parseTrade(row, fullAsset) 1087 if t != nil { 1088 t.QuoteToken = asset 1089 r = append(r, *t) 1090 } 1091 } 1092 } else { 1093 err = fmt.Errorf("Empty response for %s on %s", asset.Symbol, exchange) 1094 log.Error(err) 1095 return r, err 1096 } 1097 return r, nil 1098 } 1099 1100 // GetNumTradesExchange24H returns the number of trades on @exchange in the last 24 hours. 1101 func (datastore *DB) GetNumTradesExchange24H(exchange string) (numTrades int64, err error) { 1102 endtime := time.Now() 1103 return datastore.GetNumTrades(exchange, "", "", endtime.AddDate(0, 0, -1), endtime) 1104 } 1105 1106 // GetNumTrades returns the number of trades on @exchange for asset with @address and @blockchain in the given time-range. 1107 // If @address and @blockchain are empty, it returns all trades on @exchange in the given-time range. 1108 func (datastore *DB) GetNumTrades(exchange string, address string, blockchain string, starttime time.Time, endtime time.Time) (numTrades int64, err error) { 1109 var q string 1110 1111 if address != "" && blockchain != "" { 1112 queryString := ` 1113 SELECT COUNT(*) 1114 FROM %s 1115 WHERE exchange='%s' 1116 AND quotetokenaddress='%s' AND quotetokenblockchain='%s' 1117 AND time > %d AND time<= %d 1118 ` 1119 q = fmt.Sprintf(queryString, influxDbTradesTable, exchange, address, blockchain, starttime.UnixNano(), endtime.UnixNano()) 1120 } else { 1121 queryString := ` 1122 SELECT COUNT(*) 1123 FROM %s 1124 WHERE exchange='%s' 1125 AND time > %d AND time<= %d 1126 ` 1127 q = fmt.Sprintf(queryString, influxDbTradesTable, exchange, starttime.UnixNano(), endtime.UnixNano()) 1128 } 1129 1130 res, err := queryInfluxDB(datastore.influxClient, q) 1131 if err != nil { 1132 log.Errorln("GetNumTrades ", err) 1133 return 1134 } 1135 1136 if len(res) > 0 && len(res[0].Series) > 0 { 1137 num, ok := res[0].Series[0].Values[0][1].(json.Number) 1138 if ok { 1139 numTrades, err = num.Int64() 1140 if err != nil { 1141 return numTrades, err 1142 } 1143 } 1144 } 1145 1146 return 1147 } 1148 1149 // GetNumTradesSeries returns a time-series of number of trades in the respective time-ranges. 1150 // If pair is the empty string, trades are identified by address/blockchain. 1151 // @grouping defines the time-ranges in the notation of influx such as 30s, 40m, 2h,... 1152 func (datastore *DB) GetNumTradesSeries( 1153 asset dia.Asset, 1154 exchange string, 1155 starttime time.Time, 1156 endtime time.Time, 1157 grouping string, 1158 ) (numTrades []int64, err error) { 1159 var query string 1160 selectQuery := "SELECT COUNT(price) FROM %s " 1161 midQuery := "WHERE quotetokenaddress='%s' AND quotetokenblockchain='%s' AND time<=%d AND time>%d " 1162 endQuery := "GROUP BY time(%s) ORDER BY ASC" 1163 exchangeQuery := "AND exchange='%s' " 1164 if exchange != "" { 1165 query = fmt.Sprintf(selectQuery+midQuery+exchangeQuery+endQuery, 1166 influxDbTradesTable, 1167 exchange, 1168 asset.Address, 1169 asset.Blockchain, 1170 endtime.UnixNano(), 1171 starttime.UnixNano(), 1172 grouping, 1173 ) 1174 } else { 1175 query = fmt.Sprintf(selectQuery+midQuery+endQuery, 1176 influxDbTradesTable, 1177 asset.Address, 1178 asset.Blockchain, 1179 endtime.UnixNano(), 1180 starttime.UnixNano(), 1181 grouping, 1182 ) 1183 } 1184 1185 res, err := queryInfluxDB(datastore.influxClient, query) 1186 if err != nil { 1187 return 1188 } 1189 if len(res) > 0 && len(res[0].Series) > 0 { 1190 for _, val := range res[0].Series[0].Values { 1191 num, ok := val[1].(json.Number) 1192 if ok { 1193 aux, err := num.Int64() 1194 if err != nil { 1195 return numTrades, err 1196 } 1197 numTrades = append(numTrades, aux) 1198 } 1199 } 1200 } 1201 return 1202 } 1203 1204 func (datastore *DB) GetFirstTradeDate(table string) (time.Time, error) { 1205 var query string 1206 queryString := "SELECT \"exchange\",price FROM %s where time<now() order by asc limit 1" 1207 query = fmt.Sprintf(queryString, table) 1208 1209 res, err := queryInfluxDB(datastore.influxClient, query) 1210 if err != nil { 1211 return time.Time{}, err 1212 } 1213 if len(res) > 0 && len(res[0].Series) > 0 { 1214 return time.Parse(time.RFC3339, res[0].Series[0].Values[0][0].(string)) 1215 } 1216 return time.Time{}, errors.New("no trade found") 1217 1218 } 1219 1220 func getKeyLastTradeTimeForExchange(asset dia.Asset, exchange string) string { 1221 if exchange == "" { 1222 return "dia_TLT_" + asset.Blockchain + "_" + asset.Address 1223 1224 } else { 1225 return "dia_TLT_" + asset.Blockchain + "_" + asset.Address + "_" + exchange 1226 } 1227 } 1228 1229 func (datastore *DB) GetLastTradeTimeForExchange(asset dia.Asset, exchange string) (*time.Time, error) { 1230 key := getKeyLastTradeTimeForExchange(asset, exchange) 1231 t, err := datastore.redisClient.Get(key).Result() 1232 if err != nil { 1233 log.Errorln("Error: on GetLastTradeTimeForExchange", err, key) 1234 return nil, err 1235 } 1236 i64, err := strconv.ParseInt(t, 10, 64) 1237 if err == nil { 1238 t2 := time.Unix(i64, 0) 1239 return &t2, nil 1240 } else { 1241 return nil, err 1242 } 1243 } 1244 1245 func (datastore *DB) SetLastTradeTimeForExchange(asset dia.Asset, exchange string, t time.Time) error { 1246 if datastore.redisClient == nil { 1247 return nil 1248 } 1249 key := getKeyLastTradeTimeForExchange(asset, exchange) 1250 log.Debug("setting ", key, t) 1251 err := datastore.redisPipe.Set(key, t.Unix(), TimeOutRedis).Err() 1252 if err != nil { 1253 log.Printf("Error: %v on SetLastTradeTimeForExchange %v\n", err, asset.Symbol) 1254 } 1255 return err 1256 }