mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-17 15:09:59 +00:00
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:
@@ -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
|
||||
|
||||
19
README.md
19
README.md
@@ -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 |
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
@@ -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
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
765
cmd/gctcli/trades.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
148
common/timeperiods/timeperiods.go
Normal file
148
common/timeperiods/timeperiods.go
Normal 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)
|
||||
})
|
||||
}
|
||||
188
common/timeperiods/timeperiods_test.go
Normal file
188
common/timeperiods/timeperiods_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
32
common/timeperiods/timeperiods_types.go
Normal file
32
common/timeperiods/timeperiods_types.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
20
database/migrations/20200909083123_trade/postgres.sql
Normal file
20
database/migrations/20200909083123_trade/postgres.sql
Normal 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;
|
||||
20
database/migrations/20200909083123_trade/sqlite3.sql
Normal file
20
database/migrations/20200909083123_trade/sqlite3.sql
Normal 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;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
1071
database/models/postgres/trade.go
Normal file
1071
database/models/postgres/trade.go
Normal file
File diff suppressed because it is too large
Load Diff
841
database/models/postgres/trade_test.go
Normal file
841
database/models/postgres/trade_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
994
database/models/sqlite3/trade.go
Normal file
994
database/models/sqlite3/trade.go
Normal 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
|
||||
}
|
||||
793
database/models/sqlite3/trade_test.go
Normal file
793
database/models/sqlite3/trade_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
359
database/repository/trade/trade.go
Normal file
359
database/repository/trade/trade.go
Normal 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
|
||||
}
|
||||
201
database/repository/trade/trade_test.go
Normal file
201
database/repository/trade/trade_test.go
Normal 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
|
||||
}
|
||||
19
database/repository/trade/trade_types.go
Normal file
19
database/repository/trade/trade_types.go
Normal 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
|
||||
}
|
||||
@@ -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{}
|
||||
|
||||
@@ -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.")
|
||||
}()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ type Settings struct {
|
||||
EnableExchangeRESTSupport bool
|
||||
EnableExchangeWebsocketSupport bool
|
||||
MaxHTTPRequestJobsLimit int
|
||||
TradeBufferProcessingInterval time.Duration
|
||||
RequestMaxRetryAttempts int
|
||||
|
||||
// Global HTTP related settings
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
787
engine/rpcserver_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ func TestNewCurrencyPairSyncer(t *testing.T) {
|
||||
}
|
||||
|
||||
Bot.Settings.DisableExchangeAutoPairUpdates = true
|
||||
Bot.Settings.Verbose = true
|
||||
Bot.Settings.EnableExchangeWebsocketSupport = true
|
||||
|
||||
Bot.SetupExchanges()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 +
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user