Trade history, recent trades, live trade processing and storage (#558)

* End of day commit moving packages and setting foundation into how trade processing will go

* Conformity

* tdd candle generation based on received trade data, renames orderbookbuffer back to buffer for now...

* Formalises test functions and designs the trade processor

* Theoretical amending old candles to allow any trades that were part of an old processed candle to be more accurate. Saving of candles will only occur on previous cycles, extending memory usage a bit longer

* Figures out sqlboiler for sqlite. Updates websocket entries to process trade data

* One more trade data

* Adds more exchange support

* Adds PSQL stuff

* Begins creating sql implementation

* End of day commit. Helper functions and understanding sql usage in GCT

* Adds delete and cleans up table design

* Finishes trades conceptually. Awaits candle data update in order to translate trades to candles

* Initial handling of trades in coinbene

* Proto

* Fixing of some bugs, attempting to address coinbene asset type ws issues

* Fixes up coinbene websocket implementation for the most part

* finalises coinbene websocket implementation. Adds new ability to parse currencies without a delimiter

* Implements rpc commands and adds testing

* updates the following to be compatible with trade data update: Theoretical amending old candles to allow any trades that were part of an old processed candle to be more accurate. Saving of candles will only occur on previous cycles, extending memory usage a bit longer

* Changes trade to be its own entity rather than attached to a websocket.

* Adds coverage to trades. Changes signature of `AddTradesToBuffer` to return error. Now automatically shuts down without need for channel listening. Will automatically start up again if it gets data

* Implements trade fetching at the wrapper level for a bunch of exchanges. Adds trade id to script updoot. Probably breaking change

* Implements trade fetching for all wrappers hurray hurrah. Updates all the tests

* Adds new interface func to get recent trades. Ensures GetExchangeHistory continues until conditions are met

* Adds new readme, tests all new wrapper endpoints, updates exchange_wrapper_issues to test new endpoints. Updates exchange_wrapper_coverage with new coverage... Fixes lame bug causing wrapper tests to fail from being poorly setup. Adds loopy loop to ensure that all data is captured when requesting exchange history

* Bugfix on psql migrations. Rebases latest changes, updates table design to use base and quote, updates trades to use exchange_name_id

* Adds new config field for saving trades to the database per exchange. Now exits trade processing when trade saving is not enabled. Similarly for wrapper, does not save if not enabled

* Minor bitfinex trade fixes. continues on buffer processing errors, now saves transactionid to the db

* Adds support for generating candles from candlesextended. May extend it further, idk

* Updates trade candles to be able to fill missing data with trades. Adds more tests. Also does a thing where you can forcefully override a candle based on internal trade data instead of API data

* Fixes bug where force deletions did not follow up with insertions. Adds force to candle commands

* Fixes specific exchange based issues. Extends recent trades to 24 hours where possible

* Fixes issue with saved tests. Fixes tests for trades. Adds parallel to tests. Pre-fixes people's nits

* Adds new GRPC functions to find out what data is missing from trades and candles. Fixes some assumptions from missing period code.

* Adds unique constraint. Fixes up niggling issues for wrappers and websockets

* Fixes issues with using unix times in the database trying to retrieve data via the CLI. Reduces save time to 15 seconds

* Updates trades to use timestamps instead of int64 unix

* Adds missing FTX wrapper implementation. Regens docs

* Linting the linters. Updating readme

* Adds new command to set whether an exchange can process trades

* Doc update

* Adds recent trades and historic trade endpoints to grpc

* formats pair_test.go to appease linter gods

* Addresses data race. Removes logging of missing intervals on unrelated function (now that it has its own rpc command). The buffer time isnt customisable, but I don't feel it needs to be at a config level at all really.

* Fixes a few niterinos regarding spacing, type conversion, a weird Bitmex 0 trade value error, unsubscriptions and cli command references

* Reduces map lookups. Adds base func and moves wrappers to use it

* Uses better currency formatter. Adds time based validation to trade history. Reverts configtest.json

* Reverts config and updates test names. Also WAYYYYY LESS SPAMMY

* oopsie doopsie missed a whoopsie

* mint flavoured lint

* Fixes issues caused by rebase

* Fixes issue with timestamps not converting properly from command to RPCServer. Adds new error type. Adds shorthand entries to some commands. Removes os.Exit from tests. Makes Gemini test rolling. Adds enabled exchange check to RPC function. Escapes timestamp on bitstamp. Renames var

* fixes whoopsie oopsie doopsie I forgot to remove code shoopsie

* missed a line

* 🎉 🎉 :tada:Breaks everything in an end of day commit 🎉 🎉 🎉

* Modifies function 'createlocaloffset' to return a string instead. Uses strings for all time based start and end commands. Uses UTC times in RPC server and updates SQLITE to use formatted time based queries

* Adds concurrency-safe way of changing SaveTradeData and checking it. Fixes embarrassing typo

* End of day fix, adds bitfinex update to loop until either the return trades shows no new dates, or meets specifications. Fixes egregious typo

* Improves testing and handling of historical trades function

* Fixes tests after latest changes

* Fix potential fatal err now that db is enabled in test config now

* Fixes up some database settings to use a local engine instead of global var

* DELICIOUS LINT CHOCOLATE FIXES

* Fixes data race by slashing competitor's tyres

* Adds mock test fixes to allow for live and stored data test

* Removes verbosity in engine level tests. Adds new timezone format to highlight the timezone for RPC functions. Removes reference to Preix index fund

* Oopsie doopsie, fixed a whoopsie

* Loggers can no longer do data drag races on my lawn 👴

* Removes bad lock

* Addresses command nits. End of day conceptual commit, trying to calculate spans of time in the context of missing periods. Tests will fail

* Adds new stream response for retrieving trade history as it can take time to do. Unsuccessfully attempts to simplify time range calculation for missing trades response

* Adds new timeperiods package to calculate time periods, time ranges and whether data is in those ranges. Removes kline basic implementation of same concept

* Fixes lint issues. Fixes test. Moves trade cli commands to their own trade subcommands

* Updates lakebtc to no longer have gethistorictrades as it is unsupported. Adds more validation to rpc functions

* Removes requirement to have trades when testing trade wrapper functions. Doesn't really prove it works if there are no trades for a given currency in a time period.

* Addresses nits, runs linting fix and ensures a test is consistent

* Fix merge issues

* Moves sort to timeperiods. Adds test coverage. Fixes typo

* Removes log package in CLI

* Fixes `GetTrades` url

* Reorders all instances of validation occuring after settingup RPC connection

* Fixes test to ensure that it is setup before testing that it is setup

* Fixed issue with bool retrieval. Removes double append

* Fixes Binance times, fixes bitfinex sell sides, fixes huobi times, sorts all responses

* Fixes poloniex trade id consistency. Makes recent trade for poloniex consistent with others (15 minutes). Fixes coinbene. Fixes localbitcoins to use quote currency. Fixes coinut times. Updates huobi trade id, saves okgroup trades. Fixes bid and ask to buy and sell

* Removes websocket trades for lakebtc as it did not meet our requirements for processing. Adds new constraints to the database to ensure we have uniqueness on trades where ID doesn't exist and doesn't trigger errors for trades where the tid does

* Fixes migration for postgres to downscale properly

* Really really fixes the psql index changes

* Fixes broken tests

* Now with working tests and no pocket lint

* Makes the side column nullable with no more constraint for it. adds migrations and runs generation. comments lakebtc

* Lint & Sprüngli

* Updates zb to use more appropriate side

* Fixes oopsie

* Attempts to address a data race from globals

* Fixes build

* Fixes missed regen rpc files

* Updates readme to point to trade readme. Fixes exchange_wrapper_coverage wrapper count and untested panics, tests bitfinex funding pair test for `fUSD`, adds shiny new param `tradeprocessinginterval`

* mint flavoured lint

* Uses the real default to set the default value by default

* Fixes some extra tests surrounding email sending and number incompatibility

* Reverts test config

* re-adds gom2/usdt currency

* Fixes typo, don't look!

* Fixes minor codelingo pickups

* Adds more precision to handling of trade data from Kraken. Expands test

* interface christmas tree

* lint
This commit is contained in:
Scott
2020-10-29 13:00:02 +11:00
committed by GitHub
parent 12263997c0
commit 80bc8c7e9e
204 changed files with 51177 additions and 6023 deletions

View File

@@ -10,9 +10,9 @@ vadimzhukck | https://github.com/vadimzhukck
MadCozBadd | https://github.com/MadCozBadd
140am | https://github.com/140am
marcofranssen | https://github.com/marcofranssen
dackroyd | https://github.com/dackroyd
Rots | https://github.com/Rots
vazha | https://github.com/vazha
dackroyd | https://github.com/dackroyd
cranktakular | https://github.com/cranktakular
woshidama323 | https://github.com/woshidama323
crackcomm | https://github.com/crackcomm

View File

@@ -26,21 +26,21 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| BitMEX | Yes | Yes | NA |
| Bitstamp | Yes | Yes | No |
| Bittrex | Yes | No | NA |
| BTCMarkets | Yes | No | NA |
| BTCMarkets | Yes | Yes | NA |
| BTSE | Yes | Yes | NA |
| CoinbasePro | Yes | Yes | No|
| Coinbene | Yes | Yes | No |
| COINUT | Yes | Yes | NA |
| Exmo | Yes | NA | NA |
| FTX | Yes | Yes | No |
| CoinbasePro | Yes | Yes | No|
| Coinbene | Yes | No | No |
| GateIO | Yes | Yes | NA |
| Gemini | Yes | Yes | No |
| HitBTC | Yes | Yes | No |
| Huobi.Pro | Yes | Yes | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | NA |
| LakeBTC | Yes | Yes | NA |
| Lbank | Yes | No | NA |
| LakeBTC | Yes | No | NA |
| LocalBitcoins | Yes | NA | NA |
| OKCoin International | Yes | Yes | No |
| OKEX | Yes | Yes | No |
@@ -76,6 +76,7 @@ However, we welcome pull requests for any exchange which does not match this cri
+ Basic event trigger system.
+ OHLCV/Candle retrieval support. See [OHLCV](/docs/OHLCV.md).
+ Scripting support. See [gctscript](/gctscript/README.md).
+ Recent and historic trade processing. See [trades](/exchanges/trade/README.md).
+ WebGUI (discontinued).
## Planned Features
@@ -142,18 +143,18 @@ Binaries will be published once the codebase reaches a stable condition.
|User|Contribution Amount|
|--|--|
| [thrasher-](https://github.com/thrasher-) | 643 |
| [shazbert](https://github.com/shazbert) | 196 |
| [gloriousCode](https://github.com/gloriousCode) | 170 |
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 69 |
| [shazbert](https://github.com/shazbert) | 197 |
| [gloriousCode](https://github.com/gloriousCode) | 171 |
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 70 |
| [xtda](https://github.com/xtda) | 47 |
| [ermalguni](https://github.com/ermalguni) | 14 |
| [vadimzhukck](https://github.com/vadimzhukck) | 10 |
| [MadCozBadd](https://github.com/MadCozBadd) | 9 |
| [140am](https://github.com/140am) | 8 |
| [marcofranssen](https://github.com/marcofranssen) | 8 |
| [Rots](https://github.com/Rots) | 7 |
| [vazha](https://github.com/vazha) | 7 |
| [dackroyd](https://github.com/dackroyd) | 5 |
| [Rots](https://github.com/Rots) | 5 |
| [vazha](https://github.com/vazha) | 5 |
| [cranktakular](https://github.com/cranktakular) | 5 |
| [woshidama323](https://github.com/woshidama323) | 3 |
| [crackcomm](https://github.com/crackcomm) | 3 |

View File

@@ -101,7 +101,9 @@ func main() {
flag.Parse()
var err error
c := log.GenDefaultSettings()
log.RWM.Lock()
log.GlobalLogConfig = &c
log.RWM.Unlock()
log.SetupGlobalLogger()
configData, err = readFileData(jsonFile)
if err != nil {

View File

@@ -28,7 +28,9 @@ func TestMain(m *testing.M) {
testMode = true
c := log.GenDefaultSettings()
c.Enabled = convert.BoolPtr(true)
log.RWM.Lock()
log.GlobalLogConfig = &c
log.RWM.Unlock()
log.Infoln(log.Global, "set verbose to true for more detailed output")
var err error
configData, err = readFileData(jsonFile)
@@ -158,7 +160,7 @@ func TestAdd(t *testing.T) {
}
err := addExch("FalseName", htmlScrape, data2, false)
if err == nil {
t.Log("expected an error due to invalid path being parsed in")
t.Error("expected an error due to invalid path being parsed in")
}
}
@@ -635,7 +637,7 @@ func TestWriteAuthVars(t *testing.T) {
trelloCardID = "jdsfl"
err := writeAuthVars(testMode)
if err != nil {
t.Log(err)
t.Error(err)
}
}
}

View File

@@ -0,0 +1,78 @@
{{define "exchanges trade" -}}
{{template "header" .}}
## Current Features for {{.Name}}
+ The trade package contains a processor for both REST and websocket trade history processing
+ Its primary purpose is to collect trade data from multiple sources and save it to the database's trade table
+ If you do not have database enabled, then trades will not be processed
### Requirements to save a trade to the database
+ Database has to be enabled
+ Under `config.json`, under your selected exchange, enable the field `saveTradeData`
+ This will enable trade processing to occur for that specific exchange
+ This can also be done via gRPC under the `SetExchangeTradeProcessing` command
### Usage
+ To send trade data to be processed, use the following example:
```
err := trade.AddTradesToBuffer(b.Name, trade.Data{
Exchange: b.Name,
TID: strconv.FormatInt(tradeData[i].TID, 10),
CurrencyPair: p,
AssetType: assetType,
Side: side,
Price: tradeData[i].Price,
Amount: tradeData[i].Amount,
Timestamp: tradeTS,
})
```
_b in this context is an `IBotExchange` implemented struct_
### Rules
+ If the trade processor has not started, it will automatically start upon being sent trade data.
+ The processor will add all received trades to a buffer
+ After 15 seconds, the trade processor will parse and save all trades on the buffer to the trade table
+ This is to save on constant writing to the database. Trade data, especially when received via websocket would cause massive issues on the round trip of saving data for every trade
+ If the processor has not received any trades in that 15 second timeframe, it will shut down.
+ Sending trade data to it later will automatically start it up again
## Exchange Support Table
| Exchange | Recent Trades via REST | Live trade updates via Websocket | Trade history via REST |
|----------|------|-----------|-----|
| Alphapoint | No | No | No |
| Binance| Yes | Yes | No |
| Bitfinex | Yes | Yes | Yes |
| Bitflyer | Yes | No | No |
| Bithumb | Yes | NA | No |
| BitMEX | Yes | Yes | Yes |
| Bitstamp | Yes | Yes | No |
| Bittrex | Yes | No | No |
| BTCMarkets | Yes | Yes | No |
| BTSE | Yes | Yes | No |
| Coinbene | Yes | Yes | No |
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | No |
| Exmo | Yes | NA | No |
| FTX | Yes | Yes | Yes |
| GateIO | Yes | Yes | No |
| Gemini | Yes | Yes | Yes |
| HitBTC | Yes | Yes | Yes |
| Huobi.Pro | Yes | Yes | No |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | No |
| LakeBTC | Yes | No | No |
| Lbank | Yes | No | Yes |
| LocalBitcoins | Yes | NA | No |
| OKCoin International | Yes | Yes | No |
| OKEX | Yes | Yes | No |
| Poloniex | Yes | Yes | Yes |
| Yobit | Yes | NA | No |
| ZB.COM | Yes | Yes | No |
### Please click GoDocs chevron above to view current GoDoc information for this package
{{template "contributions"}}
{{template "donations" .}}
{{end}}

View File

@@ -27,21 +27,21 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| BitMEX | Yes | Yes | NA |
| Bitstamp | Yes | Yes | No |
| Bittrex | Yes | No | NA |
| BTCMarkets | Yes | No | NA |
| BTCMarkets | Yes | Yes | NA |
| BTSE | Yes | Yes | NA |
| CoinbasePro | Yes | Yes | No|
| Coinbene | Yes | Yes | No |
| COINUT | Yes | Yes | NA |
| Exmo | Yes | NA | NA |
| FTX | Yes | Yes | No |
| CoinbasePro | Yes | Yes | No|
| Coinbene | Yes | No | No |
| GateIO | Yes | Yes | NA |
| Gemini | Yes | Yes | No |
| HitBTC | Yes | Yes | No |
| Huobi.Pro | Yes | Yes | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | NA |
| LakeBTC | Yes | Yes | NA |
| Lbank | Yes | No | NA |
| LakeBTC | Yes | No | NA |
| LocalBitcoins | Yes | NA | NA |
| OKCoin International | Yes | Yes | No |
| OKEX | Yes | Yes | No |
@@ -77,6 +77,7 @@ However, we welcome pull requests for any exchange which does not match this cri
+ Basic event trigger system.
+ OHLCV/Candle retrieval support. See [OHLCV](/docs/OHLCV.md).
+ Scripting support. See [gctscript](/gctscript/README.md).
+ Recent and historic trade processing. See [trades](/exchanges/trade/README.md).
+ WebGUI (discontinued).
## Planned Features

View File

@@ -1,61 +1,61 @@
{{define "test"}}
package {{.Name}}
import (
"log"
"os"
"testing"
"github.com/thrasher-corp/gocryptotrader/config"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
)
// Please supply your own keys here to do authenticated endpoint testing
const (
apiKey = ""
apiSecret = ""
canManipulateRealOrders = false
)
var {{.Variable}} {{.CapitalName}}
func TestMain(m *testing.M) {
{{.Variable}}.SetDefaults()
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
log.Fatal(err)
}
exchCfg, err := cfg.GetExchangeConfig("{{.CapitalName}}")
if err != nil {
log.Fatal(err)
}
exchCfg.API.AuthenticatedSupport = true
{{ if .WS }} exchCfg.API.AuthenticatedWebsocketSupport = true {{ end }}
exchCfg.API.Credentials.Key = apiKey
exchCfg.API.Credentials.Secret = apiSecret
err = {{.Variable}}.Setup(exchCfg)
if err != nil {
log.Fatal(err)
}
os.Exit(m.Run())
}
// Ensures that this exchange package is compatible with IBotExchange
func TestInterface(t *testing.T) {
var e exchange.IBotExchange
if e = new({{.CapitalName}}); e == nil {
t.Fatal("unable to allocate exchange")
}
}
func areTestAPIKeysSet() bool {
return {{.Variable}}.ValidateAPICredentials()
}
// Implement tests for API endpoints below
{{end}}
{{define "test"}}
package {{.Name}}
import (
"log"
"os"
"testing"
"github.com/thrasher-corp/gocryptotrader/config"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
)
// Please supply your own keys here to do authenticated endpoint testing
const (
apiKey = ""
apiSecret = ""
canManipulateRealOrders = false
)
var {{.Variable}} {{.CapitalName}}
func TestMain(m *testing.M) {
{{.Variable}}.SetDefaults()
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
log.Fatal(err)
}
exchCfg, err := cfg.GetExchangeConfig("{{.CapitalName}}")
if err != nil {
log.Fatal(err)
}
exchCfg.API.AuthenticatedSupport = true
{{ if .WS }} exchCfg.API.AuthenticatedWebsocketSupport = true {{ end }}
exchCfg.API.Credentials.Key = apiKey
exchCfg.API.Credentials.Secret = apiSecret
err = {{.Variable}}.Setup(exchCfg)
if err != nil {
log.Fatal(err)
}
os.Exit(m.Run())
}
// Ensures that this exchange package is compatible with IBotExchange
func TestInterface(t *testing.T) {
var e exchange.IBotExchange
if e = new({{.CapitalName}}); e == nil {
t.Fatal("unable to allocate exchange")
}
}
func areTestAPIKeysSet() bool {
return {{.Variable}}.ValidateAPICredentials()
}
// Implement tests for API endpoints below
{{end}}

View File

@@ -16,8 +16,9 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -323,8 +324,13 @@ func ({{.Variable}} *{{.CapitalName}}) GetFundingHistory() ([]exchange.FundHisto
return nil, common.ErrNotYetImplemented
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func ({{.Variable}} *{{.CapitalName}}) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
// GetRecentTrades returns the most recent trades for a currency and asset
func ({{.Variable}} *{{.CapitalName}}) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
return nil, common.ErrNotYetImplemented
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func ({{.Variable}} *{{.CapitalName}}) GetHistoricTrades (p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -11,10 +11,11 @@ import (
"github.com/thrasher-corp/gocryptotrader/engine"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
)
const (
totalWrappers = 20
totalWrappers = 25
)
func main() {
@@ -116,9 +117,14 @@ func testWrappers(e exchange.IBotExchange) []string {
funcs = append(funcs, "GetAccountInfo")
}
_, err = e.GetExchangeHistory(p, assetType, time.Time{}, time.Time{})
_, err = e.GetRecentTrades(p, assetType)
if err == common.ErrNotYetImplemented {
funcs = append(funcs, "GetExchangeHistory")
funcs = append(funcs, "GetRecentTrades")
}
_, err = e.GetHistoricTrades(p, assetType, time.Time{}, time.Time{})
if err == common.ErrNotYetImplemented {
funcs = append(funcs, "GetHistoricTrades")
}
_, err = e.GetFundingHistory()
@@ -180,5 +186,25 @@ func testWrappers(e exchange.IBotExchange) []string {
funcs = append(funcs, "WithdrawFiatFundsToInternationalBank")
}
_, err = e.GetHistoricCandles(currency.Pair{}, asset.Spot, time.Unix(0, 0), time.Unix(0, 0), kline.OneDay)
if err == common.ErrNotYetImplemented {
funcs = append(funcs, "GetHistoricCandles")
}
_, err = e.GetHistoricCandlesExtended(currency.Pair{}, asset.Spot, time.Unix(0, 0), time.Unix(0, 0), kline.OneDay)
if err == common.ErrNotYetImplemented {
funcs = append(funcs, "GetHistoricCandlesExtended")
}
_, err = e.UpdateAccountInfo()
if err == common.ErrNotYetImplemented {
funcs = append(funcs, "UpdateAccountInfo")
}
_, err = e.GetFeeByType(&exchange.FeeBuilder{})
if err == common.ErrNotYetImplemented {
funcs = append(funcs, "GetFeeByType")
}
return funcs
}

View File

@@ -26,6 +26,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -335,8 +336,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
log.Printf("Executing wrappers for %v %v %v", base.GetName(), assetTypes[i], p)
if !authenticatedOnly {
var r1 *ticker.Price
r1, err = e.FetchTicker(p, assetTypes[i])
var fetchTickerResponse *ticker.Price
fetchTickerResponse, err = e.FetchTicker(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
@@ -346,11 +347,11 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "FetchTicker",
Error: msg,
Response: jsonifyInterface([]interface{}{r1}),
Response: jsonifyInterface([]interface{}{fetchTickerResponse}),
})
var r2 *ticker.Price
r2, err = e.UpdateTicker(p, assetTypes[i])
var updateTickerResponse *ticker.Price
updateTickerResponse, err = e.UpdateTicker(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
@@ -360,11 +361,11 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "UpdateTicker",
Error: msg,
Response: jsonifyInterface([]interface{}{r2}),
Response: jsonifyInterface([]interface{}{updateTickerResponse}),
})
var r3 *orderbook.Base
r3, err = e.FetchOrderbook(p, assetTypes[i])
var fetchOrderbookResponse *orderbook.Base
fetchOrderbookResponse, err = e.FetchOrderbook(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
@@ -374,11 +375,11 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "FetchOrderbook",
Error: msg,
Response: jsonifyInterface([]interface{}{r3}),
Response: jsonifyInterface([]interface{}{fetchOrderbookResponse}),
})
var r4 *orderbook.Base
r4, err = e.UpdateOrderbook(p, assetTypes[i])
var updateOrderbookResponse *orderbook.Base
updateOrderbookResponse, err = e.UpdateOrderbook(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
@@ -388,11 +389,11 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "UpdateOrderbook",
Error: msg,
Response: jsonifyInterface([]interface{}{r4}),
Response: jsonifyInterface([]interface{}{updateOrderbookResponse}),
})
var r5 []string
r5, err = e.FetchTradablePairs(assetTypes[i])
var fetchTradablePairsResponse []string
fetchTradablePairsResponse, err = e.FetchTradablePairs(assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
@@ -402,7 +403,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{assetTypes[i]}),
Function: "FetchTradablePairs",
Error: msg,
Response: jsonifyInterface([]interface{}{r5}),
Response: jsonifyInterface([]interface{}{fetchTradablePairsResponse}),
})
// r6
err = e.UpdateTradablePairs(false)
@@ -417,10 +418,67 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
Error: msg,
Response: jsonifyInterface([]interface{}{nil}),
})
var getHistoricTradesResponse []trade.Data
getHistoricTradesResponse, err = e.GetHistoricTrades(p, assetTypes[i], time.Now().Add(-time.Hour*24), time.Now())
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], time.Now().Add(-time.Hour * 24), time.Now()}),
Function: "GetHistoricTrades",
Error: msg,
Response: jsonifyInterface([]interface{}{getHistoricTradesResponse}),
})
var getRecentTradesResponse []trade.Data
getRecentTradesResponse, err = e.GetRecentTrades(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "GetRecentTrades",
Error: msg,
Response: jsonifyInterface([]interface{}{getRecentTradesResponse}),
})
var getHistoricCandlesResponse kline.Item
startTime, endTime := time.Now().AddDate(0, -1, 0), time.Now()
getHistoricCandlesResponse, err = e.GetHistoricCandles(p, assetTypes[i], startTime, endTime, kline.OneDay)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
Function: "GetHistoricCandles",
Error: msg,
Response: getHistoricCandlesResponse,
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], startTime, endTime, kline.OneDay}),
})
var getHisotirCandlesExtendedResponse kline.Item
getHisotirCandlesExtendedResponse, err = e.GetHistoricCandlesExtended(p, assetTypes[i], startTime, endTime, kline.OneDay)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
Function: "GetHistoricCandlesExtended",
Error: msg,
Response: getHisotirCandlesExtendedResponse,
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], startTime, endTime, kline.OneDay}),
})
}
var r7 account.Holdings
r7, err = e.FetchAccountInfo()
var fetchAccountInfoResponse account.Holdings
fetchAccountInfoResponse, err = e.FetchAccountInfo()
msg = ""
if err != nil {
msg = err.Error()
@@ -429,25 +487,11 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
Function: "FetchAccountInfo",
Error: msg,
Response: jsonifyInterface([]interface{}{r7}),
Response: jsonifyInterface([]interface{}{fetchAccountInfoResponse}),
})
var r8 []exchange.TradeHistory
r8, err = e.GetExchangeHistory(p, assetTypes[i], time.Now().Add(-time.Minute), time.Now())
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], time.Now().Add(-time.Minute), time.Now()}),
Function: "GetExchangeHistory",
Error: msg,
Response: jsonifyInterface([]interface{}{r8}),
})
var r9 []exchange.FundHistory
r9, err = e.GetFundingHistory()
var getFundingHistoryResponse []exchange.FundHistory
getFundingHistoryResponse, err = e.GetFundingHistory()
msg = ""
if err != nil {
msg = err.Error()
@@ -456,7 +500,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
Function: "GetFundingHistory",
Error: msg,
Response: jsonifyInterface([]interface{}{r9}),
Response: jsonifyInterface([]interface{}{getFundingHistoryResponse}),
})
feeType := exchange.FeeBuilder{
@@ -465,8 +509,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
PurchasePrice: config.OrderSubmission.Price,
Amount: config.OrderSubmission.Amount,
}
var r10 float64
r10, err = e.GetFeeByType(&feeType)
var getFeeByTypeResponse float64
getFeeByTypeResponse, err = e.GetFeeByType(&feeType)
msg = ""
if err != nil {
msg = err.Error()
@@ -476,7 +520,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{feeType}),
Function: "GetFeeByType-Trade",
Error: msg,
Response: jsonifyInterface([]interface{}{r10}),
Response: jsonifyInterface([]interface{}{getFeeByTypeResponse}),
})
s := &order.Submit{
@@ -488,8 +532,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
ClientID: config.OrderSubmission.OrderID,
AssetType: assetTypes[i],
}
var r11 order.SubmitResponse
r11, err = e.SubmitOrder(s)
var submitOrderResponse order.SubmitResponse
submitOrderResponse, err = e.SubmitOrder(s)
msg = ""
if err != nil {
msg = err.Error()
@@ -499,7 +543,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{*s}),
Function: "SubmitOrder",
Error: msg,
Response: jsonifyInterface([]interface{}{r11}),
Response: jsonifyInterface([]interface{}{submitOrderResponse}),
})
modifyRequest := order.Modify{
@@ -510,8 +554,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
Price: config.OrderSubmission.Price,
Amount: config.OrderSubmission.Amount,
}
var r12 string
r12, err = e.ModifyOrder(&modifyRequest)
var modifyOrderResponse string
modifyOrderResponse, err = e.ModifyOrder(&modifyRequest)
msg = ""
if err != nil {
msg = err.Error()
@@ -521,9 +565,9 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{modifyRequest}),
Function: "ModifyOrder",
Error: msg,
Response: r12,
Response: modifyOrderResponse,
})
// r13
cancelRequest := order.Cancel{
Side: testOrderSide,
Pair: p,
@@ -543,8 +587,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
Response: jsonifyInterface([]interface{}{nil}),
})
var r14 order.CancelAllResponse
r14, err = e.CancelAllOrders(&cancelRequest)
var cancellAllOrdersResponse order.CancelAllResponse
cancellAllOrdersResponse, err = e.CancelAllOrders(&cancelRequest)
msg = ""
if err != nil {
msg = err.Error()
@@ -554,7 +598,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{cancelRequest}),
Function: "CancelAllOrders",
Error: msg,
Response: jsonifyInterface([]interface{}{r14}),
Response: jsonifyInterface([]interface{}{cancellAllOrdersResponse}),
})
var r15 order.Detail
@@ -576,8 +620,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
Side: testOrderSide,
Pairs: []currency.Pair{p},
}
var r16 []order.Detail
r16, err = e.GetOrderHistory(&historyRequest)
var getOrderHistoryResponse []order.Detail
getOrderHistoryResponse, err = e.GetOrderHistory(&historyRequest)
msg = ""
if err != nil {
msg = err.Error()
@@ -587,7 +631,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{historyRequest}),
Function: "GetOrderHistory",
Error: msg,
Response: jsonifyInterface([]interface{}{r16}),
Response: jsonifyInterface([]interface{}{getOrderHistoryResponse}),
})
orderRequest := order.GetOrdersRequest{
@@ -595,8 +639,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
Side: testOrderSide,
Pairs: []currency.Pair{p},
}
var r17 []order.Detail
r17, err = e.GetActiveOrders(&orderRequest)
var getActiveOrdersResponse []order.Detail
getActiveOrdersResponse, err = e.GetActiveOrders(&orderRequest)
msg = ""
if err != nil {
msg = err.Error()
@@ -606,11 +650,11 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{orderRequest}),
Function: "GetActiveOrders",
Error: msg,
Response: jsonifyInterface([]interface{}{r17}),
Response: jsonifyInterface([]interface{}{getActiveOrdersResponse}),
})
var r18 string
r18, err = e.GetDepositAddress(p.Base, "")
var getDepositAddressResponse string
getDepositAddressResponse, err = e.GetDepositAddress(p.Base, "")
msg = ""
if err != nil {
msg = err.Error()
@@ -620,7 +664,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{p.Base, ""}),
Function: "GetDepositAddress",
Error: msg,
Response: r18,
Response: getDepositAddressResponse,
})
feeType = exchange.FeeBuilder{
@@ -629,8 +673,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
PurchasePrice: config.OrderSubmission.Price,
Amount: config.OrderSubmission.Amount,
}
var r19 float64
r19, err = e.GetFeeByType(&feeType)
var GetFeeByTypeResponse float64
GetFeeByTypeResponse, err = e.GetFeeByType(&feeType)
msg = ""
if err != nil {
msg = err.Error()
@@ -640,7 +684,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{feeType}),
Function: "GetFeeByType-Crypto-Withdraw",
Error: msg,
Response: jsonifyInterface([]interface{}{r19}),
Response: jsonifyInterface([]interface{}{GetFeeByTypeResponse}),
})
withdrawRequest := withdraw.Request{
@@ -650,8 +694,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
},
Amount: config.OrderSubmission.Amount,
}
var r20 *withdraw.ExchangeResponse
r20, err = e.WithdrawCryptocurrencyFunds(&withdrawRequest)
var withdrawCryptocurrencyFundsResponse *withdraw.ExchangeResponse
withdrawCryptocurrencyFundsResponse, err = e.WithdrawCryptocurrencyFunds(&withdrawRequest)
msg = ""
if err != nil {
msg = err.Error()
@@ -661,7 +705,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{withdrawRequest}),
Function: "WithdrawCryptocurrencyFunds",
Error: msg,
Response: r20,
Response: withdrawCryptocurrencyFundsResponse,
})
feeType = exchange.FeeBuilder{
@@ -672,8 +716,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
FiatCurrency: currency.AUD,
BankTransactionType: exchange.WireTransfer,
}
var r21 float64
r21, err = e.GetFeeByType(&feeType)
var getFeeByTypeFiatResponse float64
getFeeByTypeFiatResponse, err = e.GetFeeByType(&feeType)
msg = ""
if err != nil {
msg = err.Error()
@@ -683,7 +727,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{feeType}),
Function: "GetFeeByType-FIAT-Withdraw",
Error: msg,
Response: jsonifyInterface([]interface{}{r21}),
Response: jsonifyInterface([]interface{}{getFeeByTypeFiatResponse}),
})
withdrawRequestFiat := withdraw.Request{
@@ -716,8 +760,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
IntermediaryBankCode: config.BankDetails.IntermediaryBankCode,
},
}
var r22 *withdraw.ExchangeResponse
r22, err = e.WithdrawFiatFunds(&withdrawRequestFiat)
var withdrawFiatFundsResponse *withdraw.ExchangeResponse
withdrawFiatFundsResponse, err = e.WithdrawFiatFunds(&withdrawRequestFiat)
msg = ""
if err != nil {
msg = err.Error()
@@ -727,11 +771,11 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{withdrawRequestFiat}),
Function: "WithdrawFiatFunds",
Error: msg,
Response: r22,
Response: withdrawFiatFundsResponse,
})
var r23 *withdraw.ExchangeResponse
r23, err = e.WithdrawFiatFundsToInternationalBank(&withdrawRequestFiat)
var withdrawFiatFundsInternationalResponse *withdraw.ExchangeResponse
withdrawFiatFundsInternationalResponse, err = e.WithdrawFiatFundsToInternationalBank(&withdrawRequestFiat)
msg = ""
if err != nil {
msg = err.Error()
@@ -741,39 +785,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
SentParams: jsonifyInterface([]interface{}{withdrawRequestFiat}),
Function: "WithdrawFiatFundsToInternationalBank",
Error: msg,
Response: r23,
Response: withdrawFiatFundsInternationalResponse,
})
if !authenticatedOnly {
var r24 kline.Item
startTime, endTime := time.Now().AddDate(0, -1, 0), time.Now()
r24, err = e.GetHistoricCandles(p, assetTypes[i], startTime, endTime, kline.OneDay)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
Function: "GetHistoricCandles",
Error: msg,
Response: r24,
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], startTime, endTime, kline.OneDay}),
})
var r25 kline.Item
r25, err = e.GetHistoricCandlesExtended(p, assetTypes[i], startTime, endTime, kline.OneDay)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
Function: "GetHistoricCandlesExtended",
Error: msg,
Response: r25,
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], startTime, endTime, kline.OneDay}),
})
}
response = append(response, responseContainer)
}
return response

View File

@@ -551,17 +551,17 @@ func getTicker(c *cli.Context) error {
return errInvalidAsset
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetTicker(context.Background(),
&gctrpc.GetTickerRequest{
@@ -667,17 +667,17 @@ func getOrderbook(c *cli.Context) error {
return errInvalidAsset
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetOrderbook(context.Background(),
&gctrpc.GetOrderbookRequest{
@@ -938,12 +938,6 @@ func addPortfolioAddress(c *cli.Context) error {
return cli.ShowCommandHelp(c, "addportfolioaddress")
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
var address string
var coinType string
var description string
@@ -968,7 +962,7 @@ func addPortfolioAddress(c *cli.Context) error {
} else {
description = c.Args().Get(2)
}
var err error
if c.IsSet("balance") {
balance = c.Float64("balance")
} else if c.Args().Get(3) != "" {
@@ -993,6 +987,12 @@ func addPortfolioAddress(c *cli.Context) error {
supportedExchanges = c.Args().Get(5)
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.AddPortfolioAddress(context.Background(),
&gctrpc.AddPortfolioAddressRequest{
@@ -1039,12 +1039,6 @@ func removePortfolioAddress(c *cli.Context) error {
return cli.ShowCommandHelp(c, "removeportfolioaddress")
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
var address string
var coinType string
var description string
@@ -1067,6 +1061,12 @@ func removePortfolioAddress(c *cli.Context) error {
description = c.Args().Get(2)
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.RemovePortfolioAddress(context.Background(),
&gctrpc.RemovePortfolioAddressRequest{
@@ -1187,17 +1187,17 @@ func getOrders(c *cli.Context) error {
return errInvalidPair
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetOrders(context.Background(), &gctrpc.GetOrdersRequest{
Exchange: exchangeName,
@@ -1438,17 +1438,17 @@ func submitOrder(c *cli.Context) error {
return errInvalidAsset
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.SubmitOrder(context.Background(), &gctrpc.SubmitOrderRequest{
Exchange: exchangeName,
@@ -1551,17 +1551,17 @@ func simulateOrder(c *cli.Context) error {
return errors.New("amount must be set")
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.SimulateOrder(context.Background(), &gctrpc.SimulateOrderRequest{
Exchange: exchangeName,
@@ -1656,17 +1656,17 @@ func whaleBomb(c *cli.Context) error {
}
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.WhaleBomb(context.Background(), &gctrpc.WhaleBombRequest{
Exchange: exchangeName,
@@ -2029,17 +2029,17 @@ func addEvent(c *cli.Context) error {
return fmt.Errorf("action is required")
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.AddEvent(context.Background(), &gctrpc.AddEventRequest{
Exchange: exchangeName,
@@ -2673,19 +2673,15 @@ func withdrawlRequestByDate(c *cli.Context) error {
if err != nil {
return fmt.Errorf("invalid time format for start: %v", err)
}
e, err := time.Parse(common.SimpleTimeFormat, endTime)
if err != nil {
return fmt.Errorf("invalid time format for end: %v", err)
}
if e.Before(s) {
return errors.New("start cannot be after before")
return errors.New("start cannot be after end")
}
_, offset := time.Now().Zone()
loc := time.FixedZone("", -offset)
conn, err := setupClient()
if err != nil {
return err
@@ -2696,8 +2692,8 @@ func withdrawlRequestByDate(c *cli.Context) error {
result, err := client.WithdrawalEventsByDate(context.Background(),
&gctrpc.WithdrawalEventsByDateRequest{
Exchange: exchange,
Start: s.In(loc).Format(common.SimpleTimeFormat),
End: e.In(loc).Format(common.SimpleTimeFormat),
Start: negateLocalOffset(s),
End: negateLocalOffset(e),
Limit: int32(limit),
},
)
@@ -2885,17 +2881,17 @@ func getOrderbookStream(c *cli.Context) error {
return errInvalidAsset
}
p, err := currency.NewPairDelimiter(pair, pairDelimiter)
if err != nil {
return err
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
p, err := currency.NewPairDelimiter(pair, pairDelimiter)
if err != nil {
return err
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetOrderbookStream(context.Background(),
&gctrpc.GetOrderbookStreamRequest{
@@ -3094,17 +3090,17 @@ func getTickerStream(c *cli.Context) error {
return errInvalidAsset
}
p, err := currency.NewPairDelimiter(pair, pairDelimiter)
if err != nil {
return err
}
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
p, err := currency.NewPairDelimiter(pair, pairDelimiter)
if err != nil {
return err
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetTickerStream(context.Background(),
&gctrpc.GetTickerStreamRequest{
@@ -3288,7 +3284,7 @@ func getAuditEvent(c *cli.Context) error {
}
if e.Before(s) {
return errors.New("start cannot be after before")
return errors.New("start cannot be after end")
}
conn, err := setupClient()
@@ -3300,16 +3296,12 @@ func getAuditEvent(c *cli.Context) error {
client := gctrpc.NewGoCryptoTraderClient(conn)
_, offset := time.Now().Zone()
loc := time.FixedZone("", -offset)
result, err := client.GetAuditEvent(context.Background(),
&gctrpc.GetAuditEventRequest{
StartDate: s.In(loc).Format(common.SimpleTimeFormat),
EndDate: e.In(loc).Format(common.SimpleTimeFormat),
StartDate: negateLocalOffset(s),
EndDate: negateLocalOffset(e),
Limit: int32(limit),
OrderBy: order,
Offset: int32(offset),
})
if err != nil {
@@ -3788,6 +3780,10 @@ var getHistoricCandlesCommand = cli.Command{
Value: 86400,
Destination: &candleGranularity,
},
cli.BoolFlag{
Name: "fillmissingdatawithtrades, fill",
Usage: "will create candles for missing intervals using stored trade data <true/false>",
},
},
}
@@ -3849,6 +3845,13 @@ func getHistoricCandles(c *cli.Context) error {
}
}
var fillMissingData bool
if c.IsSet("fillmissingdatawithtrades") {
fillMissingData = c.Bool("fillmissingdatawithtrades")
} else if c.IsSet("fill") {
fillMissingData = c.Bool("fill")
}
conn, err := setupClient()
if err != nil {
return err
@@ -3857,8 +3860,8 @@ func getHistoricCandles(c *cli.Context) error {
candleInterval := time.Duration(candleGranularity) * time.Second
end := time.Now().UTC().Truncate(candleInterval)
start := end.Add(-candleInterval * time.Duration(candleRangeSize))
e := time.Now().Truncate(candleInterval)
s := e.Add(-candleInterval * time.Duration(candleRangeSize))
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetHistoricCandles(context.Background(),
@@ -3869,10 +3872,11 @@ func getHistoricCandles(c *cli.Context) error {
Base: p.Base.String(),
Quote: p.Quote.String(),
},
AssetType: assetType,
Start: start.Unix(),
End: end.Unix(),
TimeInterval: int64(candleInterval),
AssetType: assetType,
Start: negateLocalOffset(s),
End: negateLocalOffset(e),
TimeInterval: int64(candleInterval),
FillMissingWithTrades: fillMissingData,
})
if err != nil {
return err
@@ -3893,11 +3897,11 @@ var getHistoricCandlesExtendedCommand = cli.Command{
Usage: "the exchange to get the candles from",
},
cli.StringFlag{
Name: "pair",
Name: "pair, p",
Usage: "the currency pair to get the candles for",
},
cli.StringFlag{
Name: "asset",
Name: "asset, a",
Usage: "the asset type of the currency pair",
},
cli.Int64Flag{
@@ -3922,10 +3926,18 @@ var getHistoricCandlesExtendedCommand = cli.Command{
Name: "sync",
Usage: "<true/false>",
},
cli.BoolFlag{
Name: "force",
Usage: "will overwrite any conflicting candle data on save <true/false>",
},
cli.BoolFlag{
Name: "db",
Usage: "source data from database <true/false>",
},
cli.BoolFlag{
Name: "fillmissingdatawithtrades, fill",
Usage: "will create candles for missing intervals using stored trade data <true/false>",
},
},
}
@@ -3996,9 +4008,40 @@ func getHistoricCandlesExtended(c *cli.Context) error {
sync = c.Bool("sync")
}
var db bool
var useDB bool
if c.IsSet("db") {
db = c.Bool("db")
useDB = c.Bool("db")
}
var fillMissingData bool
if c.IsSet("fillmissingdatawithtrades") {
fillMissingData = c.Bool("fillmissingdatawithtrades")
} else if c.IsSet("fill") {
fillMissingData = c.Bool("fill")
}
var force bool
if c.IsSet("force") {
force = c.Bool("force")
}
if force && !sync {
return errors.New("cannot forcefully overwrite without sync")
}
candleInterval := time.Duration(candleGranularity) * time.Second
var s, e time.Time
s, err = time.Parse(common.SimpleTimeFormat, startTime)
if err != nil {
return fmt.Errorf("invalid time format for start: %v", err)
}
e, err = time.Parse(common.SimpleTimeFormat, endTime)
if err != nil {
return fmt.Errorf("invalid time format for end: %v", err)
}
if e.Before(s) {
return errors.New("start cannot be after end")
}
conn, err := setupClient()
@@ -4007,22 +4050,6 @@ func getHistoricCandlesExtended(c *cli.Context) error {
}
defer conn.Close()
candleInterval := time.Duration(candleGranularity) * time.Second
s, err := time.Parse(common.SimpleTimeFormat, startTime)
if err != nil {
return fmt.Errorf("invalid time format for start: %v", err)
}
e, err := time.Parse(common.SimpleTimeFormat, endTime)
if err != nil {
return fmt.Errorf("invalid time format for end: %v", err)
}
if e.Before(s) {
return errors.New("start cannot be after before")
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetHistoricCandles(context.Background(),
&gctrpc.GetHistoricCandlesRequest{
@@ -4032,13 +4059,15 @@ func getHistoricCandlesExtended(c *cli.Context) error {
Base: p.Base.String(),
Quote: p.Quote.String(),
},
AssetType: assetType,
Start: s.Unix(),
End: e.Unix(),
TimeInterval: int64(candleInterval),
ExRequest: true,
Sync: sync,
UseDb: db,
AssetType: assetType,
Start: negateLocalOffset(s),
End: negateLocalOffset(e),
TimeInterval: int64(candleInterval),
ExRequest: true,
Sync: sync,
UseDb: useDB,
FillMissingWithTrades: fillMissingData,
Force: force,
})
if err != nil {
return err
@@ -4047,3 +4076,167 @@ func getHistoricCandlesExtended(c *cli.Context) error {
jsonOutput(result)
return nil
}
var findMissingSavedCandleIntervalsCommand = cli.Command{
Name: "findmissingsavedcandleintervals",
Usage: "will highlight any interval that is missing candle data so you can fill that gap",
ArgsUsage: "<exchange> <pair> <asset> <interval> <start> <end>",
Action: findMissingSavedCandleIntervals,
Flags: []cli.Flag{
cli.StringFlag{
Name: "exchange, e",
Usage: "the exchange to find the missing candles",
},
cli.StringFlag{
Name: "pair, p",
Usage: "the currency pair",
},
cli.StringFlag{
Name: "asset, a",
Usage: "the asset type of the currency pair",
},
cli.Int64Flag{
Name: "interval, i",
Usage: fmt.Sprintf(klineMessage, "interval"),
Value: 86400,
Destination: &candleGranularity,
},
cli.StringFlag{
Name: "start",
Usage: "<start> rounded down to the nearest hour",
Value: time.Now().AddDate(0, -1, 0).Truncate(time.Hour).Format(common.SimpleTimeFormat),
Destination: &startTime,
},
cli.StringFlag{
Name: "end",
Usage: "<end> rounded down to the nearest hour",
Value: time.Now().Truncate(time.Hour).Format(common.SimpleTimeFormat),
Destination: &endTime,
},
},
}
func findMissingSavedCandleIntervals(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowCommandHelp(c, "findmissingsavedcandleintervals")
}
var exchangeName string
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}
if !validExchange(exchangeName) {
return errInvalidExchange
}
var currencyPair string
if c.IsSet("pair") {
currencyPair = c.String("pair")
} else {
currencyPair = c.Args().Get(1)
}
if !validPair(currencyPair) {
return errInvalidPair
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
var assetType string
if c.IsSet("asset") {
assetType = c.String("asset")
} else {
assetType = c.Args().Get(2)
}
if !validAsset(assetType) {
return errInvalidAsset
}
if c.IsSet("interval") {
candleGranularity = c.Int64("interval")
} else if c.Args().Get(3) != "" {
candleGranularity, err = strconv.ParseInt(c.Args().Get(3), 10, 64)
if err != nil {
return err
}
}
if !c.IsSet("start") {
if c.Args().Get(4) != "" {
startTime = c.Args().Get(4)
}
}
if !c.IsSet("end") {
if c.Args().Get(5) != "" {
endTime = c.Args().Get(5)
}
}
candleInterval := time.Duration(candleGranularity) * time.Second
var s, e time.Time
s, err = time.Parse(common.SimpleTimeFormat, startTime)
if err != nil {
return fmt.Errorf("invalid time format for start: %v", err)
}
e, err = time.Parse(common.SimpleTimeFormat, endTime)
if err != nil {
return fmt.Errorf("invalid time format for end: %v", err)
}
if e.Before(s) {
return errors.New("start cannot be after end")
}
conn, err := setupClient()
if err != nil {
return err
}
defer func() {
err = conn.Close()
if err != nil {
fmt.Print(err)
}
}()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.FindMissingSavedCandleIntervals(context.Background(),
&gctrpc.FindMissingCandlePeriodsRequest{
ExchangeName: exchangeName,
Pair: &gctrpc.CurrencyPair{
Delimiter: p.Delimiter,
Base: p.Base.String(),
Quote: p.Quote.String(),
},
AssetType: assetType,
Start: negateLocalOffset(s),
End: negateLocalOffset(e),
Interval: int64(candleInterval),
})
if err != nil {
return err
}
jsonOutput(result)
return nil
}
// negateLocalOffset helps negate the offset of time generation
// when the unix time gets to rpcserver, it no longer is the same time
// that was sent as it handles it as a UTC value, even though when
// using starttime it is generated as your local time
// eg 2020-01-01 12:00:00 +10 will convert into
// 2020-01-01 12:00:00 +00 when at RPCServer
// so this function will minus the offset from the local sent time
// to allow for proper use at RPCServer
func negateLocalOffset(t time.Time) string {
_, offset := time.Now().Zone()
loc := time.FixedZone("", -offset)
return t.In(loc).Format(common.SimpleTimeFormat)
}

View File

@@ -141,8 +141,10 @@ func main() {
getAuditEventCommand,
getHistoricCandlesCommand,
getHistoricCandlesExtendedCommand,
findMissingSavedCandleIntervalsCommand,
gctScriptCommand,
websocketManagerCommand,
tradeCommand,
}
err := app.Run(os.Args)

765
cmd/gctcli/trades.go Normal file
View File

@@ -0,0 +1,765 @@
package main
import (
"context"
"errors"
"fmt"
"strconv"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/gctrpc"
"github.com/urfave/cli"
)
var tradeCommand = cli.Command{
Name: "trade",
Usage: "execute trade related commands",
ArgsUsage: "<command> <args>",
Subcommands: []cli.Command{
{
Name: "setexchangetradeprocessing",
Usage: "sets whether an exchange can save trades to the database",
ArgsUsage: "<exchange> <status>",
Action: setExchangeTradeProcessing,
Flags: []cli.Flag{
cli.StringFlag{
Name: "exchange, e",
Usage: "the exchange to change the status of",
},
cli.BoolFlag{
Name: "status",
Usage: "<true>/<false>",
},
},
},
{
Name: "getrecent",
Usage: "gets recent trades",
ArgsUsage: "<exchange> <pair> <asset>",
Action: getRecentTrades,
Flags: []cli.Flag{
cli.StringFlag{
Name: "exchange, e",
Usage: "the exchange to get the trades from",
},
cli.StringFlag{
Name: "pair, p",
Usage: "the currency pair to get the trades for",
},
cli.StringFlag{
Name: "asset, a",
Usage: "the asset type of the currency pair",
},
},
},
{
Name: "gethistoric",
Usage: "gets trades between two periods",
ArgsUsage: "<exchange> <pair> <asset> <start> <end>",
Action: getHistoricTrades,
Flags: []cli.Flag{
cli.StringFlag{
Name: "exchange, e",
Usage: "the exchange to get the trades from",
},
cli.StringFlag{
Name: "pair, p",
Usage: "the currency pair to get the trades for",
},
cli.StringFlag{
Name: "asset, a",
Usage: "the asset type of the currency pair",
},
cli.StringFlag{
Name: "start",
Usage: "<start>",
Value: time.Now().Add(-time.Hour * 6).Format(common.SimpleTimeFormat),
Destination: &startTime,
},
cli.StringFlag{
Name: "end",
Usage: "<end> WARNING: large date ranges may take considerable time",
Value: time.Now().Format(common.SimpleTimeFormat),
Destination: &endTime,
},
},
},
{
Name: "getsaved",
Usage: "gets trades from the database",
ArgsUsage: "<exchange> <pair> <asset> <start> <end>",
Action: getSavedTrades,
Flags: []cli.Flag{
cli.StringFlag{
Name: "exchange, e",
Usage: "the exchange to get the trades from",
},
cli.StringFlag{
Name: "pair, p",
Usage: "the currency pair to get the trades for",
},
cli.StringFlag{
Name: "asset, a",
Usage: "the asset type of the currency pair",
},
cli.StringFlag{
Name: "start",
Usage: "<start>",
Value: time.Now().AddDate(0, -1, 0).Format(common.SimpleTimeFormat),
Destination: &startTime,
},
cli.StringFlag{
Name: "end",
Usage: "<end>",
Value: time.Now().Format(common.SimpleTimeFormat),
Destination: &endTime,
},
},
},
{
Name: "findmissingsavedtradeintervals",
Usage: "will highlight any interval that is missing trade data so you can fill that gap",
ArgsUsage: "<exchange> <pair> <asset> <start> <end>",
Action: findMissingSavedTradeIntervals,
Flags: []cli.Flag{
cli.StringFlag{
Name: "exchange, e",
Usage: "the exchange to find the missing trades",
},
cli.StringFlag{
Name: "pair, p",
Usage: "the currency pair",
},
cli.StringFlag{
Name: "asset, a",
Usage: "the asset type of the currency pair",
},
cli.StringFlag{
Name: "start",
Usage: "<start> rounded down to the nearest hour",
Value: time.Now().Add(-time.Hour * 24).Truncate(time.Hour).Format(common.SimpleTimeFormat),
Destination: &startTime,
},
cli.StringFlag{
Name: "end",
Usage: "<end> rounded down to the nearest hour",
Value: time.Now().Truncate(time.Hour).Format(common.SimpleTimeFormat),
Destination: &endTime,
},
},
},
{
Name: "convertsavedtradestocandles",
Usage: "explicitly converts stored trade data to candles and saves the result to the database",
ArgsUsage: "<exchange> <pair> <asset> <interval> <start> <end>",
Action: convertSavedTradesToCandles,
Flags: []cli.Flag{
cli.StringFlag{
Name: "exchange, e",
Usage: "the exchange",
},
cli.StringFlag{
Name: "pair, p",
Usage: "the currency pair to get the trades for",
},
cli.StringFlag{
Name: "asset, a",
Usage: "the asset type of the currency pair",
},
cli.Int64Flag{
Name: "interval, i",
Usage: fmt.Sprintf(klineMessage, "interval"),
Value: 86400,
Destination: &candleGranularity,
},
cli.StringFlag{
Name: "start",
Usage: "<start>",
Value: time.Now().AddDate(0, -1, 0).Format(common.SimpleTimeFormat),
Destination: &startTime,
},
cli.StringFlag{
Name: "end",
Usage: "<end>",
Value: time.Now().Format(common.SimpleTimeFormat),
Destination: &endTime,
},
cli.BoolFlag{
Name: "sync, s",
Usage: "will sync the resulting candles to the database <true/false>",
},
cli.BoolFlag{
Name: "force, f",
Usage: "will overwrite any conflicting candle data on save <true/false>",
},
},
},
},
}
func findMissingSavedTradeIntervals(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowCommandHelp(c, "findmissingsavedtradeintervals")
}
var exchangeName string
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}
if !validExchange(exchangeName) {
return errInvalidExchange
}
var currencyPair string
if c.IsSet("pair") {
currencyPair = c.String("pair")
} else {
currencyPair = c.Args().Get(1)
}
if !validPair(currencyPair) {
return errInvalidPair
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
var assetType string
if c.IsSet("asset") {
assetType = c.String("asset")
} else {
assetType = c.Args().Get(2)
}
if !validAsset(assetType) {
return errInvalidAsset
}
if !c.IsSet("start") {
if c.Args().Get(3) != "" {
startTime = c.Args().Get(3)
}
}
if !c.IsSet("end") {
if c.Args().Get(4) != "" {
endTime = c.Args().Get(4)
}
}
var s, e time.Time
s, err = time.Parse(common.SimpleTimeFormat, startTime)
if err != nil {
return fmt.Errorf("invalid time format for start: %v", err)
}
e, err = time.Parse(common.SimpleTimeFormat, endTime)
if err != nil {
return fmt.Errorf("invalid time format for end: %v", err)
}
conn, err := setupClient()
if err != nil {
return err
}
defer func() {
err = conn.Close()
if err != nil {
fmt.Print(err)
}
}()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.FindMissingSavedTradeIntervals(context.Background(),
&gctrpc.FindMissingTradePeriodsRequest{
ExchangeName: exchangeName,
Pair: &gctrpc.CurrencyPair{
Delimiter: p.Delimiter,
Base: p.Base.String(),
Quote: p.Quote.String(),
},
AssetType: assetType,
Start: negateLocalOffset(s),
End: negateLocalOffset(e),
})
if err != nil {
return err
}
jsonOutput(result)
return nil
}
func setExchangeTradeProcessing(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowCommandHelp(c, "setexchangetradeprocessing")
}
var exchangeName string
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}
if !validExchange(exchangeName) {
return errInvalidExchange
}
var status bool
if c.IsSet("status") {
status = c.Bool("status")
} else {
statusStr := c.Args().Get(1)
var err error
status, err = strconv.ParseBool(statusStr)
if err != nil {
return err
}
}
conn, err := setupClient()
if err != nil {
return err
}
defer func() {
err = conn.Close()
if err != nil {
fmt.Print(err)
}
}()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.SetExchangeTradeProcessing(context.Background(),
&gctrpc.SetExchangeTradeProcessingRequest{
Exchange: exchangeName,
Status: status,
})
if err != nil {
return err
}
jsonOutput(result)
return nil
}
func getSavedTrades(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowCommandHelp(c, "getsaved")
}
var exchangeName string
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}
if !validExchange(exchangeName) {
return errInvalidExchange
}
var currencyPair string
if c.IsSet("pair") {
currencyPair = c.String("pair")
} else {
currencyPair = c.Args().Get(1)
}
if !validPair(currencyPair) {
return errInvalidPair
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
var assetType string
if c.IsSet("asset") {
assetType = c.String("asset")
} else {
assetType = c.Args().Get(2)
}
if !validAsset(assetType) {
return errInvalidAsset
}
if !c.IsSet("start") {
if c.Args().Get(3) != "" {
startTime = c.Args().Get(3)
}
}
if !c.IsSet("end") {
if c.Args().Get(4) != "" {
endTime = c.Args().Get(4)
}
}
var s, e time.Time
s, err = time.Parse(common.SimpleTimeFormat, startTime)
if err != nil {
return fmt.Errorf("invalid time format for start: %v", err)
}
e, err = time.Parse(common.SimpleTimeFormat, endTime)
if err != nil {
return fmt.Errorf("invalid time format for end: %v", err)
}
if e.Before(s) {
return errors.New("start cannot be after end")
}
conn, err := setupClient()
if err != nil {
return err
}
defer func() {
err = conn.Close()
if err != nil {
fmt.Print(err)
}
}()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetSavedTrades(context.Background(),
&gctrpc.GetSavedTradesRequest{
Exchange: exchangeName,
Pair: &gctrpc.CurrencyPair{
Delimiter: p.Delimiter,
Base: p.Base.String(),
Quote: p.Quote.String(),
},
AssetType: assetType,
Start: negateLocalOffset(s),
End: negateLocalOffset(e),
})
if err != nil {
return err
}
jsonOutput(result)
return nil
}
func getRecentTrades(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowCommandHelp(c, "getrecent")
}
var exchangeName string
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}
if !validExchange(exchangeName) {
return errInvalidExchange
}
var currencyPair string
if c.IsSet("pair") {
currencyPair = c.String("pair")
} else {
currencyPair = c.Args().Get(1)
}
if !validPair(currencyPair) {
return errInvalidPair
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
var assetType string
if c.IsSet("asset") {
assetType = c.String("asset")
} else {
assetType = c.Args().Get(2)
}
if !validAsset(assetType) {
return errInvalidAsset
}
conn, err := setupClient()
if err != nil {
return err
}
defer func() {
err = conn.Close()
if err != nil {
fmt.Print(err)
}
}()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetRecentTrades(context.Background(),
&gctrpc.GetSavedTradesRequest{
Exchange: exchangeName,
Pair: &gctrpc.CurrencyPair{
Delimiter: p.Delimiter,
Base: p.Base.String(),
Quote: p.Quote.String(),
},
AssetType: assetType,
})
if err != nil {
return err
}
jsonOutput(result)
return nil
}
func getHistoricTrades(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowCommandHelp(c, "gethistoric")
}
var exchangeName string
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}
if !validExchange(exchangeName) {
return errInvalidExchange
}
var currencyPair string
if c.IsSet("pair") {
currencyPair = c.String("pair")
} else {
currencyPair = c.Args().Get(1)
}
if !validPair(currencyPair) {
return errInvalidPair
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
var assetType string
if c.IsSet("asset") {
assetType = c.String("asset")
} else {
assetType = c.Args().Get(2)
}
if !validAsset(assetType) {
return errInvalidAsset
}
if !c.IsSet("start") {
if c.Args().Get(3) != "" {
startTime = c.Args().Get(3)
}
}
if !c.IsSet("end") {
if c.Args().Get(4) != "" {
endTime = c.Args().Get(4)
}
}
var s, e time.Time
s, err = time.Parse(common.SimpleTimeFormat, startTime)
if err != nil {
return fmt.Errorf("invalid time format for start: %v", err)
}
e, err = time.Parse(common.SimpleTimeFormat, endTime)
if err != nil {
return fmt.Errorf("invalid time format for end: %v", err)
}
if e.Before(s) {
return errors.New("start cannot be after end")
}
conn, err := setupClient()
if err != nil {
return err
}
defer func() {
err = conn.Close()
if err != nil {
fmt.Print(err)
}
}()
streamStartTime := time.Now()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetHistoricTrades(context.Background(),
&gctrpc.GetSavedTradesRequest{
Exchange: exchangeName,
Pair: &gctrpc.CurrencyPair{
Delimiter: p.Delimiter,
Base: p.Base.String(),
Quote: p.Quote.String(),
},
AssetType: assetType,
Start: negateLocalOffset(s),
End: negateLocalOffset(e),
})
if err != nil {
return err
}
fmt.Printf("%v\t| Beginning stream retrieving trades in 1 hour batches from %v to %v\n",
time.Now().Format(time.Kitchen),
s.UTC().Format(common.SimpleTimeFormatWithTimezone),
e.UTC().Format(common.SimpleTimeFormatWithTimezone))
fmt.Printf("%v\t| If you have provided a large time range, please be patient\n\n",
time.Now().Format(time.Kitchen))
for {
resp, err := result.Recv()
if err != nil {
return err
}
if len(resp.Trades) == 0 {
break
}
fmt.Printf("%v\t| Processed %v trades between %v and %v\n",
time.Now().Format(time.Kitchen),
len(resp.Trades),
resp.Trades[0].Timestamp,
resp.Trades[len(resp.Trades)-1].Timestamp)
}
fmt.Printf("%v\t| Trade retrieval complete! Process took %v\n",
time.Now().Format(time.Kitchen),
time.Since(streamStartTime))
return nil
}
func convertSavedTradesToCandles(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowCommandHelp(c, "convertsavedtradestocandles")
}
var exchangeName string
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}
if !validExchange(exchangeName) {
return errInvalidExchange
}
var currencyPair string
if c.IsSet("pair") {
currencyPair = c.String("pair")
} else {
currencyPair = c.Args().Get(1)
}
if !validPair(currencyPair) {
return errInvalidPair
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
var assetType string
if c.IsSet("asset") {
assetType = c.String("asset")
} else {
assetType = c.Args().Get(2)
}
if !validAsset(assetType) {
return errInvalidAsset
}
if c.IsSet("interval") {
candleGranularity = c.Int64("interval")
} else if c.Args().Get(3) != "" {
candleGranularity, err = strconv.ParseInt(c.Args().Get(3), 10, 64)
if err != nil {
return err
}
}
if !c.IsSet("start") {
if c.Args().Get(4) != "" {
startTime = c.Args().Get(4)
}
}
if !c.IsSet("end") {
if c.Args().Get(5) != "" {
endTime = c.Args().Get(5)
}
}
var sync bool
if c.IsSet("sync") {
sync = c.Bool("sync")
}
var force bool
if c.IsSet("force") {
force = c.Bool("force")
}
if force && !sync {
return errors.New("cannot forcefully overwrite without sync")
}
candleInterval := time.Duration(candleGranularity) * time.Second
var s, e time.Time
s, err = time.Parse(common.SimpleTimeFormat, startTime)
if err != nil {
return fmt.Errorf("invalid time format for start: %v", err)
}
e, err = time.Parse(common.SimpleTimeFormat, endTime)
if err != nil {
return fmt.Errorf("invalid time format for end: %v", err)
}
if e.Before(s) {
return errors.New("start cannot be after end")
}
conn, err := setupClient()
if err != nil {
return err
}
defer func() {
err = conn.Close()
if err != nil {
fmt.Print(err)
}
}()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.ConvertTradesToCandles(context.Background(),
&gctrpc.ConvertTradesToCandlesRequest{
Exchange: exchangeName,
Pair: &gctrpc.CurrencyPair{
Delimiter: p.Delimiter,
Base: p.Base.String(),
Quote: p.Quote.String(),
},
AssetType: assetType,
Start: negateLocalOffset(s),
End: negateLocalOffset(e),
TimeInterval: int64(candleInterval),
Sync: sync,
Force: force,
})
if err != nil {
return err
}
jsonOutput(result)
return nil
}

View File

@@ -42,7 +42,10 @@ const (
)
// SimpleTimeFormat a common, but non-implemented time format in golang
const SimpleTimeFormat = "2006-01-02 15:04:05"
const (
SimpleTimeFormat = "2006-01-02 15:04:05"
SimpleTimeFormatWithTimezone = "2006-01-02 15:04:05 MST"
)
func initialiseHTTPClient() {
// If the HTTPClient isn't set, start a new client with a default timeout of 15 seconds

View File

@@ -0,0 +1,148 @@
package timeperiods
import (
"errors"
"sort"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
)
// FindTimeRangesContainingData will break the start and end into time periods using the provided period
// it will then check whether any comparisonTimes are within those periods and concatenate them
// eg if no comparisonTimes match, you will receive 1 TimeRange of Start End with dataInRange = false
// eg2 if 1 comparisonTime matches in the middle of start and end, you will receive three ranges
func FindTimeRangesContainingData(start, end time.Time, period time.Duration, comparisonTimes []time.Time) ([]TimeRange, error) {
var errs common.Errors
if start.IsZero() {
errs = append(errs, errors.New("invalid start time"))
}
if end.IsZero() {
errs = append(errs, errors.New("invalid end time"))
}
if err := validatePeriod(period); err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {
return nil, errs
}
var t TimePeriodCalculator
t.periodDuration = period
t.start = start.Truncate(period)
t.end = end.Truncate(period)
t.comparisonTimes = comparisonTimes
t.setTimePeriodExists()
t.Sort(false)
t.calculateRanges()
return t.TimeRanges, nil
}
func validatePeriod(period time.Duration) error {
if period != time.Hour &&
period != time.Second &&
period != time.Minute &&
period != time.Hour*24 {
return errors.New("invalid period")
}
return nil
}
// CalculateTimePeriodsInRange can break down start and end times into time periods
// eg 1 hourly intervals
func CalculateTimePeriodsInRange(start, end time.Time, period time.Duration) ([]TimePeriod, error) {
var errs common.Errors
if start.IsZero() {
errs = append(errs, errors.New("invalid start time"))
}
if end.IsZero() {
errs = append(errs, errors.New("invalid end time"))
}
if err := validatePeriod(period); err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {
return nil, errs
}
var t TimePeriodCalculator
t.periodDuration = period
t.start = start.Truncate(period)
t.end = end.Truncate(period)
t.calculatePeriods()
return t.TimePeriods, nil
}
func (t *TimePeriodCalculator) calculateRanges() {
var tr TimeRange
for i := range t.TimePeriods {
if i != 0 {
if (t.TimePeriods[i].dataInRange && !t.TimePeriods[i-1].dataInRange) ||
(!t.TimePeriods[i].dataInRange && t.TimePeriods[i-1].dataInRange) {
// the status has changed and therefore a range has ended
tr.HasDataInRange = t.TimePeriods[i-1].dataInRange
tr.EndOfRange = t.TimePeriods[i].Time
t.TimeRanges = append(t.TimeRanges, tr)
tr = TimeRange{}
}
}
if tr.StartOfRange.IsZero() {
// start of new time range
tr.StartOfRange = t.TimePeriods[i].Time
}
}
if !tr.StartOfRange.IsZero() {
if tr.EndOfRange.IsZero() {
tr.EndOfRange = t.end
}
tr.HasDataInRange = t.TimePeriods[len(t.TimePeriods)-1].dataInRange
t.TimeRanges = append(t.TimeRanges, tr)
tr = TimeRange{}
}
}
func (t *TimePeriodCalculator) calculatePeriods() {
if t.start.IsZero() || t.end.IsZero() {
return
}
if t.start.After(t.end) {
return
}
iterateDateMate := t.start
for !iterateDateMate.Equal(t.end) && !iterateDateMate.After(t.end) {
tp := TimePeriod{
Time: iterateDateMate,
dataInRange: false,
}
t.TimePeriods = append(t.TimePeriods, tp)
iterateDateMate = iterateDateMate.Add(t.periodDuration)
}
}
// setTimePeriodExists compares loaded comparisonTimes
// against calculated TimePeriods to determine whether
// there is existing data within the time period
func (t *TimePeriodCalculator) setTimePeriodExists() {
t.calculatePeriods()
for i := range t.TimePeriods {
for j := range t.comparisonTimes {
if t.comparisonTimes[j].Truncate(t.periodDuration).Equal(t.TimePeriods[i].Time) {
t.TimePeriods[i].dataInRange = true
break
}
}
}
}
// Sort will sort the time period asc or desc
func (t *TimePeriodCalculator) Sort(desc bool) {
sort.Slice(t.TimePeriods, func(i, j int) bool {
if desc {
return t.TimePeriods[i].Time.After(t.TimePeriods[j].Time)
}
return t.TimePeriods[i].Time.Before(t.TimePeriods[j].Time)
})
}

View File

@@ -0,0 +1,188 @@
package timeperiods
import (
"testing"
"time"
)
func TestFindTimeRangesContainingData(t *testing.T) {
// validation issues
_, err := FindTimeRangesContainingData(
time.Time{},
time.Time{},
0,
nil,
)
if err != nil && err.Error() != "invalid start time, invalid end time, invalid period" {
t.Fatal(err)
}
// empty trade times
searchStartTime := time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC)
searchEndTime := time.Date(2020, 1, 1, 10, 0, 0, 0, time.UTC)
var tradeTimes []time.Time
var ranges []TimeRange
ranges, err = FindTimeRangesContainingData(
searchStartTime,
searchEndTime,
time.Hour,
tradeTimes,
)
if err != nil {
t.Error(err)
}
if len(ranges) != 1 {
t.Errorf("expected 1 time range, received %v", len(ranges))
}
// 1 trade with 3 periods
tradeTimes = append(tradeTimes, time.Date(2020, 1, 1, 2, 0, 0, 0, time.UTC))
ranges, err = FindTimeRangesContainingData(
searchStartTime,
searchEndTime,
time.Hour,
tradeTimes,
)
if err != nil {
t.Error(err)
}
if len(ranges) != 3 {
t.Errorf("expected 3 time ranges, received %v", len(ranges))
}
// 2 trades with 3 periods
tradeTimes = append(tradeTimes, time.Date(2020, 1, 1, 3, 0, 0, 0, time.UTC))
ranges, err = FindTimeRangesContainingData(
searchStartTime,
searchEndTime,
time.Hour,
tradeTimes,
)
if err != nil {
t.Error(err)
}
if len(ranges) != 3 {
t.Errorf("expected 3 time ranges, received %v", len(ranges))
}
// 3 trades with 5 periods
tradeTimes = append(tradeTimes, time.Date(2020, 1, 1, 5, 0, 0, 0, time.UTC))
ranges, err = FindTimeRangesContainingData(
searchStartTime,
searchEndTime,
time.Hour,
tradeTimes,
)
if err != nil {
t.Error(err)
}
if len(ranges) != 5 {
t.Errorf("expected 5 time ranges, received %v", len(ranges))
}
// 4 trades with 5 periods
tradeTimes = append(tradeTimes, time.Date(2020, 1, 1, 6, 0, 0, 0, time.UTC))
ranges, err = FindTimeRangesContainingData(
searchStartTime,
searchEndTime,
time.Hour,
tradeTimes,
)
if err != nil {
t.Error(err)
}
if len(ranges) != 5 {
t.Errorf("expected 5 time ranges, received %v", len(ranges))
}
// 5 trades with 6 periods
tradeTimes = append(tradeTimes, time.Date(2020, 1, 1, 9, 0, 0, 0, time.UTC))
ranges, err = FindTimeRangesContainingData(
searchStartTime,
searchEndTime,
time.Hour,
tradeTimes,
)
if err != nil {
t.Error(err)
}
if len(ranges) != 6 {
t.Errorf("expected 6 time ranges, received %v", len(ranges))
}
}
func TestCalculateTimePeriodsInRange(t *testing.T) {
// validation issues
_, err := CalculateTimePeriodsInRange(time.Time{}, time.Time{}, 0)
if err != nil && err.Error() != "invalid start time, invalid end time, invalid period" {
t.Fatal(err)
}
// start after end
var intervals []TimePeriod
timeStart := time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC)
timeEnd := time.Date(2020, 1, 2, 1, 0, 0, 0, time.UTC)
intervals, err = CalculateTimePeriodsInRange(timeEnd, timeStart, time.Hour)
if err != nil {
t.Error(err)
}
if len(intervals) != 0 {
t.Errorf("expected 0 interval(s), received %v", len(intervals))
}
// 1 interval
intervals, err = CalculateTimePeriodsInRange(timeStart, timeStart.Add(time.Hour), time.Hour)
if err != nil {
t.Error(err)
}
if len(intervals) != 1 {
t.Errorf("expected 1 interval(s), received %v", len(intervals))
}
// multiple intervals
intervals, err = CalculateTimePeriodsInRange(timeStart, timeEnd, time.Hour)
if err != nil {
t.Error(err)
}
if len(intervals) != 24 {
t.Errorf("expected 24 interval(s), received %v", len(intervals))
}
// odd time
intervals, err = CalculateTimePeriodsInRange(timeStart.Add(-time.Minute*30), timeEnd, time.Hour)
if err != nil {
t.Error(err)
}
if len(intervals) != 25 {
t.Errorf("expected 25 interval(s), received %v", len(intervals))
}
// truncate always goes to zero, no mid rounding
intervals, err = CalculateTimePeriodsInRange(timeStart, timeStart.Add(time.Minute), time.Hour)
if err != nil {
t.Error(err)
}
if len(intervals) != 0 {
t.Errorf("expected 0 interval(s), received %v", len(intervals))
}
}
func TestValidateCalculatePeriods(t *testing.T) {
var tpc TimePeriodCalculator
tpc.calculatePeriods()
if len(tpc.TimePeriods) > 0 {
t.Error("validation has been removed")
}
}
func TestSort(t *testing.T) {
var tpc TimePeriodCalculator
date1 := time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC)
date2 := time.Date(1901, 1, 1, 1, 1, 1, 1, time.UTC)
tpc.TimePeriods = append(tpc.TimePeriods,
TimePeriod{
Time: date1,
},
TimePeriod{
Time: date2,
},
)
tpc.Sort(false)
if !tpc.TimePeriods[0].Time.Equal(date2) {
t.Errorf("expected %v, received %v", date2, tpc.TimePeriods[0].Time)
}
tpc.Sort(true)
if !tpc.TimePeriods[0].Time.Equal(date1) {
t.Errorf("expected %v, received %v", date1, tpc.TimePeriods[0].Time)
}
}

View File

@@ -0,0 +1,32 @@
package timeperiods
import (
"time"
)
// TimePeriodCalculator is able analyse
// a time span and either break them down into
// chunks, or determine ranges that contain data or not
type TimePeriodCalculator struct {
start time.Time
end time.Time
comparisonTimes []time.Time
TimePeriods []TimePeriod
periodDuration time.Duration
TimeRanges []TimeRange
}
// TimePeriod is a basic type which will know
// whether a period in time contains data
type TimePeriod struct {
Time time.Time
dataInRange bool
}
// TimeRange holds a start and end dat range
// and whether that range contains data
type TimeRange struct {
StartOfRange time.Time
EndOfRange time.Time
HasDataInRange bool
}

View File

@@ -64,6 +64,13 @@ func (s *SMTPservice) Send(subject, msg string) error {
if subject == "" || msg == "" {
return errors.New("STMPservice Send() please add subject and alert")
}
if s.Host == "" ||
s.Port == "" ||
s.AccountName == "" ||
s.AccountPassword == "" ||
s.From == "" {
return errors.New("STMPservice Send() cannot send with unset service properties")
}
log.Debugf(log.CommunicationMgr, "SMTP: Sending email to %v. Subject: %s Message: %s [From: %s]\n", s.RecipientList,
subject, msg, s.From)

View File

@@ -333,6 +333,7 @@ type FeaturesSupportedConfig struct {
type FeaturesEnabledConfig struct {
AutoPairUpdates bool `json:"autoPairUpdates"`
Websocket bool `json:"websocketAPI"`
SaveTradeData bool `json:"saveTradeData"`
}
// FeaturesConfig stores the exchanges supported and enabled features

View File

@@ -107,3 +107,25 @@ func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFor
}
return NewPairFromString(currencyPair)
}
// MatchPairsWithNoDelimiter will move along a predictable index on the provided currencyPair
// it will then split on that index and verify whether that currencypair exists in the
// supplied pairs
// this allows for us to match strange currencies with no delimiter where it is difficult to
// infer where the delimiter is located eg BETHERETH is BETHER ETH
func MatchPairsWithNoDelimiter(currencyPair string, pairs Pairs, pairFmt PairFormat) (Pair, error) {
for i := range pairs {
fPair := pairs[i].Format(pairFmt.Delimiter, pairFmt.Uppercase)
maxLen := 6
if len(currencyPair) < maxLen {
maxLen = len(currencyPair)
}
for j := 1; j <= maxLen; j++ {
if fPair.Base.String() == currencyPair[0:j] &&
fPair.Quote.String() == currencyPair[j:] {
return fPair, nil
}
}
}
return Pair{}, fmt.Errorf("currency %v not found in supplied pairs", currencyPair)
}

View File

@@ -693,3 +693,86 @@ func TestIsInvalid(t *testing.T) {
t.Error("IsInvalid() error expect true but received false")
}
}
func TestMatchPairsWithNoDelimiter(t *testing.T) {
p1, err := NewPairDelimiter("BTC-USDT", "-")
if err != nil {
t.Fatal(err)
}
p2, err := NewPairDelimiter("LTC-USD", "-")
if err != nil {
t.Fatal(err)
}
p3, err := NewPairFromStrings("EQUAD", "BTC")
if err != nil {
t.Fatal(err)
}
p4, err := NewPairFromStrings("HTDF", "USDT")
if err != nil {
t.Fatal(err)
}
p5, err := NewPairFromStrings("BETHER", "ETH")
if err != nil {
t.Fatal(err)
}
pairs := Pairs{
p1,
p2,
p3,
p4,
p5,
}
p, err := MatchPairsWithNoDelimiter("BTCUSDT", pairs, PairFormat{
Uppercase: true,
})
if err != nil {
t.Fatal(err)
}
if p.Quote.String() != "USDT" && p.Base.String() != "BTC" {
t.Error("unexpected response")
}
p, err = MatchPairsWithNoDelimiter("EQUADBTC", pairs, PairFormat{
Uppercase: true,
})
if err != nil {
t.Fatal(err)
}
if p.Base.String() != "EQUAD" && p.Quote.String() != "BTC" {
t.Errorf("unexpected response base: %v quote: %v", p.Base.String(), p.Quote.String())
}
p, err = MatchPairsWithNoDelimiter("EQUADBTC", pairs, PairFormat{
Uppercase: true,
Delimiter: "/",
})
if err != nil {
t.Fatal(err)
}
if p.Base.String() != "EQUAD" && p.Quote.String() != "BTC" {
t.Errorf("unexpected response base: %v quote: %v", p.Base.String(), p.Quote.String())
}
p, err = MatchPairsWithNoDelimiter("HTDFUSDT", pairs, PairFormat{
Uppercase: true,
Delimiter: "/",
})
if err != nil {
t.Fatal(err)
}
if p.Base.String() != "HTDF" && p.Quote.String() != "USDT" {
t.Errorf("unexpected response base: %v quote: %v", p.Base.String(), p.Quote.String())
}
p, err = MatchPairsWithNoDelimiter("BETHERETH", pairs, PairFormat{
Uppercase: true,
Delimiter: "/",
})
if err != nil {
t.Fatal(err)
}
if p.Base.String() != "BETHER" && p.Quote.String() != "ETH" {
t.Errorf("unexpected response base: %v quote: %v", p.Base.String(), p.Quote.String())
}
}

View File

@@ -0,0 +1,20 @@
-- +goose Up
CREATE TABLE IF NOT EXISTS trade
(
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
exchange_name_id uuid REFERENCES exchange(id) NOT NULL,
tid varchar,
base varchar(30) NOT NULL,
quote varchar(30) NOT NULL,
asset varchar NOT NULL,
price DOUBLE PRECISION NOT NULL,
amount DOUBLE PRECISION NOT NULL,
side varchar NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
CONSTRAINT uniquetradeid
unique(exchange_name_id, tid),
CONSTRAINT uniquetrade
unique(exchange_name_id, base, quote, asset, price, amount, side, timestamp)
);
-- +goose Down
DROP TABLE trade;

View File

@@ -0,0 +1,20 @@
-- +goose Up
CREATE TABLE IF NOT EXISTS trade
(
id text not null primary key,
exchange_name_id uuid REFERENCES exchange(id) NOT NULL,
tid TEXT,
base text NOT NULL,
quote text NOT NULL,
asset TEXT NOT NULL,
price REAL NOT NULL,
amount REAL NOT NULL,
side TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL,
CONSTRAINT uniquetradeid
unique(exchange_name_id, tid) ON CONFLICT IGNORE,
CONSTRAINT uniquetrade
unique(exchange_name_id, base, quote, asset, price, amount, side, timestamp) ON CONFLICT IGNORE
);
-- +goose Down
DROP TABLE trade;

View File

@@ -0,0 +1,14 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE trade DROP CONSTRAINT uniquetrade;
CREATE UNIQUE INDEX unique_trade_no_id ON trade (base,quote,asset,price,amount,side, timestamp)
WHERE tid IS NULL;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP INDEX unique_trade_no_id;
ALTER TABLE trade ADD CONSTRAINT uniquetrade
unique(exchange_name_id, base, quote, asset, price, amount, side, timestamp);
-- +goose StatementEnd

View File

@@ -0,0 +1,47 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE "trade_new" (
id text not null primary key,
exchange_name_id uuid REFERENCES exchange(id) NOT NULL,
tid TEXT,
base text NOT NULL,
quote text NOT NULL,
asset TEXT NOT NULL,
price REAL NOT NULL,
amount REAL NOT NULL,
side TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL,
CONSTRAINT uniquetradeid
unique(exchange_name_id, tid) ON CONFLICT IGNORE
);
INSERT INTO trade_new SELECT id, exchange_name_id, tid, base, quote, asset, price, amount, side, timestamp FROM trade;
DROP TABLE trade;
ALTER TABLE trade_new RENAME TO trade;
CREATE UNIQUE INDEX unique_trade_no_id ON trade (base,quote,asset,price,amount,side, timestamp)
WHERE tid IS NULL;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
CREATE TABLE "trade_new" (
id text not null primary key,
exchange_name_id uuid REFERENCES exchange(id) NOT NULL,
tid TEXT,
base text NOT NULL,
quote text NOT NULL,
asset TEXT NOT NULL,
price REAL NOT NULL,
amount REAL NOT NULL,
side TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL,
CONSTRAINT uniquetradeid
unique(exchange_name_id, tid) ON CONFLICT IGNORE,
CONSTRAINT uniquetrade
unique(exchange_name_id, base, quote, asset, price, amount, side, timestamp) ON CONFLICT IGNORE
);
INSERT INTO trade_new SELECT id, exchange_name_id, tid, base, quote, asset, price, amount, side, timestamp FROM trade;
DROP TABLE trade;
ALTER TABLE trade_new RENAME TO trade;
-- +goose StatementEnd

View File

@@ -0,0 +1,23 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE trade ALTER COLUMN side DROP NOT NULL;
DROP INDEX unique_trade_no_id;
CREATE UNIQUE INDEX unique_trade_no_id ON trade (base,quote,asset,price,amount,timestamp)
WHERE tid IS NULL;
UPDATE TRADE set side = null where side = 'UNKNOWN';
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
UPDATE TRADE set side = '' where side IS NULL;
ALTER TABLE trade ALTER COLUMN side SET NOT NULL;
DROP INDEX unique_trade_no_id;
CREATE UNIQUE INDEX unique_trade_no_id ON trade (base,quote,asset,price,amount,side,timestamp)
WHERE tid IS NULL;
-- +goose StatementEnd

View File

@@ -0,0 +1,55 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE "trade_new" (
id text not null primary key,
exchange_name_id uuid REFERENCES exchange(id) NOT NULL,
tid TEXT,
base text NOT NULL,
quote text NOT NULL,
asset TEXT NOT NULL,
price REAL NOT NULL,
amount REAL NOT NULL,
side TEXT,
timestamp TIMESTAMP NOT NULL,
CONSTRAINT uniquetradeid
unique(exchange_name_id, tid) ON CONFLICT IGNORE
);
INSERT INTO trade_new SELECT id, exchange_name_id, tid, base, quote, asset, price, amount, side, timestamp FROM trade;
DROP TABLE trade;
ALTER TABLE trade_new RENAME TO trade;
CREATE UNIQUE INDEX unique_trade_no_id ON trade (base,quote,asset,price,amount,timestamp)
WHERE tid IS NULL;
UPDATE trade SET side = null WHERE side = 'UNKNOWN' OR side = '';
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
CREATE TABLE "trade_new" (
id text not null primary key,
exchange_name_id uuid REFERENCES exchange(id) NOT NULL,
tid TEXT,
base text NOT NULL,
quote text NOT NULL,
asset TEXT NOT NULL,
price REAL NOT NULL,
amount REAL NOT NULL,
side TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL,
CONSTRAINT uniquetradeid
unique(exchange_name_id, tid) ON CONFLICT IGNORE
);
UPDATE trade SET side = '' WHERE side IS NULL;
INSERT INTO trade_new SELECT id, exchange_name_id, tid, base, quote, asset, price, amount, side, timestamp FROM trade;
DROP TABLE trade;
ALTER TABLE trade_new RENAME TO trade;
CREATE UNIQUE INDEX unique_trade_no_id ON trade (base,quote,asset,price,amount,side,timestamp)
WHERE tid IS NULL;
-- +goose StatementEnd

View File

@@ -9,6 +9,7 @@ var TableNames = struct {
Exchange string
Script string
ScriptExecution string
Trade string
WithdrawalCrypto string
WithdrawalFiat string
WithdrawalHistory string
@@ -18,6 +19,7 @@ var TableNames = struct {
Exchange: "exchange",
Script: "script",
ScriptExecution: "script_execution",
Trade: "trade",
WithdrawalCrypto: "withdrawal_crypto",
WithdrawalFiat: "withdrawal_fiat",
WithdrawalHistory: "withdrawal_history",

View File

@@ -51,15 +51,18 @@ var ExchangeWhere = struct {
// ExchangeRels is where relationship names are stored.
var ExchangeRels = struct {
ExchangeNameCandles string
ExchangeNameTrades string
ExchangeNameWithdrawalHistories string
}{
ExchangeNameCandles: "ExchangeNameCandles",
ExchangeNameTrades: "ExchangeNameTrades",
ExchangeNameWithdrawalHistories: "ExchangeNameWithdrawalHistories",
}
// exchangeR is where relationships are stored.
type exchangeR struct {
ExchangeNameCandles CandleSlice
ExchangeNameTrades TradeSlice
ExchangeNameWithdrawalHistories WithdrawalHistorySlice
}
@@ -374,6 +377,27 @@ func (o *Exchange) ExchangeNameCandles(mods ...qm.QueryMod) candleQuery {
return query
}
// ExchangeNameTrades retrieves all the trade's Trades with an executor via exchange_name_id column.
func (o *Exchange) ExchangeNameTrades(mods ...qm.QueryMod) tradeQuery {
var queryMods []qm.QueryMod
if len(mods) != 0 {
queryMods = append(queryMods, mods...)
}
queryMods = append(queryMods,
qm.Where("\"trade\".\"exchange_name_id\"=?", o.ID),
)
query := Trades(queryMods...)
queries.SetFrom(query.Query, "\"trade\"")
if len(queries.GetSelect(query.Query)) == 0 {
queries.SetSelect(query.Query, []string{"\"trade\".*"})
}
return query
}
// ExchangeNameWithdrawalHistories retrieves all the withdrawal_history's WithdrawalHistories with an executor via exchange_name_id column.
func (o *Exchange) ExchangeNameWithdrawalHistories(mods ...qm.QueryMod) withdrawalHistoryQuery {
var queryMods []qm.QueryMod
@@ -490,6 +514,101 @@ func (exchangeL) LoadExchangeNameCandles(ctx context.Context, e boil.ContextExec
return nil
}
// LoadExchangeNameTrades allows an eager lookup of values, cached into the
// loaded structs of the objects. This is for a 1-M or N-M relationship.
func (exchangeL) LoadExchangeNameTrades(ctx context.Context, e boil.ContextExecutor, singular bool, maybeExchange interface{}, mods queries.Applicator) error {
var slice []*Exchange
var object *Exchange
if singular {
object = maybeExchange.(*Exchange)
} else {
slice = *maybeExchange.(*[]*Exchange)
}
args := make([]interface{}, 0, 1)
if singular {
if object.R == nil {
object.R = &exchangeR{}
}
args = append(args, object.ID)
} else {
Outer:
for _, obj := range slice {
if obj.R == nil {
obj.R = &exchangeR{}
}
for _, a := range args {
if a == obj.ID {
continue Outer
}
}
args = append(args, obj.ID)
}
}
if len(args) == 0 {
return nil
}
query := NewQuery(qm.From(`trade`), qm.WhereIn(`trade.exchange_name_id in ?`, args...))
if mods != nil {
mods.Apply(query)
}
results, err := query.QueryContext(ctx, e)
if err != nil {
return errors.Wrap(err, "failed to eager load trade")
}
var resultSlice []*Trade
if err = queries.Bind(results, &resultSlice); err != nil {
return errors.Wrap(err, "failed to bind eager loaded slice trade")
}
if err = results.Close(); err != nil {
return errors.Wrap(err, "failed to close results in eager load on trade")
}
if err = results.Err(); err != nil {
return errors.Wrap(err, "error occurred during iteration of eager loaded relations for trade")
}
if len(tradeAfterSelectHooks) != 0 {
for _, obj := range resultSlice {
if err := obj.doAfterSelectHooks(ctx, e); err != nil {
return err
}
}
}
if singular {
object.R.ExchangeNameTrades = resultSlice
for _, foreign := range resultSlice {
if foreign.R == nil {
foreign.R = &tradeR{}
}
foreign.R.ExchangeName = object
}
return nil
}
for _, foreign := range resultSlice {
for _, local := range slice {
if local.ID == foreign.ExchangeNameID {
local.R.ExchangeNameTrades = append(local.R.ExchangeNameTrades, foreign)
if foreign.R == nil {
foreign.R = &tradeR{}
}
foreign.R.ExchangeName = local
break
}
}
}
return nil
}
// LoadExchangeNameWithdrawalHistories allows an eager lookup of values, cached into the
// loaded structs of the objects. This is for a 1-M or N-M relationship.
func (exchangeL) LoadExchangeNameWithdrawalHistories(ctx context.Context, e boil.ContextExecutor, singular bool, maybeExchange interface{}, mods queries.Applicator) error {
@@ -638,6 +757,59 @@ func (o *Exchange) AddExchangeNameCandles(ctx context.Context, exec boil.Context
return nil
}
// AddExchangeNameTrades adds the given related objects to the existing relationships
// of the exchange, optionally inserting them as new records.
// Appends related to o.R.ExchangeNameTrades.
// Sets related.R.ExchangeName appropriately.
func (o *Exchange) AddExchangeNameTrades(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Trade) error {
var err error
for _, rel := range related {
if insert {
rel.ExchangeNameID = o.ID
if err = rel.Insert(ctx, exec, boil.Infer()); err != nil {
return errors.Wrap(err, "failed to insert into foreign table")
}
} else {
updateQuery := fmt.Sprintf(
"UPDATE \"trade\" SET %s WHERE %s",
strmangle.SetParamNames("\"", "\"", 1, []string{"exchange_name_id"}),
strmangle.WhereClause("\"", "\"", 2, tradePrimaryKeyColumns),
)
values := []interface{}{o.ID, rel.ID}
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, updateQuery)
fmt.Fprintln(boil.DebugWriter, values)
}
if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil {
return errors.Wrap(err, "failed to update foreign table")
}
rel.ExchangeNameID = o.ID
}
}
if o.R == nil {
o.R = &exchangeR{
ExchangeNameTrades: related,
}
} else {
o.R.ExchangeNameTrades = append(o.R.ExchangeNameTrades, related...)
}
for _, rel := range related {
if rel.R == nil {
rel.R = &tradeR{
ExchangeName: o,
}
} else {
rel.R.ExchangeName = o
}
}
return nil
}
// AddExchangeNameWithdrawalHistories adds the given related objects to the existing relationships
// of the exchange, optionally inserting them as new records.
// Appends related to o.R.ExchangeNameWithdrawalHistories.

View File

@@ -572,6 +572,84 @@ func testExchangeToManyExchangeNameCandles(t *testing.T) {
}
}
func testExchangeToManyExchangeNameTrades(t *testing.T) {
var err error
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
var a Exchange
var b, c Trade
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, exchangeDBTypes, true, exchangeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Exchange struct: %s", err)
}
if err := a.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &b, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &c, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Fatal(err)
}
b.ExchangeNameID = a.ID
c.ExchangeNameID = a.ID
if err = b.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
if err = c.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
check, err := a.ExchangeNameTrades().All(ctx, tx)
if err != nil {
t.Fatal(err)
}
bFound, cFound := false, false
for _, v := range check {
if v.ExchangeNameID == b.ExchangeNameID {
bFound = true
}
if v.ExchangeNameID == c.ExchangeNameID {
cFound = true
}
}
if !bFound {
t.Error("expected to find b")
}
if !cFound {
t.Error("expected to find c")
}
slice := ExchangeSlice{&a}
if err = a.L.LoadExchangeNameTrades(ctx, tx, false, (*[]*Exchange)(&slice), nil); err != nil {
t.Fatal(err)
}
if got := len(a.R.ExchangeNameTrades); got != 2 {
t.Error("number of eager loaded records wrong, got:", got)
}
a.R.ExchangeNameTrades = nil
if err = a.L.LoadExchangeNameTrades(ctx, tx, true, &a, nil); err != nil {
t.Fatal(err)
}
if got := len(a.R.ExchangeNameTrades); got != 2 {
t.Error("number of eager loaded records wrong, got:", got)
}
if t.Failed() {
t.Logf("%#v", check)
}
}
func testExchangeToManyExchangeNameWithdrawalHistories(t *testing.T) {
var err error
ctx := context.Background()
@@ -725,6 +803,81 @@ func testExchangeToManyAddOpExchangeNameCandles(t *testing.T) {
}
}
}
func testExchangeToManyAddOpExchangeNameTrades(t *testing.T) {
var err error
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
var a Exchange
var b, c, d, e Trade
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, exchangeDBTypes, false, strmangle.SetComplement(exchangePrimaryKeyColumns, exchangeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
foreigners := []*Trade{&b, &c, &d, &e}
for _, x := range foreigners {
if err = randomize.Struct(seed, x, tradeDBTypes, false, strmangle.SetComplement(tradePrimaryKeyColumns, tradeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
}
if err := a.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
if err = b.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
if err = c.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
foreignersSplitByInsertion := [][]*Trade{
{&b, &c},
{&d, &e},
}
for i, x := range foreignersSplitByInsertion {
err = a.AddExchangeNameTrades(ctx, tx, i != 0, x...)
if err != nil {
t.Fatal(err)
}
first := x[0]
second := x[1]
if a.ID != first.ExchangeNameID {
t.Error("foreign key was wrong value", a.ID, first.ExchangeNameID)
}
if a.ID != second.ExchangeNameID {
t.Error("foreign key was wrong value", a.ID, second.ExchangeNameID)
}
if first.R.ExchangeName != &a {
t.Error("relationship was not added properly to the foreign slice")
}
if second.R.ExchangeName != &a {
t.Error("relationship was not added properly to the foreign slice")
}
if a.R.ExchangeNameTrades[i*2] != first {
t.Error("relationship struct slice not set to correct value")
}
if a.R.ExchangeNameTrades[i*2+1] != second {
t.Error("relationship struct slice not set to correct value")
}
count, err := a.ExchangeNameTrades().Count(ctx, tx)
if err != nil {
t.Fatal(err)
}
if want := int64((i + 1) * 2); count != want {
t.Error("want", want, "got", count)
}
}
}
func testExchangeToManyAddOpExchangeNameWithdrawalHistories(t *testing.T) {
var err error

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,841 @@
// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package postgres
import (
"bytes"
"context"
"reflect"
"testing"
"github.com/thrasher-corp/sqlboiler/boil"
"github.com/thrasher-corp/sqlboiler/queries"
"github.com/thrasher-corp/sqlboiler/randomize"
"github.com/thrasher-corp/sqlboiler/strmangle"
)
var (
// Relationships sometimes use the reflection helper queries.Equal/queries.Assign
// so force a package dependency in case they don't.
_ = queries.Equal
)
func testTrades(t *testing.T) {
t.Parallel()
query := Trades()
if query.Query == nil {
t.Error("expected a query, got nothing")
}
}
func testTradesDelete(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if rowsAff, err := o.Delete(ctx, tx); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("should only have deleted one row, but affected:", rowsAff)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 0 {
t.Error("want zero records, got:", count)
}
}
func testTradesQueryDeleteAll(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if rowsAff, err := Trades().DeleteAll(ctx, tx); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("should only have deleted one row, but affected:", rowsAff)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 0 {
t.Error("want zero records, got:", count)
}
}
func testTradesSliceDeleteAll(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
slice := TradeSlice{o}
if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("should only have deleted one row, but affected:", rowsAff)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 0 {
t.Error("want zero records, got:", count)
}
}
func testTradesExists(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
e, err := TradeExists(ctx, tx, o.ID)
if err != nil {
t.Errorf("Unable to check if Trade exists: %s", err)
}
if !e {
t.Errorf("Expected TradeExists to return true, but got false.")
}
}
func testTradesFind(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
tradeFound, err := FindTrade(ctx, tx, o.ID)
if err != nil {
t.Error(err)
}
if tradeFound == nil {
t.Error("want a record, got nil")
}
}
func testTradesBind(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if err = Trades().Bind(ctx, tx, o); err != nil {
t.Error(err)
}
}
func testTradesOne(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if x, err := Trades().One(ctx, tx); err != nil {
t.Error(err)
} else if x == nil {
t.Error("expected to get a non nil record")
}
}
func testTradesAll(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
tradeOne := &Trade{}
tradeTwo := &Trade{}
if err = randomize.Struct(seed, tradeOne, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if err = randomize.Struct(seed, tradeTwo, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = tradeOne.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if err = tradeTwo.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
slice, err := Trades().All(ctx, tx)
if err != nil {
t.Error(err)
}
if len(slice) != 2 {
t.Error("want 2 records, got:", len(slice))
}
}
func testTradesCount(t *testing.T) {
t.Parallel()
var err error
seed := randomize.NewSeed()
tradeOne := &Trade{}
tradeTwo := &Trade{}
if err = randomize.Struct(seed, tradeOne, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if err = randomize.Struct(seed, tradeTwo, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = tradeOne.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if err = tradeTwo.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 2 {
t.Error("want 2 records, got:", count)
}
}
func tradeBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func testTradesHooks(t *testing.T) {
t.Parallel()
var err error
ctx := context.Background()
empty := &Trade{}
o := &Trade{}
seed := randomize.NewSeed()
if err = randomize.Struct(seed, o, tradeDBTypes, false); err != nil {
t.Errorf("Unable to randomize Trade object: %s", err)
}
AddTradeHook(boil.BeforeInsertHook, tradeBeforeInsertHook)
if err = o.doBeforeInsertHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doBeforeInsertHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o)
}
tradeBeforeInsertHooks = []TradeHook{}
AddTradeHook(boil.AfterInsertHook, tradeAfterInsertHook)
if err = o.doAfterInsertHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterInsertHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o)
}
tradeAfterInsertHooks = []TradeHook{}
AddTradeHook(boil.AfterSelectHook, tradeAfterSelectHook)
if err = o.doAfterSelectHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterSelectHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o)
}
tradeAfterSelectHooks = []TradeHook{}
AddTradeHook(boil.BeforeUpdateHook, tradeBeforeUpdateHook)
if err = o.doBeforeUpdateHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o)
}
tradeBeforeUpdateHooks = []TradeHook{}
AddTradeHook(boil.AfterUpdateHook, tradeAfterUpdateHook)
if err = o.doAfterUpdateHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterUpdateHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o)
}
tradeAfterUpdateHooks = []TradeHook{}
AddTradeHook(boil.BeforeDeleteHook, tradeBeforeDeleteHook)
if err = o.doBeforeDeleteHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o)
}
tradeBeforeDeleteHooks = []TradeHook{}
AddTradeHook(boil.AfterDeleteHook, tradeAfterDeleteHook)
if err = o.doAfterDeleteHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterDeleteHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o)
}
tradeAfterDeleteHooks = []TradeHook{}
AddTradeHook(boil.BeforeUpsertHook, tradeBeforeUpsertHook)
if err = o.doBeforeUpsertHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o)
}
tradeBeforeUpsertHooks = []TradeHook{}
AddTradeHook(boil.AfterUpsertHook, tradeAfterUpsertHook)
if err = o.doAfterUpsertHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterUpsertHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o)
}
tradeAfterUpsertHooks = []TradeHook{}
}
func testTradesInsert(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
}
func testTradesInsertWhitelist(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Whitelist(tradeColumnsWithoutDefault...)); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
}
func testTradeToOneExchangeUsingExchangeName(t *testing.T) {
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
var local Trade
var foreign Exchange
seed := randomize.NewSeed()
if err := randomize.Struct(seed, &local, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if err := randomize.Struct(seed, &foreign, exchangeDBTypes, false, exchangeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Exchange struct: %s", err)
}
if err := foreign.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
local.ExchangeNameID = foreign.ID
if err := local.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
check, err := local.ExchangeName().One(ctx, tx)
if err != nil {
t.Fatal(err)
}
if check.ID != foreign.ID {
t.Errorf("want: %v, got %v", foreign.ID, check.ID)
}
slice := TradeSlice{&local}
if err = local.L.LoadExchangeName(ctx, tx, false, (*[]*Trade)(&slice), nil); err != nil {
t.Fatal(err)
}
if local.R.ExchangeName == nil {
t.Error("struct should have been eager loaded")
}
local.R.ExchangeName = nil
if err = local.L.LoadExchangeName(ctx, tx, true, &local, nil); err != nil {
t.Fatal(err)
}
if local.R.ExchangeName == nil {
t.Error("struct should have been eager loaded")
}
}
func testTradeToOneSetOpExchangeUsingExchangeName(t *testing.T) {
var err error
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
var a Trade
var b, c Exchange
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, tradeDBTypes, false, strmangle.SetComplement(tradePrimaryKeyColumns, tradeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &b, exchangeDBTypes, false, strmangle.SetComplement(exchangePrimaryKeyColumns, exchangeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &c, exchangeDBTypes, false, strmangle.SetComplement(exchangePrimaryKeyColumns, exchangeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
if err := a.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
if err = b.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
for i, x := range []*Exchange{&b, &c} {
err = a.SetExchangeName(ctx, tx, i != 0, x)
if err != nil {
t.Fatal(err)
}
if a.R.ExchangeName != x {
t.Error("relationship struct not set to correct value")
}
if x.R.ExchangeNameTrades[0] != &a {
t.Error("failed to append to foreign relationship struct")
}
if a.ExchangeNameID != x.ID {
t.Error("foreign key was wrong value", a.ExchangeNameID)
}
zero := reflect.Zero(reflect.TypeOf(a.ExchangeNameID))
reflect.Indirect(reflect.ValueOf(&a.ExchangeNameID)).Set(zero)
if err = a.Reload(ctx, tx); err != nil {
t.Fatal("failed to reload", err)
}
if a.ExchangeNameID != x.ID {
t.Error("foreign key was wrong value", a.ExchangeNameID, x.ID)
}
}
}
func testTradesReload(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if err = o.Reload(ctx, tx); err != nil {
t.Error(err)
}
}
func testTradesReloadAll(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
slice := TradeSlice{o}
if err = slice.ReloadAll(ctx, tx); err != nil {
t.Error(err)
}
}
func testTradesSelect(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
slice, err := Trades().All(ctx, tx)
if err != nil {
t.Error(err)
}
if len(slice) != 1 {
t.Error("want one record, got:", len(slice))
}
}
var (
tradeDBTypes = map[string]string{`ID`: `uuid`, `ExchangeNameID`: `uuid`, `Tid`: `character varying`, `Base`: `character varying`, `Quote`: `character varying`, `Asset`: `character varying`, `Price`: `double precision`, `Amount`: `double precision`, `Side`: `character varying`, `Timestamp`: `timestamp with time zone`}
_ = bytes.MinRead
)
func testTradesUpdate(t *testing.T) {
t.Parallel()
if 0 == len(tradePrimaryKeyColumns) {
t.Skip("Skipping table with no primary key columns")
}
if len(tradeAllColumns) == len(tradePrimaryKeyColumns) {
t.Skip("Skipping table with only primary key columns")
}
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradePrimaryKeyColumns...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("should only affect one row but affected", rowsAff)
}
}
func testTradesSliceUpdateAll(t *testing.T) {
t.Parallel()
if len(tradeAllColumns) == len(tradePrimaryKeyColumns) {
t.Skip("Skipping table with only primary key columns")
}
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradePrimaryKeyColumns...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
// Remove Primary keys and unique columns from what we plan to update
var fields []string
if strmangle.StringSliceMatch(tradeAllColumns, tradePrimaryKeyColumns) {
fields = tradeAllColumns
} else {
fields = strmangle.SetComplement(
tradeAllColumns,
tradePrimaryKeyColumns,
)
}
value := reflect.Indirect(reflect.ValueOf(o))
typ := reflect.TypeOf(o).Elem()
n := typ.NumField()
updateMap := M{}
for _, col := range fields {
for i := 0; i < n; i++ {
f := typ.Field(i)
if f.Tag.Get("boil") == col {
updateMap[col] = value.Field(i).Interface()
}
}
}
slice := TradeSlice{o}
if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("wanted one record updated but got", rowsAff)
}
}
func testTradesUpsert(t *testing.T) {
t.Parallel()
if len(tradeAllColumns) == len(tradePrimaryKeyColumns) {
t.Skip("Skipping table with only primary key columns")
}
seed := randomize.NewSeed()
var err error
// Attempt the INSERT side of an UPSERT
o := Trade{}
if err = randomize.Struct(seed, &o, tradeDBTypes, true); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Upsert(ctx, tx, false, nil, boil.Infer(), boil.Infer()); err != nil {
t.Errorf("Unable to upsert Trade: %s", err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
// Attempt the UPDATE side of an UPSERT
if err = randomize.Struct(seed, &o, tradeDBTypes, false, tradePrimaryKeyColumns...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if err = o.Upsert(ctx, tx, true, nil, boil.Infer(), boil.Infer()); err != nil {
t.Errorf("Unable to upsert Trade: %s", err)
}
count, err = Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
}

View File

@@ -17,6 +17,7 @@ func TestParent(t *testing.T) {
t.Run("Exchanges", testExchanges)
t.Run("Scripts", testScripts)
t.Run("ScriptExecutions", testScriptExecutions)
t.Run("Trades", testTrades)
t.Run("WithdrawalCryptos", testWithdrawalCryptos)
t.Run("WithdrawalFiats", testWithdrawalFiats)
t.Run("WithdrawalHistories", testWithdrawalHistories)
@@ -28,6 +29,7 @@ func TestDelete(t *testing.T) {
t.Run("Exchanges", testExchangesDelete)
t.Run("Scripts", testScriptsDelete)
t.Run("ScriptExecutions", testScriptExecutionsDelete)
t.Run("Trades", testTradesDelete)
t.Run("WithdrawalCryptos", testWithdrawalCryptosDelete)
t.Run("WithdrawalFiats", testWithdrawalFiatsDelete)
t.Run("WithdrawalHistories", testWithdrawalHistoriesDelete)
@@ -39,6 +41,7 @@ func TestQueryDeleteAll(t *testing.T) {
t.Run("Exchanges", testExchangesQueryDeleteAll)
t.Run("Scripts", testScriptsQueryDeleteAll)
t.Run("ScriptExecutions", testScriptExecutionsQueryDeleteAll)
t.Run("Trades", testTradesQueryDeleteAll)
t.Run("WithdrawalCryptos", testWithdrawalCryptosQueryDeleteAll)
t.Run("WithdrawalFiats", testWithdrawalFiatsQueryDeleteAll)
t.Run("WithdrawalHistories", testWithdrawalHistoriesQueryDeleteAll)
@@ -50,6 +53,7 @@ func TestSliceDeleteAll(t *testing.T) {
t.Run("Exchanges", testExchangesSliceDeleteAll)
t.Run("Scripts", testScriptsSliceDeleteAll)
t.Run("ScriptExecutions", testScriptExecutionsSliceDeleteAll)
t.Run("Trades", testTradesSliceDeleteAll)
t.Run("WithdrawalCryptos", testWithdrawalCryptosSliceDeleteAll)
t.Run("WithdrawalFiats", testWithdrawalFiatsSliceDeleteAll)
t.Run("WithdrawalHistories", testWithdrawalHistoriesSliceDeleteAll)
@@ -61,6 +65,7 @@ func TestExists(t *testing.T) {
t.Run("Exchanges", testExchangesExists)
t.Run("Scripts", testScriptsExists)
t.Run("ScriptExecutions", testScriptExecutionsExists)
t.Run("Trades", testTradesExists)
t.Run("WithdrawalCryptos", testWithdrawalCryptosExists)
t.Run("WithdrawalFiats", testWithdrawalFiatsExists)
t.Run("WithdrawalHistories", testWithdrawalHistoriesExists)
@@ -72,6 +77,7 @@ func TestFind(t *testing.T) {
t.Run("Exchanges", testExchangesFind)
t.Run("Scripts", testScriptsFind)
t.Run("ScriptExecutions", testScriptExecutionsFind)
t.Run("Trades", testTradesFind)
t.Run("WithdrawalCryptos", testWithdrawalCryptosFind)
t.Run("WithdrawalFiats", testWithdrawalFiatsFind)
t.Run("WithdrawalHistories", testWithdrawalHistoriesFind)
@@ -83,6 +89,7 @@ func TestBind(t *testing.T) {
t.Run("Exchanges", testExchangesBind)
t.Run("Scripts", testScriptsBind)
t.Run("ScriptExecutions", testScriptExecutionsBind)
t.Run("Trades", testTradesBind)
t.Run("WithdrawalCryptos", testWithdrawalCryptosBind)
t.Run("WithdrawalFiats", testWithdrawalFiatsBind)
t.Run("WithdrawalHistories", testWithdrawalHistoriesBind)
@@ -94,6 +101,7 @@ func TestOne(t *testing.T) {
t.Run("Exchanges", testExchangesOne)
t.Run("Scripts", testScriptsOne)
t.Run("ScriptExecutions", testScriptExecutionsOne)
t.Run("Trades", testTradesOne)
t.Run("WithdrawalCryptos", testWithdrawalCryptosOne)
t.Run("WithdrawalFiats", testWithdrawalFiatsOne)
t.Run("WithdrawalHistories", testWithdrawalHistoriesOne)
@@ -105,6 +113,7 @@ func TestAll(t *testing.T) {
t.Run("Exchanges", testExchangesAll)
t.Run("Scripts", testScriptsAll)
t.Run("ScriptExecutions", testScriptExecutionsAll)
t.Run("Trades", testTradesAll)
t.Run("WithdrawalCryptos", testWithdrawalCryptosAll)
t.Run("WithdrawalFiats", testWithdrawalFiatsAll)
t.Run("WithdrawalHistories", testWithdrawalHistoriesAll)
@@ -116,6 +125,7 @@ func TestCount(t *testing.T) {
t.Run("Exchanges", testExchangesCount)
t.Run("Scripts", testScriptsCount)
t.Run("ScriptExecutions", testScriptExecutionsCount)
t.Run("Trades", testTradesCount)
t.Run("WithdrawalCryptos", testWithdrawalCryptosCount)
t.Run("WithdrawalFiats", testWithdrawalFiatsCount)
t.Run("WithdrawalHistories", testWithdrawalHistoriesCount)
@@ -127,6 +137,7 @@ func TestHooks(t *testing.T) {
t.Run("Exchanges", testExchangesHooks)
t.Run("Scripts", testScriptsHooks)
t.Run("ScriptExecutions", testScriptExecutionsHooks)
t.Run("Trades", testTradesHooks)
t.Run("WithdrawalCryptos", testWithdrawalCryptosHooks)
t.Run("WithdrawalFiats", testWithdrawalFiatsHooks)
t.Run("WithdrawalHistories", testWithdrawalHistoriesHooks)
@@ -143,6 +154,8 @@ func TestInsert(t *testing.T) {
t.Run("Scripts", testScriptsInsertWhitelist)
t.Run("ScriptExecutions", testScriptExecutionsInsert)
t.Run("ScriptExecutions", testScriptExecutionsInsertWhitelist)
t.Run("Trades", testTradesInsert)
t.Run("Trades", testTradesInsertWhitelist)
t.Run("WithdrawalCryptos", testWithdrawalCryptosInsert)
t.Run("WithdrawalCryptos", testWithdrawalCryptosInsertWhitelist)
t.Run("WithdrawalFiats", testWithdrawalFiatsInsert)
@@ -156,6 +169,7 @@ func TestInsert(t *testing.T) {
func TestToOne(t *testing.T) {
t.Run("CandleToExchangeUsingExchangeName", testCandleToOneExchangeUsingExchangeName)
t.Run("ScriptExecutionToScriptUsingScript", testScriptExecutionToOneScriptUsingScript)
t.Run("TradeToExchangeUsingExchangeName", testTradeToOneExchangeUsingExchangeName)
t.Run("WithdrawalCryptoToWithdrawalHistoryUsingWithdrawalHistory", testWithdrawalCryptoToOneWithdrawalHistoryUsingWithdrawalHistory)
t.Run("WithdrawalFiatToWithdrawalHistoryUsingWithdrawalHistory", testWithdrawalFiatToOneWithdrawalHistoryUsingWithdrawalHistory)
t.Run("WithdrawalHistoryToExchangeUsingExchangeName", testWithdrawalHistoryToOneExchangeUsingExchangeName)
@@ -165,6 +179,7 @@ func TestToOne(t *testing.T) {
// or deadlocks can occur.
func TestOneToOne(t *testing.T) {
t.Run("ExchangeToCandleUsingExchangeNameCandle", testExchangeOneToOneCandleUsingExchangeNameCandle)
t.Run("ExchangeToTradeUsingExchangeNameTrade", testExchangeOneToOneTradeUsingExchangeNameTrade)
}
// TestToMany tests cannot be run in parallel
@@ -181,6 +196,7 @@ func TestToMany(t *testing.T) {
func TestToOneSet(t *testing.T) {
t.Run("CandleToExchangeUsingExchangeNameCandle", testCandleToOneSetOpExchangeUsingExchangeName)
t.Run("ScriptExecutionToScriptUsingScriptExecutions", testScriptExecutionToOneSetOpScriptUsingScript)
t.Run("TradeToExchangeUsingExchangeNameTrade", testTradeToOneSetOpExchangeUsingExchangeName)
t.Run("WithdrawalCryptoToWithdrawalHistoryUsingWithdrawalCryptos", testWithdrawalCryptoToOneSetOpWithdrawalHistoryUsingWithdrawalHistory)
t.Run("WithdrawalFiatToWithdrawalHistoryUsingWithdrawalFiats", testWithdrawalFiatToOneSetOpWithdrawalHistoryUsingWithdrawalHistory)
t.Run("WithdrawalHistoryToExchangeUsingExchangeNameWithdrawalHistories", testWithdrawalHistoryToOneSetOpExchangeUsingExchangeName)
@@ -194,6 +210,7 @@ func TestToOneRemove(t *testing.T) {}
// or deadlocks can occur.
func TestOneToOneSet(t *testing.T) {
t.Run("ExchangeToCandleUsingExchangeNameCandle", testExchangeOneToOneSetOpCandleUsingExchangeNameCandle)
t.Run("ExchangeToTradeUsingExchangeNameTrade", testExchangeOneToOneSetOpTradeUsingExchangeNameTrade)
}
// TestOneToOneRemove tests cannot be run in parallel
@@ -223,6 +240,7 @@ func TestReload(t *testing.T) {
t.Run("Exchanges", testExchangesReload)
t.Run("Scripts", testScriptsReload)
t.Run("ScriptExecutions", testScriptExecutionsReload)
t.Run("Trades", testTradesReload)
t.Run("WithdrawalCryptos", testWithdrawalCryptosReload)
t.Run("WithdrawalFiats", testWithdrawalFiatsReload)
t.Run("WithdrawalHistories", testWithdrawalHistoriesReload)
@@ -234,6 +252,7 @@ func TestReloadAll(t *testing.T) {
t.Run("Exchanges", testExchangesReloadAll)
t.Run("Scripts", testScriptsReloadAll)
t.Run("ScriptExecutions", testScriptExecutionsReloadAll)
t.Run("Trades", testTradesReloadAll)
t.Run("WithdrawalCryptos", testWithdrawalCryptosReloadAll)
t.Run("WithdrawalFiats", testWithdrawalFiatsReloadAll)
t.Run("WithdrawalHistories", testWithdrawalHistoriesReloadAll)
@@ -245,6 +264,7 @@ func TestSelect(t *testing.T) {
t.Run("Exchanges", testExchangesSelect)
t.Run("Scripts", testScriptsSelect)
t.Run("ScriptExecutions", testScriptExecutionsSelect)
t.Run("Trades", testTradesSelect)
t.Run("WithdrawalCryptos", testWithdrawalCryptosSelect)
t.Run("WithdrawalFiats", testWithdrawalFiatsSelect)
t.Run("WithdrawalHistories", testWithdrawalHistoriesSelect)
@@ -256,6 +276,7 @@ func TestUpdate(t *testing.T) {
t.Run("Exchanges", testExchangesUpdate)
t.Run("Scripts", testScriptsUpdate)
t.Run("ScriptExecutions", testScriptExecutionsUpdate)
t.Run("Trades", testTradesUpdate)
t.Run("WithdrawalCryptos", testWithdrawalCryptosUpdate)
t.Run("WithdrawalFiats", testWithdrawalFiatsUpdate)
t.Run("WithdrawalHistories", testWithdrawalHistoriesUpdate)
@@ -267,6 +288,7 @@ func TestSliceUpdateAll(t *testing.T) {
t.Run("Exchanges", testExchangesSliceUpdateAll)
t.Run("Scripts", testScriptsSliceUpdateAll)
t.Run("ScriptExecutions", testScriptExecutionsSliceUpdateAll)
t.Run("Trades", testTradesSliceUpdateAll)
t.Run("WithdrawalCryptos", testWithdrawalCryptosSliceUpdateAll)
t.Run("WithdrawalFiats", testWithdrawalFiatsSliceUpdateAll)
t.Run("WithdrawalHistories", testWithdrawalHistoriesSliceUpdateAll)

View File

@@ -7,8 +7,10 @@ var TableNames = struct {
AuditEvent string
Candle string
Exchange string
GooseDBVersion string
Script string
ScriptExecution string
Trade string
WithdrawalCrypto string
WithdrawalFiat string
WithdrawalHistory string
@@ -16,8 +18,10 @@ var TableNames = struct {
AuditEvent: "audit_event",
Candle: "candle",
Exchange: "exchange",
GooseDBVersion: "goose_db_version",
Script: "script",
ScriptExecution: "script_execution",
Trade: "trade",
WithdrawalCrypto: "withdrawal_crypto",
WithdrawalFiat: "withdrawal_fiat",
WithdrawalHistory: "withdrawal_history",

View File

@@ -50,15 +50,18 @@ var ExchangeWhere = struct {
// ExchangeRels is where relationship names are stored.
var ExchangeRels = struct {
ExchangeNameCandle string
ExchangeNameTrade string
ExchangeNameWithdrawalHistories string
}{
ExchangeNameCandle: "ExchangeNameCandle",
ExchangeNameTrade: "ExchangeNameTrade",
ExchangeNameWithdrawalHistories: "ExchangeNameWithdrawalHistories",
}
// exchangeR is where relationships are stored.
type exchangeR struct {
ExchangeNameCandle *Candle
ExchangeNameTrade *Trade
ExchangeNameWithdrawalHistories WithdrawalHistorySlice
}
@@ -366,6 +369,20 @@ func (o *Exchange) ExchangeNameCandle(mods ...qm.QueryMod) candleQuery {
return query
}
// ExchangeNameTrade pointed to by the foreign key.
func (o *Exchange) ExchangeNameTrade(mods ...qm.QueryMod) tradeQuery {
queryMods := []qm.QueryMod{
qm.Where("\"exchange_name_id\" = ?", o.ID),
}
queryMods = append(queryMods, mods...)
query := Trades(queryMods...)
queries.SetFrom(query.Query, "\"trade\"")
return query
}
// ExchangeNameWithdrawalHistories retrieves all the withdrawal_history's WithdrawalHistories with an executor via exchange_name_id column.
func (o *Exchange) ExchangeNameWithdrawalHistories(mods ...qm.QueryMod) withdrawalHistoryQuery {
var queryMods []qm.QueryMod
@@ -485,6 +502,104 @@ func (exchangeL) LoadExchangeNameCandle(ctx context.Context, e boil.ContextExecu
return nil
}
// LoadExchangeNameTrade allows an eager lookup of values, cached into the
// loaded structs of the objects. This is for a 1-1 relationship.
func (exchangeL) LoadExchangeNameTrade(ctx context.Context, e boil.ContextExecutor, singular bool, maybeExchange interface{}, mods queries.Applicator) error {
var slice []*Exchange
var object *Exchange
if singular {
object = maybeExchange.(*Exchange)
} else {
slice = *maybeExchange.(*[]*Exchange)
}
args := make([]interface{}, 0, 1)
if singular {
if object.R == nil {
object.R = &exchangeR{}
}
args = append(args, object.ID)
} else {
Outer:
for _, obj := range slice {
if obj.R == nil {
obj.R = &exchangeR{}
}
for _, a := range args {
if a == obj.ID {
continue Outer
}
}
args = append(args, obj.ID)
}
}
if len(args) == 0 {
return nil
}
query := NewQuery(qm.From(`trade`), qm.WhereIn(`trade.exchange_name_id in ?`, args...))
if mods != nil {
mods.Apply(query)
}
results, err := query.QueryContext(ctx, e)
if err != nil {
return errors.Wrap(err, "failed to eager load Trade")
}
var resultSlice []*Trade
if err = queries.Bind(results, &resultSlice); err != nil {
return errors.Wrap(err, "failed to bind eager loaded slice Trade")
}
if err = results.Close(); err != nil {
return errors.Wrap(err, "failed to close results of eager load for trade")
}
if err = results.Err(); err != nil {
return errors.Wrap(err, "error occurred during iteration of eager loaded relations for trade")
}
if len(exchangeAfterSelectHooks) != 0 {
for _, obj := range resultSlice {
if err := obj.doAfterSelectHooks(ctx, e); err != nil {
return err
}
}
}
if len(resultSlice) == 0 {
return nil
}
if singular {
foreign := resultSlice[0]
object.R.ExchangeNameTrade = foreign
if foreign.R == nil {
foreign.R = &tradeR{}
}
foreign.R.ExchangeName = object
}
for _, local := range slice {
for _, foreign := range resultSlice {
if local.ID == foreign.ExchangeNameID {
local.R.ExchangeNameTrade = foreign
if foreign.R == nil {
foreign.R = &tradeR{}
}
foreign.R.ExchangeName = local
break
}
}
}
return nil
}
// LoadExchangeNameWithdrawalHistories allows an eager lookup of values, cached into the
// loaded structs of the objects. This is for a 1-M or N-M relationship.
func (exchangeL) LoadExchangeNameWithdrawalHistories(ctx context.Context, e boil.ContextExecutor, singular bool, maybeExchange interface{}, mods queries.Applicator) error {
@@ -631,6 +746,57 @@ func (o *Exchange) SetExchangeNameCandle(ctx context.Context, exec boil.ContextE
return nil
}
// SetExchangeNameTrade of the exchange to the related item.
// Sets o.R.ExchangeNameTrade to related.
// Adds o to related.R.ExchangeName.
func (o *Exchange) SetExchangeNameTrade(ctx context.Context, exec boil.ContextExecutor, insert bool, related *Trade) error {
var err error
if insert {
related.ExchangeNameID = o.ID
if err = related.Insert(ctx, exec, boil.Infer()); err != nil {
return errors.Wrap(err, "failed to insert into foreign table")
}
} else {
updateQuery := fmt.Sprintf(
"UPDATE \"trade\" SET %s WHERE %s",
strmangle.SetParamNames("\"", "\"", 0, []string{"exchange_name_id"}),
strmangle.WhereClause("\"", "\"", 0, tradePrimaryKeyColumns),
)
values := []interface{}{o.ID, related.ID}
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, updateQuery)
fmt.Fprintln(boil.DebugWriter, values)
}
if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil {
return errors.Wrap(err, "failed to update foreign table")
}
related.ExchangeNameID = o.ID
}
if o.R == nil {
o.R = &exchangeR{
ExchangeNameTrade: related,
}
} else {
o.R.ExchangeNameTrade = related
}
if related.R == nil {
related.R = &tradeR{
ExchangeName: o,
}
} else {
related.R.ExchangeName = o
}
return nil
}
// AddExchangeNameWithdrawalHistories adds the given related objects to the existing relationships
// of the exchange, optionally inserting them as new records.
// Appends related to o.R.ExchangeNameWithdrawalHistories.

View File

@@ -545,6 +545,57 @@ func testExchangeOneToOneCandleUsingExchangeNameCandle(t *testing.T) {
}
}
func testExchangeOneToOneTradeUsingExchangeNameTrade(t *testing.T) {
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
var foreign Trade
var local Exchange
seed := randomize.NewSeed()
if err := randomize.Struct(seed, &foreign, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if err := randomize.Struct(seed, &local, exchangeDBTypes, true, exchangeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Exchange struct: %s", err)
}
if err := local.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
foreign.ExchangeNameID = local.ID
if err := foreign.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
check, err := local.ExchangeNameTrade().One(ctx, tx)
if err != nil {
t.Fatal(err)
}
if check.ExchangeNameID != foreign.ExchangeNameID {
t.Errorf("want: %v, got %v", foreign.ExchangeNameID, check.ExchangeNameID)
}
slice := ExchangeSlice{&local}
if err = local.L.LoadExchangeNameTrade(ctx, tx, false, (*[]*Exchange)(&slice), nil); err != nil {
t.Fatal(err)
}
if local.R.ExchangeNameTrade == nil {
t.Error("struct should have been eager loaded")
}
local.R.ExchangeNameTrade = nil
if err = local.L.LoadExchangeNameTrade(ctx, tx, true, &local, nil); err != nil {
t.Fatal(err)
}
if local.R.ExchangeNameTrade == nil {
t.Error("struct should have been eager loaded")
}
}
func testExchangeOneToOneSetOpCandleUsingExchangeNameCandle(t *testing.T) {
var err error
@@ -606,6 +657,67 @@ func testExchangeOneToOneSetOpCandleUsingExchangeNameCandle(t *testing.T) {
}
}
}
func testExchangeOneToOneSetOpTradeUsingExchangeNameTrade(t *testing.T) {
var err error
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
var a Exchange
var b, c Trade
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, exchangeDBTypes, false, strmangle.SetComplement(exchangePrimaryKeyColumns, exchangeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &b, tradeDBTypes, false, strmangle.SetComplement(tradePrimaryKeyColumns, tradeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &c, tradeDBTypes, false, strmangle.SetComplement(tradePrimaryKeyColumns, tradeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
if err := a.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
if err = b.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
for i, x := range []*Trade{&b, &c} {
err = a.SetExchangeNameTrade(ctx, tx, i != 0, x)
if err != nil {
t.Fatal(err)
}
if a.R.ExchangeNameTrade != x {
t.Error("relationship struct not set to correct value")
}
if x.R.ExchangeName != &a {
t.Error("failed to append to foreign relationship struct")
}
if a.ID != x.ExchangeNameID {
t.Error("foreign key was wrong value", a.ID)
}
zero := reflect.Zero(reflect.TypeOf(x.ExchangeNameID))
reflect.Indirect(reflect.ValueOf(&x.ExchangeNameID)).Set(zero)
if err = x.Reload(ctx, tx); err != nil {
t.Fatal("failed to reload", err)
}
if a.ID != x.ExchangeNameID {
t.Error("foreign key was wrong value", a.ID, x.ExchangeNameID)
}
if _, err = x.Delete(ctx, tx); err != nil {
t.Fatal("failed to delete x", err)
}
}
}
func testExchangeToManyExchangeNameWithdrawalHistories(t *testing.T) {
var err error

View File

@@ -0,0 +1,994 @@
// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package sqlite3
import (
"context"
"database/sql"
"fmt"
"reflect"
"strings"
"sync"
"time"
"github.com/pkg/errors"
"github.com/thrasher-corp/sqlboiler/boil"
"github.com/thrasher-corp/sqlboiler/queries"
"github.com/thrasher-corp/sqlboiler/queries/qm"
"github.com/thrasher-corp/sqlboiler/queries/qmhelper"
"github.com/thrasher-corp/sqlboiler/strmangle"
"github.com/volatiletech/null"
)
// Trade is an object representing the database table.
type Trade struct {
ID string `boil:"id" json:"id" toml:"id" yaml:"id"`
ExchangeNameID string `boil:"exchange_name_id" json:"exchange_name_id" toml:"exchange_name_id" yaml:"exchange_name_id"`
Tid null.String `boil:"tid" json:"tid,omitempty" toml:"tid" yaml:"tid,omitempty"`
Base string `boil:"base" json:"base" toml:"base" yaml:"base"`
Quote string `boil:"quote" json:"quote" toml:"quote" yaml:"quote"`
Asset string `boil:"asset" json:"asset" toml:"asset" yaml:"asset"`
Price float64 `boil:"price" json:"price" toml:"price" yaml:"price"`
Amount float64 `boil:"amount" json:"amount" toml:"amount" yaml:"amount"`
Side null.String `boil:"side" json:"side,omitempty" toml:"side" yaml:"side,omitempty"`
Timestamp string `boil:"timestamp" json:"timestamp" toml:"timestamp" yaml:"timestamp"`
R *tradeR `boil:"-" json:"-" toml:"-" yaml:"-"`
L tradeL `boil:"-" json:"-" toml:"-" yaml:"-"`
}
var TradeColumns = struct {
ID string
ExchangeNameID string
Tid string
Base string
Quote string
Asset string
Price string
Amount string
Side string
Timestamp string
}{
ID: "id",
ExchangeNameID: "exchange_name_id",
Tid: "tid",
Base: "base",
Quote: "quote",
Asset: "asset",
Price: "price",
Amount: "amount",
Side: "side",
Timestamp: "timestamp",
}
// Generated where
type whereHelpernull_String struct{ field string }
func (w whereHelpernull_String) EQ(x null.String) qm.QueryMod {
return qmhelper.WhereNullEQ(w.field, false, x)
}
func (w whereHelpernull_String) NEQ(x null.String) qm.QueryMod {
return qmhelper.WhereNullEQ(w.field, true, x)
}
func (w whereHelpernull_String) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) }
func (w whereHelpernull_String) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) }
func (w whereHelpernull_String) LT(x null.String) qm.QueryMod {
return qmhelper.Where(w.field, qmhelper.LT, x)
}
func (w whereHelpernull_String) LTE(x null.String) qm.QueryMod {
return qmhelper.Where(w.field, qmhelper.LTE, x)
}
func (w whereHelpernull_String) GT(x null.String) qm.QueryMod {
return qmhelper.Where(w.field, qmhelper.GT, x)
}
func (w whereHelpernull_String) GTE(x null.String) qm.QueryMod {
return qmhelper.Where(w.field, qmhelper.GTE, x)
}
var TradeWhere = struct {
ID whereHelperstring
ExchangeNameID whereHelperstring
Tid whereHelpernull_String
Base whereHelperstring
Quote whereHelperstring
Asset whereHelperstring
Price whereHelperfloat64
Amount whereHelperfloat64
Side whereHelpernull_String
Timestamp whereHelperstring
}{
ID: whereHelperstring{field: "\"trade\".\"id\""},
ExchangeNameID: whereHelperstring{field: "\"trade\".\"exchange_name_id\""},
Tid: whereHelpernull_String{field: "\"trade\".\"tid\""},
Base: whereHelperstring{field: "\"trade\".\"base\""},
Quote: whereHelperstring{field: "\"trade\".\"quote\""},
Asset: whereHelperstring{field: "\"trade\".\"asset\""},
Price: whereHelperfloat64{field: "\"trade\".\"price\""},
Amount: whereHelperfloat64{field: "\"trade\".\"amount\""},
Side: whereHelpernull_String{field: "\"trade\".\"side\""},
Timestamp: whereHelperstring{field: "\"trade\".\"timestamp\""},
}
// TradeRels is where relationship names are stored.
var TradeRels = struct {
ExchangeName string
}{
ExchangeName: "ExchangeName",
}
// tradeR is where relationships are stored.
type tradeR struct {
ExchangeName *Exchange
}
// NewStruct creates a new relationship struct
func (*tradeR) NewStruct() *tradeR {
return &tradeR{}
}
// tradeL is where Load methods for each relationship are stored.
type tradeL struct{}
var (
tradeAllColumns = []string{"id", "exchange_name_id", "tid", "base", "quote", "asset", "price", "amount", "side", "timestamp"}
tradeColumnsWithoutDefault = []string{"id", "exchange_name_id", "tid", "base", "quote", "asset", "price", "amount", "side", "timestamp"}
tradeColumnsWithDefault = []string{}
tradePrimaryKeyColumns = []string{"id"}
)
type (
// TradeSlice is an alias for a slice of pointers to Trade.
// This should generally be used opposed to []Trade.
TradeSlice []*Trade
// TradeHook is the signature for custom Trade hook methods
TradeHook func(context.Context, boil.ContextExecutor, *Trade) error
tradeQuery struct {
*queries.Query
}
)
// Cache for insert, update and upsert
var (
tradeType = reflect.TypeOf(&Trade{})
tradeMapping = queries.MakeStructMapping(tradeType)
tradePrimaryKeyMapping, _ = queries.BindMapping(tradeType, tradeMapping, tradePrimaryKeyColumns)
tradeInsertCacheMut sync.RWMutex
tradeInsertCache = make(map[string]insertCache)
tradeUpdateCacheMut sync.RWMutex
tradeUpdateCache = make(map[string]updateCache)
tradeUpsertCacheMut sync.RWMutex
tradeUpsertCache = make(map[string]insertCache)
)
var (
// Force time package dependency for automated UpdatedAt/CreatedAt.
_ = time.Second
// Force qmhelper dependency for where clause generation (which doesn't
// always happen)
_ = qmhelper.Where
)
var tradeBeforeInsertHooks []TradeHook
var tradeBeforeUpdateHooks []TradeHook
var tradeBeforeDeleteHooks []TradeHook
var tradeBeforeUpsertHooks []TradeHook
var tradeAfterInsertHooks []TradeHook
var tradeAfterSelectHooks []TradeHook
var tradeAfterUpdateHooks []TradeHook
var tradeAfterDeleteHooks []TradeHook
var tradeAfterUpsertHooks []TradeHook
// doBeforeInsertHooks executes all "before insert" hooks.
func (o *Trade) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range tradeBeforeInsertHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doBeforeUpdateHooks executes all "before Update" hooks.
func (o *Trade) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range tradeBeforeUpdateHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doBeforeDeleteHooks executes all "before Delete" hooks.
func (o *Trade) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range tradeBeforeDeleteHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doBeforeUpsertHooks executes all "before Upsert" hooks.
func (o *Trade) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range tradeBeforeUpsertHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterInsertHooks executes all "after Insert" hooks.
func (o *Trade) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range tradeAfterInsertHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterSelectHooks executes all "after Select" hooks.
func (o *Trade) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range tradeAfterSelectHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterUpdateHooks executes all "after Update" hooks.
func (o *Trade) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range tradeAfterUpdateHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterDeleteHooks executes all "after Delete" hooks.
func (o *Trade) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range tradeAfterDeleteHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterUpsertHooks executes all "after Upsert" hooks.
func (o *Trade) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range tradeAfterUpsertHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// AddTradeHook registers your hook function for all future operations.
func AddTradeHook(hookPoint boil.HookPoint, tradeHook TradeHook) {
switch hookPoint {
case boil.BeforeInsertHook:
tradeBeforeInsertHooks = append(tradeBeforeInsertHooks, tradeHook)
case boil.BeforeUpdateHook:
tradeBeforeUpdateHooks = append(tradeBeforeUpdateHooks, tradeHook)
case boil.BeforeDeleteHook:
tradeBeforeDeleteHooks = append(tradeBeforeDeleteHooks, tradeHook)
case boil.BeforeUpsertHook:
tradeBeforeUpsertHooks = append(tradeBeforeUpsertHooks, tradeHook)
case boil.AfterInsertHook:
tradeAfterInsertHooks = append(tradeAfterInsertHooks, tradeHook)
case boil.AfterSelectHook:
tradeAfterSelectHooks = append(tradeAfterSelectHooks, tradeHook)
case boil.AfterUpdateHook:
tradeAfterUpdateHooks = append(tradeAfterUpdateHooks, tradeHook)
case boil.AfterDeleteHook:
tradeAfterDeleteHooks = append(tradeAfterDeleteHooks, tradeHook)
case boil.AfterUpsertHook:
tradeAfterUpsertHooks = append(tradeAfterUpsertHooks, tradeHook)
}
}
// One returns a single trade record from the query.
func (q tradeQuery) One(ctx context.Context, exec boil.ContextExecutor) (*Trade, error) {
o := &Trade{}
queries.SetLimit(q.Query, 1)
err := q.Bind(ctx, exec, o)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, sql.ErrNoRows
}
return nil, errors.Wrap(err, "sqlite3: failed to execute a one query for trade")
}
if err := o.doAfterSelectHooks(ctx, exec); err != nil {
return o, err
}
return o, nil
}
// All returns all Trade records from the query.
func (q tradeQuery) All(ctx context.Context, exec boil.ContextExecutor) (TradeSlice, error) {
var o []*Trade
err := q.Bind(ctx, exec, &o)
if err != nil {
return nil, errors.Wrap(err, "sqlite3: failed to assign all query results to Trade slice")
}
if len(tradeAfterSelectHooks) != 0 {
for _, obj := range o {
if err := obj.doAfterSelectHooks(ctx, exec); err != nil {
return o, err
}
}
}
return o, nil
}
// Count returns the count of all Trade records in the query.
func (q tradeQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) {
var count int64
queries.SetSelect(q.Query, nil)
queries.SetCount(q.Query)
err := q.Query.QueryRowContext(ctx, exec).Scan(&count)
if err != nil {
return 0, errors.Wrap(err, "sqlite3: failed to count trade rows")
}
return count, nil
}
// Exists checks if the row exists in the table.
func (q tradeQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) {
var count int64
queries.SetSelect(q.Query, nil)
queries.SetCount(q.Query)
queries.SetLimit(q.Query, 1)
err := q.Query.QueryRowContext(ctx, exec).Scan(&count)
if err != nil {
return false, errors.Wrap(err, "sqlite3: failed to check if trade exists")
}
return count > 0, nil
}
// ExchangeName pointed to by the foreign key.
func (o *Trade) ExchangeName(mods ...qm.QueryMod) exchangeQuery {
queryMods := []qm.QueryMod{
qm.Where("\"id\" = ?", o.ExchangeNameID),
}
queryMods = append(queryMods, mods...)
query := Exchanges(queryMods...)
queries.SetFrom(query.Query, "\"exchange\"")
return query
}
// LoadExchangeName allows an eager lookup of values, cached into the
// loaded structs of the objects. This is for an N-1 relationship.
func (tradeL) LoadExchangeName(ctx context.Context, e boil.ContextExecutor, singular bool, maybeTrade interface{}, mods queries.Applicator) error {
var slice []*Trade
var object *Trade
if singular {
object = maybeTrade.(*Trade)
} else {
slice = *maybeTrade.(*[]*Trade)
}
args := make([]interface{}, 0, 1)
if singular {
if object.R == nil {
object.R = &tradeR{}
}
args = append(args, object.ExchangeNameID)
} else {
Outer:
for _, obj := range slice {
if obj.R == nil {
obj.R = &tradeR{}
}
for _, a := range args {
if a == obj.ExchangeNameID {
continue Outer
}
}
args = append(args, obj.ExchangeNameID)
}
}
if len(args) == 0 {
return nil
}
query := NewQuery(qm.From(`exchange`), qm.WhereIn(`exchange.id in ?`, args...))
if mods != nil {
mods.Apply(query)
}
results, err := query.QueryContext(ctx, e)
if err != nil {
return errors.Wrap(err, "failed to eager load Exchange")
}
var resultSlice []*Exchange
if err = queries.Bind(results, &resultSlice); err != nil {
return errors.Wrap(err, "failed to bind eager loaded slice Exchange")
}
if err = results.Close(); err != nil {
return errors.Wrap(err, "failed to close results of eager load for exchange")
}
if err = results.Err(); err != nil {
return errors.Wrap(err, "error occurred during iteration of eager loaded relations for exchange")
}
if len(tradeAfterSelectHooks) != 0 {
for _, obj := range resultSlice {
if err := obj.doAfterSelectHooks(ctx, e); err != nil {
return err
}
}
}
if len(resultSlice) == 0 {
return nil
}
if singular {
foreign := resultSlice[0]
object.R.ExchangeName = foreign
if foreign.R == nil {
foreign.R = &exchangeR{}
}
foreign.R.ExchangeNameTrade = object
return nil
}
for _, local := range slice {
for _, foreign := range resultSlice {
if local.ExchangeNameID == foreign.ID {
local.R.ExchangeName = foreign
if foreign.R == nil {
foreign.R = &exchangeR{}
}
foreign.R.ExchangeNameTrade = local
break
}
}
}
return nil
}
// SetExchangeName of the trade to the related item.
// Sets o.R.ExchangeName to related.
// Adds o to related.R.ExchangeNameTrade.
func (o *Trade) SetExchangeName(ctx context.Context, exec boil.ContextExecutor, insert bool, related *Exchange) error {
var err error
if insert {
if err = related.Insert(ctx, exec, boil.Infer()); err != nil {
return errors.Wrap(err, "failed to insert into foreign table")
}
}
updateQuery := fmt.Sprintf(
"UPDATE \"trade\" SET %s WHERE %s",
strmangle.SetParamNames("\"", "\"", 0, []string{"exchange_name_id"}),
strmangle.WhereClause("\"", "\"", 0, tradePrimaryKeyColumns),
)
values := []interface{}{related.ID, o.ID}
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, updateQuery)
fmt.Fprintln(boil.DebugWriter, values)
}
if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil {
return errors.Wrap(err, "failed to update local table")
}
o.ExchangeNameID = related.ID
if o.R == nil {
o.R = &tradeR{
ExchangeName: related,
}
} else {
o.R.ExchangeName = related
}
if related.R == nil {
related.R = &exchangeR{
ExchangeNameTrade: o,
}
} else {
related.R.ExchangeNameTrade = o
}
return nil
}
// Trades retrieves all the records using an executor.
func Trades(mods ...qm.QueryMod) tradeQuery {
mods = append(mods, qm.From("\"trade\""))
return tradeQuery{NewQuery(mods...)}
}
// FindTrade retrieves a single record by ID with an executor.
// If selectCols is empty Find will return all columns.
func FindTrade(ctx context.Context, exec boil.ContextExecutor, iD string, selectCols ...string) (*Trade, error) {
tradeObj := &Trade{}
sel := "*"
if len(selectCols) > 0 {
sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",")
}
query := fmt.Sprintf(
"select %s from \"trade\" where \"id\"=?", sel,
)
q := queries.Raw(query, iD)
err := q.Bind(ctx, exec, tradeObj)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, sql.ErrNoRows
}
return nil, errors.Wrap(err, "sqlite3: unable to select from trade")
}
return tradeObj, nil
}
// Insert a single record using an executor.
// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts.
func (o *Trade) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error {
if o == nil {
return errors.New("sqlite3: no trade provided for insertion")
}
var err error
if err := o.doBeforeInsertHooks(ctx, exec); err != nil {
return err
}
nzDefaults := queries.NonZeroDefaultSet(tradeColumnsWithDefault, o)
key := makeCacheKey(columns, nzDefaults)
tradeInsertCacheMut.RLock()
cache, cached := tradeInsertCache[key]
tradeInsertCacheMut.RUnlock()
if !cached {
wl, returnColumns := columns.InsertColumnSet(
tradeAllColumns,
tradeColumnsWithDefault,
tradeColumnsWithoutDefault,
nzDefaults,
)
cache.valueMapping, err = queries.BindMapping(tradeType, tradeMapping, wl)
if err != nil {
return err
}
cache.retMapping, err = queries.BindMapping(tradeType, tradeMapping, returnColumns)
if err != nil {
return err
}
if len(wl) != 0 {
cache.query = fmt.Sprintf("INSERT INTO \"trade\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1))
} else {
cache.query = "INSERT INTO \"trade\" () VALUES ()%s%s"
}
var queryOutput, queryReturning string
if len(cache.retMapping) != 0 {
cache.retQuery = fmt.Sprintf("SELECT \"%s\" FROM \"trade\" WHERE %s", strings.Join(returnColumns, "\",\""), strmangle.WhereClause("\"", "\"", 0, tradePrimaryKeyColumns))
}
cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning)
}
value := reflect.Indirect(reflect.ValueOf(o))
vals := queries.ValuesFromMapping(value, cache.valueMapping)
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, cache.query)
fmt.Fprintln(boil.DebugWriter, vals)
}
_, err = exec.ExecContext(ctx, cache.query, vals...)
if err != nil {
return errors.Wrap(err, "sqlite3: unable to insert into trade")
}
var identifierCols []interface{}
if len(cache.retMapping) == 0 {
goto CacheNoHooks
}
identifierCols = []interface{}{
o.ID,
}
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, cache.retQuery)
fmt.Fprintln(boil.DebugWriter, identifierCols...)
}
err = exec.QueryRowContext(ctx, cache.retQuery, identifierCols...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...)
if err != nil {
return errors.Wrap(err, "sqlite3: unable to populate default values for trade")
}
CacheNoHooks:
if !cached {
tradeInsertCacheMut.Lock()
tradeInsertCache[key] = cache
tradeInsertCacheMut.Unlock()
}
return o.doAfterInsertHooks(ctx, exec)
}
// Update uses an executor to update the Trade.
// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates.
// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records.
func (o *Trade) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) {
var err error
if err = o.doBeforeUpdateHooks(ctx, exec); err != nil {
return 0, err
}
key := makeCacheKey(columns, nil)
tradeUpdateCacheMut.RLock()
cache, cached := tradeUpdateCache[key]
tradeUpdateCacheMut.RUnlock()
if !cached {
wl := columns.UpdateColumnSet(
tradeAllColumns,
tradePrimaryKeyColumns,
)
if len(wl) == 0 {
return 0, errors.New("sqlite3: unable to update trade, could not build whitelist")
}
cache.query = fmt.Sprintf("UPDATE \"trade\" SET %s WHERE %s",
strmangle.SetParamNames("\"", "\"", 0, wl),
strmangle.WhereClause("\"", "\"", 0, tradePrimaryKeyColumns),
)
cache.valueMapping, err = queries.BindMapping(tradeType, tradeMapping, append(wl, tradePrimaryKeyColumns...))
if err != nil {
return 0, err
}
}
values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping)
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, cache.query)
fmt.Fprintln(boil.DebugWriter, values)
}
var result sql.Result
result, err = exec.ExecContext(ctx, cache.query, values...)
if err != nil {
return 0, errors.Wrap(err, "sqlite3: unable to update trade row")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "sqlite3: failed to get rows affected by update for trade")
}
if !cached {
tradeUpdateCacheMut.Lock()
tradeUpdateCache[key] = cache
tradeUpdateCacheMut.Unlock()
}
return rowsAff, o.doAfterUpdateHooks(ctx, exec)
}
// UpdateAll updates all rows with the specified column values.
func (q tradeQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) {
queries.SetUpdate(q.Query, cols)
result, err := q.Query.ExecContext(ctx, exec)
if err != nil {
return 0, errors.Wrap(err, "sqlite3: unable to update all for trade")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "sqlite3: unable to retrieve rows affected for trade")
}
return rowsAff, nil
}
// UpdateAll updates all rows with the specified column values, using an executor.
func (o TradeSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) {
ln := int64(len(o))
if ln == 0 {
return 0, nil
}
if len(cols) == 0 {
return 0, errors.New("sqlite3: update all requires at least one column argument")
}
colNames := make([]string, len(cols))
args := make([]interface{}, len(cols))
i := 0
for name, value := range cols {
colNames[i] = name
args[i] = value
i++
}
// Append all of the primary key values for each column
for _, obj := range o {
pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), tradePrimaryKeyMapping)
args = append(args, pkeyArgs...)
}
sql := fmt.Sprintf("UPDATE \"trade\" SET %s WHERE %s",
strmangle.SetParamNames("\"", "\"", 0, colNames),
strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, tradePrimaryKeyColumns, len(o)))
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, sql)
fmt.Fprintln(boil.DebugWriter, args...)
}
result, err := exec.ExecContext(ctx, sql, args...)
if err != nil {
return 0, errors.Wrap(err, "sqlite3: unable to update all in trade slice")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "sqlite3: unable to retrieve rows affected all in update all trade")
}
return rowsAff, nil
}
// Delete deletes a single Trade record with an executor.
// Delete will match against the primary key column to find the record to delete.
func (o *Trade) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) {
if o == nil {
return 0, errors.New("sqlite3: no Trade provided for delete")
}
if err := o.doBeforeDeleteHooks(ctx, exec); err != nil {
return 0, err
}
args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), tradePrimaryKeyMapping)
sql := "DELETE FROM \"trade\" WHERE \"id\"=?"
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, sql)
fmt.Fprintln(boil.DebugWriter, args...)
}
result, err := exec.ExecContext(ctx, sql, args...)
if err != nil {
return 0, errors.Wrap(err, "sqlite3: unable to delete from trade")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "sqlite3: failed to get rows affected by delete for trade")
}
if err := o.doAfterDeleteHooks(ctx, exec); err != nil {
return 0, err
}
return rowsAff, nil
}
// DeleteAll deletes all matching rows.
func (q tradeQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) {
if q.Query == nil {
return 0, errors.New("sqlite3: no tradeQuery provided for delete all")
}
queries.SetDelete(q.Query)
result, err := q.Query.ExecContext(ctx, exec)
if err != nil {
return 0, errors.Wrap(err, "sqlite3: unable to delete all from trade")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "sqlite3: failed to get rows affected by deleteall for trade")
}
return rowsAff, nil
}
// DeleteAll deletes all rows in the slice, using an executor.
func (o TradeSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) {
if len(o) == 0 {
return 0, nil
}
if len(tradeBeforeDeleteHooks) != 0 {
for _, obj := range o {
if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil {
return 0, err
}
}
}
var args []interface{}
for _, obj := range o {
pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), tradePrimaryKeyMapping)
args = append(args, pkeyArgs...)
}
sql := "DELETE FROM \"trade\" WHERE " +
strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, tradePrimaryKeyColumns, len(o))
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, sql)
fmt.Fprintln(boil.DebugWriter, args)
}
result, err := exec.ExecContext(ctx, sql, args...)
if err != nil {
return 0, errors.Wrap(err, "sqlite3: unable to delete all from trade slice")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "sqlite3: failed to get rows affected by deleteall for trade")
}
if len(tradeAfterDeleteHooks) != 0 {
for _, obj := range o {
if err := obj.doAfterDeleteHooks(ctx, exec); err != nil {
return 0, err
}
}
}
return rowsAff, nil
}
// Reload refetches the object from the database
// using the primary keys with an executor.
func (o *Trade) Reload(ctx context.Context, exec boil.ContextExecutor) error {
ret, err := FindTrade(ctx, exec, o.ID)
if err != nil {
return err
}
*o = *ret
return nil
}
// ReloadAll refetches every row with matching primary key column values
// and overwrites the original object slice with the newly updated slice.
func (o *TradeSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error {
if o == nil || len(*o) == 0 {
return nil
}
slice := TradeSlice{}
var args []interface{}
for _, obj := range *o {
pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), tradePrimaryKeyMapping)
args = append(args, pkeyArgs...)
}
sql := "SELECT \"trade\".* FROM \"trade\" WHERE " +
strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, tradePrimaryKeyColumns, len(*o))
q := queries.Raw(sql, args...)
err := q.Bind(ctx, exec, &slice)
if err != nil {
return errors.Wrap(err, "sqlite3: unable to reload all in TradeSlice")
}
*o = slice
return nil
}
// TradeExists checks if the Trade row exists.
func TradeExists(ctx context.Context, exec boil.ContextExecutor, iD string) (bool, error) {
var exists bool
sql := "select exists(select 1 from \"trade\" where \"id\"=? limit 1)"
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, sql)
fmt.Fprintln(boil.DebugWriter, iD)
}
row := exec.QueryRowContext(ctx, sql, iD)
err := row.Scan(&exists)
if err != nil {
return false, errors.Wrap(err, "sqlite3: unable to check if trade exists")
}
return exists, nil
}

View File

@@ -0,0 +1,793 @@
// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package sqlite3
import (
"bytes"
"context"
"reflect"
"testing"
"github.com/thrasher-corp/sqlboiler/boil"
"github.com/thrasher-corp/sqlboiler/queries"
"github.com/thrasher-corp/sqlboiler/randomize"
"github.com/thrasher-corp/sqlboiler/strmangle"
)
var (
// Relationships sometimes use the reflection helper queries.Equal/queries.Assign
// so force a package dependency in case they don't.
_ = queries.Equal
)
func testTrades(t *testing.T) {
t.Parallel()
query := Trades()
if query.Query == nil {
t.Error("expected a query, got nothing")
}
}
func testTradesDelete(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if rowsAff, err := o.Delete(ctx, tx); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("should only have deleted one row, but affected:", rowsAff)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 0 {
t.Error("want zero records, got:", count)
}
}
func testTradesQueryDeleteAll(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if rowsAff, err := Trades().DeleteAll(ctx, tx); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("should only have deleted one row, but affected:", rowsAff)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 0 {
t.Error("want zero records, got:", count)
}
}
func testTradesSliceDeleteAll(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
slice := TradeSlice{o}
if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("should only have deleted one row, but affected:", rowsAff)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 0 {
t.Error("want zero records, got:", count)
}
}
func testTradesExists(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
e, err := TradeExists(ctx, tx, o.ID)
if err != nil {
t.Errorf("Unable to check if Trade exists: %s", err)
}
if !e {
t.Errorf("Expected TradeExists to return true, but got false.")
}
}
func testTradesFind(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
tradeFound, err := FindTrade(ctx, tx, o.ID)
if err != nil {
t.Error(err)
}
if tradeFound == nil {
t.Error("want a record, got nil")
}
}
func testTradesBind(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if err = Trades().Bind(ctx, tx, o); err != nil {
t.Error(err)
}
}
func testTradesOne(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if x, err := Trades().One(ctx, tx); err != nil {
t.Error(err)
} else if x == nil {
t.Error("expected to get a non nil record")
}
}
func testTradesAll(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
tradeOne := &Trade{}
tradeTwo := &Trade{}
if err = randomize.Struct(seed, tradeOne, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if err = randomize.Struct(seed, tradeTwo, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = tradeOne.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if err = tradeTwo.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
slice, err := Trades().All(ctx, tx)
if err != nil {
t.Error(err)
}
if len(slice) != 2 {
t.Error("want 2 records, got:", len(slice))
}
}
func testTradesCount(t *testing.T) {
t.Parallel()
var err error
seed := randomize.NewSeed()
tradeOne := &Trade{}
tradeTwo := &Trade{}
if err = randomize.Struct(seed, tradeOne, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if err = randomize.Struct(seed, tradeTwo, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = tradeOne.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if err = tradeTwo.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 2 {
t.Error("want 2 records, got:", count)
}
}
func tradeBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func tradeAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Trade) error {
*o = Trade{}
return nil
}
func testTradesHooks(t *testing.T) {
t.Parallel()
var err error
ctx := context.Background()
empty := &Trade{}
o := &Trade{}
seed := randomize.NewSeed()
if err = randomize.Struct(seed, o, tradeDBTypes, false); err != nil {
t.Errorf("Unable to randomize Trade object: %s", err)
}
AddTradeHook(boil.BeforeInsertHook, tradeBeforeInsertHook)
if err = o.doBeforeInsertHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doBeforeInsertHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o)
}
tradeBeforeInsertHooks = []TradeHook{}
AddTradeHook(boil.AfterInsertHook, tradeAfterInsertHook)
if err = o.doAfterInsertHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterInsertHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o)
}
tradeAfterInsertHooks = []TradeHook{}
AddTradeHook(boil.AfterSelectHook, tradeAfterSelectHook)
if err = o.doAfterSelectHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterSelectHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o)
}
tradeAfterSelectHooks = []TradeHook{}
AddTradeHook(boil.BeforeUpdateHook, tradeBeforeUpdateHook)
if err = o.doBeforeUpdateHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o)
}
tradeBeforeUpdateHooks = []TradeHook{}
AddTradeHook(boil.AfterUpdateHook, tradeAfterUpdateHook)
if err = o.doAfterUpdateHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterUpdateHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o)
}
tradeAfterUpdateHooks = []TradeHook{}
AddTradeHook(boil.BeforeDeleteHook, tradeBeforeDeleteHook)
if err = o.doBeforeDeleteHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o)
}
tradeBeforeDeleteHooks = []TradeHook{}
AddTradeHook(boil.AfterDeleteHook, tradeAfterDeleteHook)
if err = o.doAfterDeleteHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterDeleteHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o)
}
tradeAfterDeleteHooks = []TradeHook{}
AddTradeHook(boil.BeforeUpsertHook, tradeBeforeUpsertHook)
if err = o.doBeforeUpsertHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o)
}
tradeBeforeUpsertHooks = []TradeHook{}
AddTradeHook(boil.AfterUpsertHook, tradeAfterUpsertHook)
if err = o.doAfterUpsertHooks(ctx, nil); err != nil {
t.Errorf("Unable to execute doAfterUpsertHooks: %s", err)
}
if !reflect.DeepEqual(o, empty) {
t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o)
}
tradeAfterUpsertHooks = []TradeHook{}
}
func testTradesInsert(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
}
func testTradesInsertWhitelist(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Whitelist(tradeColumnsWithoutDefault...)); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
}
func testTradeToOneExchangeUsingExchangeName(t *testing.T) {
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
var local Trade
var foreign Exchange
seed := randomize.NewSeed()
if err := randomize.Struct(seed, &local, tradeDBTypes, false, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if err := randomize.Struct(seed, &foreign, exchangeDBTypes, false, exchangeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Exchange struct: %s", err)
}
if err := foreign.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
local.ExchangeNameID = foreign.ID
if err := local.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
check, err := local.ExchangeName().One(ctx, tx)
if err != nil {
t.Fatal(err)
}
if check.ID != foreign.ID {
t.Errorf("want: %v, got %v", foreign.ID, check.ID)
}
slice := TradeSlice{&local}
if err = local.L.LoadExchangeName(ctx, tx, false, (*[]*Trade)(&slice), nil); err != nil {
t.Fatal(err)
}
if local.R.ExchangeName == nil {
t.Error("struct should have been eager loaded")
}
local.R.ExchangeName = nil
if err = local.L.LoadExchangeName(ctx, tx, true, &local, nil); err != nil {
t.Fatal(err)
}
if local.R.ExchangeName == nil {
t.Error("struct should have been eager loaded")
}
}
func testTradeToOneSetOpExchangeUsingExchangeName(t *testing.T) {
var err error
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
var a Trade
var b, c Exchange
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, tradeDBTypes, false, strmangle.SetComplement(tradePrimaryKeyColumns, tradeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &b, exchangeDBTypes, false, strmangle.SetComplement(exchangePrimaryKeyColumns, exchangeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &c, exchangeDBTypes, false, strmangle.SetComplement(exchangePrimaryKeyColumns, exchangeColumnsWithoutDefault)...); err != nil {
t.Fatal(err)
}
if err := a.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
if err = b.Insert(ctx, tx, boil.Infer()); err != nil {
t.Fatal(err)
}
for i, x := range []*Exchange{&b, &c} {
err = a.SetExchangeName(ctx, tx, i != 0, x)
if err != nil {
t.Fatal(err)
}
if a.R.ExchangeName != x {
t.Error("relationship struct not set to correct value")
}
if x.R.ExchangeNameTrade != &a {
t.Error("failed to append to foreign relationship struct")
}
if a.ExchangeNameID != x.ID {
t.Error("foreign key was wrong value", a.ExchangeNameID)
}
zero := reflect.Zero(reflect.TypeOf(a.ExchangeNameID))
reflect.Indirect(reflect.ValueOf(&a.ExchangeNameID)).Set(zero)
if err = a.Reload(ctx, tx); err != nil {
t.Fatal("failed to reload", err)
}
if a.ExchangeNameID != x.ID {
t.Error("foreign key was wrong value", a.ExchangeNameID, x.ID)
}
}
}
func testTradesReload(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
if err = o.Reload(ctx, tx); err != nil {
t.Error(err)
}
}
func testTradesReloadAll(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
slice := TradeSlice{o}
if err = slice.ReloadAll(ctx, tx); err != nil {
t.Error(err)
}
}
func testTradesSelect(t *testing.T) {
t.Parallel()
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
slice, err := Trades().All(ctx, tx)
if err != nil {
t.Error(err)
}
if len(slice) != 1 {
t.Error("want one record, got:", len(slice))
}
}
var (
tradeDBTypes = map[string]string{`ID`: `TEXT`, `ExchangeNameID`: `UUID`, `Tid`: `TEXT`, `Base`: `TEXT`, `Quote`: `TEXT`, `Asset`: `TEXT`, `Price`: `REAL`, `Amount`: `REAL`, `Side`: `TEXT`, `Timestamp`: `TIMESTAMP`}
_ = bytes.MinRead
)
func testTradesUpdate(t *testing.T) {
t.Parallel()
if 0 == len(tradePrimaryKeyColumns) {
t.Skip("Skipping table with no primary key columns")
}
if len(tradeAllColumns) == len(tradePrimaryKeyColumns) {
t.Skip("Skipping table with only primary key columns")
}
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradePrimaryKeyColumns...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("should only affect one row but affected", rowsAff)
}
}
func testTradesSliceUpdateAll(t *testing.T) {
t.Parallel()
if len(tradeAllColumns) == len(tradePrimaryKeyColumns) {
t.Skip("Skipping table with only primary key columns")
}
seed := randomize.NewSeed()
var err error
o := &Trade{}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradeColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
ctx := context.Background()
tx := MustTx(boil.BeginTx(ctx, nil))
defer func() { _ = tx.Rollback() }()
if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
t.Error(err)
}
count, err := Trades().Count(ctx, tx)
if err != nil {
t.Error(err)
}
if count != 1 {
t.Error("want one record, got:", count)
}
if err = randomize.Struct(seed, o, tradeDBTypes, true, tradePrimaryKeyColumns...); err != nil {
t.Errorf("Unable to randomize Trade struct: %s", err)
}
// Remove Primary keys and unique columns from what we plan to update
var fields []string
if strmangle.StringSliceMatch(tradeAllColumns, tradePrimaryKeyColumns) {
fields = tradeAllColumns
} else {
fields = strmangle.SetComplement(
tradeAllColumns,
tradePrimaryKeyColumns,
)
}
value := reflect.Indirect(reflect.ValueOf(o))
typ := reflect.TypeOf(o).Elem()
n := typ.NumField()
updateMap := M{}
for _, col := range fields {
for i := 0; i < n; i++ {
f := typ.Field(i)
if f.Tag.Get("boil") == col {
updateMap[col] = value.Field(i).Interface()
}
}
}
slice := TradeSlice{o}
if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil {
t.Error(err)
} else if rowsAff != 1 {
t.Error("wanted one record updated but got", rowsAff)
}
}

View File

@@ -49,29 +49,6 @@ var WithdrawalCryptoColumns = struct {
// Generated where
type whereHelpernull_String struct{ field string }
func (w whereHelpernull_String) EQ(x null.String) qm.QueryMod {
return qmhelper.WhereNullEQ(w.field, false, x)
}
func (w whereHelpernull_String) NEQ(x null.String) qm.QueryMod {
return qmhelper.WhereNullEQ(w.field, true, x)
}
func (w whereHelpernull_String) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) }
func (w whereHelpernull_String) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) }
func (w whereHelpernull_String) LT(x null.String) qm.QueryMod {
return qmhelper.Where(w.field, qmhelper.LT, x)
}
func (w whereHelpernull_String) LTE(x null.String) qm.QueryMod {
return qmhelper.Where(w.field, qmhelper.LTE, x)
}
func (w whereHelpernull_String) GT(x null.String) qm.QueryMod {
return qmhelper.Where(w.field, qmhelper.GT, x)
}
func (w whereHelpernull_String) GTE(x null.String) qm.QueryMod {
return qmhelper.Where(w.field, qmhelper.GTE, x)
}
var WithdrawalCryptoWhere = struct {
ID whereHelperint64
Address whereHelperstring

View File

@@ -33,8 +33,7 @@ func Series(exchangeName, base, quote string, interval int64, asset string, star
qm.Where("base = ?", strings.ToUpper(base)),
qm.Where("quote = ?", strings.ToUpper(quote)),
qm.Where("interval = ?", interval),
qm.Where("asset = ?", asset),
qm.Where("timestamp between ? and ?", start.UTC(), end.UTC()),
qm.Where("asset = ?", strings.ToLower(asset)),
}
exchangeUUID, errS := exchange.UUIDByName(exchangeName)
@@ -42,8 +41,8 @@ func Series(exchangeName, base, quote string, interval int64, asset string, star
return out, errS
}
queries = append(queries, qm.Where("exchange_name_id = ?", exchangeUUID.String()))
if repository.GetSQLDialect() == database.DBSQLite3 {
queries = append(queries, qm.Where("timestamp between ? and ?", start.UTC().Format(time.RFC3339), end.UTC().Format(time.RFC3339)))
retCandle, errC := modelSQLite.Candles(queries...).All(context.Background(), database.DB.SQL)
if errC != nil {
return out, errC
@@ -63,6 +62,7 @@ func Series(exchangeName, base, quote string, interval int64, asset string, star
})
}
} else {
queries = append(queries, qm.Where("timestamp between ? and ?", start.UTC(), end.UTC()))
retCandle, errC := modelPSQL.Candles(queries...).All(context.Background(), database.DB.SQL)
if errC != nil {
return out, errC
@@ -91,6 +91,86 @@ func Series(exchangeName, base, quote string, interval int64, asset string, star
return out, err
}
// DeleteCandles will delete all existing matching candles
func DeleteCandles(in *Item) (int64, error) {
if database.DB.SQL == nil {
return 0, database.ErrDatabaseSupportDisabled
}
if len(in.Candles) < 1 {
return 0, errNoCandleData
}
ctx := context.Background()
queries := []qm.QueryMod{
qm.Where("base = ?", strings.ToUpper(in.Base)),
qm.Where("quote = ?", strings.ToUpper(in.Quote)),
qm.Where("interval = ?", in.Interval),
qm.Where("asset = ?", strings.ToLower(in.Asset)),
qm.Where("exchange_name_id = ?", in.ExchangeID),
}
if repository.GetSQLDialect() == database.DBSQLite3 {
queries = append(queries, qm.Where("timestamp between ? and ?", in.Candles[0].Timestamp.UTC().Format(time.RFC3339), in.Candles[len(in.Candles)-1].Timestamp.UTC().Format(time.RFC3339)))
return deleteSQLite(ctx, queries)
}
queries = append(queries, qm.Where("timestamp between ? and ?", in.Candles[0].Timestamp.UTC(), in.Candles[len(in.Candles)-1].Timestamp.UTC()))
return deletePostgres(ctx, queries)
}
func deleteSQLite(ctx context.Context, queries []qm.QueryMod) (int64, error) {
retCandle, err := modelSQLite.Candles(queries...).All(context.Background(), database.DB.SQL)
if err != nil {
return 0, err
}
var tx *sql.Tx
tx, err = database.DB.SQL.BeginTx(ctx, nil)
if err != nil {
return 0, err
}
var totalDeleted int64
totalDeleted, err = retCandle.DeleteAll(ctx, tx)
if err != nil {
errRB := tx.Rollback()
if errRB != nil {
log.Errorln(log.DatabaseMgr, errRB)
}
return 0, err
}
err = tx.Commit()
if err != nil {
return 0, err
}
return totalDeleted, nil
}
func deletePostgres(ctx context.Context, queries []qm.QueryMod) (int64, error) {
retCandle, err := modelPSQL.Candles(queries...).All(context.Background(), database.DB.SQL)
if err != nil {
return 0, err
}
var tx *sql.Tx
tx, err = database.DB.SQL.BeginTx(ctx, nil)
if err != nil {
return 0, err
}
var totalDeleted int64
totalDeleted, err = retCandle.DeleteAll(ctx, tx)
if err != nil {
errRB := tx.Rollback()
if errRB != nil {
log.Errorln(log.DatabaseMgr, errRB)
}
return 0, err
}
err = tx.Commit()
if err != nil {
return 0, err
}
return totalDeleted, nil
}
// Insert series of candles
func Insert(in *Item) (uint64, error) {
if database.DB.SQL == nil {
@@ -136,7 +216,7 @@ func insertSQLite(ctx context.Context, tx *sql.Tx, in *Item) (uint64, error) {
Base: strings.ToUpper(in.Base),
Quote: strings.ToUpper(in.Quote),
Interval: strconv.FormatInt(in.Interval, 10),
Asset: in.Asset,
Asset: strings.ToLower(in.Asset),
Timestamp: in.Candles[x].Timestamp.UTC().Format(time.RFC3339),
Open: in.Candles[x].Open,
High: in.Candles[x].High,
@@ -168,7 +248,7 @@ func insertPostgresSQL(ctx context.Context, tx *sql.Tx, in *Item) (uint64, error
Base: strings.ToUpper(in.Base),
Quote: strings.ToUpper(in.Quote),
Interval: in.Interval,
Asset: in.Asset,
Asset: strings.ToLower(in.Asset),
Timestamp: in.Candles[x].Timestamp,
Open: in.Candles[x].Open,
High: in.Candles[x].High,

View File

@@ -105,8 +105,17 @@ func TestInsert(t *testing.T) {
}
if r != 365 {
t.Fatalf("unexpected number inserted: %v", r)
t.Errorf("unexpected number inserted: %v", r)
}
d, err := DeleteCandles(&data)
if err != nil {
t.Fatal(err)
}
if d != 365 {
t.Errorf("unexpected number deleted: %v", d)
}
err = testhelpers.CloseDatabase(dbConn)
if err != nil {
t.Error(err)

View File

@@ -10,7 +10,7 @@ const (
)
var (
errInvalidInput = errors.New("exchange, base , quote, asset, interval, start & end cannot be empty")
errInvalidInput = errors.New("exchange, base, quote, asset, interval, start & end cannot be empty")
errNoCandleData = errors.New("no candle data provided")
)

View File

@@ -72,7 +72,6 @@ func Insert(in Details) error {
if err != nil {
return err
}
if repository.GetSQLDialect() == database.DBSQLite3 {
err = insertSQLite(ctx, tx, []Details{in})
} else {
@@ -139,7 +138,7 @@ func insertSQLite(ctx context.Context, tx *sql.Tx, in []Details) (err error) {
return errUUID
}
var tempInsert = modelSQLite.Exchange{
Name: in[x].Name,
Name: strings.ToLower(in[x].Name),
ID: tempUUID.String(),
}
@@ -159,7 +158,7 @@ func insertSQLite(ctx context.Context, tx *sql.Tx, in []Details) (err error) {
func insertPostgresql(ctx context.Context, tx *sql.Tx, in []Details) (err error) {
for x := range in {
var tempInsert = modelPSQL.Exchange{
Name: in[x].Name,
Name: strings.ToLower(in[x].Name),
}
err = tempInsert.Upsert(ctx, tx, true, []string{"name"}, boil.Infer(), boil.Infer())

View File

@@ -0,0 +1,359 @@
package trade
import (
"context"
"database/sql"
"errors"
"fmt"
"strings"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/database"
modelPSQL "github.com/thrasher-corp/gocryptotrader/database/models/postgres"
modelSQLite "github.com/thrasher-corp/gocryptotrader/database/models/sqlite3"
"github.com/thrasher-corp/gocryptotrader/database/repository"
"github.com/thrasher-corp/gocryptotrader/database/repository/exchange"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/sqlboiler/boil"
"github.com/thrasher-corp/sqlboiler/queries/qm"
)
// Insert saves trade data to the database
func Insert(trades ...Data) error {
for i := range trades {
if trades[i].ExchangeNameID == "" && trades[i].Exchange != "" {
exchangeUUID, err := exchange.UUIDByName(trades[i].Exchange)
if err != nil {
return err
}
trades[i].ExchangeNameID = exchangeUUID.String()
} else if trades[i].ExchangeNameID == "" && trades[i].Exchange == "" {
return errors.New("exchange name/uuid not set, cannot insert")
}
}
ctx := context.Background()
ctx = boil.SkipTimestamps(ctx)
tx, err := database.DB.SQL.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("beginTx %w", err)
}
defer func() {
if err != nil {
errRB := tx.Rollback()
if errRB != nil {
log.Errorf(log.DatabaseMgr, "Insert tx.Rollback %v", errRB)
}
}
}()
if repository.GetSQLDialect() == database.DBSQLite3 || repository.GetSQLDialect() == database.DBSQLite {
err = insertSQLite(ctx, tx, trades...)
} else {
err = insertPostgres(ctx, tx, trades...)
}
if err != nil {
return err
}
return tx.Commit()
}
func insertSQLite(ctx context.Context, tx *sql.Tx, trades ...Data) error {
for i := range trades {
if trades[i].ID == "" {
freshUUID, err := uuid.NewV4()
if err != nil {
return err
}
trades[i].ID = freshUUID.String()
}
var tempEvent = modelSQLite.Trade{
ID: trades[i].ID,
ExchangeNameID: trades[i].ExchangeNameID,
Base: strings.ToUpper(trades[i].Base),
Quote: strings.ToUpper(trades[i].Quote),
Asset: strings.ToLower(trades[i].AssetType),
Price: trades[i].Price,
Amount: trades[i].Amount,
Timestamp: trades[i].Timestamp.UTC().Format(time.RFC3339),
}
if trades[i].Side != "" {
tempEvent.Side.SetValid(strings.ToUpper(trades[i].Side))
}
if trades[i].TID != "" {
tempEvent.Tid.SetValid(trades[i].TID)
}
err := tempEvent.Insert(ctx, tx, boil.Infer())
if err != nil {
return err
}
}
return nil
}
func insertPostgres(ctx context.Context, tx *sql.Tx, trades ...Data) error {
var err error
for i := range trades {
if trades[i].ID == "" {
var freshUUID uuid.UUID
freshUUID, err = uuid.NewV4()
if err != nil {
return err
}
trades[i].ID = freshUUID.String()
}
var tempEvent = modelPSQL.Trade{
ExchangeNameID: trades[i].ExchangeNameID,
Base: strings.ToUpper(trades[i].Base),
Quote: strings.ToUpper(trades[i].Quote),
Asset: strings.ToLower(trades[i].AssetType),
Price: trades[i].Price,
Amount: trades[i].Amount,
Timestamp: trades[i].Timestamp.UTC(),
ID: trades[i].ID,
}
if trades[i].Side != "" {
tempEvent.Side.SetValid(strings.ToUpper(trades[i].Side))
}
if trades[i].TID != "" {
tempEvent.Tid.SetValid(trades[i].TID)
}
err = tempEvent.Upsert(ctx, tx, false, nil, boil.Infer(), boil.Infer())
if err != nil {
return err
}
}
return nil
}
// GetByUUID returns a trade by its unique ID
func GetByUUID(uuid string) (td Data, err error) {
if repository.GetSQLDialect() == database.DBSQLite3 || repository.GetSQLDialect() == database.DBSQLite {
td, err = getByUUIDSQLite(uuid)
if err != nil {
return td, fmt.Errorf("trade.Get getByUUIDSQLite %w", err)
}
} else {
td, err = getByUUIDPostgres(uuid)
if err != nil {
return td, fmt.Errorf("trade.Get getByUUIDPostgres %w", err)
}
}
return td, nil
}
func getByUUIDSQLite(uuid string) (Data, error) {
var td Data
var ts time.Time
query := modelSQLite.Trades(qm.Where("id = ?", uuid))
result, err := query.One(context.Background(), database.DB.SQL)
if err != nil {
return td, err
}
ts, err = time.Parse(time.RFC3339, result.Timestamp)
if err != nil {
return td, err
}
td = Data{
ID: result.ID,
Exchange: result.ExchangeNameID,
Base: strings.ToUpper(result.Base),
Quote: strings.ToUpper(result.Quote),
AssetType: strings.ToLower(result.Asset),
Price: result.Price,
Amount: result.Amount,
Timestamp: ts,
}
if result.Side.Valid {
td.Side = result.Side.String
}
return td, nil
}
func getByUUIDPostgres(uuid string) (td Data, err error) {
query := modelPSQL.Trades(qm.Where("id = ?", uuid))
var result *modelPSQL.Trade
result, err = query.One(context.Background(), database.DB.SQL)
if err != nil {
return td, err
}
td = Data{
ID: result.ID,
Timestamp: result.Timestamp,
Exchange: result.ExchangeNameID,
Base: strings.ToUpper(result.Base),
Quote: strings.ToUpper(result.Quote),
AssetType: strings.ToLower(result.Asset),
Price: result.Price,
Amount: result.Amount,
}
if result.Side.Valid {
td.Side = result.Side.String
}
return td, nil
}
// GetInRange returns all trades by an exchange in a date range
func GetInRange(exchangeName, assetType, base, quote string, startDate, endDate time.Time) (td []Data, err error) {
if repository.GetSQLDialect() == database.DBSQLite3 || repository.GetSQLDialect() == database.DBSQLite {
td, err = getInRangeSQLite(exchangeName, assetType, base, quote, startDate, endDate)
if err != nil {
return td, fmt.Errorf("trade.GetByExchangeInRange getInRangeSQLite %w", err)
}
} else {
td, err = getInRangePostgres(exchangeName, assetType, base, quote, startDate, endDate)
if err != nil {
return td, fmt.Errorf("trade.GetByExchangeInRange getInRangePostgres %w", err)
}
}
return td, nil
}
func getInRangeSQLite(exchangeName, assetType, base, quote string, startDate, endDate time.Time) (td []Data, err error) {
var exchangeUUID uuid.UUID
exchangeUUID, err = exchange.UUIDByName(exchangeName)
if err != nil {
return nil, err
}
wheres := map[string]interface{}{
"exchange_name_id": exchangeUUID,
"asset": strings.ToLower(assetType),
"base": strings.ToUpper(base),
"quote": strings.ToUpper(quote),
}
q := generateQuery(wheres, startDate, endDate)
query := modelSQLite.Trades(q...)
var result []*modelSQLite.Trade
result, err = query.All(context.Background(), database.DB.SQL)
if err != nil {
return td, err
}
for i := range result {
ts, err := time.Parse(time.RFC3339, result[i].Timestamp)
if err != nil {
return td, err
}
t := Data{
ID: result[i].ID,
Timestamp: ts,
Exchange: strings.ToLower(exchangeName),
Base: strings.ToUpper(result[i].Base),
Quote: strings.ToUpper(result[i].Quote),
AssetType: strings.ToLower(result[i].Asset),
Price: result[i].Price,
Amount: result[i].Amount,
}
if result[i].Side.Valid {
t.Side = result[i].Side.String
}
td = append(td, t)
}
return td, nil
}
func getInRangePostgres(exchangeName, assetType, base, quote string, startDate, endDate time.Time) (td []Data, err error) {
var exchangeUUID uuid.UUID
exchangeUUID, err = exchange.UUIDByName(exchangeName)
if err != nil {
return nil, err
}
wheres := map[string]interface{}{
"exchange_name_id": exchangeUUID,
"asset": strings.ToLower(assetType),
"base": strings.ToUpper(base),
"quote": strings.ToUpper(quote),
}
q := generateQuery(wheres, startDate, endDate)
query := modelPSQL.Trades(q...)
var result []*modelPSQL.Trade
result, err = query.All(context.Background(), database.DB.SQL)
if err != nil {
return td, err
}
for i := range result {
t := Data{
ID: result[i].ID,
Timestamp: result[i].Timestamp,
Exchange: strings.ToLower(exchangeName),
Base: strings.ToUpper(result[i].Base),
Quote: strings.ToUpper(result[i].Quote),
AssetType: strings.ToLower(result[i].Asset),
Price: result[i].Price,
Amount: result[i].Amount,
}
if result[i].Side.Valid {
t.Side = result[i].Side.String
}
td = append(td, t)
}
return td, nil
}
// DeleteTrades will remove trades from the database using trade.Data
func DeleteTrades(trades ...Data) error {
ctx := context.Background()
ctx = boil.SkipTimestamps(ctx)
tx, err := database.DB.SQL.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("beginTx %w", err)
}
defer func() {
if err != nil {
errRB := tx.Rollback()
if errRB != nil {
log.Errorf(log.DatabaseMgr, "DeleteTrades tx.Rollback %v", errRB)
}
}
}()
if repository.GetSQLDialect() == database.DBSQLite3 || repository.GetSQLDialect() == database.DBSQLite {
err = deleteTradesSQLite(context.Background(), tx, trades...)
} else {
err = deleteTradesPostgres(context.Background(), tx, trades...)
}
if err != nil {
return err
}
return tx.Commit()
}
func deleteTradesSQLite(ctx context.Context, tx *sql.Tx, trades ...Data) error {
var tradeIDs []interface{}
for i := range trades {
tradeIDs = append(tradeIDs, trades[i].ID)
}
query := modelSQLite.Trades(qm.WhereIn(`id in ?`, tradeIDs...))
_, err := query.DeleteAll(ctx, tx)
return err
}
func deleteTradesPostgres(ctx context.Context, tx *sql.Tx, trades ...Data) error {
var tradeIDs []interface{}
for i := range trades {
tradeIDs = append(tradeIDs, trades[i].ID)
}
query := modelPSQL.Trades(qm.WhereIn(`id in ?`, tradeIDs...))
_, err := query.DeleteAll(ctx, tx)
return err
}
func generateQuery(clauses map[string]interface{}, start, end time.Time) []qm.QueryMod {
query := []qm.QueryMod{
qm.Where("timestamp BETWEEN ? AND ?", start.UTC().Format(time.RFC3339), end.UTC().Format(time.RFC3339)),
}
for k, v := range clauses {
query = append(query, qm.Where(k+` = ?`, v))
}
return query
}

View File

@@ -0,0 +1,201 @@
package trade
import (
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/database"
"github.com/thrasher-corp/gocryptotrader/database/drivers"
"github.com/thrasher-corp/gocryptotrader/database/repository/exchange"
"github.com/thrasher-corp/gocryptotrader/database/testhelpers"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
)
var (
verbose = false
testExchanges = []exchange.Details{
{
Name: "one",
},
{
Name: "two",
},
}
)
func TestMain(m *testing.M) {
if verbose {
testhelpers.EnableVerboseTestOutput()
}
var err error
testhelpers.PostgresTestDatabase = testhelpers.GetConnectionDetails()
testhelpers.TempDir, err = ioutil.TempDir("", "gct-temp")
if err != nil {
log.Fatal(err)
}
os.Exit(m.Run())
}
func TestTrades(t *testing.T) {
testCases := []struct {
name string
config *database.Config
seedDB func() error
runner func(t *testing.T)
closer func(dbConn *database.Instance) error
}{
{
name: "postgresql",
config: testhelpers.PostgresTestDatabase,
seedDB: seedDB,
},
{
name: "SQLite",
config: &database.Config{
Driver: database.DBSQLite3,
ConnectionDetails: drivers.ConnectionDetails{Database: "./testdb"},
},
seedDB: seedDB,
},
}
for x := range testCases {
test := testCases[x]
t.Run(test.name, func(t *testing.T) {
if !testhelpers.CheckValidConfig(&test.config.ConnectionDetails) {
t.Skip("database not configured skipping test")
}
dbConn, err := testhelpers.ConnectToDatabase(test.config)
if err != nil {
t.Fatal(err)
}
if test.seedDB != nil {
err = test.seedDB()
if err != nil {
t.Error(err)
}
}
tradeSQLTester(t)
err = testhelpers.CloseDatabase(dbConn)
if err != nil {
t.Error(err)
}
})
}
}
func tradeSQLTester(t *testing.T) {
var trades, trades2 []Data
for i := 0; i < 20; i++ {
uu, _ := uuid.NewV4()
trades = append(trades, Data{
ID: uu.String(),
Timestamp: time.Now(),
Exchange: testExchanges[0].Name,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
AssetType: asset.Spot.String(),
Price: float64(i * (i + 3)),
Amount: float64(i * (i + 2)),
Side: order.Buy.String(),
TID: fmt.Sprintf("%v", i),
})
}
err := Insert(trades...)
if err != nil {
t.Fatal(err)
}
// insert the same trades to test conflict resolution
for i := 0; i < 20; i++ {
uu, _ := uuid.NewV4()
trades2 = append(trades2, Data{
ID: uu.String(),
Timestamp: time.Now(),
Exchange: testExchanges[0].Name,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
AssetType: asset.Spot.String(),
Price: float64(i * (i + 3)),
Amount: float64(i * (i + 2)),
Side: order.Buy.String(),
TID: fmt.Sprintf("%v", i),
})
}
err = Insert(trades2...)
if err != nil {
t.Fatal(err)
}
resp, err := GetInRange(
testExchanges[0].Name,
asset.Spot.String(),
currency.BTC.String(),
currency.USD.String(),
time.Now().Add(-time.Hour),
time.Now().Add(time.Hour),
)
if err != nil {
t.Error(err)
}
if len(resp) != 20 {
t.Fatalf("unique constraints failing, got %v", resp)
}
v, err := GetInRange(
testExchanges[0].Name,
asset.Spot.String(),
currency.BTC.String(),
currency.USD.String(),
time.Now().Add(-time.Hour),
time.Now().Add(time.Hour))
if err != nil {
t.Error(err)
}
if len(v) == 0 {
t.Error("Bad get!")
}
err = DeleteTrades(trades...)
if err != nil {
t.Error(err)
}
err = DeleteTrades(trades2...)
if err != nil {
t.Error(err)
}
v, err = GetInRange(
testExchanges[0].Name,
asset.Spot.String(),
currency.BTC.String(),
currency.USD.String(),
time.Now().Add(-time.Hour),
time.Now().Add(time.Hour))
if err != nil {
t.Error(err)
}
if len(v) != 0 {
t.Errorf("should all be ded %v", v)
}
}
func seedDB() error {
err := exchange.InsertMany(testExchanges)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,19 @@
package trade
import "time"
// Data defines trade data in its simplest
// db friendly form
type Data struct {
ID string
TID string
Exchange string
ExchangeNameID string
Base string
Quote string
AssetType string
Price float64
Amount float64
Side string
Timestamp time.Time
}

View File

@@ -117,7 +117,9 @@ func migrateDB(db *sql.DB) error {
// EnableVerboseTestOutput enables debug output for SQL queries
func EnableVerboseTestOutput() {
c := log.GenDefaultSettings()
log.RWM.Lock()
log.GlobalLogConfig = &c
log.RWM.Unlock()
log.SetupGlobalLogger()
DBLogger := database.Logger{}

View File

@@ -27,7 +27,7 @@ func (a *databaseManager) Started() bool {
return atomic.LoadInt32(&a.started) == 1
}
func (a *databaseManager) Start() (err error) {
func (a *databaseManager) Start(bot *Engine) (err error) {
if atomic.AddInt32(&a.started, 1) != 1 {
return errors.New("database manager already started")
}
@@ -42,20 +42,20 @@ func (a *databaseManager) Start() (err error) {
a.shutdown = make(chan struct{})
if Bot.Config.Database.Enabled {
if Bot.Config.Database.Driver == database.DBPostgreSQL {
if bot.Config.Database.Enabled {
if bot.Config.Database.Driver == database.DBPostgreSQL {
log.Debugf(log.DatabaseMgr,
"Attempting to establish database connection to host %s/%s utilising %s driver\n",
Bot.Config.Database.Host,
Bot.Config.Database.Database,
Bot.Config.Database.Driver)
bot.Config.Database.Host,
bot.Config.Database.Database,
bot.Config.Database.Driver)
dbConn, err = dbpsql.Connect()
} else if Bot.Config.Database.Driver == database.DBSQLite ||
Bot.Config.Database.Driver == database.DBSQLite3 {
} else if bot.Config.Database.Driver == database.DBSQLite ||
bot.Config.Database.Driver == database.DBSQLite3 {
log.Debugf(log.DatabaseMgr,
"Attempting to establish database connection to %s utilising %s driver\n",
Bot.Config.Database.Database,
Bot.Config.Database.Driver)
bot.Config.Database.Database,
bot.Config.Database.Driver)
dbConn, err = dbsqlite3.Connect()
}
if err != nil {
@@ -64,12 +64,12 @@ func (a *databaseManager) Start() (err error) {
dbConn.Connected = true
DBLogger := database.Logger{}
if Bot.Config.Database.Verbose {
if bot.Config.Database.Verbose {
boil.DebugMode = true
boil.DebugWriter = DBLogger
}
go a.run()
go a.run(bot)
return nil
}
@@ -94,9 +94,9 @@ func (a *databaseManager) Stop() error {
return nil
}
func (a *databaseManager) run() {
func (a *databaseManager) run(bot *Engine) {
log.Debugln(log.DatabaseMgr, "Database manager started.")
Bot.ServicesWG.Add(1)
bot.ServicesWG.Add(1)
t := time.NewTicker(time.Second * 2)
@@ -105,7 +105,7 @@ func (a *databaseManager) run() {
atomic.CompareAndSwapInt32(&a.stopped, 1, 0)
atomic.CompareAndSwapInt32(&a.started, 1, 0)
Bot.ServicesWG.Done()
bot.ServicesWG.Done()
log.Debugln(log.DatabaseMgr, "Database manager shutdown.")
}()

View File

@@ -16,6 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency/coinmarketcap"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
gctscript "github.com/thrasher-corp/gocryptotrader/gctscript/vm"
gctlog "github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio"
@@ -216,6 +217,17 @@ func validateSettings(b *Engine, s *Settings, flagSet map[string]bool) {
request.MaxRequestJobs = int32(b.Settings.MaxHTTPRequestJobsLimit)
}
b.Settings.TradeBufferProcessingInterval = s.TradeBufferProcessingInterval
if b.Settings.TradeBufferProcessingInterval != trade.DefaultProcessorIntervalTime {
if b.Settings.TradeBufferProcessingInterval >= time.Second {
trade.BufferProcessorIntervalTime = b.Settings.TradeBufferProcessingInterval
} else {
b.Settings.TradeBufferProcessingInterval = trade.DefaultProcessorIntervalTime
gctlog.Warnf(gctlog.Global, "-tradeprocessinginterval must be >= to 1 second, using default value of %v",
trade.DefaultProcessorIntervalTime)
}
}
b.Settings.RequestMaxRetryAttempts = s.RequestMaxRetryAttempts
if b.Settings.RequestMaxRetryAttempts != request.DefaultMaxRetryAttempts && s.RequestMaxRetryAttempts > 0 {
request.MaxRetryAttempts = b.Settings.RequestMaxRetryAttempts
@@ -297,6 +309,7 @@ func PrintSettings(s *Settings) {
gctlog.Debugf(gctlog.Global, "\t Enable exchange HTTP debugging: %v", s.EnableExchangeHTTPDebugging)
gctlog.Debugf(gctlog.Global, "\t Max HTTP request jobs: %v", s.MaxHTTPRequestJobsLimit)
gctlog.Debugf(gctlog.Global, "\t HTTP request max retry attempts: %v", s.RequestMaxRetryAttempts)
gctlog.Debugf(gctlog.Global, "\t Trade buffer processing interval: %v", s.TradeBufferProcessingInterval)
gctlog.Debugf(gctlog.Global, "\t HTTP timeout: %v", s.HTTPTimeout)
gctlog.Debugf(gctlog.Global, "\t HTTP user agent: %v", s.HTTPUserAgent)
gctlog.Debugf(gctlog.Global, "- GCTSCRIPT SETTINGS: ")
@@ -319,7 +332,7 @@ func (bot *Engine) Start() error {
}
if bot.Settings.EnableDatabaseManager {
if err := bot.DatabaseManager.Start(); err != nil {
if err := bot.DatabaseManager.Start(bot); err != nil {
gctlog.Errorf(gctlog.Global, "Database manager unable to start: %v", err)
}
}

View File

@@ -59,6 +59,7 @@ type Settings struct {
EnableExchangeRESTSupport bool
EnableExchangeWebsocketSupport bool
MaxHTTPRequestJobsLimit int
TradeBufferProcessingInterval time.Duration
RequestMaxRetryAttempts int
// Global HTTP related settings

View File

@@ -124,7 +124,6 @@ func TestProcessTicker(t *testing.T) {
if Bot == nil {
Bot = new(Engine)
}
Bot.Settings.Verbose = true
e := Event{
Exchange: testExchange,
@@ -191,7 +190,6 @@ func TestProcessOrderbook(t *testing.T) {
if Bot == nil {
Bot = new(Engine)
}
Bot.Settings.Verbose = true
e := Event{
Exchange: testExchange,
@@ -226,7 +224,6 @@ func TestCheckEventCondition(t *testing.T) {
if Bot == nil {
Bot = new(Engine)
}
Bot.Settings.Verbose = true
e := Event{
Item: ItemPrice,

View File

@@ -52,17 +52,17 @@ type exchangeManager struct {
exchanges map[string]exchange.IBotExchange
}
func dryrunParamInteraction(param string) {
if !Bot.Settings.CheckParamInteraction {
func (bot *Engine) dryrunParamInteraction(param string) {
if !bot.Settings.CheckParamInteraction {
return
}
if !Bot.Settings.EnableDryRun {
if !bot.Settings.EnableDryRun {
log.Warnf(log.Global,
"Command line argument '-%s' induces dry run mode."+
" Set -dryrun=false if you wish to override this.",
param)
Bot.Settings.EnableDryRun = true
bot.Settings.EnableDryRun = true
}
}
@@ -235,7 +235,7 @@ func (bot *Engine) LoadExchange(name string, useWG bool, wg *sync.WaitGroup) err
if bot.Settings.EnableAllPairs {
if exchCfg.CurrencyPairs != nil {
dryrunParamInteraction("enableallpairs")
bot.dryrunParamInteraction("enableallpairs")
assets := exchCfg.CurrencyPairs.GetAssetTypes()
for x := range assets {
var pairs currency.Pairs
@@ -249,12 +249,12 @@ func (bot *Engine) LoadExchange(name string, useWG bool, wg *sync.WaitGroup) err
}
if bot.Settings.EnableExchangeVerbose {
dryrunParamInteraction("exchangeverbose")
bot.dryrunParamInteraction("exchangeverbose")
exchCfg.Verbose = true
}
if bot.Settings.EnableExchangeWebsocketSupport {
dryrunParamInteraction("exchangewebsocketsupport")
bot.dryrunParamInteraction("exchangewebsocketsupport")
if exchCfg.Features != nil {
if exchCfg.Features.Supports.Websocket {
exchCfg.Features.Enabled.Websocket = true
@@ -263,7 +263,7 @@ func (bot *Engine) LoadExchange(name string, useWG bool, wg *sync.WaitGroup) err
}
if bot.Settings.EnableExchangeAutoPairUpdates {
dryrunParamInteraction("exchangeautopairupdates")
bot.dryrunParamInteraction("exchangeautopairupdates")
if exchCfg.Features != nil {
if exchCfg.Features.Supports.RESTCapabilities.AutoPairUpdates {
exchCfg.Features.Enabled.AutoPairUpdates = true
@@ -272,7 +272,7 @@ func (bot *Engine) LoadExchange(name string, useWG bool, wg *sync.WaitGroup) err
}
if bot.Settings.DisableExchangeAutoPairUpdates {
dryrunParamInteraction("exchangedisableautopairupdates")
bot.dryrunParamInteraction("exchangedisableautopairupdates")
if exchCfg.Features != nil {
if exchCfg.Features.Supports.RESTCapabilities.AutoPairUpdates {
exchCfg.Features.Enabled.AutoPairUpdates = false
@@ -281,27 +281,27 @@ func (bot *Engine) LoadExchange(name string, useWG bool, wg *sync.WaitGroup) err
}
if bot.Settings.HTTPUserAgent != "" {
dryrunParamInteraction("httpuseragent")
bot.dryrunParamInteraction("httpuseragent")
exchCfg.HTTPUserAgent = bot.Settings.HTTPUserAgent
}
if bot.Settings.HTTPProxy != "" {
dryrunParamInteraction("httpproxy")
bot.dryrunParamInteraction("httpproxy")
exchCfg.ProxyAddress = bot.Settings.HTTPProxy
}
if bot.Settings.HTTPTimeout != exchange.DefaultHTTPTimeout {
dryrunParamInteraction("httptimeout")
bot.dryrunParamInteraction("httptimeout")
exchCfg.HTTPTimeout = bot.Settings.HTTPTimeout
}
if bot.Settings.EnableExchangeHTTPDebugging {
dryrunParamInteraction("exchangehttpdebugging")
bot.dryrunParamInteraction("exchangehttpdebugging")
exchCfg.HTTPDebugging = bot.Settings.EnableExchangeHTTPDebugging
}
if bot.Settings.EnableAllExchanges {
dryrunParamInteraction("enableallexchanges")
bot.dryrunParamInteraction("enableallexchanges")
}
if !bot.Settings.EnableExchangeHTTPRateLimiter {

View File

@@ -14,6 +14,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -107,7 +108,10 @@ func (h *FakePassingExchange) SetPairs(_ currency.Pairs, _ asset.Item, _ bool) e
return nil
}
func (h *FakePassingExchange) GetAssetTypes() asset.Items { return asset.Items{asset.Spot} }
func (h *FakePassingExchange) GetExchangeHistory(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]exchange.TradeHistory, error) {
func (h *FakePassingExchange) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, nil
}
func (h *FakePassingExchange) GetRecentTrades(_ currency.Pair, _ asset.Item) ([]trade.Data, error) {
return nil, nil
}
func (h *FakePassingExchange) SupportsAutoPairUpdates() bool { return true }

View File

@@ -116,7 +116,7 @@ func (bot *Engine) SetSubsystem(subsys string, enable bool) error {
return bot.NTPManager.Stop()
case "database":
if enable {
return bot.DatabaseManager.Start()
return bot.DatabaseManager.Start(bot)
}
return bot.DatabaseManager.Stop()
case "exchange_syncer":

View File

@@ -288,14 +288,6 @@ func WebsocketDataHandler(exchName string, data interface{}) error {
log.Info(log.WebsocketMgr, d)
case error:
return fmt.Errorf("routines.go exchange %s websocket error - %s", exchName, data)
case stream.TradeData:
if Bot.Settings.Verbose {
log.Infof(log.WebsocketMgr, "%s websocket %s %s trade updated %+v",
exchName,
FormatCurrency(d.CurrencyPair),
d.AssetType,
d)
}
case stream.FundingData:
if Bot.Settings.Verbose {
log.Infof(log.WebsocketMgr, "%s websocket %s %s funding updated %+v",

View File

@@ -33,10 +33,6 @@ func TestHandleData(t *testing.T) {
if err == nil {
t.Error("Expected nil data error")
}
err = WebsocketDataHandler(exchName, stream.TradeData{})
if err != nil {
t.Error(err)
}
err = WebsocketDataHandler(exchName, stream.FundingData{})
if err != nil {
t.Error(err)

View File

@@ -22,6 +22,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/common/file"
"github.com/thrasher-corp/gocryptotrader/common/file/archive"
"github.com/thrasher-corp/gocryptotrader/common/timeperiods"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/database"
"github.com/thrasher-corp/gocryptotrader/database/models/postgres"
@@ -34,6 +35,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/gctrpc"
"github.com/thrasher-corp/gocryptotrader/gctrpc/auth"
gctscript "github.com/thrasher-corp/gocryptotrader/gctscript/vm"
@@ -48,15 +50,18 @@ import (
)
const (
errExchangeNameUnset = "exchange name unset"
errCurrencyPairUnset = "currency pair unset"
errAssetTypeUnset = "asset type unset"
errDispatchSystem = "dispatch system offline"
errExchangeNameUnset = "exchange name unset"
errCurrencyPairUnset = "currency pair unset"
errStartEndTimesUnset = "invalid start and end times"
errAssetTypeUnset = "asset type unset"
errDispatchSystem = "dispatch system offline"
invalidArguments = "invalid arguments received"
)
var (
errExchangeNotLoaded = errors.New("exchange is not loaded/doesn't exist")
errExchangeBaseNotFound = errors.New("cannot get exchange base")
errInvalidArguments = errors.New(invalidArguments)
)
// RPCServer struct
@@ -1246,13 +1251,13 @@ func (s *RPCServer) WithdrawalEventsByDate(_ context.Context, r *gctrpc.Withdraw
if err != nil {
return nil, err
}
UTCSEndTime, err := time.Parse(common.SimpleTimeFormat, r.End)
var UTCEndTime time.Time
UTCEndTime, err = time.Parse(common.SimpleTimeFormat, r.End)
if err != nil {
return nil, err
}
ret, err := WithdrawEventByDate(r.Exchange, UTCStartTime, UTCSEndTime, int(r.Limit))
var ret []*withdraw.Response
ret, err = WithdrawEventByDate(r.Exchange, UTCStartTime, UTCEndTime, int(r.Limit))
if err != nil {
return nil, err
}
@@ -1295,7 +1300,6 @@ func (s *RPCServer) GetExchangePairs(_ context.Context, r *gctrpc.GetExchangePai
if err != nil {
return nil, err
}
assetTypes := exchCfg.CurrencyPairs.GetAssetTypes()
var a asset.Item
@@ -1635,14 +1639,11 @@ func (s *RPCServer) GetAuditEvent(_ context.Context, r *gctrpc.GetAuditEventRequ
return nil, err
}
UTCSEndTime, err := time.Parse(common.SimpleTimeFormat, r.EndDate)
UTCEndTime, err := time.Parse(common.SimpleTimeFormat, r.EndDate)
if err != nil {
return nil, err
}
loc := time.FixedZone("", int(r.Offset))
events, err := audit.GetEvent(UTCStartTime, UTCSEndTime, r.OrderBy, int(r.Limit))
events, err := audit.GetEvent(UTCStartTime, UTCEndTime, r.OrderBy, int(r.Limit))
if err != nil {
return nil, err
}
@@ -1656,7 +1657,7 @@ func (s *RPCServer) GetAuditEvent(_ context.Context, r *gctrpc.GetAuditEventRequ
Type: v[x].Type,
Identifier: v[x].Identifier,
Message: v[x].Message,
Timestamp: v[x].CreatedAt.In(loc).Format(common.SimpleTimeFormat),
Timestamp: v[x].CreatedAt.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone),
}
resp.Events = append(resp.Events, tempEvent)
@@ -1677,79 +1678,103 @@ func (s *RPCServer) GetAuditEvent(_ context.Context, r *gctrpc.GetAuditEventRequ
}
// GetHistoricCandles returns historical candles for a given exchange
func (s *RPCServer) GetHistoricCandles(_ context.Context, req *gctrpc.GetHistoricCandlesRequest) (*gctrpc.GetHistoricCandlesResponse, error) {
if req.Exchange == "" {
func (s *RPCServer) GetHistoricCandles(_ context.Context, r *gctrpc.GetHistoricCandlesRequest) (*gctrpc.GetHistoricCandlesResponse, error) {
if r.Exchange == "" {
return nil, errors.New(errExchangeNameUnset)
}
if req.Pair.String() == "" {
if r.Pair.String() == "" {
return nil, errors.New(errCurrencyPairUnset)
}
var candles kline.Item
var err error
resp := gctrpc.GetHistoricCandlesResponse{
Interval: kline.Interval(req.TimeInterval).Short(),
Pair: req.Pair,
Start: req.Start,
End: req.End,
if r.Start == r.End {
return nil, errors.New(errStartEndTimesUnset)
}
a, err := asset.New(req.AssetType)
var klineItem kline.Item
UTCStartTime, err := time.Parse(common.SimpleTimeFormat, r.Start)
if err != nil {
return nil, err
}
var UTCEndTime time.Time
UTCEndTime, err = time.Parse(common.SimpleTimeFormat, r.End)
if err != nil {
return nil, err
}
interval := kline.Interval(r.TimeInterval)
resp := gctrpc.GetHistoricCandlesResponse{
Interval: interval.Short(),
Pair: r.Pair,
Start: r.Start,
End: r.End,
}
a, err := asset.New(r.AssetType)
if err != nil {
return nil, err
}
pair := currency.Pair{
Delimiter: req.Pair.Delimiter,
Base: currency.NewCode(req.Pair.Base),
Quote: currency.NewCode(req.Pair.Quote),
Delimiter: r.Pair.Delimiter,
Base: currency.NewCode(r.Pair.Base),
Quote: currency.NewCode(r.Pair.Quote),
}
if req.UseDb {
candles, err = kline.LoadFromDatabase(req.Exchange,
if r.UseDb {
klineItem, err = kline.LoadFromDatabase(r.Exchange,
pair,
a,
kline.Interval(req.TimeInterval),
time.Unix(req.Start, 0),
time.Unix(req.End, 0),
)
} else {
exchangeEngine := s.GetExchangeByName(req.Exchange)
if exchangeEngine == nil {
return nil, errors.New("Exchange " + req.Exchange + " not found")
interval,
UTCStartTime,
UTCEndTime)
if err != nil {
return nil, err
}
if req.ExRequest {
candles, err = exchangeEngine.GetHistoricCandlesExtended(pair,
} else {
exchangeEngine := s.GetExchangeByName(r.Exchange)
if exchangeEngine == nil {
return nil, errors.New("Exchange " + r.Exchange + " not found")
}
if r.ExRequest {
klineItem, err = exchangeEngine.GetHistoricCandlesExtended(pair,
a,
time.Unix(req.Start, 0),
time.Unix(req.End, 0),
kline.Interval(req.TimeInterval))
UTCStartTime,
UTCEndTime,
interval)
} else {
candles, err = exchangeEngine.GetHistoricCandles(pair,
klineItem, err = exchangeEngine.GetHistoricCandles(pair,
a,
time.Unix(req.Start, 0),
time.Unix(req.End, 0),
kline.Interval(req.TimeInterval))
UTCStartTime,
UTCEndTime,
interval)
}
}
if err != nil {
return nil, err
}
resp.Exchange = candles.Exchange
for i := range candles.Candles {
if r.FillMissingWithTrades {
var tradeDataKline *kline.Item
tradeDataKline, err = fillMissingCandlesWithStoredTrades(UTCStartTime, UTCEndTime, &klineItem)
if err != nil {
return nil, err
}
klineItem.Candles = append(klineItem.Candles, tradeDataKline.Candles...)
}
resp.Exchange = klineItem.Exchange
for i := range klineItem.Candles {
resp.Candle = append(resp.Candle, &gctrpc.Candle{
Time: candles.Candles[i].Time.Unix(),
Low: candles.Candles[i].Low,
High: candles.Candles[i].High,
Open: candles.Candles[i].Open,
Close: candles.Candles[i].Close,
Volume: candles.Candles[i].Volume,
Time: klineItem.Candles[i].Time.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone),
Low: klineItem.Candles[i].Low,
High: klineItem.Candles[i].High,
Open: klineItem.Candles[i].Open,
Close: klineItem.Candles[i].Close,
Volume: klineItem.Candles[i].Volume,
})
}
if req.Sync && !req.UseDb {
_, err = kline.StoreInDatabase(&candles)
if r.Sync && !r.UseDb {
_, err = kline.StoreInDatabase(&klineItem, r.Force)
if err != nil {
if errors.Is(err, exchangeDB.ErrNoExchangeFound) {
return nil, errors.New("exchange was not found in database, you can seed existing data or insert a new exchange via the dbseed")
@@ -1757,9 +1782,66 @@ func (s *RPCServer) GetHistoricCandles(_ context.Context, req *gctrpc.GetHistori
return nil, err
}
}
return &resp, nil
}
func fillMissingCandlesWithStoredTrades(startTime, endTime time.Time, klineItem *kline.Item) (*kline.Item, error) {
var response kline.Item
var candleTimes []time.Time
for i := range klineItem.Candles {
candleTimes = append(candleTimes, klineItem.Candles[i].Time)
}
ranges, err := timeperiods.FindTimeRangesContainingData(startTime, endTime, klineItem.Interval.Duration(), candleTimes)
if err != nil {
return nil, err
}
for i := range ranges {
if ranges[i].HasDataInRange {
continue
}
var tradeCandles kline.Item
trades, err := trade.GetTradesInRange(
klineItem.Exchange,
klineItem.Asset.String(),
klineItem.Pair.Base.String(),
klineItem.Pair.Quote.String(),
ranges[i].StartOfRange,
ranges[i].EndOfRange,
)
if err != nil {
return klineItem, err
}
if len(trades) == 0 {
continue
}
tradeCandles, err = trade.ConvertTradesToCandles(klineItem.Interval, trades...)
if err != nil {
return klineItem, err
}
if len(tradeCandles.Candles) == 0 {
continue
}
for i := range tradeCandles.Candles {
response.Candles = append(response.Candles, tradeCandles.Candles[i])
}
for i := range response.Candles {
log.Infof(log.GRPCSys,
"Filled requested OHLCV data for %v %v %v interval at %v with trade data",
klineItem.Exchange,
klineItem.Pair.String(),
klineItem.Asset,
response.Candles[i].Time.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone),
)
}
}
return &response, nil
}
// GCTScriptStatus returns a slice of current running scripts that includes next run time and uuid
func (s *RPCServer) GCTScriptStatus(_ context.Context, r *gctrpc.GCTScriptStatusRequest) (*gctrpc.GCTScriptStatusResponse, error) {
if !gctscript.GCTScriptConfig.Enabled {
@@ -2313,3 +2395,485 @@ func (s *RPCServer) WebsocketSetURL(_ context.Context, r *gctrpc.WebsocketSetURL
r.Exchange,
r.Url)}, nil
}
// GetSavedTrades returns trades from the database
func (s *RPCServer) GetSavedTrades(_ context.Context, r *gctrpc.GetSavedTradesRequest) (*gctrpc.SavedTradesResponse, error) {
if r.End == "" || r.Start == "" || r.Exchange == "" || r.Pair == nil || r.AssetType == "" || r.Pair.String() == "" {
return nil, errInvalidArguments
}
exch := s.GetExchangeByName(r.Exchange)
if exch == nil {
return nil, errExchangeNotLoaded
}
cp, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
if err != nil {
return nil, err
}
a := asset.Item(r.AssetType)
if !a.IsValid() {
return nil, errors.New("invalid asset")
}
var pairs currency.Pairs
pairs, err = exch.GetEnabledPairs(a)
if err != nil {
return nil, err
}
if !pairs.Contains(cp, false) {
return nil, errors.New("currency not enabled")
}
var UTCStartTime, UTCEndTime time.Time
UTCStartTime, err = time.Parse(common.SimpleTimeFormat, r.Start)
if err != nil {
return nil, err
}
UTCEndTime, err = time.Parse(common.SimpleTimeFormat, r.End)
if err != nil {
return nil, err
}
var trades []trade.Data
trades, err = trade.GetTradesInRange(r.Exchange, r.AssetType, r.Pair.Base, r.Pair.Quote, UTCStartTime, UTCEndTime)
if err != nil {
return nil, err
}
resp := &gctrpc.SavedTradesResponse{
ExchangeName: r.Exchange,
Asset: r.AssetType,
Pair: r.Pair,
}
for i := range trades {
resp.Trades = append(resp.Trades, &gctrpc.SavedTrades{
Price: trades[i].Price,
Amount: trades[i].Amount,
Side: trades[i].Side.String(),
Timestamp: trades[i].Timestamp.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone),
TradeId: trades[i].TID,
})
}
if len(resp.Trades) == 0 {
return nil, fmt.Errorf("request for %v %v trade data between %v and %v and returned no results", r.Exchange, r.AssetType, r.Start, r.End)
}
return resp, nil
}
// ConvertTradesToCandles converts trades to candles using the interval requested
// returns the data too for extra fun scrutiny
func (s *RPCServer) ConvertTradesToCandles(_ context.Context, r *gctrpc.ConvertTradesToCandlesRequest) (*gctrpc.GetHistoricCandlesResponse, error) {
if r.End == "" || r.Start == "" || r.Exchange == "" || r.Pair == nil || r.AssetType == "" || r.Pair.String() == "" || r.TimeInterval == 0 {
return nil, errInvalidArguments
}
exch := s.GetExchangeByName(r.Exchange)
UTCStartTime, err := time.Parse(common.SimpleTimeFormat, r.Start)
if err != nil {
return nil, err
}
var UTCEndTime time.Time
UTCEndTime, err = time.Parse(common.SimpleTimeFormat, r.End)
if err != nil {
return nil, err
}
if exch == nil {
return nil, errExchangeNotLoaded
}
var cp currency.Pair
cp, err = currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
if err != nil {
return nil, err
}
a := asset.Item(r.AssetType)
if !a.IsValid() {
return nil, errors.New("invalid asset")
}
var pairs currency.Pairs
pairs, err = exch.GetEnabledPairs(a)
if err != nil {
return nil, err
}
if !pairs.Contains(cp, false) {
return nil, errors.New("currency not enabled")
}
var trades []trade.Data
trades, err = trade.GetTradesInRange(r.Exchange, r.AssetType, r.Pair.Base, r.Pair.Quote, UTCStartTime, UTCEndTime)
if err != nil {
return nil, err
}
if len(trades) == 0 {
return nil, fmt.Errorf("no trades returned from supplied params")
}
interval := kline.Interval(r.TimeInterval)
var klineItem kline.Item
klineItem, err = trade.ConvertTradesToCandles(interval, trades...)
if err != nil {
return nil, err
}
if len(klineItem.Candles) == 0 {
return nil, fmt.Errorf("no candles generated from trades")
}
resp := &gctrpc.GetHistoricCandlesResponse{
Exchange: r.Exchange,
Pair: r.Pair,
Start: r.Start,
End: r.End,
Interval: interval.String(),
}
for i := range klineItem.Candles {
resp.Candle = append(resp.Candle, &gctrpc.Candle{
Time: klineItem.Candles[i].Time.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone),
Low: klineItem.Candles[i].Low,
High: klineItem.Candles[i].High,
Open: klineItem.Candles[i].Open,
Close: klineItem.Candles[i].Close,
Volume: klineItem.Candles[i].Volume,
})
}
if r.Sync {
_, err = kline.StoreInDatabase(&klineItem, r.Force)
if err != nil {
return nil, err
}
}
return resp, nil
}
// FindMissingSavedCandleIntervals is used to help determine what candle data is missing
func (s *RPCServer) FindMissingSavedCandleIntervals(_ context.Context, r *gctrpc.FindMissingCandlePeriodsRequest) (*gctrpc.FindMissingIntervalsResponse, error) {
if r.End == "" || r.Start == "" || r.ExchangeName == "" || r.Pair == nil || r.AssetType == "" || r.Pair.String() == "" || r.Interval <= 0 {
return nil, errInvalidArguments
}
exch := s.GetExchangeByName(r.ExchangeName)
if exch == nil {
return nil, errExchangeNotLoaded
}
cp, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
if err != nil {
return nil, err
}
a := asset.Item(r.AssetType)
if !a.IsValid() {
return nil, errors.New("invalid asset")
}
var pairs currency.Pairs
pairs, err = exch.GetEnabledPairs(a)
if err != nil {
return nil, err
}
if !pairs.Contains(cp, false) {
return nil, errors.New("currency not enabled")
}
var UTCStartTime, UTCEndTime time.Time
UTCStartTime, err = time.Parse(common.SimpleTimeFormat, r.Start)
if err != nil {
return nil, err
}
UTCEndTime, err = time.Parse(common.SimpleTimeFormat, r.End)
if err != nil {
return nil, err
}
klineItem, err := kline.LoadFromDatabase(
r.ExchangeName,
currency.Pair{
Delimiter: r.Pair.Delimiter,
Base: currency.NewCode(r.Pair.Base),
Quote: currency.NewCode(r.Pair.Quote),
},
asset.Item(strings.ToLower(r.AssetType)),
kline.Interval(r.Interval),
UTCStartTime,
UTCEndTime,
)
if err != nil {
return nil, err
}
resp := &gctrpc.FindMissingIntervalsResponse{
ExchangeName: r.ExchangeName,
AssetType: r.AssetType,
Pair: r.Pair,
MissingPeriods: []string{},
}
var candleTimes []time.Time
for i := range klineItem.Candles {
candleTimes = append(candleTimes, klineItem.Candles[i].Time)
}
var ranges []timeperiods.TimeRange
ranges, err = timeperiods.FindTimeRangesContainingData(UTCStartTime, UTCEndTime, klineItem.Interval.Duration(), candleTimes)
if err != nil {
return nil, err
}
foundCount := 0
for i := range ranges {
if !ranges[i].HasDataInRange {
resp.MissingPeriods = append(resp.MissingPeriods,
ranges[i].StartOfRange.UTC().Format(common.SimpleTimeFormatWithTimezone)+
" - "+
ranges[i].EndOfRange.UTC().Format(common.SimpleTimeFormatWithTimezone))
} else {
foundCount++
}
}
if len(resp.MissingPeriods) == 0 {
resp.Status = fmt.Sprintf("no missing candles found between %v and %v",
r.Start,
r.End,
)
} else {
resp.Status = fmt.Sprintf("Found %v candles. Missing %v candles in requested timeframe starting %v ending %v",
foundCount,
len(resp.MissingPeriods),
UTCStartTime.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone),
UTCEndTime.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone))
}
return resp, nil
}
// FindMissingSavedTradeIntervals is used to help determine what trade data is missing
func (s *RPCServer) FindMissingSavedTradeIntervals(_ context.Context, r *gctrpc.FindMissingTradePeriodsRequest) (*gctrpc.FindMissingIntervalsResponse, error) {
if r.End == "" || r.Start == "" || r.ExchangeName == "" || r.Pair == nil || r.AssetType == "" || r.Pair.String() == "" {
return nil, errInvalidArguments
}
exch := s.GetExchangeByName(r.ExchangeName)
if exch == nil {
return nil, errExchangeNotLoaded
}
var err error
var UTCStartTime, UTCEndTime time.Time
UTCStartTime, err = time.Parse(common.SimpleTimeFormat, r.Start)
if err != nil {
return nil, err
}
UTCStartTime = UTCStartTime.Truncate(time.Hour)
UTCEndTime, err = time.Parse(common.SimpleTimeFormat, r.End)
if err != nil {
return nil, err
}
UTCEndTime = UTCEndTime.Truncate(time.Hour)
intervalMap := make(map[time.Time]bool)
iterationTime := UTCStartTime
for iterationTime.Before(UTCEndTime) {
intervalMap[iterationTime] = false
iterationTime = iterationTime.Add(time.Hour)
}
var cp currency.Pair
cp, err = currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
if err != nil {
return nil, err
}
a := asset.Item(r.AssetType)
if !a.IsValid() {
return nil, errors.New("invalid asset")
}
var pairs currency.Pairs
pairs, err = exch.GetEnabledPairs(a)
if err != nil {
return nil, err
}
if !pairs.Contains(cp, false) {
return nil, errors.New("currency not enabled")
}
var trades []trade.Data
trades, err = trade.GetTradesInRange(
r.ExchangeName,
r.AssetType,
r.Pair.Base,
r.Pair.Quote,
UTCStartTime,
UTCEndTime,
)
if err != nil {
return nil, err
}
resp := &gctrpc.FindMissingIntervalsResponse{
ExchangeName: r.ExchangeName,
AssetType: r.AssetType,
Pair: r.Pair,
MissingPeriods: []string{},
}
var tradeTimes []time.Time
for i := range trades {
tradeTimes = append(tradeTimes, trades[i].Timestamp)
}
var ranges []timeperiods.TimeRange
ranges, err = timeperiods.FindTimeRangesContainingData(UTCStartTime, UTCEndTime, time.Hour, tradeTimes)
if err != nil {
return nil, err
}
foundCount := 0
for i := range ranges {
if !ranges[i].HasDataInRange {
resp.MissingPeriods = append(resp.MissingPeriods,
ranges[i].StartOfRange.UTC().Format(common.SimpleTimeFormatWithTimezone)+
" - "+
ranges[i].EndOfRange.UTC().Format(common.SimpleTimeFormatWithTimezone))
} else {
foundCount++
}
}
if len(resp.MissingPeriods) == 0 {
resp.Status = fmt.Sprintf("no missing periods found between %v and %v",
r.Start,
r.End,
)
} else {
resp.Status = fmt.Sprintf("Found %v periods. Missing %v periods between %v and %v",
foundCount,
len(resp.MissingPeriods),
UTCStartTime.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone),
UTCEndTime.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone))
}
return resp, nil
}
// SetExchangeTradeProcessing allows the setting of exchange trade processing
func (s *RPCServer) SetExchangeTradeProcessing(_ context.Context, r *gctrpc.SetExchangeTradeProcessingRequest) (*gctrpc.GenericResponse, error) {
exch := s.GetExchangeByName(r.Exchange)
if exch == nil {
return nil, errExchangeNotLoaded
}
b := exch.GetBase()
b.SetSaveTradeDataStatus(r.Status)
return &gctrpc.GenericResponse{
Status: "success",
}, nil
}
// GetHistoricTrades returns trades between a set of dates
func (s *RPCServer) GetHistoricTrades(r *gctrpc.GetSavedTradesRequest, stream gctrpc.GoCryptoTrader_GetHistoricTradesServer) error {
if r.Exchange == "" || r.Pair == nil || r.AssetType == "" || r.Pair.String() == "" {
return errInvalidArguments
}
exch := s.GetExchangeByName(r.Exchange)
if exch == nil {
return errExchangeNotLoaded
}
cp, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
if err != nil {
return err
}
a := asset.Item(r.AssetType)
if !a.IsValid() {
return errors.New("invalid asset")
}
var pairs currency.Pairs
pairs, err = exch.GetEnabledPairs(a)
if err != nil {
return err
}
if !pairs.Contains(cp, false) {
return errors.New("currency not enabled")
}
var trades []trade.Data
var UTCStartTime, UTCEndTime time.Time
UTCStartTime, err = time.Parse(common.SimpleTimeFormat, r.Start)
if err != nil {
return err
}
UTCEndTime, err = time.Parse(common.SimpleTimeFormat, r.End)
if err != nil {
return err
}
resp := &gctrpc.SavedTradesResponse{
ExchangeName: r.Exchange,
Asset: r.AssetType,
Pair: r.Pair,
}
iterateStartTime := UTCStartTime
iterateEndTime := iterateStartTime.Add(time.Hour)
for iterateStartTime.Before(UTCEndTime) {
trades, err = exch.GetHistoricTrades(cp, asset.Item(r.AssetType), iterateStartTime, iterateEndTime)
if err != nil {
return err
}
grpcTrades := &gctrpc.SavedTradesResponse{
ExchangeName: r.Exchange,
Asset: r.AssetType,
Pair: r.Pair,
}
for i := range trades {
tradeTS := trades[i].Timestamp.In(time.UTC)
if tradeTS.After(UTCEndTime) {
break
}
grpcTrades.Trades = append(grpcTrades.Trades, &gctrpc.SavedTrades{
Price: trades[i].Price,
Amount: trades[i].Amount,
Side: trades[i].Side.String(),
Timestamp: tradeTS.Format(common.SimpleTimeFormatWithTimezone),
TradeId: trades[i].TID,
})
}
stream.Send(grpcTrades)
iterateStartTime = iterateStartTime.Add(time.Hour)
iterateEndTime = iterateEndTime.Add(time.Hour)
}
stream.Send(resp)
return nil
}
// GetRecentTrades returns trades
func (s *RPCServer) GetRecentTrades(_ context.Context, r *gctrpc.GetSavedTradesRequest) (*gctrpc.SavedTradesResponse, error) {
if r.Exchange == "" || r.Pair == nil || r.AssetType == "" || r.Pair.String() == "" {
return nil, errInvalidArguments
}
exch := s.GetExchangeByName(r.Exchange)
if exch == nil {
return nil, errExchangeNotLoaded
}
cp, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
if err != nil {
return nil, err
}
a := asset.Item(r.AssetType)
if !a.IsValid() {
return nil, errors.New("invalid asset")
}
var pairs currency.Pairs
pairs, err = exch.GetEnabledPairs(a)
if err != nil {
return nil, err
}
if !pairs.Contains(cp, false) {
return nil, errors.New("currency not enabled")
}
var trades []trade.Data
trades, err = exch.GetRecentTrades(cp, asset.Item(r.AssetType))
if err != nil {
return nil, err
}
resp := &gctrpc.SavedTradesResponse{
ExchangeName: r.Exchange,
Asset: r.AssetType,
Pair: r.Pair,
}
for i := range trades {
resp.Trades = append(resp.Trades, &gctrpc.SavedTrades{
Price: trades[i].Price,
Amount: trades[i].Amount,
Side: trades[i].Side.String(),
Timestamp: trades[i].Timestamp.In(time.UTC).Format(common.SimpleTimeFormatWithTimezone),
TradeId: trades[i].TID,
})
}
if len(resp.Trades) == 0 {
return nil, fmt.Errorf("request for %v %v trade data and returned no results", r.Exchange, r.AssetType)
}
return resp, nil
}

787
engine/rpcserver_test.go Normal file
View File

@@ -0,0 +1,787 @@
package engine
import (
"context"
"errors"
"log"
"os"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/database"
"github.com/thrasher-corp/gocryptotrader/database/drivers"
"github.com/thrasher-corp/gocryptotrader/database/repository"
"github.com/thrasher-corp/gocryptotrader/database/repository/exchange"
sqltrade "github.com/thrasher-corp/gocryptotrader/database/repository/trade"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/gctrpc"
"github.com/thrasher-corp/goose"
)
const (
unexpectedLackOfError = "unexpected lack of error"
migrationsFolder = "migrations"
databaseFolder = "database"
databaseName = "rpctestdb"
)
// Sets up everything required to run any function inside rpcserver
func RPCTestSetup(t *testing.T) *Engine {
database.DB.Mu.Lock()
var err error
dbConf := database.Config{
Enabled: true,
Driver: database.DBSQLite3,
ConnectionDetails: drivers.ConnectionDetails{
Database: databaseName,
},
}
engerino := new(Engine)
engerino.Config = &config.Config{}
err = engerino.Config.LoadConfig(config.TestFile, true)
if err != nil {
t.Fatalf("SetupTest: Failed to load config: %s", err)
}
if engerino.GetExchangeByName(testExchange) == nil {
err = engerino.LoadExchange(testExchange, false, nil)
if err != nil {
t.Fatalf("SetupTest: Failed to load exchange: %s", err)
}
}
engerino.Config.Database = dbConf
err = engerino.DatabaseManager.Start(engerino)
if err != nil {
log.Fatal(err)
}
path := filepath.Join("..", databaseFolder, migrationsFolder)
err = goose.Run("up", dbConn.SQL, repository.GetSQLDialect(), path, "")
if err != nil {
t.Fatalf("failed to run migrations %v", err)
}
uuider, _ := uuid.NewV4()
err = exchange.Insert(exchange.Details{Name: testExchange, UUID: uuider})
if err != nil {
t.Fatalf("failed to insert exchange %v", err)
}
database.DB.Mu.Unlock()
return engerino
}
func CleanRPCTest(t *testing.T, engerino *Engine) {
database.DB.Mu.Lock()
defer database.DB.Mu.Unlock()
err := engerino.DatabaseManager.Stop()
if err != nil {
t.Error(err)
return
}
err = os.Remove(filepath.Join(common.GetDefaultDataDir(runtime.GOOS), databaseFolder, databaseName))
if err != nil {
t.Error(err)
}
}
func TestGetSavedTrades(t *testing.T) {
engerino := RPCTestSetup(t)
defer CleanRPCTest(t, engerino)
s := RPCServer{engerino}
_, err := s.GetSavedTrades(context.Background(), &gctrpc.GetSavedTradesRequest{})
if err == nil {
t.Fatal(unexpectedLackOfError)
}
if !errors.Is(err, errInvalidArguments) {
t.Error(err)
}
_, err = s.GetSavedTrades(context.Background(), &gctrpc.GetSavedTradesRequest{
Exchange: "fake",
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
})
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if err != errExchangeNotLoaded {
t.Error(err)
}
_, err = s.GetSavedTrades(context.Background(), &gctrpc.GetSavedTradesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
})
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if err.Error() != "request for Bitstamp spot trade data between 2019-11-30 00:00:00 and 2020-01-01 01:01:01 and returned no results" {
t.Error(err)
}
err = sqltrade.Insert(sqltrade.Data{
Timestamp: time.Date(2020, 0, 0, 0, 0, 1, 0, time.UTC),
Exchange: testExchange,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
AssetType: asset.Spot.String(),
Price: 1337,
Amount: 1337,
Side: order.Buy.String(),
})
if err != nil {
t.Error(err)
return
}
_, err = s.GetSavedTrades(context.Background(), &gctrpc.GetSavedTradesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
})
if err != nil {
t.Error(err)
}
}
func TestConvertTradesToCandles(t *testing.T) {
engerino := RPCTestSetup(t)
defer CleanRPCTest(t, engerino)
s := RPCServer{engerino}
// bad param test
_, err := s.ConvertTradesToCandles(context.Background(), &gctrpc.ConvertTradesToCandlesRequest{})
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if !errors.Is(err, errInvalidArguments) {
t.Error(err)
}
// bad exchange test
_, err = s.ConvertTradesToCandles(context.Background(), &gctrpc.ConvertTradesToCandlesRequest{
Exchange: "faker",
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
TimeInterval: int64(kline.OneHour.Duration()),
})
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if err != errExchangeNotLoaded {
t.Error(err)
}
// no trades test
_, err = s.ConvertTradesToCandles(context.Background(), &gctrpc.ConvertTradesToCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 2, 2, 2, 2, 2, 2, time.UTC).Format(common.SimpleTimeFormat),
TimeInterval: int64(kline.OneHour.Duration()),
})
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if err.Error() != "no trades returned from supplied params" {
t.Error(err)
}
// add a trade
err = sqltrade.Insert(sqltrade.Data{
Timestamp: time.Date(2020, 1, 1, 1, 1, 2, 1, time.UTC),
Exchange: testExchange,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
AssetType: asset.Spot.String(),
Price: 1337,
Amount: 1337,
Side: order.Buy.String(),
})
if err != nil {
t.Error(err)
return
}
// get candle from one trade
var candles *gctrpc.GetHistoricCandlesResponse
candles, err = s.ConvertTradesToCandles(context.Background(), &gctrpc.ConvertTradesToCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 2, 2, 2, 2, 2, 2, time.UTC).Format(common.SimpleTimeFormat),
TimeInterval: int64(kline.OneHour.Duration()),
})
if err != nil {
t.Error(err)
}
if len(candles.Candle) == 0 {
t.Error("no candles returned")
}
// save generated candle to database
_, err = s.ConvertTradesToCandles(context.Background(), &gctrpc.ConvertTradesToCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 2, 2, 2, 2, 2, 2, time.UTC).Format(common.SimpleTimeFormat),
TimeInterval: int64(kline.OneHour.Duration()),
Sync: true,
})
if err != nil {
t.Error(err)
}
// forcefully remove previous candle and insert a new one
_, err = s.ConvertTradesToCandles(context.Background(), &gctrpc.ConvertTradesToCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 2, 2, 2, 2, 2, 2, time.UTC).Format(common.SimpleTimeFormat),
TimeInterval: int64(kline.OneHour.Duration()),
Sync: true,
Force: true,
})
if err != nil {
t.Error(err)
}
// load the saved candle to verify that it was overwritten
candles, err = s.GetHistoricCandles(context.Background(), &gctrpc.GetHistoricCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 2, 2, 2, 2, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
TimeInterval: int64(kline.OneHour.Duration()),
UseDb: true,
})
if err != nil {
t.Error(err)
}
if len(candles.Candle) != 1 {
t.Error("expected only one candle")
}
}
func TestGetHistoricCandles(t *testing.T) {
engerino := RPCTestSetup(t)
defer CleanRPCTest(t, engerino)
s := RPCServer{engerino}
// error checks
_, err := s.GetHistoricCandles(context.Background(), &gctrpc.GetHistoricCandlesRequest{
Exchange: "",
})
if err != nil && err.Error() != errExchangeNameUnset {
t.Error(err)
}
_, err = s.GetHistoricCandles(context.Background(), &gctrpc.GetHistoricCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{},
})
if err != nil && err.Error() != errCurrencyPairUnset {
t.Error(err)
}
_, err = s.GetHistoricCandles(context.Background(), &gctrpc.GetHistoricCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
})
if err != nil && err.Error() != errStartEndTimesUnset {
t.Error(err)
}
var results *gctrpc.GetHistoricCandlesResponse
defaultStart := time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC)
defaultEnd := time.Date(2020, 1, 2, 2, 2, 2, 2, time.UTC)
cp := currency.NewPair(currency.BTC, currency.USD)
// default run
results, err = s.GetHistoricCandles(context.Background(), &gctrpc.GetHistoricCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
Start: defaultStart.Format(common.SimpleTimeFormat),
End: defaultEnd.Format(common.SimpleTimeFormat),
AssetType: asset.Spot.String(),
TimeInterval: int64(kline.OneHour.Duration()),
})
if err != nil {
t.Error(err)
}
if len(results.Candle) == 0 {
t.Error("expected results")
}
// sync run
results, err = s.GetHistoricCandles(context.Background(), &gctrpc.GetHistoricCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
AssetType: asset.Spot.String(),
Start: defaultStart.Format(common.SimpleTimeFormat),
End: defaultEnd.Format(common.SimpleTimeFormat),
TimeInterval: int64(kline.OneHour.Duration()),
Sync: true,
ExRequest: true,
})
if err != nil {
t.Error(err)
}
if len(results.Candle) == 0 {
t.Error("expected results")
}
// db run
results, err = s.GetHistoricCandles(context.Background(), &gctrpc.GetHistoricCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
AssetType: asset.Spot.String(),
Start: defaultStart.Format(common.SimpleTimeFormat),
End: defaultEnd.Format(common.SimpleTimeFormat),
TimeInterval: int64(kline.OneHour.Duration()),
UseDb: true,
})
if err != nil {
t.Error(err)
}
if len(results.Candle) == 0 {
t.Error("expected results")
}
err = trade.SaveTradesToDatabase(trade.Data{
TID: "test123",
Exchange: testExchange,
CurrencyPair: cp,
AssetType: asset.Spot,
Price: 1337,
Amount: 1337,
Side: order.Buy,
Timestamp: time.Date(2020, 1, 2, 3, 1, 1, 7, time.UTC),
})
if err != nil {
t.Error(err)
return
}
// db run including trades
results, err = s.GetHistoricCandles(context.Background(), &gctrpc.GetHistoricCandlesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
AssetType: asset.Spot.String(),
Start: defaultStart.Format(common.SimpleTimeFormat),
End: time.Date(2020, 1, 2, 4, 2, 2, 2, time.UTC).Format(common.SimpleTimeFormat),
TimeInterval: int64(kline.OneHour.Duration()),
UseDb: true,
FillMissingWithTrades: true,
})
if err != nil {
t.Error(err)
}
if results.Candle[len(results.Candle)-1].Close != 1337 {
t.Error("expected fancy new candle based off fancy new trade data")
}
}
func TestFindMissingSavedTradeIntervals(t *testing.T) {
engerino := RPCTestSetup(t)
defer CleanRPCTest(t, engerino)
s := RPCServer{engerino}
// bad request checks
_, err := s.FindMissingSavedTradeIntervals(context.Background(), &gctrpc.FindMissingTradePeriodsRequest{})
if err == nil {
t.Error("expected error")
return
}
if !errors.Is(err, errInvalidArguments) {
t.Error(err)
return
}
cp := currency.NewPair(currency.BTC, currency.USD)
// no data found response
defaultStart := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).UTC()
defaultEnd := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC).UTC()
var resp *gctrpc.FindMissingIntervalsResponse
resp, err = s.FindMissingSavedTradeIntervals(context.Background(), &gctrpc.FindMissingTradePeriodsRequest{
ExchangeName: testExchange,
AssetType: asset.Spot.String(),
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
Start: defaultStart.UTC().Format(common.SimpleTimeFormat),
End: defaultEnd.UTC().Format(common.SimpleTimeFormat),
})
if err != nil {
t.Error(err)
}
if resp.Status == "" {
t.Errorf("expected a status message")
}
// one trade response
err = trade.SaveTradesToDatabase(trade.Data{
TID: "test1234",
Exchange: testExchange,
CurrencyPair: cp,
AssetType: asset.Spot,
Price: 1337,
Amount: 1337,
Side: order.Buy,
Timestamp: time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC),
})
if err != nil {
t.Error(err)
return
}
resp, err = s.FindMissingSavedTradeIntervals(context.Background(), &gctrpc.FindMissingTradePeriodsRequest{
ExchangeName: testExchange,
AssetType: asset.Spot.String(),
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
Start: defaultStart.In(time.UTC).Format(common.SimpleTimeFormat),
End: defaultEnd.In(time.UTC).Format(common.SimpleTimeFormat),
})
if err != nil {
t.Error(err)
}
if len(resp.MissingPeriods) != 2 {
t.Errorf("expected 2 missing period, received: %v", len(resp.MissingPeriods))
}
// two trades response
err = trade.SaveTradesToDatabase(trade.Data{
TID: "test123",
Exchange: testExchange,
CurrencyPair: cp,
AssetType: asset.Spot,
Price: 1337,
Amount: 1337,
Side: order.Buy,
Timestamp: time.Date(2020, 1, 1, 13, 0, 0, 0, time.UTC),
})
if err != nil {
t.Error(err)
return
}
resp, err = s.FindMissingSavedTradeIntervals(context.Background(), &gctrpc.FindMissingTradePeriodsRequest{
ExchangeName: testExchange,
AssetType: asset.Spot.String(),
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
Start: defaultStart.In(time.UTC).Format(common.SimpleTimeFormat),
End: defaultEnd.In(time.UTC).Format(common.SimpleTimeFormat),
})
if err != nil {
t.Error(err)
}
if len(resp.MissingPeriods) != 2 {
t.Errorf("expected 2 missing periods, received: %v", len(resp.MissingPeriods))
}
}
func TestFindMissingSavedCandleIntervals(t *testing.T) {
engerino := RPCTestSetup(t)
defer CleanRPCTest(t, engerino)
s := RPCServer{engerino}
// bad request checks
_, err := s.FindMissingSavedCandleIntervals(context.Background(), &gctrpc.FindMissingCandlePeriodsRequest{})
if err == nil {
t.Error("expected error")
return
}
if !errors.Is(err, errInvalidArguments) {
t.Error(err)
return
}
cp := currency.NewPair(currency.BTC, currency.USD)
// no data found response
defaultStart := time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC)
defaultEnd := time.Date(2020, 1, 2, 2, 2, 2, 2, time.UTC)
var resp *gctrpc.FindMissingIntervalsResponse
_, err = s.FindMissingSavedCandleIntervals(context.Background(), &gctrpc.FindMissingCandlePeriodsRequest{
ExchangeName: testExchange,
AssetType: asset.Spot.String(),
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
Interval: int64(time.Hour),
Start: defaultStart.Format(common.SimpleTimeFormat),
End: defaultEnd.Format(common.SimpleTimeFormat),
})
if err != nil && err.Error() != "no candle data found: Bitstamp BTC USD 3600 spot" {
t.Error(err)
return
}
// one candle missing periods response
_, err = kline.StoreInDatabase(&kline.Item{
Exchange: testExchange,
Pair: cp,
Asset: asset.Spot,
Interval: kline.OneHour,
Candles: []kline.Candle{
{
Time: time.Date(2020, 1, 1, 2, 1, 1, 1, time.UTC),
Open: 1337,
High: 1337,
Low: 1337,
Close: 1337,
Volume: 1337,
},
},
}, false)
if err != nil {
t.Error(err)
return
}
_, err = s.FindMissingSavedCandleIntervals(context.Background(), &gctrpc.FindMissingCandlePeriodsRequest{
ExchangeName: testExchange,
AssetType: asset.Spot.String(),
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
Interval: int64(time.Hour),
Start: defaultStart.Format(common.SimpleTimeFormat),
End: defaultEnd.Format(common.SimpleTimeFormat),
})
if err != nil {
t.Error(err)
}
// two candle missing periods response
_, err = kline.StoreInDatabase(&kline.Item{
Exchange: testExchange,
Pair: cp,
Asset: asset.Spot,
Interval: kline.OneHour,
Candles: []kline.Candle{
{
Time: time.Date(2020, 1, 1, 3, 1, 1, 1, time.UTC),
Open: 1337,
High: 1337,
Low: 1337,
Close: 1337,
Volume: 1337,
},
},
}, false)
if err != nil {
t.Error(err)
return
}
resp, err = s.FindMissingSavedCandleIntervals(context.Background(), &gctrpc.FindMissingCandlePeriodsRequest{
ExchangeName: testExchange,
AssetType: asset.Spot.String(),
Pair: &gctrpc.CurrencyPair{
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
Interval: int64(time.Hour),
Start: defaultStart.Format(common.SimpleTimeFormat),
End: defaultEnd.Format(common.SimpleTimeFormat),
})
if err != nil {
t.Error(err)
}
if len(resp.MissingPeriods) != 2 {
t.Errorf("expected 2 missing periods, received: %v", len(resp.MissingPeriods))
}
}
func TestSetExchangeTradeProcessing(t *testing.T) {
engerino := RPCTestSetup(t)
defer CleanRPCTest(t, engerino)
s := RPCServer{engerino}
_, err := s.SetExchangeTradeProcessing(context.Background(), &gctrpc.SetExchangeTradeProcessingRequest{Exchange: testExchange, Status: true})
if err != nil {
t.Error(err)
return
}
exch := s.GetExchangeByName(testExchange)
base := exch.GetBase()
if !base.IsSaveTradeDataEnabled() {
t.Error("expected true")
}
_, err = s.SetExchangeTradeProcessing(context.Background(), &gctrpc.SetExchangeTradeProcessingRequest{Exchange: testExchange, Status: false})
if err != nil {
t.Error(err)
return
}
exch = s.GetExchangeByName(testExchange)
base = exch.GetBase()
if base.IsSaveTradeDataEnabled() {
t.Error("expected false")
}
}
func TestGetRecentTrades(t *testing.T) {
engerino := RPCTestSetup(t)
defer CleanRPCTest(t, engerino)
s := RPCServer{engerino}
_, err := s.GetRecentTrades(context.Background(), &gctrpc.GetSavedTradesRequest{})
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if !errors.Is(err, errInvalidArguments) {
t.Error(err)
}
_, err = s.GetRecentTrades(context.Background(), &gctrpc.GetSavedTradesRequest{
Exchange: "fake",
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
})
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if err != errExchangeNotLoaded {
t.Error(err)
}
_, err = s.GetRecentTrades(context.Background(), &gctrpc.GetSavedTradesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
})
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
engerino := RPCTestSetup(t)
defer CleanRPCTest(t, engerino)
s := RPCServer{engerino}
err := s.GetHistoricTrades(&gctrpc.GetSavedTradesRequest{}, nil)
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if !errors.Is(err, errInvalidArguments) {
t.Error(err)
}
err = s.GetHistoricTrades(&gctrpc.GetSavedTradesRequest{
Exchange: "fake",
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
}, nil)
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if err != errExchangeNotLoaded {
t.Error(err)
}
err = s.GetHistoricTrades(&gctrpc.GetSavedTradesRequest{
Exchange: testExchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: currency.BTC.String(),
Quote: currency.USD.String(),
},
AssetType: asset.Spot.String(),
Start: time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC).Format(common.SimpleTimeFormat),
End: time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC).Format(common.SimpleTimeFormat),
}, nil)
if err == nil {
t.Error(unexpectedLackOfError)
return
}
if err != common.ErrFunctionNotSupported {
t.Error(err)
}
}

View File

@@ -20,7 +20,6 @@ func TestNewCurrencyPairSyncer(t *testing.T) {
}
Bot.Settings.DisableExchangeAutoPairUpdates = true
Bot.Settings.Verbose = true
Bot.Settings.EnableExchangeWebsocketSupport = true
Bot.SetupExchanges()

View File

@@ -5,6 +5,7 @@ import (
"log"
"os"
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/core"
@@ -595,3 +596,27 @@ func TestWithdrawInternationalBank(t *testing.T) {
t.Errorf("Expected '%v', received: '%v'", common.ErrNotYetImplemented, err)
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("btc_usdt")
if err != nil {
t.Fatal(err)
}
_, err = a.GetRecentTrades(currencyPair, asset.Spot)
if err != nil && err != common.ErrNotYetImplemented {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("btc_usdt")
if err != nil {
t.Fatal(err)
}
_, err = a.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrNotYetImplemented {
t.Error(err)
}
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -206,8 +207,13 @@ func (a *Alphapoint) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrNotYetImplemented
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (a *Alphapoint) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
// GetRecentTrades returns the most recent trades for a currency and asset
func (a *Alphapoint) GetRecentTrades(_ currency.Pair, _ asset.Item) ([]trade.Data, error) {
return nil, common.ErrNotYetImplemented
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (a *Alphapoint) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -135,9 +135,9 @@ func (b *Binance) GetOrderBook(obd OrderBookDataRequestParams) (OrderBook, error
return orderbook, nil
}
// GetRecentTrades returns recent trade activity
// GetMostRecentTrades returns recent trade activity
// limit: Up to 500 results returned
func (b *Binance) GetRecentTrades(rtr RecentTradeRequestParams) ([]RecentTrade, error) {
func (b *Binance) GetMostRecentTrades(rtr RecentTradeRequestParams) ([]RecentTrade, error) {
var resp []RecentTrade
params := url.Values{}

View File

@@ -66,16 +66,16 @@ func TestGetOrderBook(t *testing.T) {
}
}
func TestGetRecentTrades(t *testing.T) {
func TestGetMostRecentTrades(t *testing.T) {
t.Parallel()
_, err := b.GetRecentTrades(RecentTradeRequestParams{
_, err := b.GetMostRecentTrades(RecentTradeRequestParams{
Symbol: "BTCUSDT",
Limit: 15,
})
if err != nil {
t.Error("Binance GetRecentTrades() error", err)
t.Error("Binance GetMostRecentTrades() error", err)
}
}
@@ -977,3 +977,27 @@ func TestBinance_FormatExchangeKlineInterval(t *testing.T) {
})
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTCUSDT")
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTCUSDT")
if err != nil {
t.Fatal(err)
}
_, err = b.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrFunctionNotSupported {
t.Error(err)
}
}

View File

@@ -116,7 +116,7 @@ type RecentTrade struct {
ID int64 `json:"id"`
Price float64 `json:"price,string"`
Quantity float64 `json:"qty,string"`
Time float64 `json:"time"`
Time int64 `json:"time"`
IsBuyerMaker bool `json:"isBuyerMaker"`
IsBestMatch bool `json:"isBestMatch"`
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
@@ -264,41 +265,45 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
switch streamType[1] {
case "trade":
var trade TradeStream
err := json.Unmarshal(rawData, &trade)
if !b.IsSaveTradeDataEnabled() {
return nil
}
var t TradeStream
err := json.Unmarshal(rawData, &t)
if err != nil {
return fmt.Errorf("%v - Could not unmarshal trade data: %s",
b.Name,
err)
}
price, err := strconv.ParseFloat(trade.Price, 64)
price, err := strconv.ParseFloat(t.Price, 64)
if err != nil {
return fmt.Errorf("%v - price conversion error: %s",
b.Name,
err)
}
amount, err := strconv.ParseFloat(trade.Quantity, 64)
amount, err := strconv.ParseFloat(t.Quantity, 64)
if err != nil {
return fmt.Errorf("%v - amount conversion error: %s",
b.Name,
err)
}
pair, err := currency.NewPairFromFormattedPairs(trade.Symbol, pairs, format)
pair, err := currency.NewPairFromFormattedPairs(t.Symbol, pairs, format)
if err != nil {
return err
}
b.Websocket.DataHandler <- stream.TradeData{
return b.AddTradesToBuffer(trade.Data{
CurrencyPair: pair,
Timestamp: time.Unix(0, trade.TimeStamp*int64(time.Millisecond)),
Timestamp: time.Unix(0, t.TimeStamp*int64(time.Millisecond)),
Price: price,
Amount: amount,
Exchange: b.Name,
AssetType: asset.Spot,
}
TID: strconv.FormatInt(t.TradeID, 10),
})
case "ticker":
var t TickerStream
err := json.Unmarshal(rawData, &t)

View File

@@ -2,6 +2,7 @@ package binance
import (
"errors"
"sort"
"strconv"
"strings"
"sync"
@@ -21,6 +22,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -496,9 +498,44 @@ func (b *Binance) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Binance) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
// GetRecentTrades returns the most recent trades for a currency and asset
func (b *Binance) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
var resp []trade.Data
limit := 1000
tradeData, err := b.GetMostRecentTrades(RecentTradeRequestParams{p.String(), limit})
if err != nil {
return nil, err
}
for i := range tradeData {
resp = append(resp, trade.Data{
TID: strconv.FormatInt(tradeData[i].ID, 10),
Exchange: b.Name,
CurrencyPair: p,
AssetType: assetType,
Price: tradeData[i].Price,
Amount: tradeData[i].Quantity,
Timestamp: time.Unix(0, tradeData[i].Time*int64(time.Millisecond)),
})
}
if b.IsSaveTradeDataEnabled() {
err := trade.AddTradesToBuffer(b.Name, resp...)
if err != nil {
return nil, err
}
}
sort.Sort(trade.ByDate(resp))
return resp, nil
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *Binance) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, common.ErrFunctionNotSupported
}
// SubmitOrder submits a new order

View File

@@ -223,10 +223,11 @@ func (b *Bitfinex) GetTrades(currencyPair string, limit, timestampStart, timesta
if timestampEnd > 0 {
v.Set("end", strconv.FormatInt(timestampEnd, 10))
}
sortVal := "0"
if reOrderResp {
v.Set("sort", strconv.FormatInt(-1, 10))
sortVal = "1"
}
v.Set("sort", sortVal)
path := b.API.Endpoints.URL +
bitfinexAPIVersion2 +

View File

@@ -1361,6 +1361,40 @@ func TestFixCasing(t *testing.T) {
if ret != "tTNBUSD" {
t.Errorf("unexpected result: %v", ret)
}
pair, err = currency.NewPairFromStrings("fUSD", "")
if err != nil {
t.Fatal(err)
}
ret, err = b.fixCasing(pair, asset.Margin)
if err != nil {
t.Fatal(err)
}
if ret != "fUSD" {
t.Errorf("unexpected result: %v", ret)
}
pair, err = currency.NewPairFromStrings("USD", "")
if err != nil {
t.Fatal(err)
}
ret, err = b.fixCasing(pair, asset.Margin)
if err != nil {
t.Fatal(err)
}
if ret != "fUSD" {
t.Errorf("unexpected result: %v", ret)
}
pair, err = currency.NewPairFromStrings("FUSD", "")
if err != nil {
t.Fatal(err)
}
ret, err = b.fixCasing(pair, asset.Margin)
if err != nil {
t.Fatal(err)
}
if ret != "fUSD" {
t.Errorf("unexpected result: %v", ret)
}
}
func Test_FormatExchangeKlineInterval(t *testing.T) {
@@ -1401,3 +1435,42 @@ func Test_FormatExchangeKlineInterval(t *testing.T) {
})
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTCUSD")
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
currencyPair, err = currency.NewPairFromString("USD")
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Margin)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTCUSD")
if err != nil {
t.Fatal(err)
}
_, err = b.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil {
t.Error(err)
}
// longer term test
_, err = b.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Hour*24*100), time.Now().Add(-time.Hour*24*99))
if err != nil {
t.Error(err)
}
}

View File

@@ -315,7 +315,7 @@ type MovementHistory struct {
type TradeHistory struct {
Price float64 `json:"price,string"`
Amount float64 `json:"amount,string"`
Timestamp string `json:"timestamp"`
Timestamp int64 `json:"timestamp"`
Exchange string `json:"exchange"`
Type string `json:"type"`
FeeCurrency string `json:"fee_currency"`

View File

@@ -20,6 +20,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
@@ -198,7 +199,9 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
if !ok {
return errors.New("orderbook interface cast failed")
}
if len(obSnapBundle) == 0 {
return errors.New("no data within orderbook snapshot")
}
switch id := obSnapBundle[0].(type) {
case []interface{}:
for i := range obSnapBundle {
@@ -296,14 +299,20 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
}
return nil
case wsTrades:
var trades []WebsocketTrade
if !b.IsSaveTradeDataEnabled() {
return nil
}
if chanAsset == asset.MarginFunding {
return nil
}
var tradeHolder []WebsocketTrade
switch len(d) {
case 2:
snapshot := d[1].([]interface{})
for i := range snapshot {
elem := snapshot[i].([]interface{})
if len(elem) == 5 {
trades = append(trades, WebsocketTrade{
tradeHolder = append(tradeHolder, WebsocketTrade{
ID: int64(elem[0].(float64)),
Timestamp: int64(elem[1].(float64)),
Amount: elem[2].(float64),
@@ -311,7 +320,7 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
Period: int64(elem[4].(float64)),
})
} else {
trades = append(trades, WebsocketTrade{
tradeHolder = append(tradeHolder, WebsocketTrade{
ID: int64(elem[0].(float64)),
Timestamp: int64(elem[1].(float64)),
Amount: elem[2].(float64),
@@ -320,16 +329,13 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
}
}
case 3:
if d[1].(string) == wsTradeExecutionUpdate ||
d[1].(string) == wsFundingTradeUpdate {
// "(f)te - trade executed" && "(f)tu - trade updated"
// contain the same amount of data
// "(f)te" gets sent first so we can drop "(f)tu"
if d[1].(string) != wsFundingTradeUpdate &&
d[1].(string) != wsTradeExecutionUpdate {
return nil
}
data := d[2].([]interface{})
if len(data) == 5 {
trades = append(trades, WebsocketTrade{
tradeHolder = append(tradeHolder, WebsocketTrade{
ID: int64(data[0].(float64)),
Timestamp: int64(data[1].(float64)),
Amount: data[2].(float64),
@@ -337,7 +343,7 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
Period: int64(data[4].(float64)),
})
} else {
trades = append(trades, WebsocketTrade{
tradeHolder = append(tradeHolder, WebsocketTrade{
ID: int64(data[0].(float64)),
Timestamp: int64(data[1].(float64)),
Amount: data[2].(float64),
@@ -345,40 +351,31 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
})
}
}
for i := range trades {
var trades []trade.Data
for i := range tradeHolder {
side := order.Buy
newAmount := trades[i].Amount
newAmount := tradeHolder[i].Amount
if newAmount < 0 {
side = order.Sell
newAmount *= -1
}
if trades[i].Rate > 0 {
b.Websocket.DataHandler <- stream.FundingData{
CurrencyPair: pair,
Timestamp: time.Unix(0, trades[i].Timestamp*int64(time.Millisecond)),
Amount: newAmount,
Exchange: b.Name,
AssetType: chanAsset,
Side: side,
Rate: trades[i].Rate,
Period: trades[i].Period,
}
continue
price := tradeHolder[i].Price
if price == 0 && tradeHolder[i].Rate > 0 {
price = tradeHolder[i].Rate
}
b.Websocket.DataHandler <- stream.TradeData{
trades = append(trades, trade.Data{
TID: strconv.FormatInt(tradeHolder[i].ID, 10),
CurrencyPair: pair,
Timestamp: time.Unix(0, trades[i].Timestamp*int64(time.Millisecond)),
Price: trades[i].Price,
Timestamp: time.Unix(0, tradeHolder[i].Timestamp*int64(time.Millisecond)),
Price: price,
Amount: newAmount,
Exchange: b.Name,
AssetType: chanAsset,
Side: side,
}
})
}
return nil
return b.AddTradesToBuffer(trades...)
}
if authResp, ok := d[1].(string); ok {

View File

@@ -2,6 +2,8 @@ package bitfinex
import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"sync"
@@ -21,6 +23,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -481,9 +484,74 @@ func (b *Bitfinex) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
// GetRecentTrades returns the most recent trades for a currency and asset
func (b *Bitfinex) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
return b.GetHistoricTrades(p, assetType, time.Now().Add(-time.Hour), time.Now())
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *Bitfinex) GetHistoricTrades(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
if assetType == asset.MarginFunding {
return nil, fmt.Errorf("asset type '%v' not supported", assetType)
}
if timestampStart.Equal(timestampEnd) || timestampEnd.After(time.Now()) || timestampEnd.Before(timestampStart) {
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v", timestampStart, timestampEnd)
}
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
var currString string
currString, err = b.fixCasing(p, assetType)
if err != nil {
return nil, err
}
var resp []trade.Data
ts := timestampEnd
limit := 10000
allTrades:
for {
var tradeData []Trade
tradeData, err = b.GetTrades(currString, int64(limit), 0, ts.Unix()*1000, false)
if err != nil {
return nil, err
}
for i := range tradeData {
tradeTS := time.Unix(0, tradeData[i].Timestamp*int64(time.Millisecond))
if tradeTS.Before(timestampStart) && !timestampStart.IsZero() {
break allTrades
}
tID := strconv.FormatInt(tradeData[i].TID, 10)
resp = append(resp, trade.Data{
TID: tID,
Exchange: b.Name,
CurrencyPair: p,
AssetType: assetType,
Price: tradeData[i].Price,
Amount: tradeData[i].Amount,
Timestamp: time.Unix(0, tradeData[i].Timestamp*int64(time.Millisecond)),
})
if i == len(tradeData)-1 {
if ts.Equal(tradeTS) {
// reached end of trades to crawl
break allTrades
}
ts = tradeTS
}
}
if len(tradeData) != limit {
break allTrades
}
}
err = b.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
sort.Sort(trade.ByDate(resp))
return trade.FilterTradesByTime(resp, timestampStart, timestampEnd), nil
}
// SubmitOrder submits a new order
@@ -965,10 +1033,16 @@ func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) (string, error) {
y := in.Base.String()
if (y[0] != checkString[0] && y[0] != checkString[1]) ||
(y[0] == checkString[1] && y[1] == checkString[1]) || in.Base == currency.TNB {
if fmt.Quote.IsEmpty() {
return string(checkString[0]) + fmt.Base.Upper().String(), nil
}
return string(checkString[0]) + fmt.Upper().String(), nil
}
runes := []rune(fmt.Upper().String())
if fmt.Quote.IsEmpty() {
runes = []rune(fmt.Base.Upper().String())
}
runes[0] = unicode.ToLower(runes[0])
return string(runes), nil
}

View File

@@ -4,6 +4,7 @@ import (
"log"
"os"
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
@@ -25,7 +26,6 @@ const (
var b Bitflyer
func TestMain(m *testing.M) {
b.SetDefaults()
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
@@ -39,7 +39,7 @@ func TestMain(m *testing.M) {
bitflyerConfig.API.AuthenticatedSupport = true
bitflyerConfig.API.Credentials.Key = apiKey
bitflyerConfig.API.Credentials.Secret = apiSecret
b.SetDefaults()
err = b.Setup(bitflyerConfig)
if err != nil {
log.Fatal("Bitflyer setup error", err)
@@ -431,3 +431,27 @@ func TestWithdrawInternationalBank(t *testing.T) {
t.Errorf("Expected '%v', received: '%v'", common.ErrNotYetImplemented, err)
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTC_JPY")
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTC_JPY")
if err != nil {
t.Fatal(err)
}
_, err = b.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrFunctionNotSupported {
t.Fatal(err)
}
}

View File

@@ -1,6 +1,8 @@
package bitflyer
import (
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -17,6 +19,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -301,9 +304,53 @@ func (b *Bitflyer) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bitflyer) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
// GetRecentTrades returns recent historic trades
func (b *Bitflyer) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
tradeData, err := b.GetExecutionHistory(p.String())
if err != nil {
return nil, err
}
var resp []trade.Data
for i := range tradeData {
var timestamp time.Time
timestamp, err = time.Parse("2006-01-02T15:04:05.999999999", tradeData[i].ExecDate)
if err != nil {
return nil, err
}
var side order.Side
side, err = order.StringToOrderSide(tradeData[i].Side)
if err != nil {
return nil, err
}
resp = append(resp, trade.Data{
TID: strconv.FormatInt(tradeData[i].ID, 10),
Exchange: b.Name,
CurrencyPair: p,
AssetType: assetType,
Side: side,
Price: tradeData[i].Price,
Amount: tradeData[i].Size,
Timestamp: timestamp,
})
}
err = b.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
sort.Sort(trade.ByDate(resp))
return resp, nil
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *Bitflyer) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, common.ErrFunctionNotSupported
}
// SubmitOrder submits a new order

View File

@@ -596,3 +596,27 @@ func TestGetHistoricCandlesExtended(t *testing.T) {
t.Fatal(err)
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTC_KRW")
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTC_KRW")
if err != nil {
t.Fatal(err)
}
_, err = b.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrFunctionNotSupported {
t.Error(err)
}
}

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"math"
"sort"
"strconv"
"sync"
"time"
@@ -20,6 +21,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -329,9 +331,52 @@ func (b *Bithumb) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bithumb) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
// GetRecentTrades returns the most recent trades for a currency and asset
func (b *Bithumb) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
tradeData, err := b.GetTransactionHistory(p.String())
if err != nil {
return nil, err
}
var resp []trade.Data
for i := range tradeData.Data {
var side order.Side
side, err = order.StringToOrderSide(tradeData.Data[i].Type)
if err != nil {
return nil, err
}
var t time.Time
t, err = time.Parse("2006-01-02 15:04:05", tradeData.Data[i].TransactionDate)
if err != nil {
return nil, err
}
resp = append(resp, trade.Data{
Exchange: b.Name,
CurrencyPair: p,
AssetType: assetType,
Side: side,
Price: tradeData.Data[i].Price,
Amount: tradeData.Data[i].UnitsTraded,
Timestamp: t,
})
}
err = b.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
sort.Sort(trade.ByDate(resp))
return resp, nil
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *Bithumb) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, common.ErrFunctionNotSupported
}
// SubmitOrder submits a new order

View File

@@ -51,6 +51,7 @@ func TestMain(m *testing.M) {
if err != nil {
log.Fatal("Bitmex setup error", err)
}
b.UpdateTradablePairs(true)
os.Exit(m.Run())
}
@@ -361,9 +362,10 @@ func TestGetStatSummary(t *testing.T) {
func TestGetTrade(t *testing.T) {
_, err := b.GetTrade(&GenericRequestParams{
Symbol: "ETHUSD",
StartTime: time.Now().Format(time.RFC3339),
Reverse: true})
Symbol: "XBT",
Reverse: false,
StartTime: time.Now().Add(-time.Hour).Format(time.RFC3339),
})
if err != nil {
t.Error("GetTrade() error", err)
}
@@ -935,3 +937,29 @@ func TestWsTrades(t *testing.T) {
t.Error(err)
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
err := b.UpdateTradablePairs(false)
if err != nil {
t.Fatal(err)
}
currencyPair := b.CurrencyPairs.Pairs[asset.Futures].Available[0]
_, err = b.GetRecentTrades(currencyPair, asset.Futures)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
err := b.UpdateTradablePairs(false)
if err != nil {
t.Fatal(err)
}
currencyPair := b.CurrencyPairs.Pairs[asset.Futures].Available[0]
_, err = b.GetHistoricTrades(currencyPair, asset.Futures, time.Now().Add(-time.Minute*15), time.Now())
if err != nil {
t.Error(err)
}
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
@@ -214,15 +215,23 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
}
case bitmexWSTrade:
var trades TradeData
err = json.Unmarshal(respRaw, &trades)
if !b.IsSaveTradeDataEnabled() {
return nil
}
var tradeHolder TradeData
err = json.Unmarshal(respRaw, &tradeHolder)
if err != nil {
return err
}
for i := range trades.Data {
var trades []trade.Data
for i := range tradeHolder.Data {
if tradeHolder.Data[i].Price == 0 {
// Please note that indices (symbols starting with .) post trades at intervals to the trade feed.
// These have a size of 0 and are used only to indicate a changing price.
continue
}
var p currency.Pair
p, err = currency.NewPairFromString(trades.Data[i].Symbol)
p, err = currency.NewPairFromString(tradeHolder.Data[i].Symbol)
if err != nil {
return err
}
@@ -233,7 +242,7 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(trades.Data[i].Side)
oSide, err = order.StringToOrderSide(tradeHolder.Data[i].Side)
if err != nil {
b.Websocket.DataHandler <- order.ClassificationError{
Exchange: b.Name,
@@ -241,17 +250,18 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
}
}
b.Websocket.DataHandler <- stream.TradeData{
Timestamp: trades.Data[i].Timestamp,
Price: trades.Data[i].Price,
Amount: float64(trades.Data[i].Size),
CurrencyPair: p,
trades = append(trades, trade.Data{
TID: tradeHolder.Data[i].TrdMatchID,
Exchange: b.Name,
CurrencyPair: p,
AssetType: a,
Side: oSide,
}
Price: tradeHolder.Data[i].Price,
Amount: float64(tradeHolder.Data[i].Size),
Timestamp: tradeHolder.Data[i].Timestamp,
})
}
return b.AddTradesToBuffer(trades...)
case bitmexWSAnnouncement:
var announcement AnnouncementData
err = json.Unmarshal(respRaw, &announcement)

View File

@@ -2,7 +2,9 @@ package bitmex
import (
"errors"
"fmt"
"math"
"sort"
"strings"
"sync"
"time"
@@ -20,6 +22,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -420,9 +423,83 @@ func (b *Bitmex) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrNotYetImplemented
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bitmex) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
// GetRecentTrades returns the most recent trades for a currency and asset
func (b *Bitmex) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
return b.GetHistoricTrades(p, assetType, time.Now().Add(-time.Hour), time.Now())
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *Bitmex) GetHistoricTrades(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
if assetType == asset.Index {
return nil, fmt.Errorf("asset type '%v' not supported", assetType)
}
if timestampEnd.After(time.Now()) || timestampEnd.Before(timestampStart) {
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v", timestampStart, timestampEnd)
}
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
limit := 1000
req := &GenericRequestParams{
Symbol: p.String(),
Count: int32(limit),
EndTime: timestampEnd.UTC().Format("2006-01-02T15:04:05.000Z"),
}
ts := timestampStart
var resp []trade.Data
allTrades:
for {
req.StartTime = ts.UTC().Format("2006-01-02T15:04:05.000Z")
var tradeData []Trade
tradeData, err = b.GetTrade(req)
if err != nil {
return nil, err
}
for i := range tradeData {
if tradeData[i].Timestamp.Before(timestampStart) || tradeData[i].Timestamp.After(timestampEnd) {
break allTrades
}
var side order.Side
side, err = order.StringToOrderSide(tradeData[i].Side)
if err != nil {
return nil, err
}
if tradeData[i].Price == 0 {
// Please note that indices (symbols starting with .) post trades at intervals to the trade feed.
// These have a size of 0 and are used only to indicate a changing price.
continue
}
resp = append(resp, trade.Data{
Exchange: b.Name,
CurrencyPair: p,
AssetType: assetType,
Side: side,
Price: tradeData[i].Price,
Amount: float64(tradeData[i].Size),
Timestamp: tradeData[i].Timestamp,
TID: tradeData[i].TrdMatchID,
})
if i == len(tradeData)-1 {
if ts.Equal(tradeData[i].Timestamp) {
// reached end of trades to crawl
break allTrades
}
ts = tradeData[i].Timestamp
}
}
if len(tradeData) != limit {
break allTrades
}
}
err = b.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
sort.Sort(trade.ByDate(resp))
return trade.FilterTradesByTime(resp, timestampStart, timestampEnd), nil
}
// SubmitOrder submits a new order

View File

@@ -231,21 +231,21 @@ func (b *Bitstamp) GetTradingPairs() ([]TradingPair, error) {
// GetTransactions returns transaction information
// value paramater ["time"] = "minute", "hour", "day" will collate your
// response into time intervals. Implementation of value in test code.
func (b *Bitstamp) GetTransactions(currencyPair string, values url.Values) ([]Transactions, error) {
// response into time intervals.
func (b *Bitstamp) GetTransactions(currencyPair, timePeriod string) ([]Transactions, error) {
var transactions []Transactions
path := common.EncodeURLValues(
fmt.Sprintf(
"%s/v%s/%s/%s/",
b.API.Endpoints.URL,
bitstampAPIVersion,
bitstampAPITransactions,
strings.ToLower(currencyPair),
),
values,
requestURL := fmt.Sprintf(
"%s/v%s/%s/%s/",
b.API.Endpoints.URL,
bitstampAPIVersion,
bitstampAPITransactions,
strings.ToLower(currencyPair),
)
if timePeriod != "" {
requestURL += "?time=" + url.QueryEscape(timePeriod)
}
return transactions, b.SendHTTPRequest(path, &transactions)
return transactions, b.SendHTTPRequest(requestURL, &transactions)
}
// GetEURUSDConversionRate returns the conversion rate between Euro and USD

View File

@@ -1,10 +1,10 @@
package bitstamp
import (
"net/url"
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/core"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
@@ -200,11 +200,7 @@ func TestGetTradingPairs(t *testing.T) {
func TestGetTransactions(t *testing.T) {
t.Parallel()
value := url.Values{}
value.Set("time", "hour")
_, err := b.GetTransactions(currency.BTC.String()+currency.USD.String(), value)
_, err := b.GetTransactions(currency.BTC.String()+currency.USD.String(), "hour")
if err != nil {
t.Error("GetTransactions() error", err)
}
@@ -705,3 +701,27 @@ func TestBitstamp_GetHistoricCandlesExtended(t *testing.T) {
t.Fatal(err)
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("LTCUSD")
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("LTCUSD")
if err != nil {
t.Fatal(err)
}
_, err = b.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrFunctionNotSupported {
t.Error(err)
}
}

View File

@@ -205,7 +205,7 @@ type websocketTradeData struct {
Timestamp int64 `json:"timestamp,string"`
Price float64 `json:"price"`
Type int `json:"type"`
ID int `json:"id"`
ID int64 `json:"id"`
}
type websocketOrderBookResponse struct {

View File

@@ -15,6 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
@@ -91,7 +92,7 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error {
if err != nil {
return err
}
currencyPair := strings.Split(wsResponse.Channel, "_")
currencyPair := strings.Split(wsResponse.Channel, currency.UnderscoreDelimiter)
p, err := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
if err != nil {
return err
@@ -102,19 +103,22 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error {
return err
}
case "trade":
if !b.IsSaveTradeDataEnabled() {
return nil
}
wsTradeTemp := websocketTradeResponse{}
err := json.Unmarshal(respRaw, &wsTradeTemp)
if err != nil {
return err
}
currencyPair := strings.Split(wsResponse.Channel, "_")
currencyPair := strings.Split(wsResponse.Channel, currency.UnderscoreDelimiter)
p, err := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
if err != nil {
return err
}
side := order.Buy
if wsTradeTemp.Data.Type == -1 {
if wsTradeTemp.Data.Type == 1 {
side = order.Sell
}
var a asset.Item
@@ -122,16 +126,16 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error {
if err != nil {
return err
}
b.Websocket.DataHandler <- stream.TradeData{
return trade.AddTradesToBuffer(b.Name, trade.Data{
Timestamp: time.Unix(wsTradeTemp.Data.Timestamp, 0),
CurrencyPair: p,
AssetType: a,
Exchange: b.Name,
EventType: order.UnknownType,
Price: wsTradeTemp.Data.Price,
Amount: wsTradeTemp.Data.Amount,
Side: side,
}
TID: strconv.FormatInt(wsTradeTemp.Data.ID, 10),
})
case "order_created", "order_deleted", "order_changed":
if b.Verbose {
log.Debugf(log.ExchangeSys, "%v - Websocket order acknowledgement", b.Name)

View File

@@ -2,6 +2,7 @@ package bitstamp
import (
"errors"
"sort"
"strconv"
"strings"
"sync"
@@ -20,6 +21,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -396,9 +398,48 @@ func (b *Bitstamp) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bitstamp) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
// GetRecentTrades returns the most recent trades for a currency and asset
func (b *Bitstamp) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
var tradeData []Transactions
tradeData, err = b.GetTransactions(p.String(), "")
if err != nil {
return nil, err
}
var resp []trade.Data
for i := range tradeData {
s := order.Buy
if tradeData[i].Type == 1 {
s = order.Sell
}
resp = append(resp, trade.Data{
Exchange: b.Name,
TID: strconv.FormatInt(tradeData[i].TradeID, 10),
CurrencyPair: p,
AssetType: assetType,
Side: s,
Price: tradeData[i].Price,
Amount: tradeData[i].Amount,
Timestamp: time.Unix(tradeData[i].Date, 0),
})
}
err = b.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
sort.Sort(trade.ByDate(resp))
return resp, nil
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *Bitstamp) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, common.ErrFunctionNotSupported
}
// SubmitOrder submits a new order

View File

@@ -4,6 +4,7 @@ import (
"log"
"os"
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
@@ -550,3 +551,27 @@ func TestParseTime(t *testing.T) {
t.Error("invalid time values")
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(currPair)
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(currPair)
if err != nil {
t.Fatal(err)
}
_, err = b.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrFunctionNotSupported {
t.Fatal(err)
}
}

View File

@@ -2,6 +2,8 @@ package bittrex
import (
"errors"
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -18,6 +20,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -372,9 +375,53 @@ func (b *Bittrex) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bittrex) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
// GetRecentTrades returns the most recent trades for a currency and asset
func (b *Bittrex) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
tradeData, err := b.GetMarketHistory(p.String())
if err != nil {
return nil, err
}
var resp []trade.Data
for i := range tradeData.Result {
var side order.Side
side, err = order.StringToOrderSide(tradeData.Result[i].OrderType)
if err != nil {
return nil, err
}
var ts time.Time
ts, err = time.Parse("2006-01-02T15:04:05.999999999", tradeData.Result[i].Timestamp)
if err != nil {
return nil, err
}
resp = append(resp, trade.Data{
Exchange: b.Name,
TID: strconv.FormatInt(tradeData.Result[i].ID, 10),
CurrencyPair: p,
AssetType: assetType,
Side: side,
Price: tradeData.Result[i].Price,
Amount: tradeData.Result[i].Quantity,
Timestamp: ts,
})
}
err = b.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
sort.Sort(trade.ByDate(resp))
return resp, nil
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *Bittrex) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, common.ErrFunctionNotSupported
}
// SubmitOrder submits a new order

View File

@@ -102,7 +102,7 @@ func (b *BTCMarkets) GetTrades(marketID string, before, after, limit int64) ([]T
if before > 0 {
params.Set("before", strconv.FormatInt(before, 10))
}
if after >= 0 {
if after > 0 {
params.Set("after", strconv.FormatInt(after, 10))
}
if limit > 0 {

View File

@@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -777,3 +778,27 @@ func Test_FormatExchangeKlineInterval(t *testing.T) {
})
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTC-AUD")
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTC-AUD")
if err != nil {
t.Fatal(err)
}
_, err = b.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrFunctionNotSupported {
t.Error(err)
}
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
@@ -142,32 +143,35 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
return err
}
case tradeEndPoint:
var trade WsTrade
err := json.Unmarshal(respRaw, &trade)
if !b.IsSaveTradeDataEnabled() {
return nil
}
var t WsTrade
err := json.Unmarshal(respRaw, &t)
if err != nil {
return err
}
p, err := currency.NewPairFromString(trade.Currency)
p, err := currency.NewPairFromString(t.Currency)
if err != nil {
return err
}
side := order.Buy
if trade.Side == "Ask" {
if t.Side == "Ask" {
side = order.Sell
}
b.Websocket.DataHandler <- stream.TradeData{
Timestamp: trade.Timestamp,
return trade.AddTradesToBuffer(b.Name, trade.Data{
Timestamp: t.Timestamp,
CurrencyPair: p,
AssetType: asset.Spot,
Exchange: b.Name,
Price: trade.Price,
Amount: trade.Volume,
Price: t.Price,
Amount: t.Volume,
Side: side,
EventType: order.UnknownType,
}
TID: strconv.FormatInt(t.TradeID, 10),
})
case tick:
var tick WsTick
err := json.Unmarshal(respRaw, &tick)

View File

@@ -3,6 +3,7 @@ package btcmarkets
import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"sync"
@@ -21,6 +22,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -424,9 +426,51 @@ func (b *BTCMarkets) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
// GetRecentTrades returns the most recent trades for a currency and asset
func (b *BTCMarkets) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
var resp []trade.Data
var tradeData []Trade
tradeData, err = b.GetTrades(p.String(), 0, 0, 200)
if err != nil {
return nil, err
}
for i := range tradeData {
side := order.Side("")
if tradeData[i].Side != "" {
side, err = order.StringToOrderSide(tradeData[i].Side)
if err != nil {
return nil, err
}
}
resp = append(resp, trade.Data{
Exchange: b.Name,
TID: tradeData[i].TradeID,
CurrencyPair: p,
AssetType: assetType,
Side: side,
Price: tradeData[i].Price,
Amount: tradeData[i].Amount,
Timestamp: tradeData[i].Timestamp,
})
}
err = b.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
sort.Sort(trade.ByDate(resp))
return resp, nil
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *BTCMarkets) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, common.ErrFunctionNotSupported
}
// SubmitOrder submits a new order

View File

@@ -94,20 +94,22 @@ func (b *BTSE) FetchOrderBookL2(symbol string, depth int) (*Orderbook, error) {
}
// GetTrades returns a list of trades for the specified symbol
func (b *BTSE) GetTrades(symbol string, start, end time.Time, beforeSerialID, afterSerialID, count int, includeOld bool) ([]Trade, error) {
func (b *BTSE) GetTrades(symbol string, start, end time.Time, beforeSerialID, afterSerialID, count int, includeOld, spot bool) ([]Trade, error) {
var t []Trade
urlValues := url.Values{}
urlValues.Add("symbol", symbol)
if count > 0 {
urlValues.Add("count", strconv.Itoa(count))
}
if !start.IsZero() && !end.IsZero() {
if start.After(end) {
return t, errors.New("start cannot be after end time")
}
if !start.IsZero() {
urlValues.Add("start", strconv.FormatInt(start.Unix(), 10))
}
if !end.IsZero() {
urlValues.Add("end", strconv.FormatInt(end.Unix(), 10))
}
if !start.IsZero() && !end.IsZero() && start.After(end) {
return t, errors.New("start cannot be after end time")
}
if beforeSerialID > 0 {
urlValues.Add("beforeSerialId", strconv.Itoa(beforeSerialID))
}
@@ -118,7 +120,7 @@ func (b *BTSE) GetTrades(symbol string, start, end time.Time, beforeSerialID, af
urlValues.Add("includeOld", "true")
}
return t, b.SendHTTPRequest(http.MethodGet,
common.EncodeURLValues(btseTrades, urlValues), &t, true, queryFunc)
common.EncodeURLValues(btseTrades, urlValues), &t, spot, queryFunc)
}
// OHLCV retrieve and return OHLCV candle data for requested symbol

View File

@@ -24,7 +24,8 @@ const (
apiKey = ""
apiSecret = ""
canManipulateRealOrders = false
testPair = "BTC-USD"
testSPOTPair = "BTC-USD"
testFUTURESPair = "BTCPFC"
)
var b BTSE
@@ -63,7 +64,7 @@ func TestGetMarketsSummary(t *testing.T) {
t.Error(err)
}
ret, err := b.GetMarketSummary(testPair, true)
ret, err := b.GetMarketSummary(testSPOTPair, true)
if err != nil {
t.Error(err)
}
@@ -74,17 +75,17 @@ func TestGetMarketsSummary(t *testing.T) {
func TestFetchOrderBook(t *testing.T) {
t.Parallel()
_, err := b.FetchOrderBook(testPair, 0, 1, 1, true)
_, err := b.FetchOrderBook(testSPOTPair, 0, 1, 1, true)
if err != nil {
t.Error(err)
}
_, err = b.FetchOrderBook("BTCPFC", 0, 1, 1, false)
_, err = b.FetchOrderBook(testFUTURESPair, 0, 1, 1, false)
if err != nil {
t.Error(err)
}
_, err = b.FetchOrderBook(testPair, 1, 1, 1, true)
_, err = b.FetchOrderBook(testSPOTPair, 1, 1, 1, true)
if err != nil {
t.Error(err)
}
@@ -93,7 +94,7 @@ func TestFetchOrderBook(t *testing.T) {
func TestUpdateOrderbook(t *testing.T) {
t.Parallel()
p, err := currency.NewPairFromString(testPair)
p, err := currency.NewPairFromString(testSPOTPair)
if err != nil {
t.Fatal(err)
}
@@ -116,7 +117,7 @@ func TestUpdateOrderbook(t *testing.T) {
func TestFetchOrderBookL2(t *testing.T) {
t.Parallel()
_, err := b.FetchOrderBookL2(testPair, 20)
_, err := b.FetchOrderBookL2(testSPOTPair, 20)
if err != nil {
t.Error(err)
}
@@ -124,14 +125,14 @@ func TestFetchOrderBookL2(t *testing.T) {
func TestOHLCV(t *testing.T) {
t.Parallel()
_, err := b.OHLCV(testPair,
_, err := b.OHLCV(testSPOTPair,
time.Now().AddDate(0, 0, -1),
time.Now(), 60)
if err != nil {
t.Fatal(err)
}
_, err = b.OHLCV(testPair, time.Now(), time.Now().AddDate(0, 0, -1), 60)
_, err = b.OHLCV(testSPOTPair, time.Now(), time.Now().AddDate(0, 0, -1), 60)
if err == nil {
t.Fatal("expected error if start is after end date")
}
@@ -139,7 +140,7 @@ func TestOHLCV(t *testing.T) {
func TestGetPrice(t *testing.T) {
t.Parallel()
_, err := b.GetPrice(testPair)
_, err := b.GetPrice(testSPOTPair)
if err != nil {
t.Fatal(err)
}
@@ -154,7 +155,7 @@ func TestFormatExchangeKlineInterval(t *testing.T) {
func TestGetHistoricCandles(t *testing.T) {
t.Parallel()
curr, err := currency.NewPairFromString(testPair)
curr, err := currency.NewPairFromString(testSPOTPair)
if err != nil {
t.Fatal(err)
}
@@ -187,7 +188,7 @@ func TestGetHistoricCandles(t *testing.T) {
func TestGetHistoricCandlesExtended(t *testing.T) {
t.Parallel()
curr, err := currency.NewPairFromString(testPair)
curr, err := currency.NewPairFromString(testSPOTPair)
if err != nil {
t.Fatal(err)
}
@@ -203,24 +204,31 @@ func TestGetHistoricCandlesExtended(t *testing.T) {
func TestGetTrades(t *testing.T) {
t.Parallel()
_, err := b.GetTrades(testPair,
_, err := b.GetTrades(testSPOTPair,
time.Now().AddDate(0, 0, -1), time.Now(),
0, 0, 50, false)
0, 0, 50, false, true)
if err != nil {
t.Error(err)
}
_, err = b.GetTrades(testPair,
_, err = b.GetTrades(testSPOTPair,
time.Now(), time.Now().AddDate(0, -1, 0),
0, 0, 50, false)
0, 0, 50, false, true)
if err == nil {
t.Error("expected error if start time is after end time")
}
_, err = b.GetTrades(testFUTURESPair,
time.Now().AddDate(0, 0, -1), time.Now(),
0, 0, 50, false, false)
if err != nil {
t.Error(err)
}
}
func TestUpdateTicker(t *testing.T) {
t.Parallel()
curr, err := currency.NewPairFromString(testPair)
curr, err := currency.NewPairFromString(testSPOTPair)
if err != nil {
t.Fatal(err)
}
@@ -276,7 +284,7 @@ func TestGetWalletHistory(t *testing.T) {
if !areTestAPIKeysSet() {
t.Skip("API keys not set, skipping test")
}
_, err := b.GetWalletHistory(testPair,
_, err := b.GetWalletHistory(testSPOTPair,
time.Time{}, time.Time{},
50)
if err != nil {
@@ -325,7 +333,7 @@ func TestCreateOrder(t *testing.T) {
_, err := b.CreateOrder("", 0.0,
false,
-1, "BUY", 100, 0, 0,
testPair, "GTC",
testSPOTPair, "GTC",
0.0, 0.0,
"LIMIT", "LIMIT")
if err != nil {
@@ -341,7 +349,7 @@ func TestBTSEIndexOrderPeg(t *testing.T) {
_, err := b.IndexOrderPeg("", 0.0,
false,
-1, "BUY", 100, 0, 0,
testPair, "GTC",
testSPOTPair, "GTC",
0.0, 0.0,
"", "LIMIT")
if err != nil {
@@ -354,7 +362,7 @@ func TestGetOrders(t *testing.T) {
if !areTestAPIKeysSet() {
t.Skip("API keys not set, skipping test")
}
_, err := b.GetOrders(testPair, "", "")
_, err := b.GetOrders(testSPOTPair, "", "")
if err != nil {
t.Error(err)
}
@@ -387,21 +395,6 @@ func TestGetActiveOrders(t *testing.T) {
}
}
func TestGetExchangeHistory(t *testing.T) {
curr, _ := currency.NewPairFromString(testPair)
_, err := b.GetExchangeHistory(curr, asset.Spot, time.Now().AddDate(0, -6, 0), time.Now())
if err != nil {
t.Fatal(err)
}
_, err = b.GetExchangeHistory(curr, asset.Futures, time.Now().AddDate(0, -6, 0), time.Now())
if err != nil {
if !errors.Is(err, common.ErrNotYetImplemented) {
t.Fatal(err)
}
}
}
func TestGetOrderHistory(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
@@ -597,7 +590,7 @@ func TestCancelOrder(t *testing.T) {
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys are unset or canManipulateRealOrders is false")
}
_, err := b.CancelExistingOrder("", testPair, "")
_, err := b.CancelExistingOrder("", testSPOTPair, "")
if err != nil {
t.Fatal(err)
}
@@ -729,7 +722,7 @@ func TestSeedOrderSizeLimits(t *testing.T) {
func TestOrderSizeLimits(t *testing.T) {
t.Parallel()
seedOrderSizeLimitMap()
_, ok := OrderSizeLimits(testPair)
_, ok := OrderSizeLimits(testSPOTPair)
if !ok {
t.Fatal("expected BTC-USD to be found in map")
}
@@ -823,3 +816,36 @@ func TestWithinLimits(t *testing.T) {
t.Fatal("expected invalid limits")
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(testSPOTPair)
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
currencyPair, err = currency.NewPairFromString(testFUTURESPair)
if err != nil {
t.Fatal(err)
}
_, err = b.GetRecentTrades(currencyPair, asset.Futures)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
curr, _ := currency.NewPairFromString(testSPOTPair)
_, err := b.GetHistoricTrades(curr, asset.Spot, time.Now().Add(-time.Minute), time.Now())
if err == nil {
t.Fatal("expected error")
}
if err != common.ErrFunctionNotSupported {
t.Error("unexpected error")
}
}

View File

@@ -112,7 +112,7 @@ type FuturesMarket struct {
// Trade stores trade data
type Trade struct {
SerialID int `json:"serialId"`
SerialID int64 `json:"serialId"`
Symbol string `json:"symbol"`
Price float64 `json:"price"`
Amount float64 `json:"size"`

View File

@@ -17,6 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
)
const (
@@ -185,13 +186,16 @@ func (b *BTSE) wsHandleData(respRaw []byte) error {
Pair: p,
}
}
case strings.Contains(result["topic"].(string), "tradeHistory"):
if !b.IsSaveTradeDataEnabled() {
return nil
}
var tradeHistory wsTradeHistory
err = json.Unmarshal(respRaw, &tradeHistory)
if err != nil {
return err
}
var trades []trade.Data
for x := range tradeHistory.Data {
side := order.Buy
if tradeHistory.Data[x].Gain == -1 {
@@ -211,7 +215,7 @@ func (b *BTSE) wsHandleData(respRaw []byte) error {
if err != nil {
return err
}
b.Websocket.DataHandler <- stream.TradeData{
trades = append(trades, trade.Data{
Timestamp: time.Unix(0, tradeHistory.Data[x].TransactionTime*int64(time.Millisecond)),
CurrencyPair: p,
AssetType: a,
@@ -219,8 +223,10 @@ func (b *BTSE) wsHandleData(respRaw []byte) error {
Price: tradeHistory.Data[x].Price,
Amount: tradeHistory.Data[x].Amount,
Side: side,
}
TID: strconv.FormatInt(tradeHistory.Data[x].ID, 10),
})
}
return trade.AddTradesToBuffer(b.Name, trades...)
case strings.Contains(result["topic"].(string), "orderBookApi"):
var t wsOrderBook
err = json.Unmarshal(respRaw, &t)
@@ -261,7 +267,7 @@ func (b *BTSE) wsHandleData(respRaw []byte) error {
Amount: amount,
})
}
p, err := currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, "_")])
p, err := currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, currency.UnderscoreDelimiter)])
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"math"
"sort"
"strconv"
"strings"
"sync"
@@ -22,6 +23,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -396,41 +398,6 @@ func (b *BTSE) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
if assetType != asset.Spot {
return nil, common.ErrNotYetImplemented
}
fPair, err := b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
trades, err := b.GetTrades(fPair.String(),
timestampStart, timestampEnd,
0, 0, 0,
false)
if err != nil {
return nil, err
}
var resp []exchange.TradeHistory
for x := range trades {
tempExch := exchange.TradeHistory{
Timestamp: time.Unix(0, trades[x].Time*int64(time.Millisecond)),
Price: trades[x].Price,
Amount: trades[x].Amount,
Exchange: b.Name,
Side: trades[x].Side,
Type: trades[x].Type,
TID: strconv.Itoa(trades[x].SerialID),
}
resp = append(resp, tempExch)
}
return resp, nil
}
func (b *BTSE) withinLimits(pair currency.Pair, amount float64) bool {
val, found := OrderSizeLimits(pair.String())
if !found {
@@ -441,6 +408,57 @@ func (b *BTSE) withinLimits(pair currency.Pair, amount float64) bool {
amount > val.MaxOrderSize
}
// GetRecentTrades returns the most recent trades for a currency and asset
func (b *BTSE) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
var resp []trade.Data
limit := 500
var tradeData []Trade
tradeData, err = b.GetTrades(p.String(),
time.Time{}, time.Time{},
0, 0, limit,
false,
assetType == asset.Spot)
if err != nil {
return nil, err
}
for i := range tradeData {
tradeTimestamp := time.Unix(tradeData[i].Time/1000, 0)
var side order.Side
side, err = order.StringToOrderSide(tradeData[i].Side)
if err != nil {
return nil, err
}
resp = append(resp, trade.Data{
Exchange: b.Name,
TID: strconv.FormatInt(tradeData[i].SerialID, 10),
CurrencyPair: p,
AssetType: assetType,
Side: side,
Price: tradeData[i].Price,
Amount: tradeData[i].Amount,
Timestamp: tradeTimestamp,
})
}
err = b.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
sort.Sort(trade.ByDate(resp))
return resp, nil
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *BTSE) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, common.ErrFunctionNotSupported
}
// SubmitOrder submits a new order
func (b *BTSE) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
var resp order.SubmitResponse

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/core"
@@ -1008,3 +1009,27 @@ func TestCheckInterval(t *testing.T) {
t.Fatal("error cannot be nil")
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(testPair)
if err != nil {
t.Fatal(err)
}
_, err = c.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(testPair)
if err != nil {
t.Fatal(err)
}
_, err = c.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrFunctionNotSupported {
t.Error(err)
}
}

View File

@@ -20,6 +20,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
)
const (
@@ -130,11 +131,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error {
if err != nil {
return err
}
// the following cases contains data to synchronise authenticated orders
// subscribing to the "full" channel will consider ALL cbp orders as
// personal orders
// remove sending &order.Detail to the datahandler if you wish to subscribe to the
// "full" channel
case "received", "open", "done", "change", "activate":
var wsOrder wsOrderReceived
err := json.Unmarshal(respRaw, &wsOrder)
@@ -176,30 +172,32 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error {
ts = convert.TimeFromUnixTimestampDecimal(wsOrder.Timestamp)
}
var p currency.Pair
var a asset.Item
p, a, err = c.GetRequestFormattedPairAndAssetType(wsOrder.ProductID)
if err != nil {
return err
}
c.Websocket.DataHandler <- &order.Detail{
HiddenOrder: wsOrder.Private,
Price: wsOrder.Price,
Amount: wsOrder.Size,
TriggerPrice: wsOrder.StopPrice,
ExecutedAmount: wsOrder.Size - wsOrder.RemainingSize,
RemainingAmount: wsOrder.RemainingSize,
Fee: wsOrder.TakerFeeRate,
Exchange: c.Name,
ID: wsOrder.OrderID,
AccountID: wsOrder.ProfileID,
ClientID: c.API.Credentials.ClientID,
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: a,
Date: ts,
Pair: p,
if wsOrder.UserID != "" {
var p currency.Pair
var a asset.Item
p, a, err = c.GetRequestFormattedPairAndAssetType(wsOrder.ProductID)
if err != nil {
return err
}
c.Websocket.DataHandler <- &order.Detail{
HiddenOrder: wsOrder.Private,
Price: wsOrder.Price,
Amount: wsOrder.Size,
TriggerPrice: wsOrder.StopPrice,
ExecutedAmount: wsOrder.Size - wsOrder.RemainingSize,
RemainingAmount: wsOrder.RemainingSize,
Fee: wsOrder.TakerFeeRate,
Exchange: c.Name,
ID: wsOrder.OrderID,
AccountID: wsOrder.ProfileID,
ClientID: c.API.Credentials.ClientID,
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: a,
Date: ts,
Pair: p,
}
}
case "match":
var wsOrder wsOrderReceived
@@ -214,19 +212,44 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error {
Err: err,
}
}
c.Websocket.DataHandler <- &order.Detail{
ID: wsOrder.OrderID,
Trades: []order.TradeHistory{
{
Price: wsOrder.Price,
Amount: wsOrder.Size,
Exchange: c.Name,
TID: strconv.FormatInt(wsOrder.TradeID, 10),
Side: oSide,
Timestamp: wsOrder.Time,
IsMaker: wsOrder.TakerUserID == "",
var p currency.Pair
var a asset.Item
p, a, err = c.GetRequestFormattedPairAndAssetType(wsOrder.ProductID)
if err != nil {
return err
}
if wsOrder.UserID != "" {
c.Websocket.DataHandler <- &order.Detail{
ID: wsOrder.OrderID,
Pair: p,
AssetType: a,
Trades: []order.TradeHistory{
{
Price: wsOrder.Price,
Amount: wsOrder.Size,
Exchange: c.Name,
TID: strconv.FormatInt(wsOrder.TradeID, 10),
Side: oSide,
Timestamp: wsOrder.Time,
IsMaker: wsOrder.TakerUserID == "",
},
},
},
}
} else {
if !c.IsSaveTradeDataEnabled() {
return nil
}
return trade.AddTradesToBuffer(c.Name, trade.Data{
Timestamp: wsOrder.Time,
Exchange: c.Name,
CurrencyPair: p,
AssetType: a,
Price: wsOrder.Price,
Amount: wsOrder.Size,
Side: oSide,
TID: strconv.FormatInt(wsOrder.TradeID, 10),
})
}
default:
c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)}
@@ -342,7 +365,7 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error {
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
var channels = []string{"heartbeat", "level2", "ticker", "user"}
var channels = []string{"heartbeat", "level2", "ticker", "user", "matches"}
enabledCurrencies, err := c.GetEnabledPairs(asset.Spot)
if err != nil {
return nil, err

View File

@@ -3,6 +3,7 @@ package coinbasepro
import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"sync"
@@ -21,6 +22,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -436,9 +438,49 @@ func (c *CoinbasePro) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (c *CoinbasePro) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
// GetRecentTrades returns the most recent trades for a currency and asset
func (c *CoinbasePro) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
var err error
p, err = c.FormatExchangeCurrency(p, assetType)
if err != nil {
return nil, err
}
var tradeData []Trade
tradeData, err = c.GetTrades(p.String())
if err != nil {
return nil, err
}
var resp []trade.Data
for i := range tradeData {
var side order.Side
side, err = order.StringToOrderSide(tradeData[i].Side)
if err != nil {
return nil, err
}
resp = append(resp, trade.Data{
Exchange: c.Name,
TID: strconv.FormatInt(tradeData[i].TradeID, 10),
CurrencyPair: p,
AssetType: assetType,
Side: side,
Price: tradeData[i].Price,
Amount: tradeData[i].Size,
Timestamp: tradeData[i].Time,
})
}
err = c.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
sort.Sort(trade.ByDate(resp))
return resp, nil
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (c *CoinbasePro) GetHistoricTrades(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
return nil, common.ErrFunctionNotSupported
}
// SubmitOrder submits a new order

View File

@@ -169,13 +169,14 @@ func (c *Coinbene) GetTickers() ([]TickerData, error) {
}
// GetTrades gets recent trades from the exchange
func (c *Coinbene) GetTrades(symbol string) (Trades, error) {
func (c *Coinbene) GetTrades(symbol string, limit int64) (Trades, error) {
resp := struct {
Data [][]string `json:"data"`
}{}
params := url.Values{}
params.Set("symbol", symbol)
params.Set("limit", strconv.FormatInt(limit, 10))
path := common.EncodeURLValues(c.API.Endpoints.URL+coinbeneAPIVersion+coinbeneGetTrades, params)
err := c.SendHTTPRequest(path, spotMarketTrades, &resp)
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -86,7 +87,7 @@ func TestGetTicker(t *testing.T) {
func TestGetTrades(t *testing.T) {
t.Parallel()
_, err := c.GetTrades(spotTestPair)
_, err := c.GetTrades(spotTestPair, 100)
if err != nil {
t.Error(err)
}
@@ -498,63 +499,13 @@ func TestWsLogin(t *testing.T) {
}
func TestWsOrderbook(t *testing.T) {
pressXToJSON := []byte(`{
"topic": "orderBook.BTCUSDT",
"action": "insert",
"data": [{
"asks": [
["5621.7", "58", "2"],
["5621.8", "125", "5"],
["5621.9", "100", "9"],
["5622", "84", "20"],
["5623.5", "90", "12"],
["5624.2", "1540", "15"],
["5625.1", "300", "20"],
["5625.9", "350", "1"],
["5629.3", "200", "1"],
["5650", "1000", "8"]
],
"bids": [
["5621.3", "287","8"],
["5621.2", "41","1"],
["5621.1", "2","1"],
["5621", "26","2"],
["5620.8", "194","2"],
["5620", "2", "1"],
["5618.8", "204","2"],
["5618.4", "30", "9"],
["5617.2", "2","1"],
["5609.9", "100", "12"]
],
"version":1,
"timestamp": "2019-07-04T02:21:08Z"
}]
}`)
pressXToJSON := []byte(`{"topic":"spot/orderBook.BTCUSDT","action":"insert","data":[{"bids":[["0.00000015","174215.91"],["0.00000012","600000.00"],["0.00000010","10000.00"],["0.00000006","33333.00"],["0.00000004","50000.00"],["0.00000003","2000000.00"],["0.00000002","100000.00"],["0.00000001","1100000.00"]],"asks":[["0.00000262","5152.79"],["0.00000263","44626.00"],["0.00000340","2649.85"],["0.00000398","20056.93"],["0.00000400","1420385.54"],["0.00000790","8594.85"],["0.00000988","42380.97"],["0.00000997","43850.97"],["0.00001398","10541.59"],["0.00001400","3409.29"],["0.00002636","52.11"],["0.00002810","2543.66"],["0.00003200","1018.36"],["0.00004999","19.81"],["0.00005000","400.00"],["0.00005898","4060.56"],["0.00006498","3302.60"],["0.00006668","4060.56"],["0.00008000","400.00"]],"version":4915,"timestamp":1598529668288}]}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
pressXToJSON = []byte(`{
"topic": "orderBook.BTCUSDT",
"action": "update",
"data": [{
"asks": [
["5621.7", "50", "2"],
["5621.8", "0", "0"],
["5621.9", "30", "5"]
],
"bids": [
["5621.3", "10","1"],
["5621.2", "20","1"],
["5621.1", "80","5"],
["5621", "0","0"],
["5620.8", "10","1"]
],
"version":2,
"timestamp": "2019-07-04T02:21:09Z"
}]
}`)
pressXToJSON = []byte(`{"topic":"spot/orderBook.BTCUSDT","action":"update","data":[{"bids":[["2.983","8696"]],"asks":[["3.113","0"]],"version":34600866,"timestamp":1598587478738}]}`)
err = c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
@@ -562,17 +513,7 @@ func TestWsOrderbook(t *testing.T) {
}
func TestWsTrade(t *testing.T) {
pressXToJSON := []byte(`{
"topic": "tradeList.BTCUSDT",
"data": [
[
"8600.0000",
"s",
"100",
"2019-05-21T08:25:22.735Z"
]
]
}`)
pressXToJSON := []byte(`{"data":[["0.00000050","s","37.00",1598500505000],["0.00000060","s","10.00",1598499782000],["0.00000066","s","1.00",1598499782000],["0.00000067","s","1.00",1598499782000],["0.00000068","s","1.00",1598499745000],["0.00000080","b","1.00",1598262728000],["0.00000089","b","5592.81",1597738441000],["0.00000072","b","1.00",1597693134000],["0.00000069","s","21739.13",1597378140000],["0.00000069","s","1.00",1597378140000],["0.00000074","b","1.00",1597354497000],["0.00000079","b","1.00",1597325675000],["0.00000082","b","1.00",1597162162000],["0.00000089","b","1.00",1597084892000],["0.00000073","b","109404.43",1597015827000],["0.00000070","b","1067.00",1597015827000],["0.00000070","b","1.00",1594732841000],["0.00000070","b","10.00",1592178569000],["0.00000065","b","194.76",1592178545000],["0.00000064","b","2.37",1592105641000],["0.00000064","b","3.00",1592087828000],["0.00000045","b","5.00",1592087828000],["0.00000045","b","5.00",1592004274000],["0.00000030","s","100.00",1591931268000],["0.00000020","b","138.12",1591928623000],["0.00000020","b","55027.66",1591928623000],["0.00000020","b","59880.11",1591572812000],["0.00000021","s","138.12",1590413750000],["0.00000021","s","5.37",1590413750000],["0.00000056","s","1.00",1589567228000],["0.00000065","b","1.00",1589567217000],["0.00000060","b","84890.64",1589407481000],["0.00000060","b","17.13",1589407433000],["0.00000060","b","9148.70",1589389270000],["0.00000059","b","9010.00",1589389159000],["0.00000055","b","3876.00",1589389098000],["0.00000055","b","30000.00",1588899981000],["0.00000055","b","5724.00",1588891192000],["0.00000050","b","400.00",1588891192000],["0.00000048","b","26874.64",1588891129000],["0.00000048","b","2.00",1588891129000],["0.00000049","b","7547.75",1585279296000],["0.00000049","b","12180.30",1584932828000],["0.00000049","b","8256.95",1584932828000],["0.00000053","b","500.42",1583351500000],["0.00000053","b","500.00",1583351484000],["0.00000053","b","400.00",1583351470000],["0.00000053","b","394.62",1583351455000],["0.00000053","b","1.99",1583343633000],["0.00000018","s","250.00",1583338813000]],"topic":"spot/tradeList.BTCUSDT","action":"insert"}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
@@ -580,24 +521,7 @@ func TestWsTrade(t *testing.T) {
}
func TestWsTicker(t *testing.T) {
pressXToJSON := []byte(`{
"topic": "ticker.BTCUSDT",
"data": [
{
"symbol": "BTCUSDT",
"lastPrice": "8548.0",
"markPrice": "8548.0",
"bestAskPrice": "8601.0",
"bestBidPrice": "8600.0",
"bestAskVolume": "1222",
"bestBidVolume": "56505",
"high24h": "8600.0000",
"low24h": "242.4500",
"volume24h": "4994",
"timestamp": "2019-05-06T06:45:56.716Z"
}
]
}`)
pressXToJSON := []byte(`{"topic": "spot/ticker.BTCUSDT","action":"insert","data": [{"symbol":"BTCUSDT","lastPrice":"23.3746","bestAskPrice":"23.3885","bestBidPrice":"23.3603","high24h":"23.5773","open24h":"22.1961","openPrice":"22.5546","low24h":"21.8077","volume24h":"3784807.9709","timestamp":1598587472634}]}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
@@ -605,23 +529,7 @@ func TestWsTicker(t *testing.T) {
}
func TestWsKLine(t *testing.T) {
pressXToJSON := []byte(`{
"topic": "kline.BTCUSDT",
"data": [
[
"BTCUSDT",
1557428280,
"5794",
"5794",
"5794",
"5794",
"0",
"0",
"0",
"0"
]
]
}`)
pressXToJSON := []byte(`{"topic": "spot/kline.BTCUSDT.1h","action":"insert","data": [{"t":1594990800,"o":1.1e-07,"h":1.1e-07,"l":1.1e-07,"c":1.1e-07,"v":0}]}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
@@ -630,7 +538,7 @@ func TestWsKLine(t *testing.T) {
func TestWsUserAccount(t *testing.T) {
pressXToJSON := []byte(`{
"topic": "user.account",
"topic": "btc/user.account",
"data": [{
"asset": "BTC",
"availableBalance": "20.3859",
@@ -780,3 +688,75 @@ func Test_FormatExchangeKlineInterval(t *testing.T) {
})
}
}
func TestInferAssetFromTopic(t *testing.T) {
a := inferAssetFromTopic("spot/orderBook.BSVBTC")
if a != asset.Spot {
t.Error("expected spot")
}
a = inferAssetFromTopic("btc/orderBook.BSVBTC")
if a != asset.PerpetualSwap {
t.Error("expected PerpetualSwap")
}
a = inferAssetFromTopic("usdt/orderBook.BSVBTC")
if a != asset.PerpetualSwap {
t.Error("expected PerpetualSwap")
}
a = inferAssetFromTopic("orderBook.BSVBTC")
if a != asset.PerpetualSwap {
t.Error("expected PerpetualSwap")
}
a = inferAssetFromTopic("")
if a != asset.PerpetualSwap {
t.Error("expected PerpetualSwap")
}
}
func TestGetCurrencyFromWsTopic(t *testing.T) {
p, err := c.getCurrencyFromWsTopic(asset.Spot, "spot/orderbook.BTCUSDT")
if err != nil {
t.Error(err)
}
if p.Base.String() != "BTC" && p.Quote.String() != "USDT" {
t.Errorf("unexpected currency, wanted BTCUSD, received %v", p.String())
}
_, err = c.getCurrencyFromWsTopic(asset.Spot, "fake")
if err != nil && err.Error() != "no currency found in topic fake" {
t.Error(err)
}
_, err = c.getCurrencyFromWsTopic(asset.Spot, "hello.moto")
if err != nil && err.Error() != "currency moto not found in supplied pairs" {
t.Error(err)
}
_, err = c.getCurrencyFromWsTopic(asset.Spot, "spot/kline.GOM2USDT.1h")
if err != nil && err.Error() != "currency moto not found in enabled pairs" {
t.Error(err)
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(spotTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.GetRecentTrades(currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(spotTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.GetHistoricTrades(currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrFunctionNotSupported {
t.Error(err)
}
}

View File

@@ -123,17 +123,17 @@ type WsSub struct {
// WsTickerData stores websocket ticker data
type WsTickerData struct {
Symbol string `json:"symbol"`
LastPrice float64 `json:"lastPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
BestAskPrice float64 `json:"bestAskPrice,string"`
BestBidPrice float64 `json:"bestBidPrice,string"`
BestAskVolume float64 `json:"bestAskVolume,string"`
BestBidVolume float64 `json:"bestBidVolume,string"`
High24h float64 `json:"high24h,string"`
Low24h float64 `json:"low24h,string"`
Volume24h float64 `json:"volume24h,string"`
Timestamp time.Time `json:"timestamp"`
Symbol string `json:"symbol"`
LastPrice float64 `json:"lastPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
BestAskPrice float64 `json:"bestAskPrice,string"`
BestBidPrice float64 `json:"bestBidPrice,string"`
BestAskVolume float64 `json:"bestAskVolume,string"`
BestBidVolume float64 `json:"bestBidVolume,string"`
High24h float64 `json:"high24h,string"`
Low24h float64 `json:"low24h,string"`
Volume24h float64 `json:"volume24h,string"`
Timestamp int64 `json:"timestamp"`
}
// WsTicker stores websocket ticker
@@ -144,14 +144,38 @@ type WsTicker struct {
// WsTradeList stores websocket tradelist data
type WsTradeList struct {
Topic string `json:"topic"`
Data [][]string `json:"data"`
Topic string `json:"topic"`
Data [][4]interface{} `json:"data"`
}
// WsTradeData stores trade data for websocket
type WsTradeData struct {
BestAskPrice float64 `json:"bestAskPrice,string"`
BestBidPrice float64 `json:"bestBidPrice,string"`
High24h float64 `json:"high24h,string"`
LastPrice float64 `json:"lastPrice,string"`
Low24h float64 `json:"low24h,string"`
Open24h float64 `json:"open24h,string"`
OpenPrice float64 `json:"openPrice,string"`
Symbol string `json:"symbol"`
Timestamp int64 `json:"timestamp"`
Volume24h float64 `json:"volume24h,string"`
}
// WsKline stores websocket kline data
type WsKline struct {
Topic string `json:"topic"`
Data [][]interface{} `json:"data"`
Topic string `json:"topic"`
Data []WsKLineData `json:"data"`
}
// WsKLineData holds OHLCV data
type WsKLineData struct {
Open float64 `json:"o"`
High float64 `json:"h"`
Low float64 `json:"l"`
Close float64 `json:"c"`
Volume float64 `json:"v"`
Timestamp int64 `json:"t"`
}
// WsUserData stores websocket user data
@@ -191,6 +215,18 @@ type WsPosition struct {
Data []WsPositionData `json:"data"`
}
// WsOrderbookData stores ws orderbook data
type WsOrderbookData struct {
Topic string `json:"topic"`
Action string `json:"action"`
Data []struct {
Bids [][]string `json:"bids"`
Asks [][]string `json:"asks"`
Version int64 `json:"version"`
Timestamp int64 `json:"timestamp"`
} `json:"data"`
}
// WsOrderData stores websocket user order data
type WsOrderData struct {
OrderID string `json:"orderId"`

Some files were not shown because too many files have changed in this diff Show More