From e57aa817dca96c6af9ceff4d5a21e96e8ba9e579 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Fri, 9 Feb 2018 11:00:58 +1100 Subject: [PATCH] Added support for Bitflyer exchange --- README.md | 1 + config/config_test.go | 4 +- config_example.json | 25 +- exchange.go | 3 + exchanges/bitflyer/bitflyer.go | 355 +++++++++++++++++++++++++ exchanges/bitflyer/bitflyer_test.go | 173 ++++++++++++ exchanges/bitflyer/bitflyer_types.go | 280 +++++++++++++++++++ exchanges/bitflyer/bitflyer_wrapper.go | 132 +++++++++ testdata/configtest.json | 25 +- 9 files changed, 994 insertions(+), 4 deletions(-) create mode 100644 exchanges/bitflyer/bitflyer.go create mode 100644 exchanges/bitflyer/bitflyer_test.go create mode 100644 exchanges/bitflyer/bitflyer_types.go create mode 100644 exchanges/bitflyer/bitflyer_wrapper.go diff --git a/README.md b/README.md index 4d127ec2..488933f2 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | ANXPRO | Yes | No | NA | | Binance| Yes | No | NA | | Bitfinex | Yes | Yes | NA | +| Bitflyer | Yes | No | NA | | Bithumb | Yes | NA | NA | | Bitstamp | Yes | Yes | NA | | Bittrex | Yes | No | NA | diff --git a/config/config_test.go b/config/config_test.go index 40a0896e..701ab10b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -89,7 +89,7 @@ func TestGetEnabledExchanges(t *testing.T) { } exchanges := cfg.GetEnabledExchanges() - if len(exchanges) != 25 { + if len(exchanges) != 26 { t.Error( "Test failed. TestGetEnabledExchanges. Enabled exchanges value mismatch", ) @@ -141,7 +141,7 @@ func TestGetDisabledExchanges(t *testing.T) { } func TestCountEnabledExchanges(t *testing.T) { - defaultEnabledExchanges := 25 + defaultEnabledExchanges := 26 GetConfigEnabledExchanges := GetConfig() err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile) if err != nil { diff --git a/config_example.json b/config_example.json index b1123c2f..d1dc6592 100644 --- a/config_example.json +++ b/config_example.json @@ -122,6 +122,29 @@ "Uppercase": true } }, + { + "Name": "Bitflyer", + "Enabled": true, + "Verbose": false, + "Websocket": false, + "UseSandbox": false, + "RESTPollingDelay": 10, + "AuthenticatedAPISupport": false, + "APIKey": "Key", + "APISecret": "Secret", + "AvailablePairs": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC", + "EnabledPairs": "BTC_JPY,ETH_BTC,BCH_BTC", + "BaseCurrencies": "JPY", + "AssetTypes": "SPOT", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + } + }, { "Name": "Bithumb", "Enabled": true, @@ -615,4 +638,4 @@ } } ] -} \ No newline at end of file +} diff --git a/exchange.go b/exchange.go index 2bc94604..ea068276 100644 --- a/exchange.go +++ b/exchange.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/anx" "github.com/thrasher-/gocryptotrader/exchanges/binance" "github.com/thrasher-/gocryptotrader/exchanges/bitfinex" + "github.com/thrasher-/gocryptotrader/exchanges/bitflyer" "github.com/thrasher-/gocryptotrader/exchanges/bithumb" "github.com/thrasher-/gocryptotrader/exchanges/bitstamp" "github.com/thrasher-/gocryptotrader/exchanges/bittrex" @@ -136,6 +137,8 @@ func LoadExchange(name string) error { exch = new(binance.Binance) case "bitfinex": exch = new(bitfinex.Bitfinex) + case "bitflyer": + exch = new(bitflyer.Bitflyer) case "bithumb": exch = new(bithumb.Bithumb) case "bitstamp": diff --git a/exchanges/bitflyer/bitflyer.go b/exchanges/bitflyer/bitflyer.go new file mode 100644 index 00000000..5dfb3b95 --- /dev/null +++ b/exchanges/bitflyer/bitflyer.go @@ -0,0 +1,355 @@ +package bitflyer + +import ( + "errors" + "fmt" + "log" + "net/url" + "strconv" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +const ( + // Bitflyer chain analysis endpoints + // APIURL + chainAnalysis = "https://chainflyer.bitflyer.jp/v1/" + + // Public endpoints for chain analysis + latestBlock = "block/latest" + blockByBlockHash = "block/" + blockByBlockHeight = "block/height/" + transaction = "tx/" + address = "address/" + + // APIURL + japanURL = "https://api.bitflyer.jp/v1" + usURL = "https://api.bitflyer.com/v1" + europeURL = "https://api.bitflyer.com/v1" + + // Public Endpoints + pubGetMarkets = "/getmarkets/" + pubGetBoard = "/getboard" + pubGetTicker = "/getticker" + pubGetExecutionHistory = "/getexecutions" + pubGetHealth = "/gethealth" + pubGetChats = "/getchats" + + // Autheticated Endpoints + privGetPermissions = "/me/getpermissions" + privGetBalance = "/me/getbalance" + privMarginStatus = "/me/getcollateral" + privGetCollateralAcc = "/me/getcollateralaccounts" + privGetDepositAddress = "/me/getaddresses" + privDepositHistory = "/me/getcoinins" + privTransactionHistory = "/me/getcoinouts" + privBankAccSummary = "/me/getbankaccounts" + privGetDeposits = "/me/getdeposits" + privWithdraw = "/me/withdraw" + privDepositCancellationHistory = "/me/getwithdrawals" + privSendOrder = "/me/sendchildorder" + privCancelOrder = "/me/cancelchildorder" + privParentOrder = "/me/sendparentorder" + privCancelParentOrder = "/me/cancelparentorder" + privCancelOrders = "/me/cancelallchildorders" + privListOrders = "/me/getchildorders" + privListParentOrders = "/me/getparentorders" + privParentOrderDetails = "/me/getparentorder" + privExecutions = "/me/getexecutions" + privOpenInterest = "/me/getpositions" + privMarginChange = "/me/getcollateralhistory" + privTradingCommission = "/me/gettradingcommission" +) + +// Bitflyer is the overarching type across this package +type Bitflyer struct { + exchange.Base +} + +// SetDefaults sets the basic defaults for Bitflyer +func (b *Bitflyer) SetDefaults() { + b.Name = "Bitflyer" + b.Enabled = false + b.Verbose = false + b.Websocket = false + b.RESTPollingDelay = 10 + b.RequestCurrencyPairFormat.Delimiter = "_" + b.RequestCurrencyPairFormat.Uppercase = true + b.ConfigCurrencyPairFormat.Delimiter = "_" + b.ConfigCurrencyPairFormat.Uppercase = true + b.AssetTypes = []string{ticker.Spot} +} + +// Setup takes in the supplied exchange configuration details and sets params +func (b *Bitflyer) Setup(exch config.ExchangeConfig) { + if !exch.Enabled { + b.SetEnabled(false) + } else { + b.Enabled = true + b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport + b.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) + b.RESTPollingDelay = exch.RESTPollingDelay + b.Verbose = exch.Verbose + b.Websocket = exch.Websocket + b.APIUrl = japanURL + b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") + b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") + b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := b.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } + err = b.SetAssetTypes() + if err != nil { + log.Fatal(err) + } + } +} + +// GetLatestBlockCA returns the latest block information from bitflyer chain +// analysis system +func (b *Bitflyer) GetLatestBlockCA() (ChainAnalysisBlock, error) { + var resp ChainAnalysisBlock + path := fmt.Sprintf("%s%s", chainAnalysis, latestBlock) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetBlockCA returns block information by blockhash from bitflyer chain +// analysis system +func (b *Bitflyer) GetBlockCA(blockhash string) (ChainAnalysisBlock, error) { + var resp ChainAnalysisBlock + path := fmt.Sprintf("%s%s%s", chainAnalysis, blockByBlockHash, blockhash) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetBlockbyHeightCA returns the block information by height from bitflyer chain +// analysis system +func (b *Bitflyer) GetBlockbyHeightCA(height int64) (ChainAnalysisBlock, error) { + var resp ChainAnalysisBlock + path := fmt.Sprintf("%s%s%s", chainAnalysis, blockByBlockHeight, strconv.FormatInt(height, 10)) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetTransactionByHashCA returns transaction information by txHash from +// bitflyer chain analysis system +func (b *Bitflyer) GetTransactionByHashCA(txHash string) (ChainAnalysisTransaction, error) { + var resp ChainAnalysisTransaction + path := fmt.Sprintf("%s%s%s", chainAnalysis, transaction, txHash) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetAddressInfoCA returns balance information for address by addressln string +// from bitflyer chain analysis system +func (b *Bitflyer) GetAddressInfoCA(addressln string) (ChainAnalysisAddress, error) { + var resp ChainAnalysisAddress + path := fmt.Sprintf("%s%s%s", chainAnalysis, address, addressln) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetMarkets returns market information +func (b *Bitflyer) GetMarkets() ([]MarketInfo, error) { + var resp []MarketInfo + path := fmt.Sprintf("%s%s", b.APIUrl, pubGetMarkets) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetOrderBook returns market orderbook depth +func (b *Bitflyer) GetOrderBook(symbol string) (Orderbook, error) { + var resp Orderbook + v := url.Values{} + v.Set("product_code", symbol) + path := fmt.Sprintf("%s%s?%s", japanURL, pubGetBoard, v.Encode()) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetTicker returns ticker information +func (b *Bitflyer) GetTicker(symbol string) (Ticker, error) { + var resp Ticker + v := url.Values{} + v.Set("product_code", symbol) + path := fmt.Sprintf("%s%s?%s", japanURL, pubGetTicker, v.Encode()) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetExecutionHistory returns past trades that were executed on the market +func (b *Bitflyer) GetExecutionHistory(symbol string) ([]ExecutedTrade, error) { + var resp []ExecutedTrade + v := url.Values{} + v.Set("product_code", symbol) + path := fmt.Sprintf("%s%s?%s", japanURL, pubGetExecutionHistory, v.Encode()) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetExchangeStatus returns exchange status information +func (b *Bitflyer) GetExchangeStatus() (string, error) { + resp := make(map[string]string) + + path := fmt.Sprintf("%s%s", b.APIUrl, pubGetHealth) + + err := common.SendHTTPGetRequest(path, true, b.Verbose, &resp) + if err != nil { + return "", err + } + + switch resp["status"] { + case "BUSY": + return "the exchange is experiencing high traffic", nil + case "VERY BUSY": + return "the exchange is experiencing heavy traffic", nil + case "SUPER BUSY": + return "the exchange is experiencing extremely heavy traffic. There is a possibility that orders will fail or be processed after a delay.", nil + case "STOP": + return "STOP", errors.New("the exchange has been stopped. Orders will not be accepted") + } + + return "NORMAL", nil +} + +// GetChats returns trollbox chat log +// Note: returns vary from instant to infinty +func (b *Bitflyer) GetChats(FromDate string) ([]ChatLog, error) { + var resp []ChatLog + v := url.Values{} + v.Set("from_date", FromDate) + path := fmt.Sprintf("%s%s?%s", b.APIUrl, pubGetChats, v.Encode()) + + return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp) +} + +// GetPermissions returns current permissions for associated with your API +// keys +func (b *Bitflyer) GetPermissions() { + // Needs to be updated +} + +// GetAccountBalance returnsthe full list of account funds +func (b *Bitflyer) GetAccountBalance() { + // Needs to be updated +} + +// GetMarginStatus returns current margin status +func (b *Bitflyer) GetMarginStatus() { + // Needs to be updated +} + +// GetCollateralAccounts returns a full list of collateralised accounts +func (b *Bitflyer) GetCollateralAccounts() { + // Needs to be updated +} + +// GetDepositAddress returns an address for cryptocurrency deposits +func (b *Bitflyer) GetDepositAddress() { + // Needs to be updated +} + +// GetDepositHistory returns a full history of deposits +func (b *Bitflyer) GetDepositHistory() { + // Needs to be updated +} + +// GetTransactionHistory returns a full history of transactions +func (b *Bitflyer) GetTransactionHistory() { + // Needs to be updated +} + +// GetBankAccSummary returns a full list of bank accounts assoc. with your keys +func (b *Bitflyer) GetBankAccSummary() { + // Needs to be updated +} + +// GetCashDeposits returns a full list of cash deposits to the exchange +func (b *Bitflyer) GetCashDeposits() { + // Needs to be updated +} + +// WithdrawFunds withdraws funds to a certain bank +func (b *Bitflyer) WithdrawFunds() { + // Needs to be updated +} + +// GetDepositCancellationHistory returns the cancellation history of deposits +func (b *Bitflyer) GetDepositCancellationHistory() { + // Needs to be updated +} + +// SendOrder creates new order +func (b *Bitflyer) SendOrder() { + // Needs to be updated +} + +// CancelOrder cancels an order +func (b *Bitflyer) CancelOrder() { + // Needs to be updated +} + +// SendParentOrder sends a special order +func (b *Bitflyer) SendParentOrder() { + // Needs to be updated +} + +// CancelParentOrder cancels a special order +func (b *Bitflyer) CancelParentOrder() { + // Needs to be updated +} + +// CancelAllOrders cancels all orders on the exchange +func (b *Bitflyer) CancelAllOrders() { + // Needs to be updated +} + +// GetAllOrders returns a list of all orders +func (b *Bitflyer) GetAllOrders() { + // Needs to be updated +} + +// GetParentOrders returns a list of all parent orders +func (b *Bitflyer) GetParentOrders() { + // Needs to be updated +} + +// GetParentOrderDetails returns a detailing of a parent order +func (b *Bitflyer) GetParentOrderDetails() { + // Needs to be updated +} + +// GetExecutions returns execution details +func (b *Bitflyer) GetExecutions() { + // Needs to be updated +} + +// GetOpenInterest returns a summary of open interest +func (b *Bitflyer) GetOpenInterest() { + // Needs to be updated +} + +// GetMarginChange returns collateral history +func (b *Bitflyer) GetMarginChange() { + // Needs to be updated +} + +// GetTradingCommission returns trading commission +func (b *Bitflyer) GetTradingCommission() { + // Needs to be updated +} + +// SendAuthHTTPRequest sends an authenticated HTTP request +// Note: HTTP not done due to incorrect account privileges, please open a PR +// if you have access and update the authenticated requests +func (b *Bitflyer) SendAuthHTTPRequest(path string, params url.Values, result interface{}) { + headers := make(map[string]string) + headers["ACCESS-KEY"] = b.APIKey + headers["ACCESS-TIMESTAMP"] = strconv.FormatInt(int64(time.Now().UnixNano()), 10) +} diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go new file mode 100644 index 00000000..af97c8e3 --- /dev/null +++ b/exchanges/bitflyer/bitflyer_test.go @@ -0,0 +1,173 @@ +package bitflyer + +import ( + "log" + "testing" + + "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency/pair" +) + +// Please supply your own keys here for due diligence testing +const ( + testAPIKey = "" + testAPISecret = "" +) + +var b Bitflyer + +func TestSetDefaults(t *testing.T) { + b.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + bitflyerConfig, err := cfg.GetExchangeConfig("Bitflyer") + if err != nil { + t.Error("Test Failed - bitflyer Setup() init error") + } + + bitflyerConfig.AuthenticatedAPISupport = true + bitflyerConfig.APIKey = testAPIKey + bitflyerConfig.APISecret = testAPISecret + + b.Setup(bitflyerConfig) +} + +func TestGetLatestBlockCA(t *testing.T) { + t.Parallel() + _, err := b.GetLatestBlockCA() + if err != nil { + t.Error("test failed - Bitflyer - GetLatestBlockCA() error:", err) + } +} + +func TestGetBlockCA(t *testing.T) { + t.Parallel() + _, err := b.GetBlockCA("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") + if err != nil { + t.Error("test failed - Bitflyer - GetBlockCA() error:", err) + } +} + +func TestGetBlockbyHeightCA(t *testing.T) { + t.Parallel() + _, err := b.GetBlockbyHeightCA(0) + if err != nil { + t.Error("test failed - Bitflyer - GetBlockbyHeightCA() error:", err) + } +} + +func TestGetTransactionByHashCA(t *testing.T) { + t.Parallel() + _, err := b.GetTransactionByHashCA("0562d1f063cd4127053d838b165630445af5e480ceb24e1fd9ecea52903cb772") + if err != nil { + t.Error("test failed - Bitflyer - GetTransactionByHashCA() error:", err) + } +} + +func TestGetAddressInfoCA(t *testing.T) { + t.Parallel() + v, err := b.GetAddressInfoCA("1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB") + if err != nil { + t.Error("test failed - Bitflyer - GetAddressInfoCA() error:", err) + } + if v.UnconfirmedBalance == 0 || v.ConfirmedBalance == 0 { + log.Println("WARNING!: Donation wallet is empty :( - please consider donating") + } +} + +func TestGetMarkets(t *testing.T) { + t.Parallel() + _, err := b.GetMarkets() + if err != nil { + t.Error("test failed - Bitflyer - GetMarkets() error:", err) + } +} + +func TestGetOrderBook(t *testing.T) { + t.Parallel() + _, err := b.GetOrderBook("BTC_JPY") + if err != nil { + t.Error("test failed - Bitflyer - GetOrderBook() error:", err) + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := b.GetTicker("BTC_JPY") + if err != nil { + t.Error("test failed - Bitflyer - GetTicker() error:", err) + } +} + +func TestGetExecutionHistory(t *testing.T) { + t.Parallel() + _, err := b.GetExecutionHistory("BTC_JPY") + if err != nil { + t.Error("test failed - Bitflyer - GetExecutionHistory() error:", err) + } +} + +func TestGetExchangeStatus(t *testing.T) { + t.Parallel() + _, err := b.GetExchangeStatus() + if err != nil { + t.Error("test failed - Bitflyer - GetExchangeStatus() error:", err) + } +} + +// func TestGetChats(t *testing.T) { +// t.Parallel() +// time := time.Now().Format(time.RFC3339) +// _, err := b.GetChats(time) +// if err != nil { +// t.Error("test failed - Bitflyer - GetChats() error:", err) +// } +// } + +func TestUpdateTicker(t *testing.T) { + t.Parallel() + p := pair.NewCurrencyPairFromString("BTC_JPY") + _, err := b.UpdateTicker(p, "SPOT") + if err != nil { + t.Error("test failed - Bitflyer - UpdateTicker() error:", err) + } +} + +func TestUpdateOrderbook(t *testing.T) { + t.Parallel() + p := pair.NewCurrencyPairFromString("BTC_JPY") + _, err := b.UpdateOrderbook(p, "SPOT") + if err != nil { + t.Error("test failed - Bitflyer - UpdateOrderbook() error:", err) + } +} + +func TestCheckFXString(t *testing.T) { + t.Parallel() + p := pair.NewCurrencyPairDelimiter("FXBTC_JPY", "_") + p = b.CheckFXString(p) + if p.GetFirstCurrency().String() != "FX_BTC" { + t.Error("test failed - Bitflyer - CheckFXString() error") + } +} + +func TestGetTickerPrice(t *testing.T) { + t.Parallel() + var p pair.CurrencyPair + + currencies := b.GetAvailableCurrencies() + for _, pair := range currencies { + if pair.Pair().String() == "FXBTC_JPY" { + p = pair + break + } + } + + _, err := b.GetTickerPrice(p, b.AssetTypes[0]) + if err != nil { + t.Error("test failed - Bitflyer - GetTickerPrice() error", err) + } +} diff --git a/exchanges/bitflyer/bitflyer_types.go b/exchanges/bitflyer/bitflyer_types.go new file mode 100644 index 00000000..3dd6c3e8 --- /dev/null +++ b/exchanges/bitflyer/bitflyer_types.go @@ -0,0 +1,280 @@ +package bitflyer + +// ChainAnalysisBlock holds block information from the bitcoin network +type ChainAnalysisBlock struct { + BlockHash string `json:"block_hash"` + Height int64 `json:"height"` + IsMain bool `json:"is_main"` + Version float64 `json:"version"` + PreviousBlock string `json:"prev_block"` + MerkleRoot string `json:"merkle_root"` + Timestamp string `json:"timestamp"` + Bits int64 `json:"bits"` + Nonce int64 `json:"nonce"` + TxNum int64 `json:"txnum"` + TotalFees float64 `json:"total_fees"` + TxHashes []string `json:"tx_hashes"` +} + +// ChainAnalysisTransaction holds transaction data from the bitcoin network +type ChainAnalysisTransaction struct { + TxHash string `json:"tx_hash"` + BlockHeight int64 `json:"block_height"` + Confirmations int64 `json:"confirmed"` + Fees float64 `json:"fees"` + Size int64 `json:"size"` + ReceivedDate string `json:"received_date"` + Version float64 `json:"version"` + LockTime int64 `json:"lock_time"` + Inputs []struct { + PrevHash string `json:"prev_hash"` + PrevIndex int `json:"prev_index"` + Value int64 `json:"value"` + Script string `json:"script"` + Address string `json:"address"` + Sequence int64 `json:"sequence"` + } `json:"inputs"` + Outputs []struct { + Value int64 `json:"value"` + Script string `json:"script"` + Address string `json:"address"` + } `json:"outputs"` +} + +// ChainAnalysisAddress holds address information from the bitcoin network +type ChainAnalysisAddress struct { + Address string `json:"address"` + UnconfirmedBalance float64 `json:"unconfirmed_balance"` + ConfirmedBalance float64 `json:"confirmed_balance"` +} + +// MarketInfo holds market information returned from bitflyer +type MarketInfo struct { + ProductCode string `json:"product_code"` + Alias string `json:"alias"` +} + +// Orderbook holds orderbook information +type Orderbook struct { + MidPrice float64 `json:"mid_price"` + Bids []struct { + Price float64 `json:"price"` + Size float64 `json:"size"` + } `json:"bids"` + Asks []struct { + Price float64 `json:"price"` + Size float64 `json:"size"` + } `json:"asks"` +} + +// Ticker holds ticker information +type Ticker struct { + ProductCode string `json:"product_code"` + TimeStamp string `json:"timestamp"` + TickID int64 `json:"tick_id"` + BestBid float64 `json:"best_bid"` + BestAsk float64 `json:"best_ask"` + BestBidSize float64 `json:"best_bid_size"` + BestAskSize float64 `json:"best_ask_size"` + TotalBidDepth float64 `json:"total_bid_depth"` + TotalAskDepth float64 `json:"total_ask_depth"` + Last float64 `json:"ltp"` + Volume float64 `json:"volume"` + VolumeByProduct float64 `json:"volume_by_product"` +} + +// ExecutedTrade holds past trade information +type ExecutedTrade struct { + ID int64 `json:"id"` + Side string `json:"side"` + Price float64 `json:"price"` + Size float64 `json:"size"` + ExecDate string `json:"exec_date"` + BuyAcceptedID string `json:"buy_child_order_acceptance_id"` + SellAcceptedID string `json:"sell_child_order_acceptance_id"` +} + +// ChatLog holds chat log information +type ChatLog struct { + Nickname string `json:"nickname"` + Message string `json:"message"` + Date string `json:"date"` +} + +// AccountBalance holds account balance information +type AccountBalance struct { + CurrencyCode string `json:"currency_code"` + Amount float64 `json:"amount"` + Available float64 `json:"available"` +} + +// MarginStatus holds margin status information +type MarginStatus struct { + Collateral float64 `json:"collateral"` + OpenPosPNL float64 `json:"open_position_pnl"` + RequiredCollateral float64 `json:"require_collateral"` + KeepRate float64 `json:"keep_rate"` +} + +// CollateralAccounts holds collateral balances +type CollateralAccounts struct { + CurrencyCode string `json:"currency_code"` + Amount float64 `json:"amount"` +} + +// DepositAddress hold depositing address information +type DepositAddress struct { + Type string `json:"type"` + CurrencyCode string `json:"currency_code"` + Address string `json:"address"` +} + +// DepositHistory holds deposit history information +type DepositHistory struct { + ID int64 `json:"id"` + OrderID int64 `json:"order_id"` + CurrencyCode string `json:"currency_code"` + Amount float64 `json:"amount"` + Address string `json:"address"` + TXHash string `json:"tx_hash"` + Status string `json:"status"` + EventDate string `json:"event_date"` +} + +// TransactionHistory holds prior transaction history data +type TransactionHistory struct { + ID int64 `json:"id"` + OrderID int64 `json:"order_id"` + CurrencyCode string `json:"currency_code"` + Amount float64 `json:"amount"` + Address string `json:"address"` + TXHash string `json:"tx_hash"` + Fee float64 `json:"fee"` + AdditionalFee float64 `json:"additional_fee"` + Status string `json:"status"` + EventDate string `json:"event_date"` +} + +// BankAccount holds bank account information +type BankAccount struct { + ID int64 `json:"id"` + IsVerified bool `json:"is_verified"` + BankName string `json:"bank_name"` + BranchName string `json:"branch_name"` + AccountType string `json:"account_type"` + AccountNumber int `json:"account_number"` + AccountName string `json:"account_name"` +} + +// CashDeposit holds cash deposit information +type CashDeposit struct { + ID int64 `json:"id"` + OrderID string `json:"order_id"` + CurrencyCode string `json:"currency_code"` + Amount float64 `json:"amount"` + Status string `json:"status"` + EventDate string `json:"event_date"` +} + +// CancellationHistory cancellation history +type CancellationHistory struct { + ID int64 `json:"id"` + OrderID string `json:"order_id"` + CurrencyCode string `json:"currency_code"` + Amount float64 `json:"amount"` + Status string `json:"status"` + EventDate string `json:"event_date"` +} + +// Orders holds order full order information +type Orders struct { + ID int64 `json:"id"` + ChildOrderID string `json:"child_order_id"` + ProductCode string `json:"product_code"` + Side string `json:"side"` + ChildOrderType string `json:"child_order_type"` + Price float64 `json:"price"` + AveragePrice float64 `json:"average_price"` + Size float64 `json:"size"` + ChildOrderState string `json:"child_order_state"` + ExpireDate string `json:"expire_date"` + ChildOrderDate string `json:"child_order_date"` + ChildOrderAcceptanceID string `json:"child_order_acceptance_id"` + OutstandingSize float64 `json:"outstanding_size"` + CancelSize float64 `json:"cancel_size"` + ExecutedSize float64 `json:"executed_size"` + TotalCommission float64 `json:"total_commission"` +} + +// ParentOrders holds order full order information +type ParentOrders struct { + ID int64 `json:"id"` + ParentOrderID string `json:"parent_order_id"` + ProductCode string `json:"product_code"` + Side string `json:"side"` + ParentOrderType string `json:"parent_order_type"` + Price float64 `json:"price"` + AveragePrice float64 `json:"average_price"` + Size float64 `json:"size"` + ParentOrderState string `json:"parent_order_state"` + ExpireDate string `json:"expire_date"` + ParentOrderDate string `json:"parent_order_date"` + ParentOrderAcceptanceID string `json:"parent_order_acceptance_id"` + OutstandingSize float64 `json:"outstanding_size"` + CancelSize float64 `json:"cancel_size"` + ExecutedSize float64 `json:"executed_size"` + TotalCommission float64 `json:"total_commission"` +} + +// ParentOrderDetail holds detailed information about an order +type ParentOrderDetail struct { + ID int64 `json:"id"` + ParentOrderID string `json:"parent_order_id"` + OrderMethod string `json:"order_method"` + MinutesToExpire float64 `json:"minute_to_expire"` + Parameters []struct { + ProductCode string `json:"product_code"` + ConditionType string `json:"condition_type"` + Side string `json:"side"` + Price float64 `json:"price"` + Size float64 `json:"size"` + TriggerPrice float64 `json:"trigger_price"` + Offset float64 `json:"offset"` + } `json:"parameters"` +} + +// Executions holds past executed trade details +type Executions struct { + ID int64 `json:"id"` + ChildOrderID string `json:"child_order_id"` + Side string `json:"side"` + Price float64 `json:"price"` + Size float64 `json:"size"` + Commission float64 `json:"commission"` + ExecDate string `json:"exec_date"` + ChildOrderAcceptanceID string `json:"child_order_acceptance_id"` +} + +// OpenInterest holds open interest information +type OpenInterest struct { + ProductCode string `json:"product_code"` + Side string `json:"side"` + Price float64 `json:"price"` + Size float64 `json:"size"` + Commission float64 `json:"commission"` + SwapPointAccumulate float64 `json:"swap_point_accumulate"` + RequiredCollateral float64 `json:"require_collateral"` + OpenDate string `json:"open_date"` + Leverage float64 `json:"leverage"` + PNL float64 `json:"pnl"` +} + +// CollateralHistory holds collateral history data +type CollateralHistory struct { + ID int64 `json:"id"` + CurrencyCode string `json:"currency_code"` + Change float64 `json:"change"` + Amount float64 `json:"amount"` + Reason string `json:"reason_code"` + Date string `json:"date"` +} diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go new file mode 100644 index 00000000..563ad167 --- /dev/null +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -0,0 +1,132 @@ +package bitflyer + +import ( + "errors" + "log" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// Start starts the Bitfinex go routine +func (b *Bitflyer) Start() { + go b.Run() +} + +// Run implements the Bitfinex wrapper +func (b *Bitflyer) Run() { + if b.Verbose { + log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket)) + log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) + log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + } + + marketInfo, err := b.GetMarkets() + if err != nil { + log.Printf("%s Failed to get available symbols.\n", b.GetName()) + } else { + var exchangeProducts []string + + for _, info := range marketInfo { + exchangeProducts = append(exchangeProducts, info.ProductCode) + } + + err = b.UpdateAvailableCurrencies(exchangeProducts, false) + if err != nil { + log.Printf("%s Failed to get config.\n", b.GetName()) + } + } +} + +// UpdateTicker updates and returns the ticker for a currency pair +func (b *Bitflyer) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price + + p = b.CheckFXString(p) + + tickerNew, err := b.GetTicker(p.Pair().String()) + if err != nil { + return tickerPrice, err + } + + tickerPrice.Pair = p + tickerPrice.Ask = tickerNew.BestAsk + tickerPrice.Bid = tickerNew.BestBid + // tickerPrice.Low + tickerPrice.Last = tickerNew.Last + tickerPrice.Volume = tickerNew.Volume + // tickerPrice.High + ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(b.Name, p, assetType) +} + +// GetTickerPrice returns the ticker for a currency pair +func (b *Bitflyer) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tick, err := ticker.GetTicker(b.GetName(), p, ticker.Spot) + if err != nil { + return b.UpdateTicker(p, assetType) + } + return tick, nil +} + +// CheckFXString upgrades currency pair if needed +func (b *Bitflyer) CheckFXString(p pair.CurrencyPair) pair.CurrencyPair { + if common.StringContains(p.GetFirstCurrency().String(), "FX") { + p.FirstCurrency = "FX_BTC" + return p + } + return p +} + +// GetOrderbookEx returns the orderbook for a currency pair +func (b *Bitflyer) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType) + if err != nil { + return b.UpdateOrderbook(p, assetType) + } + return ob, nil +} + +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (b *Bitflyer) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base + + p = b.CheckFXString(p) + + orderbookNew, err := b.GetOrderBook(p.Pair().String()) + if err != nil { + return orderBook, err + } + + for x := range orderbookNew.Asks { + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Price: orderbookNew.Asks[x].Price, Amount: orderbookNew.Asks[x].Size}) + } + + for x := range orderbookNew.Bids { + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Price: orderbookNew.Bids[x].Price, Amount: orderbookNew.Bids[x].Size}) + } + + orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(b.Name, p, assetType) +} + +// GetExchangeAccountInfo retrieves balances for all enabled currencies on the +// Bitfinex exchange +func (b *Bitflyer) GetExchangeAccountInfo() (exchange.AccountInfo, error) { + var response exchange.AccountInfo + response.ExchangeName = b.GetName() + // accountBalance, err := b.GetAccountBalance() + // if err != nil { + // return response, err + // } + if !b.Enabled { + return response, errors.New("exchange not enabled") + } + + // implement once authenticated requests are introduced + + return response, nil +} diff --git a/testdata/configtest.json b/testdata/configtest.json index c292e7fb..d6245ce5 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -122,6 +122,29 @@ "Uppercase": true } }, + { + "Name": "Bitflyer", + "Enabled": true, + "Verbose": false, + "Websocket": false, + "UseSandbox": false, + "RESTPollingDelay": 10, + "AuthenticatedAPISupport": false, + "APIKey": "Key", + "APISecret": "Secret", + "AvailablePairs": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC", + "EnabledPairs": "BTC_JPY,ETH_BTC,BCH_BTC", + "BaseCurrencies": "JPY", + "AssetTypes": "SPOT", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + } + }, { "Name": "Bithumb", "Enabled": true, @@ -615,4 +638,4 @@ } } ] -} \ No newline at end of file +}