pax_global_header00006660000000000000000000000064137127574470014533gustar00rootroot0000000000000052 comment=dbf1c3d69ddb5bf5ff3b24f63722b75a6e88f20a bitfinex-api-go-2.2.9/000077500000000000000000000000001371275744700145275ustar00rootroot00000000000000bitfinex-api-go-2.2.9/.github/000077500000000000000000000000001371275744700160675ustar00rootroot00000000000000bitfinex-api-go-2.2.9/.github/ISSUE_TEMPLATE000066400000000000000000000002541371275744700201760ustar00rootroot00000000000000#### Issue type - [ ] bug - [ ] missing functionality - [ ] performance - [ ] feature request #### Brief description #### Steps to reproduce - ##### Additional Notes: - bitfinex-api-go-2.2.9/.github/PULL_REQUEST_TEMPLATE000066400000000000000000000002331371275744700212670ustar00rootroot00000000000000### Description: ... ### Breaking changes: - [ ] ### New features: - [ ] ### Fixes: - [ ] ### PR status: - [ ] Version bumped - [ ] Change-log updated bitfinex-api-go-2.2.9/.gitignore000066400000000000000000000000201371275744700165070ustar00rootroot00000000000000.idea .DS_Store bitfinex-api-go-2.2.9/.travis.yml000066400000000000000000000004051371275744700166370ustar00rootroot00000000000000language: go go: - 1.14.2 install: - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.30.0 - go get -t ./... script: - golangci-lint run --skip-dirs v1 - go test ./... -timeout 60s bitfinex-api-go-2.2.9/CHANGELOG000066400000000000000000000044061371275744700157450ustar00rootroot000000000000002.2.9 - Adds new rest v2 functions - Orders.CancelOrderMulti - Orders.CancelOrdersMultiOp - Orders.CancelOrderMultiOp - Orders.OrderNewMultiOp - Orders.OrderUpdateMultiOp - Orders.OrderMultiOp - Invoice.GenerateInvoice - Funding.KeepFunding - Market.AveragePrice - Market.ForeignExchangeRate 2.2.8 - Adds new rest v2 functions - Pulse.PublicPulseProfile - Pulse.PublicPulseHistory - Pulse.AddPulse - Pulse.PulseHistory - Pulse.DeletePulse 2.2.7 - Separates subscriptions by socketID to prevent clashing 2.2.6 - Adds AffiliateCode field to order submission - Extracts and exposes Meta field from order object 2.2.5 - hotfix: parse notify info even if type not recognised 2.2.4 - Adds new rest v2 functions - Funding.Offers - Funding.OfferHistory - Funding.Loans - Funding.LoanHistory - Funding.Credits - Funding.CreditHistory - Funding.Trades - Funding.SubmitOffer - Funding.CancelOffer - Orders.SubmitOrder - Orders.CancelOrder - Orders.SubmitUpdateOrder - Orders.SubmitCancelOrder - Positions.Claim - Wallet.Transfer - Wallet.DepositAddress - Wallet.CreateDepositAddress - Wallet.Withdraw - Adds new ws v2 functions - Ws/SubmitFundingOffer - Ws/SubmitFundingCancel 2.2.3 - Add transport keep alive pinger to keep tls connection open - Fix multiple small race conditions 2.2.2 - Uses channel/write combo for websocket send requests to avoid race conditions - Use mutex for building snapshot to avoid race conditions - Use TradeExecution as type for authenticated trade data 2.2.1 - Adds v2/rest Derivatives service with new functions - SetCollateral - Adds v2/rest Status service with new functions - DerivativeStatus - DerivativeStatusMulti - DerivativeStatusAll - Adds support for auth ws feed 'status' - Adds ws subscription function SubscribeStatus 2.2.0 - Adds v2/ws connection multiplexer - Adds v2/ws api functions ConnectionCount and StartNewConnection 2.1.1 - Removes usage of string(int) conversion in v2/rest and uses strconv.FormatInt instead - Adds version file - Adds changelog file - Fix v2/rest response error message to be passed down correctly - Rework v2/rest/orders api, change function names and add additionals bitfinex-api-go-2.2.9/LICENSE.txt000066400000000000000000000020641371275744700163540ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 iFinex INC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bitfinex-api-go-2.2.9/Makefile000066400000000000000000000003441371275744700161700ustar00rootroot00000000000000NAME = bitfinex-api-go ############################# ### Swagger gen-docs: @echo "Generating documentation" godocdown ./v2/websocket > ./docs/ws_v2.md godocdown ./v2/rest > ./docs/rest_v2.md godocdown ./v1/ > ./docs/v1.md bitfinex-api-go-2.2.9/README.md000066400000000000000000000137721371275744700160200ustar00rootroot00000000000000# Bitfinex Trading Library for GoLang - Bitcoin, Ethereum, Ripple and more ![https://api.travis-ci.org/bitfinexcom/bitfinex-api-go.svg?branch=master](https://api.travis-ci.org/bitfinexcom/bitfinex-api-go.svg?branch=master) A Golang reference implementation of the Bitfinex API for both REST and websocket interaction. ### Features * Official implementation * REST V1/V2 and Websocket * Connection multiplexing * Types for all data schemas ## Installation ``` bash go get github.com/bitfinexcom/bitfinex-api-go ``` Optional - run the 'trade-feed' example to begin receiving realtime trade updates via the websocket ```bash cd $GOPATH/src/github.com/bitfinexcom/bitfinex-api-go go run examples/v2/trade-feed/main.go ``` ## Quickstart ``` go package main import ( "fmt" "github.com/bitfinexcom/bitfinex-api-go/v2" ) func main() { client := bitfinex.NewClient().Credentials("API_KEY", "API_SEC") // create order response, err := c.Orders.SubmitOrder(&bitfinex.OrderNewRequest{ Symbol: "tBTCUSD", CID: time.Now().Unix() / 1000, Amount: 0.02, Type: "EXCHANGE LIMIT", Price: 5000, }) if err != nil { panic(err) } } ``` ## Docs * [V1](docs/v1.md) - Documentation (depreciated) * [V2 Rest](docs/rest_v2.md) - Documentation * [V2 Websocket](docs/ws_v2.md) - Documentation ## Examples #### Authentication ``` go func main() { client := bitfinex.NewClient().Credentials("API_KEY", "API_SEC") } ``` #### Subscribe to Trades ``` go // using github.com/bitfinexcom/bitfinex-api-go/v2/websocket as client _, err := client.SubscribeTrades(context.Background(), "tBTCUSD") if err != nil { log.Printf("Could not subscribe to trades: %s", err.Error()) } ``` #### Get candles via REST ```go // using github.com/bitfinexcom/bitfinex-api-go/v2/rest as client os, err := client.Orders.AllHistory() if err != nil { log.Fatalf("getting orders: %s", err) } ``` See the [examples](https://github.com/bitfinexcom/bitfinex-api-go/tree/master/examples) directory for more, like: - [Creating/updating an order](https://github.com/bitfinexcom/bitfinex-api-go/blob/master/examples/v2/ws-update-order/main.go) - [Subscribing to orderbook updates](https://github.com/bitfinexcom/bitfinex-api-go/blob/master/examples/v2/book-feed/main.go) - [Integrating a custom logger](https://github.com/bitfinexcom/bitfinex-api-go/blob/master/examples/v2/ws-custom-logger/main.go) - [Submitting funding offers](https://github.com/bitfinexcom/bitfinex-api-go/blob/master/examples/v2/rest-funding/main.go) - [Retrieving active positions](https://github.com/bitfinexcom/bitfinex-api-go/blob/master/examples/v2/rest-positions/main.go) ## FAQ ### Is there any rate limiting? For a Websocket connection there is no limit to the number of requests sent down the connection (unlimited order operations) however an account can only create 15 new connections every 5 mins and each connection is only able to subscribe to 30 inbound data channels. Fortunately this library handles all of the load balancing/multiplexing for channels and will automatically create/destroy new connections when needed, however the user may still encounter the max connections rate limiting error. For rest the base limit per-user is 1,000 orders per 5 minute interval, and is shared between all account API connections. It increases proportionally to your trade volume based on the following formula: 1000 + (TOTAL_PAIRS_PLATFORM * 60 * 5) / (250000000 / USER_VOL_LAST_30d) Where TOTAL_PAIRS_PLATFORM is the number of pairs on the Bitfinex platform (currently ~101) and USER_VOL_LAST_30d is in USD. ### Will I always receive an `on` packet? No; if your order fills immediately, the first packet referencing the order will be an `oc` signaling the order has closed. If the order fills partially immediately after creation, an `on` packet will arrive with a status of `PARTIALLY FILLED...` For example, if you submit a `LIMIT` buy for 0.2 BTC and it is added to the order book, an `on` packet will arrive via ws2. After a partial fill of 0.1 BTC, an `ou` packet will arrive, followed by a final `oc` after the remaining 0.1 BTC fills. On the other hand, if the order fills immediately for 0.2 BTC, you will only receive an `oc` packet. ### My websocket won't connect! Did you call `client.Connect()`? :) ### nonce too small I make multiple parallel request and I receive an error that the nonce is too small. What does it mean? Nonces are used to guard against replay attacks. When multiple HTTP requests arrive at the API with the wrong nonce, e.g. because of an async timing issue, the API will reject the request. If you need to go parallel, you have to use multiple API keys right now. ### How do `te` and `tu` messages differ? A `te` packet is sent first to the client immediately after a trade has been matched & executed, followed by a `tu` message once it has completed processing. During times of high load, the `tu` message may be noticably delayed, and as such only the `te` message should be used for a realtime feed. ### What are the sequence numbers for? If you enable sequencing on v2 of the WS API, each incoming packet will have a public sequence number at the end, along with an auth sequence number in the case of channel `0` packets. The public seq numbers increment on each packet, and the auth seq numbers increment on each authenticated action (new orders, etc). These values allow you to verify that no packets have been missed/dropped, since they always increase monotonically. ### What is the difference between R* and P* order books? Order books with precision `R0` are considered 'raw' and contain entries for each order submitted to the book, whereas `P*` books contain entries for each price level (which aggregate orders). ## Contributing 1. Fork it (https://github.com/bitfinexcom/bitfinex-api-go/fork) 2. Create your feature branch (`git checkout -b my-new-feature) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request bitfinex-api-go-2.2.9/VERSION000066400000000000000000000000061371275744700155730ustar00rootroot000000000000002.2.9 bitfinex-api-go-2.2.9/docs/000077500000000000000000000000001371275744700154575ustar00rootroot00000000000000bitfinex-api-go-2.2.9/docs/rest_v2.md000066400000000000000000000711001371275744700173640ustar00rootroot00000000000000# rest -- import "github.com/bitfinexcom/bitfinex-api-go/v2/rest" ## Usage ```go const ( DERIV_TYPE = "deriv" ) ``` #### type AveragePriceRequest ```go type AveragePriceRequest struct { Symbol string Amount string RateLimit string Period int } ``` AveragePriceRequest data structure for constructing average price query params #### type BookService ```go type BookService struct { Synchronous } ``` #### func (*BookService) All ```go func (b *BookService) All(symbol string, precision bitfinex.BookPrecision, priceLevels int) (*bitfinex.BookUpdateSnapshot, error) ``` Retrieve all books for the given symbol with the given precision at the given price level see https://docs.bitfinex.com/reference#rest-public-books for more info #### type CancelOrderMultiRequest ```go type CancelOrderMultiRequest struct { OrderIDs OrderIDs `json:"id,omitempty"` GroupOrderIDs GroupOrderIDs `json:"gid,omitempty"` ClientOrderIDs ClientOrderIDs `json:"cid,omitempty"` All int `json:"all,omitempty"` } ``` CancelOrderMultiRequest - data structure for constructing cancel order multi request payload #### type CandleService ```go type CandleService struct { Synchronous } ``` CandleService manages the Candles endpoint. #### func (*CandleService) History ```go func (c *CandleService) History(symbol string, resolution bitfinex.CandleResolution) (*bitfinex.CandleSnapshot, error) ``` Retrieves all candles (Max=1000) with the given symbol and the given candle resolution See https://docs.bitfinex.com/reference#rest-public-candles for more info #### func (*CandleService) HistoryWithQuery ```go func (c *CandleService) HistoryWithQuery( symbol string, resolution bitfinex.CandleResolution, start bitfinex.Mts, end bitfinex.Mts, limit bitfinex.QueryLimit, sort bitfinex.SortOrder, ) (*bitfinex.CandleSnapshot, error) ``` Retrieves all candles (Max=1000) that fit the given query criteria See https://docs.bitfinex.com/reference#rest-public-candles for more info #### func (*CandleService) Last ```go func (c *CandleService) Last(symbol string, resolution bitfinex.CandleResolution) (*bitfinex.Candle, error) ``` Retrieve the last candle for the given symbol with the given resolution See https://docs.bitfinex.com/reference#rest-public-candles for more info #### type Client ```go type Client struct { // service providers Candles CandleService Orders OrderService Positions PositionService Trades TradeService Tickers TickerService Currencies CurrenciesService Platform PlatformService Book BookService Wallet WalletService Ledgers LedgerService Stats StatsService Status StatusService Derivatives DerivativesService Funding FundingService Pulse PulseService Invoice InvoiceService Market MarketService Synchronous } ``` #### func NewClient ```go func NewClient() *Client ``` Create a new Rest client #### func NewClientWithHttpDo ```go func NewClientWithHttpDo(httpDo func(c *http.Client, r *http.Request) (*http.Response, error)) *Client ``` Create a new Rest client with a custom http handler #### func NewClientWithSynchronousNonce ```go func NewClientWithSynchronousNonce(sync Synchronous, nonce utils.NonceGenerator) *Client ``` Create a new Rest client with a synchronous HTTP handler and a custom nonce generaotr #### func NewClientWithSynchronousURLNonce ```go func NewClientWithSynchronousURLNonce(sync Synchronous, url string, nonce utils.NonceGenerator) *Client ``` Create a new Rest client with a synchronous HTTP handler and a custom base url and nonce generator #### func NewClientWithURL ```go func NewClientWithURL(url string) *Client ``` Create a new Rest client with a custom base url #### func NewClientWithURLHttpDo ```go func NewClientWithURLHttpDo(base string, httpDo func(c *http.Client, r *http.Request) (*http.Response, error)) *Client ``` Create a new Rest client with a custom base url and HTTP handler #### func NewClientWithURLHttpDoNonce ```go func NewClientWithURLHttpDoNonce(base string, httpDo func(c *http.Client, r *http.Request) (*http.Response, error), nonce utils.NonceGenerator) *Client ``` Create a new Rest client with a custom base url, HTTP handler and none generator #### func NewClientWithURLNonce ```go func NewClientWithURLNonce(url string, nonce utils.NonceGenerator) *Client ``` Create a new Rest client with a custom nonce generator #### func (*Client) Credentials ```go func (c *Client) Credentials(key string, secret string) *Client ``` Set the clients credentials in order to make authenticated requests #### func (*Client) NewAuthenticatedRequest ```go func (c *Client) NewAuthenticatedRequest(permissionType bitfinex.PermissionType, refURL string) (Request, error) ``` Create a new authenticated GET request with the given permission type and endpoint url For example permissionType = "r" and refUrl = "/orders" then the target endpoint will be https://api.bitfinex.com/v2/auth/r/orders/:Symbol #### func (*Client) NewAuthenticatedRequestWithBytes ```go func (c *Client) NewAuthenticatedRequestWithBytes(permissionType bitfinex.PermissionType, refURL string, data []byte) (Request, error) ``` Create a new authenticated POST request with the given permission type,endpoint url and data (bytes) as the body For example permissionType = "r" and refUrl = "/orders" then the target endpoint will be https://api.bitfinex.com/v2/auth/r/orders/:Symbol #### func (*Client) NewAuthenticatedRequestWithData ```go func (c *Client) NewAuthenticatedRequestWithData(permissionType bitfinex.PermissionType, refURL string, data map[string]interface{}) (Request, error) ``` Create a new authenticated POST request with the given permission type,endpoint url and data (map[string]interface{}) as the body For example permissionType = "r" and refUrl = "/orders" then the target endpoint will be https://api.bitfinex.com/v2/auth/r/orders/:Symbol #### type ClientOrderIDs ```go type ClientOrderIDs [][]interface{} ``` #### type CurrenciesService ```go type CurrenciesService struct { Synchronous } ``` TradeService manages the Trade endpoint. #### func (*CurrenciesService) Conf ```go func (cs *CurrenciesService) Conf(label, symbol, unit, explorer, pairs bool) ([]bitfinex.CurrencyConf, error) ``` Retreive currency and symbol service configuration data see https://docs.bitfinex.com/reference#rest-public-conf for more info #### type DepositInvoiceRequest ```go type DepositInvoiceRequest struct { Currency string `json:"currency,omitempty"` Wallet string `json:"wallet,omitempty"` Amount string `json:"amount,omitempty"` } ``` DepositInvoiceRequest - data structure for constructing deposit invoice request payload #### type DerivativesService ```go type DerivativesService struct { Synchronous } ``` OrderService manages data flow for the Order API endpoint #### type ErrorResponse ```go type ErrorResponse struct { Response *Response Message string `json:"message"` Code int `json:"code"` } ``` In case if API will wrong response code ErrorResponse will be returned to caller #### func (*ErrorResponse) Error ```go func (r *ErrorResponse) Error() string ``` #### type ForeignExchangeRateRequest ```go type ForeignExchangeRateRequest struct { FirstCurrency string `json:"ccy1"` SecondCurrency string `json:"ccy2"` } ``` ForeignExchangeRateRequest data structure for constructing average price query params #### type FundingService ```go type FundingService struct { Synchronous } ``` FundingService manages the Funding endpoint. #### func (*FundingService) CancelOffer ```go func (fs *FundingService) CancelOffer(fc *bitfinex.FundingOfferCancelRequest) (*bitfinex.Notification, error) ``` Submits a request to cancel the given offer see https://docs.bitfinex.com/reference#cancel-funding-offer for more info #### func (*FundingService) Credits ```go func (fs *FundingService) Credits(symbol string) (*bitfinex.FundingCreditSnapshot, error) ``` Retreive all of the active credits used in positions see https://docs.bitfinex.com/reference#rest-auth-funding-credits for more info #### func (*FundingService) CreditsHistory ```go func (fs *FundingService) CreditsHistory(symbol string) (*bitfinex.FundingCreditSnapshot, error) ``` Retreive all of the past in-active credits used in positions see https://docs.bitfinex.com/reference#rest-auth-funding-credits-hist for more info #### func (*FundingService) KeepFunding ```go func (fs *FundingService) KeepFunding(args KeepFundingRequest) (*bitfinex.Notification, error) ``` KeepFunding - toggle to keep funding taken. Specify loan for unused funding and credit for used funding. see https://docs.bitfinex.com/reference#rest-auth-keep-funding for more info #### func (*FundingService) Loans ```go func (fs *FundingService) Loans(symbol string) (*bitfinex.FundingLoanSnapshot, error) ``` Retreive all of the active funding loans see https://docs.bitfinex.com/reference#rest-auth-funding-loans for more info #### func (*FundingService) LoansHistory ```go func (fs *FundingService) LoansHistory(symbol string) (*bitfinex.FundingLoanSnapshot, error) ``` Retreive all of the past in-active funding loans see https://docs.bitfinex.com/reference#rest-auth-funding-loans-hist for more info #### func (*FundingService) OfferHistory ```go func (fs *FundingService) OfferHistory(symbol string) (*bitfinex.FundingOfferSnapshot, error) ``` Retreive all of the past in-active funding offers see https://docs.bitfinex.com/reference#rest-auth-funding-offers-hist for more info #### func (*FundingService) Offers ```go func (fs *FundingService) Offers(symbol string) (*bitfinex.FundingOfferSnapshot, error) ``` Retreive all of the active fundign offers see https://docs.bitfinex.com/reference#rest-auth-funding-offers for more info #### func (*FundingService) SubmitOffer ```go func (fs *FundingService) SubmitOffer(fo *bitfinex.FundingOfferRequest) (*bitfinex.Notification, error) ``` Submits a request to create a new funding offer see https://docs.bitfinex.com/reference#submit-funding-offer for more info #### func (*FundingService) Trades ```go func (fs *FundingService) Trades(symbol string) (*bitfinex.FundingTradeSnapshot, error) ``` Retreive all of the matched funding trades see https://docs.bitfinex.com/reference#rest-auth-funding-trades-hist for more info #### type GroupOrderIDs ```go type GroupOrderIDs []int ``` #### type HttpTransport ```go type HttpTransport struct { BaseURL *url.URL HTTPClient *http.Client } ``` #### func (HttpTransport) Request ```go func (h HttpTransport) Request(req Request) ([]interface{}, error) ``` #### type InvoiceService ```go type InvoiceService struct { Synchronous } ``` InvoiceService manages Invoice endpoint #### func (*InvoiceService) GenerateInvoice ```go func (is *InvoiceService) GenerateInvoice(payload DepositInvoiceRequest) (*invoice.Invoice, error) ``` GenerateInvoice generates a Lightning Network deposit invoice Accepts DepositInvoiceRequest type as argument https://docs.bitfinex.com/reference#rest-auth-deposit-invoice #### type KeepFundingRequest ```go type KeepFundingRequest struct { Type string `json:"type"` ID int `json:"id"` } ``` KeepFundingRequest - data structure for constructing keep funding request payload #### type LedgerService ```go type LedgerService struct { Synchronous } ``` LedgerService manages the Ledgers endpoint. #### func (*LedgerService) Ledgers ```go func (s *LedgerService) Ledgers(currency string, start int64, end int64, max int32) (*bitfinex.LedgerSnapshot, error) ``` Retrieves all of the past ledger entreies see https://docs.bitfinex.com/reference#ledgers for more info #### type MarketService ```go type MarketService struct { Synchronous } ``` #### func (*MarketService) AveragePrice ```go func (ms *MarketService) AveragePrice(pld AveragePriceRequest) ([]float64, error) ``` AveragePrice Calculate the average execution price for Trading or rate for Margin funding. See: https://docs.bitfinex.com/reference#rest-public-calc-market-average-price #### func (*MarketService) ForeignExchangeRate ```go func (ms *MarketService) ForeignExchangeRate(pld ForeignExchangeRateRequest) ([]float64, error) ``` ForeignExchangeRate - Calculate the exchange rate between two currencies See: https://docs.bitfinex.com/reference#rest-public-calc-foreign-exchange-rate #### type Nickname ```go type Nickname string ``` #### type OrderIDs ```go type OrderIDs []int ``` #### type OrderMultiOpsRequest ```go type OrderMultiOpsRequest struct { Ops OrderOps `json:"ops"` } ``` OrderMultiOpsRequest - data structure for constructing order multi ops request payload #### type OrderOps ```go type OrderOps [][]interface{} ``` #### type OrderService ```go type OrderService struct { Synchronous } ``` OrderService manages data flow for the Order API endpoint #### func (*OrderService) All ```go func (s *OrderService) All() (*bitfinex.OrderSnapshot, error) ``` Retrieves all of the active orders See https://docs.bitfinex.com/reference#rest-auth-orders for more info #### func (*OrderService) AllHistory ```go func (s *OrderService) AllHistory() (*bitfinex.OrderSnapshot, error) ``` Retrieves all past orders See https://docs.bitfinex.com/reference#orders-history for more info #### func (*OrderService) CancelOrderMulti ```go func (s *OrderService) CancelOrderMulti(args CancelOrderMultiRequest) (*bitfinex.Notification, error) ``` CancelOrderMulti cancels multiple orders simultaneously. Orders can be canceled based on the Order ID, the combination of Client Order ID and Client Order Date, or the Group Order ID. Alternatively, the body param 'all' can be used with a value of 1 to cancel all orders. see https://docs.bitfinex.com/reference#rest-auth-order-cancel-multi for more info #### func (*OrderService) CancelOrderMultiOp ```go func (s *OrderService) CancelOrderMultiOp(orderID int) (*bitfinex.Notification, error) ``` CancelOrderMultiOp cancels order. Accepts orderID to be canceled. see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info #### func (*OrderService) CancelOrdersMultiOp ```go func (s *OrderService) CancelOrdersMultiOp(ids OrderIDs) (*bitfinex.Notification, error) ``` CancelOrdersMultiOp cancels multiple orders simultaneously. Accepts a slice of order ID's to be canceled. see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info #### func (*OrderService) GetByOrderId ```go func (s *OrderService) GetByOrderId(orderID int64) (o *bitfinex.Order, err error) ``` Retrieve an active order by the given ID See https://docs.bitfinex.com/reference#rest-auth-orders for more info #### func (*OrderService) GetBySymbol ```go func (s *OrderService) GetBySymbol(symbol string) (*bitfinex.OrderSnapshot, error) ``` Retrieves all of the active orders with for the given symbol See https://docs.bitfinex.com/reference#rest-auth-orders for more info #### func (*OrderService) GetHistoryByOrderId ```go func (s *OrderService) GetHistoryByOrderId(orderID int64) (o *bitfinex.Order, err error) ``` Retrieve a single order in history with the given id See https://docs.bitfinex.com/reference#orders-history for more info #### func (*OrderService) GetHistoryBySymbol ```go func (s *OrderService) GetHistoryBySymbol(symbol string) (*bitfinex.OrderSnapshot, error) ``` Retrieves all past orders with the given symbol See https://docs.bitfinex.com/reference#orders-history for more info #### func (*OrderService) OrderMultiOp ```go func (s *OrderService) OrderMultiOp(ops OrderOps) (*bitfinex.Notification, error) ``` OrderMultiOp - send Multiple order-related operations. Please note the sent object has only one property with a value of a slice of slices detailing each order operation. see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info #### func (*OrderService) OrderNewMultiOp ```go func (s *OrderService) OrderNewMultiOp(order bitfinex.OrderNewRequest) (*bitfinex.Notification, error) ``` OrderNewMultiOp creates new order. Accepts instance of bitfinex.OrderNewRequest see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info #### func (*OrderService) OrderTrades ```go func (s *OrderService) OrderTrades(symbol string, orderID int64) (*bitfinex.TradeExecutionUpdateSnapshot, error) ``` Retrieves the trades generated by an order See https://docs.bitfinex.com/reference#orders-history for more info #### func (*OrderService) OrderUpdateMultiOp ```go func (s *OrderService) OrderUpdateMultiOp(order bitfinex.OrderUpdateRequest) (*bitfinex.Notification, error) ``` OrderUpdateMultiOp updates order. Accepts instance of bitfinex.OrderUpdateRequest see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info #### func (*OrderService) SubmitCancelOrder ```go func (s *OrderService) SubmitCancelOrder(oc *bitfinex.OrderCancelRequest) error ``` Submit a request to cancel an order with the given Id see https://docs.bitfinex.com/reference#cancel-order for more info #### func (*OrderService) SubmitOrder ```go func (s *OrderService) SubmitOrder(order *bitfinex.OrderNewRequest) (*bitfinex.Notification, error) ``` Submit a request to create a new order see https://docs.bitfinex.com/reference#submit-order for more info #### func (*OrderService) SubmitUpdateOrder ```go func (s *OrderService) SubmitUpdateOrder(order *bitfinex.OrderUpdateRequest) (*bitfinex.Notification, error) ``` Submit a request to update an order with the given id with the given changes see https://docs.bitfinex.com/reference#order-update for more info #### type PlatformService ```go type PlatformService struct { Synchronous } ``` #### func (*PlatformService) Status ```go func (p *PlatformService) Status() (bool, error) ``` Retrieves the current status of the platform see https://docs.bitfinex.com/reference#rest-public-platform-status for more info #### type PositionService ```go type PositionService struct { Synchronous } ``` PositionService manages the Position endpoint. #### func (*PositionService) All ```go func (s *PositionService) All() (*bitfinex.PositionSnapshot, error) ``` Retrieves all of the active positions see https://docs.bitfinex.com/reference#rest-auth-positions for more info #### func (*PositionService) Claim ```go func (s *PositionService) Claim(cp *bitfinex.ClaimPositionRequest) (*bitfinex.Notification, error) ``` Submits a request to claim an active position with the given id see https://docs.bitfinex.com/reference#claim-position for more info #### type PulseService ```go type PulseService struct { Synchronous } ``` #### func (*PulseService) AddPulse ```go func (ps *PulseService) AddPulse(p *pulse.Pulse) (*pulse.Pulse, error) ``` AddPulse submits pulse message see https://docs.bitfinex.com/reference#rest-auth-pulse-add #### func (*PulseService) DeletePulse ```go func (ps *PulseService) DeletePulse(pid string) (int, error) ``` DeletePulse removes your pulse message. Returns 0 if no pulse was deleted and 1 if it was see https://docs.bitfinex.com/reference#rest-auth-pulse-del #### func (*PulseService) PublicPulseHistory ```go func (ps *PulseService) PublicPulseHistory(limit int, end bitfinex.Mts) ([]*pulse.Pulse, error) ``` PublicPulseHistory returns latest pulse messages. You can specify an end timestamp to view older messages. see https://docs.bitfinex.com/reference#rest-public-pulse-hist #### func (*PulseService) PublicPulseProfile ```go func (ps *PulseService) PublicPulseProfile(nickname Nickname) (*pulseprofile.PulseProfile, error) ``` PublicPulseProfile returns details for a specific Pulse profile https://docs.bitfinex.com/reference#rest-public-pulse-profile #### func (*PulseService) PulseHistory ```go func (ps *PulseService) PulseHistory(isPublic bool) ([]*pulse.Pulse, error) ``` PulseHistory allows you to retrieve your pulse history. Call function with "false" boolean value for private and with "true" for public pulse history. see https://docs.bitfinex.com/reference#rest-auth-pulse-hist #### type Request ```go type Request struct { RefURL string // ref url Data []byte // body data Method string // http method Params url.Values // query parameters Headers map[string]string } ``` Request is a wrapper for standard http.Request. Default method is POST with no data. #### func NewRequest ```go func NewRequest(refURL string) Request ``` Create new POST request with an empty body as payload #### func NewRequestWithBytes ```go func NewRequestWithBytes(refURL string, data []byte) Request ``` Create a new POST request with the given bytes as body #### func NewRequestWithData ```go func NewRequestWithData(refURL string, data map[string]interface{}) (Request, error) ``` Create a new POST request with the given data (map[string]interface{}) as body #### func NewRequestWithDataMethod ```go func NewRequestWithDataMethod(refURL string, data []byte, method string) Request ``` Create a new request with a given method (POST | GET) with bytes as body #### func NewRequestWithMethod ```go func NewRequestWithMethod(refURL string, method string) Request ``` Create a new request with the given method (POST | GET) #### type Response ```go type Response struct { Response *http.Response Body []byte } ``` Response is a wrapper for standard http.Response and provides more methods. #### func (*Response) String ```go func (r *Response) String() string ``` String converts response body to string. An empty string will be returned if error. #### type StatsService ```go type StatsService struct { Synchronous } ``` TradeService manages the Trade endpoint. #### func (*StatsService) CreditSizeHistory ```go func (ss *StatsService) CreditSizeHistory(symbol string, side bitfinex.OrderSide) ([]bitfinex.Stat, error) ``` Retrieves platform statistics for credit size history see https://docs.bitfinex.com/reference#rest-public-stats for more info #### func (*StatsService) CreditSizeLast ```go func (ss *StatsService) CreditSizeLast(symbol string, side bitfinex.OrderSide) (*bitfinex.Stat, error) ``` Retrieves platform statistics for credit size last see https://docs.bitfinex.com/reference#rest-public-stats for more info #### func (*StatsService) FundingHistory ```go func (ss *StatsService) FundingHistory(symbol string) ([]bitfinex.Stat, error) ``` Retrieves platform statistics for funding history see https://docs.bitfinex.com/reference#rest-public-stats for more info #### func (*StatsService) FundingLast ```go func (ss *StatsService) FundingLast(symbol string) (*bitfinex.Stat, error) ``` Retrieves platform statistics for funding last see https://docs.bitfinex.com/reference#rest-public-stats for more info #### func (*StatsService) PositionHistory ```go func (ss *StatsService) PositionHistory(symbol string, side bitfinex.OrderSide) ([]bitfinex.Stat, error) ``` Retrieves platform statistics for position history see https://docs.bitfinex.com/reference#rest-public-stats for more info #### func (*StatsService) PositionLast ```go func (ss *StatsService) PositionLast(symbol string, side bitfinex.OrderSide) (*bitfinex.Stat, error) ``` Retrieves platform statistics for position last see https://docs.bitfinex.com/reference#rest-public-stats for more info #### func (*StatsService) SymbolCreditSizeHistory ```go func (ss *StatsService) SymbolCreditSizeHistory(fundingSymbol string, tradingSymbol string) ([]bitfinex.Stat, error) ``` Retrieves platform statistics for credit size history see https://docs.bitfinex.com/reference#rest-public-stats for more info #### func (*StatsService) SymbolCreditSizeLast ```go func (ss *StatsService) SymbolCreditSizeLast(fundingSymbol string, tradingSymbol string) (*bitfinex.Stat, error) ``` Retrieves platform statistics for credit size last see https://docs.bitfinex.com/reference#rest-public-stats for more info #### type StatusService ```go type StatusService struct { Synchronous } ``` TradeService manages the Trade endpoint. #### func (*StatusService) DerivativeStatus ```go func (ss *StatusService) DerivativeStatus(symbol string) (*bitfinex.DerivativeStatus, error) ``` Retrieves derivative status information for the given symbol from the platform see https://docs.bitfinex.com/reference#rest-public-status for more info #### func (*StatusService) DerivativeStatusAll ```go func (ss *StatusService) DerivativeStatusAll() ([]*bitfinex.DerivativeStatus, error) ``` Retrieves derivative status information for all symbols from the platform see https://docs.bitfinex.com/reference#rest-public-status for more info #### func (*StatusService) DerivativeStatusMulti ```go func (ss *StatusService) DerivativeStatusMulti(symbols []string) ([]*bitfinex.DerivativeStatus, error) ``` Retrieves derivative status information for the given symbols from the platform see https://docs.bitfinex.com/reference#rest-public-status for more info #### type Synchronous ```go type Synchronous interface { Request(request Request) ([]interface{}, error) } ``` #### type TickerService ```go type TickerService struct { Synchronous } ``` TradeService manages the Trade endpoint. #### func (*TickerService) All ```go func (s *TickerService) All() (*[]bitfinex.Ticker, error) ``` Retrieves all tickers for all symbols see https://docs.bitfinex.com/reference#rest-public-ticker for more info #### func (*TickerService) Get ```go func (s *TickerService) Get(symbol string) (*bitfinex.Ticker, error) ``` Retrieves the ticker for the given symbol see https://docs.bitfinex.com/reference#rest-public-ticker for more info #### func (*TickerService) GetMulti ```go func (s *TickerService) GetMulti(symbols []string) (*[]bitfinex.Ticker, error) ``` Retrieves the tickers for the given symbols see https://docs.bitfinex.com/reference#rest-public-ticker for more info #### type TradeService ```go type TradeService struct { Synchronous } ``` TradeService manages the Trade endpoint. #### func (*TradeService) AccountAll ```go func (s *TradeService) AccountAll() (*bitfinex.TradeExecutionUpdateSnapshot, error) ``` Retrieves all matched trades for the account see https://docs.bitfinex.com/reference#rest-auth-trades-hist for more info #### func (*TradeService) AccountAllWithSymbol ```go func (s *TradeService) AccountAllWithSymbol(symbol string) (*bitfinex.TradeExecutionUpdateSnapshot, error) ``` Retrieves all matched trades with the given symbol for the account see https://docs.bitfinex.com/reference#rest-auth-trades-hist for more info #### func (*TradeService) AccountHistoryWithQuery ```go func (s *TradeService) AccountHistoryWithQuery( symbol string, start bitfinex.Mts, end bitfinex.Mts, limit bitfinex.QueryLimit, sort bitfinex.SortOrder, ) (*bitfinex.TradeExecutionUpdateSnapshot, error) ``` Queries all matched trades with group of optional parameters see https://docs.bitfinex.com/reference#rest-auth-trades-hist for more info #### func (*TradeService) PublicHistoryWithQuery ```go func (s *TradeService) PublicHistoryWithQuery( symbol string, start bitfinex.Mts, end bitfinex.Mts, limit bitfinex.QueryLimit, sort bitfinex.SortOrder, ) (*bitfinex.TradeSnapshot, error) ``` Queries all public trades with a group of optional paramters see https://docs.bitfinex.com/reference#rest-public-trades for more info #### type WalletService ```go type WalletService struct { Synchronous } ``` WalletService manages data flow for the Wallet API endpoint #### func (*WalletService) CreateDepositAddress ```go func (ws *WalletService) CreateDepositAddress(wallet, method string) (*bitfinex.Notification, error) ``` Submits a request to create a new deposit address for the give Bitfinex wallet. Old addresses are still valid. See https://docs.bitfinex.com/reference#deposit-address for more info #### func (*WalletService) DepositAddress ```go func (ws *WalletService) DepositAddress(wallet, method string) (*bitfinex.Notification, error) ``` Retrieves the deposit address for the given Bitfinex wallet see https://docs.bitfinex.com/reference#deposit-address for more info #### func (*WalletService) SetCollateral ```go func (s *WalletService) SetCollateral(symbol string, amount float64) (bool, error) ``` Update the amount of collateral for a Derivative position see https://docs.bitfinex.com/reference#rest-auth-deriv-pos-collateral-set for more info #### func (*WalletService) Transfer ```go func (ws *WalletService) Transfer(from, to, currency, currencyTo string, amount float64) (*bitfinex.Notification, error) ``` Submits a request to transfer funds from one Bitfinex wallet to another see https://docs.bitfinex.com/reference#transfer-between-wallets for more info #### func (*WalletService) Wallet ```go func (s *WalletService) Wallet() (*bitfinex.WalletSnapshot, error) ``` Retrieves all of the wallets for the account see https://docs.bitfinex.com/reference#rest-auth-wallets for more info #### func (*WalletService) Withdraw ```go func (ws *WalletService) Withdraw(wallet, method string, amount float64, address string) (*bitfinex.Notification, error) ``` Submits a request to withdraw funds from the given Bitfinex wallet to the given address See https://docs.bitfinex.com/reference#withdraw for more info bitfinex-api-go-2.2.9/docs/v1.md000066400000000000000000000512071371275744700163340ustar00rootroot00000000000000# bitfinex -- import "github.com/bitfinexcom/bitfinex-api-go/v1" Package bitfinex is the official client to access to bitfinex.com API ## Usage ```go const ( // BaseURL is the v1 REST endpoint. BaseURL = "https://api.bitfinex.com/v1/" // WebSocketURL the v1 Websocket endpoint. WebSocketURL = "wss://api-pub.bitfinex.com/ws/" ) ``` ```go const ( LEND = "lend" LOAN = "loan" ) ``` ```go const ( OrderTypeMarket = "market" OrderTypeLimit = "limit" OrderTypeStop = "stop" OrderTypeTrailingStop = "trailing-stop" OrderTypeFillOrKill = "fill-or-kill" OrderTypeExchangeMarket = "exchange market" OrderTypeExchangeLimit = "exchange limit" OrderTypeExchangeStop = "exchange stop" OrderTypeExchangeTrailingStop = "exchange trailing-stop" OrderTypeExchangeFillOrKill = "exchange fill-or-kill" ) ``` Order types that the API can return. ```go const ( WALLET_TRADING = "trading" WALLET_EXCHANGE = "exchange" WALLET_DEPOSIT = "deposit" ) ``` ```go const ( // Pairs BTCUSD = "BTCUSD" LTCUSD = "LTCUSD" LTCBTC = "LTCBTC" ETHUSD = "ETHUSD" ETHBTC = "ETHBTC" ETCUSD = "ETCUSD" ETCBTC = "ETCBTC" BFXUSD = "BFXUSD" BFXBTC = "BFXBTC" ZECUSD = "ZECUSD" ZECBTC = "ZECBTC" XMRUSD = "XMRUSD" XMRBTC = "XMRBTC" RRTUSD = "RRTUSD" RRTBTC = "RRTBTC" XRPUSD = "XRPUSD" XRPBTC = "XRPBTC" EOSETH = "EOSETH" EOSUSD = "EOSUSD" EOSBTC = "EOSBTC" IOTUSD = "IOTUSD" IOTBTC = "IOTBTC" IOTETH = "IOTETH" BCCBTC = "BCCBTC" BCUBTC = "BCUBTC" BCCUSD = "BCCUSD" BCUUSD = "BCUUSD" // Channels ChanBook = "book" ChanTrade = "trades" ChanTicker = "ticker" ) ``` Pairs available #### type AccountInfo ```go type AccountInfo struct { MakerFees float64 `json:"maker_fees,string"` TakerFees float64 `json:"taker_fees,string"` Fees []AccountPairFee } ``` #### type AccountPairFee ```go type AccountPairFee struct { Pair string MakerFees float64 `json:"maker_fees,string"` TakerFees float64 `json:"taker_fees,string"` } ``` #### type AccountService ```go type AccountService struct { } ``` #### func (*AccountService) Info ```go func (a *AccountService) Info() (AccountInfo, error) ``` GET account_infos #### func (*AccountService) KeyPermission ```go func (a *AccountService) KeyPermission() (Permissions, error) ``` #### func (*AccountService) Summary ```go func (a *AccountService) Summary() (Summary, error) ``` #### type ActiveOffer ```go type ActiveOffer struct { ID int64 Currency string Rate string Period int Direction string Timestamp string IsLive bool `json:"is_live"` IsCancelled bool `json:"is_cancelled"` OriginalAmount string `json:"original_amount"` RemainingAmount string `json:"remaining_amount"` ExecutedAmount string `json:"executed_amount"` } ``` #### type Balance ```go type Balance struct { Currency string Amount string Balance string Description string Timestamp string } ``` #### type BalancesService ```go type BalancesService struct { } ``` #### func (*BalancesService) All ```go func (b *BalancesService) All() ([]WalletBalance, error) ``` GET balances #### type BankAccount ```go type BankAccount struct { AccountName string // Account name AccountNumber string // Account number or IBAN BankName string // Bank Name BankAddress string // Bank Address BankCity string // Bank City BankCountry string // Bank Country SwiftCode string // SWIFT Code } ``` #### type Client ```go type Client struct { // Base URL for API requests. BaseURL *url.URL WebSocketURL string WebSocketTLSSkipVerify bool // Auth data APIKey string APISecret string // Services Pairs *PairsService Stats *StatsService Ticker *TickerService Account *AccountService Balances *BalancesService Offers *OffersService Credits *CreditsService Deposit *DepositService Lendbook *LendbookService MarginInfo *MarginInfoService MarginFunding *MarginFundingService OrderBook *OrderBookService Orders *OrderService Trades *TradesService Positions *PositionsService History *HistoryService WebSocket *WebSocketService Wallet *WalletService } ``` Client manages all the communication with the Bitfinex API. #### func NewClient ```go func NewClient() *Client ``` NewClient creates new Bitfinex.com API client. #### func (*Client) Auth ```go func (c *Client) Auth(key string, secret string) *Client ``` Auth sets api key and secret for usage is requests that requires authentication. #### type Credit ```go type Credit struct { Id int Currency string Status string Rate float64 `json:",string"` Period float64 Amount float64 `json:",string"` Timestamp string } ``` #### type CreditsService ```go type CreditsService struct { } ``` #### func (*CreditsService) All ```go func (c *CreditsService) All() ([]Credit, error) ``` Returns an array of Credit #### type DepositResponse ```go type DepositResponse struct { Result string Method string Currency string Address string } ``` #### func (*DepositResponse) Success ```go func (d *DepositResponse) Success() (bool, error) ``` #### type DepositService ```go type DepositService struct { } ``` #### func (*DepositService) New ```go func (s *DepositService) New(method, walletName string, renew int) (DepositResponse, error) ``` #### type ErrorResponse ```go type ErrorResponse struct { Response *Response Message string `json:"message"` } ``` ErrorResponse is the custom error type that is returned if the API returns an error. #### func (*ErrorResponse) Error ```go func (r *ErrorResponse) Error() string ``` #### type HistoryService ```go type HistoryService struct { } ``` #### func (*HistoryService) Balance ```go func (s *HistoryService) Balance(currency, wallet string, since, until time.Time, limit int) ([]Balance, error) ``` #### func (*HistoryService) Movements ```go func (s *HistoryService) Movements(currency, method string, since, until time.Time, limit int) ([]Movement, error) ``` #### func (*HistoryService) Trades ```go func (s *HistoryService) Trades(pair string, since, until time.Time, limit int, reverse bool) ([]PastTrade, error) ``` #### type KeyPerm ```go type KeyPerm struct { Read bool Write bool } ``` #### type Lend ```go type Lend struct { Rate string Amount string Period int Timestamp string Frr string } ``` #### func (*Lend) ParseTime ```go func (el *Lend) ParseTime() (*time.Time, error) ``` #### type Lendbook ```go type Lendbook struct { Bids []Lend Asks []Lend } ``` #### type LendbookService ```go type LendbookService struct { } ``` #### func (*LendbookService) Get ```go func (s *LendbookService) Get(currency string, limitBids, limitAsks int) (Lendbook, error) ``` GET /lendbook/:currency #### func (*LendbookService) Lends ```go func (s *LendbookService) Lends(currency string) ([]Lends, error) ``` GET /lends/:currency #### type Lends ```go type Lends struct { Rate string AmountLent string `json:"amount_lent"` AmountUsed string `json:"amount_used"` Timestamp int64 } ``` #### func (*Lends) Time ```go func (el *Lends) Time() *time.Time ``` #### type MarginFundingService ```go type MarginFundingService struct { } ``` #### func (*MarginFundingService) Cancel ```go func (s *MarginFundingService) Cancel(offerId int64) (MarginOffer, error) ``` #### func (*MarginFundingService) Credits ```go func (s *MarginFundingService) Credits() ([]ActiveOffer, error) ``` #### func (*MarginFundingService) NewLend ```go func (s *MarginFundingService) NewLend(currency string, amount, rate float64, period int) (MarginOffer, error) ``` #### func (*MarginFundingService) NewLoan ```go func (s *MarginFundingService) NewLoan(currency string, amount, rate float64, period int) (MarginOffer, error) ``` #### func (*MarginFundingService) Offers ```go func (s *MarginFundingService) Offers() ([]ActiveOffer, error) ``` #### func (*MarginFundingService) Status ```go func (s *MarginFundingService) Status(offerId int64) (MarginOffer, error) ``` #### type MarginInfo ```go type MarginInfo struct { MarginBalance float64 `json:"margin_balance,string"` TradableBalance float64 `json:"tradable_balance,string"` UnrealizedPl float64 `json:"unrealized_pl,string"` UnrealizedSwap float64 `json:"unrealized_swap,string"` NetValue float64 `json:"net_value,string"` RequiredMargin float64 `json:"required_margin,string"` Leverage float64 `json:"leverage,string"` MarginRequirement float64 `json:"margin_requirement,string"` MarginLimits []MarginLimit `json:"margin_limits,string"` Message string `json:"message"` } ``` #### type MarginInfoService ```go type MarginInfoService struct { } ``` #### func (*MarginInfoService) All ```go func (s *MarginInfoService) All() ([]MarginInfo, error) ``` GET /margin_infos #### type MarginLimit ```go type MarginLimit struct { OnPair string `json:"on_pair"` InitialMargin float64 `json:"initial_margin,string"` MarginRequirement float64 `json:"margin_requirement,string"` TradableBalance float64 `json:"tradable_balance,string"` } ``` #### type MarginOffer ```go type MarginOffer struct { ID int64 Currency string Rate string Period int Direction string Timestamp string IsLive bool `json:"is_live"` IsCancelled bool `json:"is_cancelled"` OriginalAmount string `json:"original_amount"` RemainingAmount string `json:"remaining_amount"` ExecutedAmount string `json:"executed_amount"` OfferId int } ``` #### type Movement ```go type Movement struct { ID int64 `json:",int"` Currency string Method string Type string Amount string Description string Status string Timestamp string } ``` #### type MultipleOrderResponse ```go type MultipleOrderResponse struct { Orders []Order `json:"order_ids"` Status string } ``` MultipleOrderResponse bundles orders returned by the CreateMulti method. #### type Offer ```go type Offer struct { Id int64 Currency string Rate string Period int64 Direction string Timestamp string IsLive bool `json:"is_live"` IsCancelled bool `json:"is_cancelled"` OriginalAmount string `json:"original_amount:string"` RemainingAmount string `json:"remaining_amount:string"` ExecutedAmount string `json:"executed_amount:string"` OfferId int64 `json:"offer_id"` } ``` #### type OffersService ```go type OffersService struct { } ``` #### func (*OffersService) Cancel ```go func (s *OffersService) Cancel(offerId int64) (Offer, error) ``` #### func (*OffersService) New ```go func (s *OffersService) New(currency string, amount, rate float64, period int64, direction string) (Offer, error) ``` Create new offer for LEND or LOAN a currency, use LEND or LOAN constants as direction #### func (*OffersService) Status ```go func (s *OffersService) Status(offerId int64) (Offer, error) ``` #### type Order ```go type Order struct { ID int64 Symbol string Exchange string Price string AvgExecutionPrice string `json:"avg_execution_price"` Side string Type string Timestamp string IsLive bool `json:"is_live"` IsCanceled bool `json:"is_cancelled"` IsHidden bool `json:"is_hidden"` WasForced bool `json:"was_forced"` OriginalAmount string `json:"original_amount"` RemainingAmount string `json:"remaining_amount"` ExecutedAmount string `json:"executed_amount"` } ``` Order represents one order on the bitfinex platform. #### type OrderBook ```go type OrderBook struct { Bids []OrderBookEntry Asks []OrderBookEntry } ``` #### type OrderBookEntry ```go type OrderBookEntry struct { Price string Rate string Amount string Period int Timestamp string Frr string } ``` #### func (*OrderBookEntry) ParseTime ```go func (el *OrderBookEntry) ParseTime() (*time.Time, error) ``` #### type OrderBookService ```go type OrderBookService struct { } ``` #### func (*OrderBookService) Get ```go func (s *OrderBookService) Get(pair string, limitBids, limitAsks int, noGroup bool) (OrderBook, error) ``` GET /book #### type OrderService ```go type OrderService struct { } ``` OrderService manages the Order endpoint. #### func (*OrderService) All ```go func (s *OrderService) All() ([]Order, error) ``` All returns all orders for the authenticated account. #### func (*OrderService) Cancel ```go func (s *OrderService) Cancel(orderID int64) error ``` Cancel the order with id `orderID`. #### func (*OrderService) CancelAll ```go func (s *OrderService) CancelAll() error ``` CancelAll active orders for the authenticated account. #### func (*OrderService) CancelMulti ```go func (s *OrderService) CancelMulti(orderIDS []int64) (string, error) ``` CancelMulti allows batch cancellation of orders. #### func (*OrderService) Create ```go func (s *OrderService) Create(symbol string, amount float64, price float64, orderType string) (*Order, error) ``` Create a new order. #### func (*OrderService) CreateMulti ```go func (s *OrderService) CreateMulti(orders []SubmitOrder) (MultipleOrderResponse, error) ``` CreateMulti allows batch creation of orders. #### func (*OrderService) Replace ```go func (s *OrderService) Replace(orderID int64, useRemaining bool, newOrder SubmitOrder) (Order, error) ``` Replace an Order #### func (*OrderService) Status ```go func (s *OrderService) Status(orderID int64) (Order, error) ``` Status retrieves the given order from the API. #### type Pair ```go type Pair struct { Pair string PricePrecision int `json:"price_precision,int"` InitialMargin float64 `json:"initial_margin,string"` MinimumMargin float64 `json:"minimum_margin,string"` MaximumOrderSize float64 `json:"maximum_order_size,string"` MinimumOrderSize float64 `json:"minimum_order_size,string"` Expiration string Margin bool } ``` Detailed Pair #### type PairsService ```go type PairsService struct { } ``` #### func (*PairsService) All ```go func (p *PairsService) All() ([]string, error) ``` Get all Pair names as array of strings #### func (*PairsService) AllDetailed ```go func (p *PairsService) AllDetailed() ([]Pair, error) ``` Return a list of detailed pairs #### type PastTrade ```go type PastTrade struct { Price string Amount string Timestamp string Exchange string Type string FeeCurrency string `json:"fee_currency"` FeeAmount string `json:"fee_amount"` TID int64 OrderId int64 `json:"order_id,int"` } ``` #### type Permissions ```go type Permissions struct { Account KeyPerm History KeyPerm Orders KeyPerm Positions KeyPerm Funding KeyPerm Wallets KeyPerm Withdraw KeyPerm } ``` #### type Position ```go type Position struct { ID int Symbol string Amount string Status string Base string Timestamp string Swap string Pl string } ``` Position structure #### func (*Position) ParseTime ```go func (p *Position) ParseTime() (*time.Time, error) ``` #### type PositionsService ```go type PositionsService struct { } ``` PositionsService structure #### func (*PositionsService) All ```go func (b *PositionsService) All() ([]Position, error) ``` All - gets all positions #### func (*PositionsService) Claim ```go func (b *PositionsService) Claim(positionId int, amount string) (Position, error) ``` Claim a position #### type Response ```go type Response struct { Response *http.Response Body []byte } ``` Response is wrapper for standard http.Response and provides more methods. #### func (*Response) String ```go func (r *Response) String() string ``` String converts response body to string. An empty string will be returned if error. #### type Stats ```go type Stats struct { Period int64 Volume float64 `json:"volume,string"` } ``` #### type StatsService ```go type StatsService struct { } ``` #### func (*StatsService) All ```go func (s *StatsService) All(pair string, period, volume string) ([]Stats, error) ``` All(pair) - Volume stats for specified pair #### type SubmitOrder ```go type SubmitOrder struct { Symbol string Amount float64 Price float64 Type string } ``` SubmitOrder is an order to be created on the bitfinex platform. #### type Summary ```go type Summary struct { TradeVolume SummaryVolume `json:"trade_vol_30d"` FundingProfit SummaryProfit `json:"funding_profit_30d"` MakerFee string `json:"maker_fee"` TakerFee string `json:"taker_fee"` } ``` #### type SummaryProfit ```go type SummaryProfit struct { Currency string `json:"curr"` Volume string `json:"amount"` } ``` #### type SummaryVolume ```go type SummaryVolume struct { Currency string `json:"curr"` Volume string `json:"vol"` } ``` #### type TermData ```go type TermData struct { // Data term. E.g: ps, ws, ou, etc... See official documentation for more details. Term string // Data will contain different number of elements for each term. // Examples: // Term: ws, Data: ["exchange","BTC",0.01410829,0] // Term: oc, Data: [0,"BTCUSD",0,-0.01,"","CANCELED",270,0,"2015-10-15T11:26:13Z",0] Data []interface{} Error string } ``` #### func (*TermData) HasError ```go func (c *TermData) HasError() bool ``` #### type Tick ```go type Tick struct { Mid string Bid string Ask string LastPrice string `json:"last_price"` Low string High string Volume string Timestamp string } ``` #### func (*Tick) ParseTime ```go func (el *Tick) ParseTime() (*time.Time, error) ``` ParseTime - return Timestamp in time.Time format #### type TickerService ```go type TickerService struct { } ``` #### func (*TickerService) Get ```go func (s *TickerService) Get(pair string) (Tick, error) ``` Get(pair) - return last Tick for specified pair #### type Trade ```go type Trade struct { Price string Amount string Exchange string Type string Timestamp int64 TradeId int64 `json:"tid,int"` } ``` #### func (*Trade) Time ```go func (el *Trade) Time() *time.Time ``` #### type TradesService ```go type TradesService struct { } ``` #### func (*TradesService) All ```go func (s *TradesService) All(pair string, timestamp time.Time, limitTrades int) ([]Trade, error) ``` #### type TransferStatus ```go type TransferStatus struct { Status string Message string } ``` #### type WalletBalance ```go type WalletBalance struct { Type string Currency string Amount string Available string } ``` #### type WalletService ```go type WalletService struct { } ``` #### func (*WalletService) Transfer ```go func (c *WalletService) Transfer(amount float64, currency, from, to string) ([]TransferStatus, error) ``` Transfer funds between wallets #### func (*WalletService) WithdrawCrypto ```go func (c *WalletService) WithdrawCrypto(amount float64, currency, wallet, destinationAddress string) ([]WithdrawStatus, error) ``` Withdraw a cryptocurrency to a digital wallet #### func (*WalletService) WithdrawWire ```go func (c *WalletService) WithdrawWire(amount float64, expressWire bool, wallet string, beneficiaryBank, intermediaryBank BankAccount, message string) ([]WithdrawStatus, error) ``` #### type WebSocketService ```go type WebSocketService struct { } ``` WebSocketService allow to connect and receive stream data from bitfinex.com ws service. nolint:megacheck,structcheck #### func NewWebSocketService ```go func NewWebSocketService(c *Client) *WebSocketService ``` NewWebSocketService returns a WebSocketService using the given client. #### func (*WebSocketService) AddSubscribe ```go func (w *WebSocketService) AddSubscribe(channel string, pair string, c chan []float64) ``` #### func (*WebSocketService) ClearSubscriptions ```go func (w *WebSocketService) ClearSubscriptions() ``` #### func (*WebSocketService) Close ```go func (w *WebSocketService) Close() ``` Close web socket connection #### func (*WebSocketService) Connect ```go func (w *WebSocketService) Connect() error ``` Connect create new bitfinex websocket connection #### func (*WebSocketService) ConnectPrivate ```go func (w *WebSocketService) ConnectPrivate(ch chan TermData) ``` #### func (*WebSocketService) Subscribe ```go func (w *WebSocketService) Subscribe() error ``` Subscribe allows to subsribe to channels and watch for new updates. This method supports next channels: book, trade, ticker. #### type WithdrawStatus ```go type WithdrawStatus struct { Status string Message string WithdrawalID int `json:"withdrawal_id"` } ``` bitfinex-api-go-2.2.9/docs/ws_v2.md000066400000000000000000000520011371275744700170370ustar00rootroot00000000000000# websocket -- import "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ## Usage ```go const ( ChanBook = "book" ChanTrades = "trades" ChanTicker = "ticker" ChanCandles = "candles" ChanStatus = "status" ) ``` Available channels ```go const ( EventSubscribe = "subscribe" EventUnsubscribe = "unsubscribe" EventPing = "ping" ) ``` Events ```go const ( ErrorCodeUnknownEvent int = 10000 ErrorCodeUnknownPair int = 10001 ErrorCodeUnknownBookPrecision int = 10011 ErrorCodeUnknownBookLength int = 10012 ErrorCodeSubscriptionFailed int = 10300 ErrorCodeAlreadySubscribed int = 10301 ErrorCodeUnknownChannel int = 10302 ErrorCodeUnsubscribeFailed int = 10400 ErrorCodeNotSubscribed int = 10401 ) ``` error codes pulled from v2 docs & API usage ```go const DMSCancelOnDisconnect int = 4 ``` DMSCancelOnDisconnect cancels session orders on disconnect. ```go const KEEP_ALIVE_TIMEOUT = 10 ``` seconds to wait in between re-sending the keep alive ping ```go const MaxChannels = 25 ``` ```go const WS_READ_CAPACITY = 10 ``` size of channel that the websocket reader routine pushes websocket updates into ```go const WS_WRITE_CAPACITY = 5000 ``` size of channel that the websocket writer routine pulls from ```go var ( ErrWSNotConnected = fmt.Errorf("websocket connection not established") ErrWSAlreadyConnected = fmt.Errorf("websocket connection already established") ) ``` ws-specific errors #### func ConvertBytesToJsonNumberArray ```go func ConvertBytesToJsonNumberArray(raw_bytes []byte) ([]interface{}, error) ``` #### type Asynchronous ```go type Asynchronous interface { Connect() error Send(ctx context.Context, msg interface{}) error Listen() <-chan []byte Close() Done() <-chan error } ``` Asynchronous interface decouples the underlying transport from API logic. #### type AsynchronousFactory ```go type AsynchronousFactory interface { Create() Asynchronous } ``` AsynchronousFactory provides an interface to re-create asynchronous transports during reconnect events. #### func NewWebsocketAsynchronousFactory ```go func NewWebsocketAsynchronousFactory(parameters *Parameters) AsynchronousFactory ``` NewWebsocketAsynchronousFactory creates a new websocket factory with a given URL. #### type AuthEvent ```go type AuthEvent struct { Event string `json:"event"` Status string `json:"status"` ChanID int64 `json:"chanId,omitempty"` UserID int64 `json:"userId,omitempty"` SubID string `json:"subId"` AuthID string `json:"auth_id,omitempty"` Message string `json:"msg,omitempty"` Caps Capabilities `json:"caps"` } ``` #### type AuthState ```go type AuthState authState // prevent user construction of authStates ``` AuthState provides a typed authentication state. ```go const ( NoAuthentication AuthState = 0 PendingAuthentication AuthState = 1 SuccessfulAuthentication AuthState = 2 RejectedAuthentication AuthState = 3 ) ``` Authentication states #### type BookFactory ```go type BookFactory struct { } ``` #### func (*BookFactory) Build ```go func (f *BookFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (*BookFactory) BuildSnapshot ```go func (f *BookFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (BookFactory) Close ```go func (s BookFactory) Close() ``` Close is terminal. Do not call heartbeat after close. #### func (BookFactory) ListenDisconnect ```go func (s BookFactory) ListenDisconnect() <-chan HeartbeatDisconnect ``` ListenDisconnect returns an error channel which receives a message when a heartbeat has expired a channel. #### func (BookFactory) ResetAll ```go func (s BookFactory) ResetAll() ``` Removes all tracked subscriptions #### func (BookFactory) ResetSocketSubscriptions ```go func (s BookFactory) ResetSocketSubscriptions(socketId SocketId) []*subscription ``` Reset clears all subscriptions assigned to the given socket ID, and returns a slice of the existing subscriptions prior to reset #### type CandlesFactory ```go type CandlesFactory struct { } ``` #### func (*CandlesFactory) Build ```go func (f *CandlesFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (*CandlesFactory) BuildSnapshot ```go func (f *CandlesFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (CandlesFactory) Close ```go func (s CandlesFactory) Close() ``` Close is terminal. Do not call heartbeat after close. #### func (CandlesFactory) ListenDisconnect ```go func (s CandlesFactory) ListenDisconnect() <-chan HeartbeatDisconnect ``` ListenDisconnect returns an error channel which receives a message when a heartbeat has expired a channel. #### func (CandlesFactory) ResetAll ```go func (s CandlesFactory) ResetAll() ``` Removes all tracked subscriptions #### func (CandlesFactory) ResetSocketSubscriptions ```go func (s CandlesFactory) ResetSocketSubscriptions(socketId SocketId) []*subscription ``` Reset clears all subscriptions assigned to the given socket ID, and returns a slice of the existing subscriptions prior to reset #### type Capabilities ```go type Capabilities struct { Orders Capability `json:"orders"` Account Capability `json:"account"` Funding Capability `json:"funding"` History Capability `json:"history"` Wallets Capability `json:"wallets"` Withdraw Capability `json:"withdraw"` Positions Capability `json:"positions"` } ``` #### type Capability ```go type Capability struct { Read int `json:"read"` Write int `json:"write"` } ``` #### type Client ```go type Client struct { Authentication AuthState } ``` Client provides a unified interface for users to interact with the Bitfinex V2 Websocket API. nolint:megacheck,structcheck #### func New ```go func New() *Client ``` New creates a default client. #### func NewWithAsyncFactory ```go func NewWithAsyncFactory(async AsynchronousFactory) *Client ``` NewWithAsyncFactory creates a new default client with a given asynchronous transport factory interface. #### func NewWithAsyncFactoryNonce ```go func NewWithAsyncFactoryNonce(async AsynchronousFactory, nonce utils.NonceGenerator) *Client ``` NewWithAsyncFactoryNonce creates a new default client with a given asynchronous transport factory and nonce generator. #### func NewWithParams ```go func NewWithParams(params *Parameters) *Client ``` NewWithParams creates a new default client with a given set of parameters. #### func NewWithParamsAsyncFactory ```go func NewWithParamsAsyncFactory(params *Parameters, async AsynchronousFactory) *Client ``` NewWithParamsAsyncFactory creates a new default client with a given set of parameters and asynchronous transport factory interface. #### func NewWithParamsAsyncFactoryNonce ```go func NewWithParamsAsyncFactoryNonce(params *Parameters, async AsynchronousFactory, nonce utils.NonceGenerator) *Client ``` NewWithParamsAsyncFactoryNonce creates a new client with a given set of parameters, asynchronous transport factory, and nonce generator interfaces. #### func NewWithParamsNonce ```go func NewWithParamsNonce(params *Parameters, nonce utils.NonceGenerator) *Client ``` NewWithParamsNonce creates a new default client with a given set of parameters and nonce generator. #### func (*Client) AvailableCapacity ```go func (c *Client) AvailableCapacity() int ``` Get the available capacity of the current websocket connections #### func (*Client) CancelOnDisconnect ```go func (c *Client) CancelOnDisconnect(cxl bool) *Client ``` CancelOnDisconnect ensures all orders will be canceled if this API session is disconnected. #### func (*Client) Close ```go func (c *Client) Close() ``` Close the websocket client which will cause for all active sockets to be exited and the Done() function to be called #### func (*Client) Connect ```go func (c *Client) Connect() error ``` Connect to the Bitfinex API, this should only be called once. #### func (*Client) ConnectionCount ```go func (c *Client) ConnectionCount() int ``` Gen the count of currently active websocket connections #### func (*Client) Credentials ```go func (c *Client) Credentials(key string, secret string) *Client ``` Credentials assigns authentication credentials to a connection request. #### func (*Client) EnableFlag ```go func (c *Client) EnableFlag(ctx context.Context, flag int) (string, error) ``` Submit a request to enable the given flag #### func (*Client) GetAuthenticatedSocket ```go func (c *Client) GetAuthenticatedSocket() (*Socket, error) ``` Get the authenticated socket. Due to rate limitations there can only be one authenticated socket active at a time #### func (*Client) GetOrderbook ```go func (c *Client) GetOrderbook(symbol string) (*Orderbook, error) ``` Retrieve the Orderbook for the given symbol which is managed locally. This requires ManageOrderbook=True and an active chanel subscribed to the given symbols orderbook #### func (*Client) IsConnected ```go func (c *Client) IsConnected() bool ``` Returns true if the underlying asynchronous transport is connected to an endpoint. #### func (*Client) Listen ```go func (c *Client) Listen() <-chan interface{} ``` Listen for all incoming api websocket messages When a websocket connection is terminated, the publisher channel will close. #### func (*Client) LookupSubscription ```go func (c *Client) LookupSubscription(subID string) (*SubscriptionRequest, error) ``` Get a subscription request using a subscription ID #### func (*Client) Send ```go func (c *Client) Send(ctx context.Context, msg interface{}) error ``` Send publishes a generic message to the Bitfinex API. #### func (*Client) StartNewConnection ```go func (c *Client) StartNewConnection() error ``` Start a new websocket connection. This function is only exposed in case you want to implicitly add new connections otherwise connection management is already handled for you. #### func (*Client) SubmitCancel ```go func (c *Client) SubmitCancel(ctx context.Context, cancel *bitfinex.OrderCancelRequest) error ``` Submit a cancel request for an existing order #### func (*Client) SubmitFundingCancel ```go func (c *Client) SubmitFundingCancel(ctx context.Context, fundingOffer *bitfinex.FundingOfferCancelRequest) error ``` Submit a request to cancel and existing funding offer #### func (*Client) SubmitFundingOffer ```go func (c *Client) SubmitFundingOffer(ctx context.Context, fundingOffer *bitfinex.FundingOfferRequest) error ``` Submit a new funding offer request #### func (*Client) SubmitOrder ```go func (c *Client) SubmitOrder(ctx context.Context, order *bitfinex.OrderNewRequest) error ``` Submit a request to create a new order #### func (*Client) SubmitUpdateOrder ```go func (c *Client) SubmitUpdateOrder(ctx context.Context, orderUpdate *bitfinex.OrderUpdateRequest) error ``` Submit and update request to change an existing orders values #### func (*Client) Subscribe ```go func (c *Client) Subscribe(ctx context.Context, req *SubscriptionRequest) (string, error) ``` Submit a request to subscribe to the given SubscriptionRequuest #### func (*Client) SubscribeBook ```go func (c *Client) SubscribeBook(ctx context.Context, symbol string, precision bitfinex.BookPrecision, frequency bitfinex.BookFrequency, priceLevel int) (string, error) ``` Submit a subscription request for market data for the given symbol, at the given frequency, with the given precision, returning no more than priceLevels price entries. Default values are Precision0, Frequency0, and priceLevels=25. #### func (*Client) SubscribeCandles ```go func (c *Client) SubscribeCandles(ctx context.Context, symbol string, resolution bitfinex.CandleResolution) (string, error) ``` Submit a subscription request to receive candle updates #### func (*Client) SubscribeStatus ```go func (c *Client) SubscribeStatus(ctx context.Context, symbol string, sType bitfinex.StatusType) (string, error) ``` Submit a subscription request for status updates #### func (*Client) SubscribeTicker ```go func (c *Client) SubscribeTicker(ctx context.Context, symbol string) (string, error) ``` Submit a request to receive ticker updates #### func (*Client) SubscribeTrades ```go func (c *Client) SubscribeTrades(ctx context.Context, symbol string) (string, error) ``` Submit a request to receive trade updates #### func (*Client) Unsubscribe ```go func (c *Client) Unsubscribe(ctx context.Context, id string) error ``` Unsubscribe from the existing subscription with the given id #### type ConfEvent ```go type ConfEvent struct { Flags int `json:"flags"` } ``` #### type ErrorEvent ```go type ErrorEvent struct { Code int `json:"code"` Message string `json:"msg"` // also contain members related to subscription reject SubID string `json:"subId"` Channel string `json:"channel"` ChanID int64 `json:"chanId"` Symbol string `json:"symbol"` Precision string `json:"prec,omitempty"` Frequency string `json:"freq,omitempty"` Key string `json:"key,omitempty"` Len string `json:"len,omitempty"` Pair string `json:"pair"` } ``` #### type FlagRequest ```go type FlagRequest struct { Event string `json:"event"` Flags int `json:"flags"` } ``` #### type HeartbeatDisconnect ```go type HeartbeatDisconnect struct { Subscription *subscription Error error } ``` #### type InfoEvent ```go type InfoEvent struct { Version float64 `json:"version"` ServerId string `json:"serverId"` Platform PlatformInfo `json:"platform"` Code int `json:"code"` Msg string `json:"msg"` } ``` #### type Orderbook ```go type Orderbook struct { } ``` #### func (*Orderbook) Asks ```go func (ob *Orderbook) Asks() []bitfinex.BookUpdate ``` #### func (*Orderbook) Bids ```go func (ob *Orderbook) Bids() []bitfinex.BookUpdate ``` #### func (*Orderbook) Checksum ```go func (ob *Orderbook) Checksum() uint32 ``` #### func (*Orderbook) SetWithSnapshot ```go func (ob *Orderbook) SetWithSnapshot(bs *bitfinex.BookUpdateSnapshot) ``` #### func (*Orderbook) Symbol ```go func (ob *Orderbook) Symbol() string ``` #### func (*Orderbook) UpdateWith ```go func (ob *Orderbook) UpdateWith(bu *bitfinex.BookUpdate) ``` #### type Parameters ```go type Parameters struct { AutoReconnect bool ReconnectInterval time.Duration ReconnectAttempts int ShutdownTimeout time.Duration CapacityPerConnection int Logger *logging.Logger ResubscribeOnReconnect bool HeartbeatTimeout time.Duration LogTransport bool URL string ManageOrderbook bool } ``` Parameters defines adapter behavior. #### func NewDefaultParameters ```go func NewDefaultParameters() *Parameters ``` #### type PlatformInfo ```go type PlatformInfo struct { Status int `json:"status"` } ``` #### type RawEvent ```go type RawEvent struct { Data interface{} } ``` #### type Socket ```go type Socket struct { Id SocketId Asynchronous IsConnected bool ResetSubscriptions []*subscription IsAuthenticated bool } ``` #### type SocketId ```go type SocketId int ``` #### type StatsFactory ```go type StatsFactory struct { } ``` #### func (*StatsFactory) Build ```go func (f *StatsFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (*StatsFactory) BuildSnapshot ```go func (f *StatsFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (StatsFactory) Close ```go func (s StatsFactory) Close() ``` Close is terminal. Do not call heartbeat after close. #### func (StatsFactory) ListenDisconnect ```go func (s StatsFactory) ListenDisconnect() <-chan HeartbeatDisconnect ``` ListenDisconnect returns an error channel which receives a message when a heartbeat has expired a channel. #### func (StatsFactory) ResetAll ```go func (s StatsFactory) ResetAll() ``` Removes all tracked subscriptions #### func (StatsFactory) ResetSocketSubscriptions ```go func (s StatsFactory) ResetSocketSubscriptions(socketId SocketId) []*subscription ``` Reset clears all subscriptions assigned to the given socket ID, and returns a slice of the existing subscriptions prior to reset #### type SubscribeEvent ```go type SubscribeEvent struct { SubID string `json:"subId"` Channel string `json:"channel"` ChanID int64 `json:"chanId"` Symbol string `json:"symbol"` Precision string `json:"prec,omitempty"` Frequency string `json:"freq,omitempty"` Key string `json:"key,omitempty"` Len string `json:"len,omitempty"` Pair string `json:"pair"` } ``` #### type SubscriptionRequest ```go type SubscriptionRequest struct { SubID string `json:"subId"` Event string `json:"event"` // authenticated APIKey string `json:"apiKey,omitempty"` AuthSig string `json:"authSig,omitempty"` AuthPayload string `json:"authPayload,omitempty"` AuthNonce string `json:"authNonce,omitempty"` Filter []string `json:"filter,omitempty"` DMS int `json:"dms,omitempty"` // dead man switch // unauthenticated Channel string `json:"channel,omitempty"` Symbol string `json:"symbol,omitempty"` Precision string `json:"prec,omitempty"` Frequency string `json:"freq,omitempty"` Key string `json:"key,omitempty"` Len string `json:"len,omitempty"` Pair string `json:"pair,omitempty"` } ``` #### func (*SubscriptionRequest) String ```go func (s *SubscriptionRequest) String() string ``` #### type SubscriptionSet ```go type SubscriptionSet []*subscription ``` SubscriptionSet is a typed version of an array of subscription pointers, intended to meet the sortable interface. We need to sort Reset()'s return values for tests with more than 1 subscription (range map order is undefined) #### func (SubscriptionSet) Len ```go func (s SubscriptionSet) Len() int ``` #### func (SubscriptionSet) Less ```go func (s SubscriptionSet) Less(i, j int) bool ``` #### func (SubscriptionSet) RemoveByChannelId ```go func (s SubscriptionSet) RemoveByChannelId(chanId int64) SubscriptionSet ``` #### func (SubscriptionSet) RemoveBySubscriptionId ```go func (s SubscriptionSet) RemoveBySubscriptionId(subID string) SubscriptionSet ``` #### func (SubscriptionSet) Swap ```go func (s SubscriptionSet) Swap(i, j int) ``` #### type TickerFactory ```go type TickerFactory struct { } ``` #### func (*TickerFactory) Build ```go func (f *TickerFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (*TickerFactory) BuildSnapshot ```go func (f *TickerFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (TickerFactory) Close ```go func (s TickerFactory) Close() ``` Close is terminal. Do not call heartbeat after close. #### func (TickerFactory) ListenDisconnect ```go func (s TickerFactory) ListenDisconnect() <-chan HeartbeatDisconnect ``` ListenDisconnect returns an error channel which receives a message when a heartbeat has expired a channel. #### func (TickerFactory) ResetAll ```go func (s TickerFactory) ResetAll() ``` Removes all tracked subscriptions #### func (TickerFactory) ResetSocketSubscriptions ```go func (s TickerFactory) ResetSocketSubscriptions(socketId SocketId) []*subscription ``` Reset clears all subscriptions assigned to the given socket ID, and returns a slice of the existing subscriptions prior to reset #### type TradeFactory ```go type TradeFactory struct { } ``` #### func (*TradeFactory) Build ```go func (f *TradeFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (*TradeFactory) BuildSnapshot ```go func (f *TradeFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) ``` #### func (TradeFactory) Close ```go func (s TradeFactory) Close() ``` Close is terminal. Do not call heartbeat after close. #### func (TradeFactory) ListenDisconnect ```go func (s TradeFactory) ListenDisconnect() <-chan HeartbeatDisconnect ``` ListenDisconnect returns an error channel which receives a message when a heartbeat has expired a channel. #### func (TradeFactory) ResetAll ```go func (s TradeFactory) ResetAll() ``` Removes all tracked subscriptions #### func (TradeFactory) ResetSocketSubscriptions ```go func (s TradeFactory) ResetSocketSubscriptions(socketId SocketId) []*subscription ``` Reset clears all subscriptions assigned to the given socket ID, and returns a slice of the existing subscriptions prior to reset #### type UnsubscribeEvent ```go type UnsubscribeEvent struct { Status string `json:"status"` ChanID int64 `json:"chanId"` } ``` #### type UnsubscribeRequest ```go type UnsubscribeRequest struct { Event string `json:"event"` ChanID int64 `json:"chanId"` } ``` #### type WebsocketAsynchronousFactory ```go type WebsocketAsynchronousFactory struct { } ``` WebsocketAsynchronousFactory creates a websocket-based asynchronous transport. #### func (*WebsocketAsynchronousFactory) Create ```go func (w *WebsocketAsynchronousFactory) Create() Asynchronous ``` Create returns a new websocket transport. bitfinex-api-go-2.2.9/examples/000077500000000000000000000000001371275744700163455ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v1/000077500000000000000000000000001371275744700166735ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v1/account/000077500000000000000000000000001371275744700203275ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v1/account/main.go000066400000000000000000000007711371275744700216070ustar00rootroot00000000000000package main // Set BFX_APIKEY and BFX_SECRET as : // // export BFX_API_KEY=YOUR_API_KEY // export BFX_API_SECRET=YOUR_API_SECRET // // you can obtain it from https://www.bitfinex.com/api import ( "fmt" "os" "github.com/bitfinexcom/bitfinex-api-go/v1" ) func main() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") client := bitfinex.NewClient().Auth(key, secret) info, err := client.Account.Info() if err != nil { fmt.Println(err) } else { fmt.Println(info) } } bitfinex-api-go-2.2.9/examples/v1/orders/000077500000000000000000000000001371275744700201715ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v1/orders/main.go000066400000000000000000000013261371275744700214460ustar00rootroot00000000000000package main import ( "fmt" "os" "github.com/bitfinexcom/bitfinex-api-go/v1" ) // Set BFX_APIKEY and BFX_SECRET as : // // export BFX_API_KEY=YOUR_API_KEY // export BFX_API_SECRET=YOUR_API_SECRET // // you can obtain it from https://www.bitfinex.com/api // WARNING: IF YOU RUN THIS EXAMPLE WITH A VALID KEY ON PRODUCTION // IT WILL SUBMIT AN ORDER ! func main() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") client := bitfinex.NewClient().Auth(key, secret) // Sell 0.01BTC at $12.000 data, err := client.Orders.Create(bitfinex.BTCUSD, -0.01, 12000, bitfinex.OrderTypeExchangeLimit) if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Response:", data) } } bitfinex-api-go-2.2.9/examples/v1/ws-book/000077500000000000000000000000001371275744700202545ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v1/ws-book/main.go000066400000000000000000000022411371275744700215260ustar00rootroot00000000000000package main import ( "fmt" "log" "github.com/bitfinexcom/bitfinex-api-go/v1" ) func main() { c := bitfinex.NewClient() // in case your proxy is using a non valid certificate set to TRUE c.WebSocketTLSSkipVerify = false err := c.WebSocket.Connect() if err != nil { log.Fatal("Error connecting to web socket : ", err) } defer c.WebSocket.Close() book_btcusd_chan := make(chan []float64) book_ltcusd_chan := make(chan []float64) trades_chan := make(chan []float64) ticker_chan := make(chan []float64) c.WebSocket.AddSubscribe(bitfinex.ChanBook, bitfinex.BTCUSD, book_btcusd_chan) c.WebSocket.AddSubscribe(bitfinex.ChanBook, bitfinex.LTCUSD, book_ltcusd_chan) c.WebSocket.AddSubscribe(bitfinex.ChanTrade, bitfinex.BTCUSD, trades_chan) c.WebSocket.AddSubscribe(bitfinex.ChanTicker, bitfinex.BTCUSD, ticker_chan) go listen(book_btcusd_chan, "BOOK BTCUSD:") go listen(book_ltcusd_chan, "BOOK LTCUSD:") go listen(trades_chan, "TRADES BTCUSD:") go listen(ticker_chan, "TICKER BTCUSD:") err = c.WebSocket.Subscribe() if err != nil { log.Fatal(err) } } func listen(in chan []float64, message string) { for { msg := <-in fmt.Println(message, msg) } } bitfinex-api-go-2.2.9/examples/v1/ws-private/000077500000000000000000000000001371275744700207745ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v1/ws-private/main.go000066400000000000000000000013201371275744700222430ustar00rootroot00000000000000package main import ( "fmt" "os" "github.com/bitfinexcom/bitfinex-api-go/v1" ) // Set BFX_APIKEY and BFX_SECRET as : // // export BFX_API_KEY=YOUR_API_KEY // export BFX_API_SECRET=YOUR_API_SECRET // // you can obtain it from https://www.bitfinex.com/api func main() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") client := bitfinex.NewClient().Auth(key, secret) dataChan := make(chan bitfinex.TermData) go client.WebSocket.ConnectPrivate(dataChan) for { select { case data := <-dataChan: if data.HasError() { // Data has error - websocket channel will be closed. fmt.Println("Error:", data.Error) return } else { fmt.Println("Data:", data) } } } } bitfinex-api-go-2.2.9/examples/v2/000077500000000000000000000000001371275744700166745ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/book-feed/000077500000000000000000000000001371275744700205275ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/book-feed/main.go000066400000000000000000000017111371275744700220020ustar00rootroot00000000000000package main import ( "context" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" "log" "net/http" _ "net/http/pprof" "os" "os/signal" ) func main() { client := websocket.New() err := client.Connect() if err != nil { log.Printf("could not connect: %s", err.Error()) return } go func() { for msg := range client.Listen() { log.Printf("recv: %#v", msg) if _, ok := msg.(*websocket.InfoEvent); ok { _, err := client.SubscribeBook(context.Background(), "BTCUSD", bitfinex.Precision0, bitfinex.FrequencyRealtime, 1) if err != nil { log.Printf("could not subscribe to book: %s", err.Error()) } } } }() done := make(chan bool, 1) interrupt := make(chan os.Signal) signal.Notify(interrupt, os.Interrupt, os.Kill) go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() go func() { <-interrupt client.Close() done <- true os.Exit(0) }() <-done } bitfinex-api-go-2.2.9/examples/v2/rest-cancel-order-multi/000077500000000000000000000000001371275744700233355ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-cancel-order-multi/main.go000066400000000000000000000040621371275744700246120ustar00rootroot00000000000000package main import ( "log" "os" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" ) // Set BFX_API_KEY and BFX_API_SECRET: // // export BFX_API_KEY= // export BFX_API_SECRET= // // you can obtain it from https://www.bitfinex.com/api // // Below you can see different variations of using CancelOrderMulti function func main() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") c := rest. NewClient(). Credentials(key, secret) cancelAllOrders(c) cancelByOrderIDs(c) cancelByGroupOrderIDs(c) cancelByClientOrderID(c) cancelOrderMix(c) } func cancelAllOrders(c *rest.Client) { args := rest.CancelOrderMultiRequest{All: 1} resp, err := c.Orders.CancelOrderMulti(args) if err != nil { log.Fatalf("cancelAllOrders error: %s", err) } spew.Dump(resp) } func cancelByOrderIDs(c *rest.Client) { args := rest.CancelOrderMultiRequest{OrderIDs: rest.OrderIDs{1189452509, 1189452510, 1189452511}} resp, err := c.Orders.CancelOrderMulti(args) if err != nil { log.Fatalf("cancelByOrderIDs error: %s", err) } spew.Dump(resp) } func cancelByGroupOrderIDs(c *rest.Client) { args := rest.CancelOrderMultiRequest{GroupOrderIDs: rest.GroupOrderIDs{888, 777}} resp, err := c.Orders.CancelOrderMulti(args) if err != nil { log.Fatalf("cancelByGroupIDs error: %s", err) } spew.Dump(resp) } func cancelByClientOrderID(c *rest.Client) { args := rest.CancelOrderMultiRequest{ ClientOrderIDs: rest.ClientOrderIDs{ {123, "2020-06-24"}, {321, "2020-06-24"}, }, } resp, err := c.Orders.CancelOrderMulti(args) if err != nil { log.Fatalf("cancelByClientOrderID error: %s", err) } spew.Dump(resp) } func cancelOrderMix(c *rest.Client) { args := rest.CancelOrderMultiRequest{ OrderIDs: rest.OrderIDs{1189452432, 1189452435}, GroupOrderIDs: rest.GroupOrderIDs{888}, ClientOrderIDs: rest.ClientOrderIDs{ {321, "2020-06-24"}, }, } resp, err := c.Orders.CancelOrderMulti(args) if err != nil { log.Fatalf("cancelOrderMix error: %s", err) } spew.Dump(resp) } bitfinex-api-go-2.2.9/examples/v2/rest-candles/000077500000000000000000000000001371275744700212605ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-candles/main.go000066400000000000000000000027251371275744700225410ustar00rootroot00000000000000package main import ( "log" bfx "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "time" ) func main() { c := rest.NewClient() log.Printf("1) Query Last Candle") candle, err := c.Candles.Last(bfx.TradingPrefix+bfx.BTCUSD, bfx.FiveMinutes) if err != nil { log.Fatalf("getting candle: %s", err) } log.Printf("last candle: %#v\n", candle) now := time.Now() millis := now.UnixNano() / 1000000 prior := now.Add(time.Duration(-24) * time.Hour) millisStart := prior.UnixNano() / 1000000 log.Printf("2) Query Candle History with no params") candles, err := c.Candles.History(bfx.TradingPrefix+bfx.BTCUSD, bfx.FiveMinutes) if err != nil { log.Fatalf("getting candles: %s", err) } log.Printf("length of candles is: %v", len(candles.Snapshot)) log.Printf("first candle is: %#v\n", candles.Snapshot[0]) log.Printf("last candle is: %#v\n", candles.Snapshot[len(candles.Snapshot)-1]) start := bfx.Mts(millisStart) end := bfx.Mts(millis) log.Printf("3) Query Candle History with params") candlesMore, err := c.Candles.HistoryWithQuery( bfx.TradingPrefix+bfx.BTCUSD, bfx.FiveMinutes, start, end, 200, bfx.OldestFirst, ) if err != nil { log.Fatalf("getting candles: %s", err) } log.Printf("length of candles is: %v", len(candlesMore.Snapshot)) log.Printf("first candle is: %#v\n", candlesMore.Snapshot[0]) log.Printf("last candle is: %#v\n", candlesMore.Snapshot[len(candlesMore.Snapshot)-1]) } bitfinex-api-go-2.2.9/examples/v2/rest-currencies/000077500000000000000000000000001371275744700220115ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-currencies/main.go000066400000000000000000000005251371275744700232660ustar00rootroot00000000000000package main import ( "fmt" "log" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" ) func main() { c := rest.NewClient() currencyConfigs, err := c.Currencies.Conf(true, true, true, true, true) if err != nil { log.Fatalf("getting currency config: %s", err) } for _, config := range currencyConfigs { fmt.Println(config) } } bitfinex-api-go-2.2.9/examples/v2/rest-delete-pulse/000077500000000000000000000000001371275744700222375ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-delete-pulse/main.go000066400000000000000000000010771371275744700235170ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" ) // Set BFX_API_KEY and BFX_API_SECRET: // // export BFX_API_KEY= // export BFX_API_SECRET= // // you can obtain it from https://www.bitfinex.com/api func main() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") c := rest. NewClient(). Credentials(key, secret) deleted, err := c.Pulse.DeletePulse("437b5b44-0f7d-4638-baff-3bbf6966482d") if err != nil { log.Fatal(err) } fmt.Printf("Deleted: %d\n", deleted) } bitfinex-api-go-2.2.9/examples/v2/rest-derivative-status/000077500000000000000000000000001371275744700233325ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-derivative-status/main.go000066400000000000000000000004451371275744700246100ustar00rootroot00000000000000package main import ( "fmt" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "log" ) func main() { c := rest.NewClient() pLStats, err := c.Status.DerivativeStatus("tBTCF0:USTF0") if err != nil { log.Fatalf("getting getting last position stats: %s", err) } fmt.Println(pLStats) } bitfinex-api-go-2.2.9/examples/v2/rest-funding/000077500000000000000000000000001371275744700213015ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-funding/main.go000066400000000000000000000045621371275744700225630ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" ) // Set BFX_APIKEY and BFX_SECRET as : // // export BFX_API_KEY=YOUR_API_KEY // export BFX_API_SECRET=YOUR_API_SECRET // // you can obtain it from https://www.bitfinex.com/api func main() { key := os.Getenv("BFX_KEY") secret := os.Getenv("BFX_SECRET") c := rest.NewClientWithURL("https://test.bitfinex.com/v2/").Credentials(key, secret) // active funding offers snap, err := c.Funding.Offers("fUSD") if err != nil { panic(err) } for _, item := range snap.Snapshot { fmt.Println(item) } // funding offer history snapHist, err := c.Funding.OfferHistory("fUSD") if err != nil { panic(err) } for _, item := range snapHist.Snapshot { fmt.Println(item) } // active loans snapLoans, err := c.Funding.Loans("fUSD") if err != nil { panic(err) } for _, item := range snapLoans.Snapshot { fmt.Println(item) } // loans history napLoansHist, err := c.Funding.LoansHistory("fUSD") if err != nil { panic(err) } for _, item := range napLoansHist.Snapshot { fmt.Println(item) } // active credits snapCredits, err := c.Funding.Credits("fUSD") if err != nil { panic(err) } for _, item := range snapCredits.Snapshot { fmt.Println(item) } // credits history napCreditsHist, err := c.Funding.CreditsHistory("fUSD") if err != nil { panic(err) } for _, item := range napCreditsHist.Snapshot { fmt.Println(item) } // funding trades napTradesHist, err := c.Funding.Trades("fUSD") if err != nil { panic(err) } for _, item := range napTradesHist.Snapshot { fmt.Println(item) } // keep funding resp, err := c.Funding.KeepFunding(rest.KeepFundingRequest{ Type: "credit", ID: 12345, // Insert correct ID }) if err != nil { log.Fatalf("KeepFunding error: %s", err) } spew.Dump(resp) /********* submit a new funding offer ***********/ fo, err := c.Funding.SubmitOffer(&bitfinex.FundingOfferRequest{ Type: "LIMIT", Symbol: "fUSD", Amount: 1000, Rate: 0.012, Period: 7, Hidden: true, }) if err != nil { panic(err) } newOffer := fo.NotifyInfo.(*bitfinex.FundingOfferNew) /********* cancel funding offer ***********/ fc, err := c.Funding.CancelOffer(&bitfinex.FundingOfferCancelRequest{ Id: newOffer.ID, }) if err != nil { panic(err) } fmt.Println(fc) } bitfinex-api-go-2.2.9/examples/v2/rest-generate-invoice/000077500000000000000000000000001371275744700230735ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-generate-invoice/main.go000066400000000000000000000012201371275744700243410ustar00rootroot00000000000000package main import ( "log" "os" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" ) // Set BFX_API_KEY and BFX_API_SECRET: // // export BFX_API_KEY= // export BFX_API_SECRET= // // you can obtain it from https://www.bitfinex.com/api func main() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") c := rest. NewClient(). Credentials(key, secret) args := rest.DepositInvoiceRequest{ Currency: "LNX", Wallet: "exchange", Amount: "0.002", } resp, err := c.Invoice.GenerateInvoice(args) if err != nil { log.Fatal(err) } spew.Dump(resp) } bitfinex-api-go-2.2.9/examples/v2/rest-market/000077500000000000000000000000001371275744700211325ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-market/main.go000066400000000000000000000013421371275744700224050ustar00rootroot00000000000000package main import ( "log" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" ) func main() { c := rest.NewClient() averagePrice(c) foreignExchangeRate(c) } func averagePrice(c *rest.Client) { args := rest.AveragePriceRequest{ Symbol: "fUSD", Amount: "100", Period: 2, } avgPrice, err := c.Market.AveragePrice(args) if err != nil { log.Fatalf("AveragePrice err: %s", err) } spew.Dump(avgPrice) } func foreignExchangeRate(c *rest.Client) { args := rest.ForeignExchangeRateRequest{ FirstCurrency: "BTC", SecondCurrency: "USD", } fxRate, err := c.Market.ForeignExchangeRate(args) if err != nil { log.Fatalf("ForeignExchangeRate err: %s", err) } spew.Dump(fxRate) } bitfinex-api-go-2.2.9/examples/v2/rest-order-multi/000077500000000000000000000000001371275744700221125ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-order-multi/main.go000066400000000000000000000043551371275744700233740ustar00rootroot00000000000000package main import ( "log" "os" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" ) // Set BFX_API_KEY and BFX_API_SECRET: // // export BFX_API_KEY= // export BFX_API_SECRET= // // you can obtain it from https://www.bitfinex.com/api // // Below you can see different variations of using Order Multi ops func main() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") c := rest. NewClient(). Credentials(key, secret) cancelOrdersMultiOp(c) cancelOrderMultiOp(c) orderNewMultiOp(c) orderUpdateMultiOp(c) orderMultiOp(c) } func cancelOrdersMultiOp(c *rest.Client) { resp, err := c.Orders.CancelOrdersMultiOp(rest.OrderIDs{1189452506, 1189452507}) if err != nil { log.Fatalf("CancelOrdersMultiOp error: %s", err) } spew.Dump(resp) } func cancelOrderMultiOp(c *rest.Client) { resp, err := c.Orders.CancelOrderMultiOp(1189502586) if err != nil { log.Fatalf("CancelOrderMultiOp error: %s", err) } spew.Dump(resp) } func orderNewMultiOp(c *rest.Client) { o := bitfinex.OrderNewRequest{ CID: 119, GID: 118, Type: "EXCHANGE LIMIT", Symbol: "tBTCUSD", Price: 12, Amount: 0.002, } resp, err := c.Orders.OrderNewMultiOp(o) if err != nil { log.Fatalf("OrderNewMultiOp error: %s", err) } spew.Dump(resp) } func orderUpdateMultiOp(c *rest.Client) { o := bitfinex.OrderUpdateRequest{ ID: 1189503586, Price: 12, Amount: 0.002, } resp, err := c.Orders.OrderUpdateMultiOp(o) if err != nil { log.Fatalf("OrderUpdateMultiOp error: %s", err) } spew.Dump(resp) } func orderMultiOp(c *rest.Client) { pld := rest.OrderOps{ { "on", bitfinex.OrderNewRequest{ CID: 987, GID: 876, Type: "EXCHANGE LIMIT", Symbol: "tBTCUSD", Price: 13, Amount: 0.001, }, }, { "oc", map[string]int{"id": 1189502587}, }, { "oc_multi", map[string][]int{"id": rest.OrderIDs{1189502588, 1189503341}}, }, { "ou", bitfinex.OrderUpdateRequest{ ID: 1189503342, Price: 15, Amount: 0.002, }, }, } resp, err := c.Orders.OrderMultiOp(pld) if err != nil { log.Fatalf("OrderMultiOp error: %s", err) } spew.Dump(resp) } bitfinex-api-go-2.2.9/examples/v2/rest-orders/000077500000000000000000000000001371275744700211455ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-orders/main.go000066400000000000000000000023511371275744700224210ustar00rootroot00000000000000package main import ( "flag" "log" "os" "strconv" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" ) var ( orderid = flag.String("id", "", "lookup trades for an order ID") api = flag.String("api", "https://api-pub.bitfinex.com/v2/", "v2 REST API URL") ) // Set BFX_APIKEY and BFX_SECRET as : // // export BFX_API_KEY=YOUR_API_KEY // export BFX_API_SECRET=YOUR_API_SECRET // // you can obtain it from https://www.bitfinex.com/api func main() { flag.Parse() key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") c := rest.NewClientWithURL(*api).Credentials(key, secret) available, err := c.Platform.Status() if err != nil { log.Fatalf("getting status: %s", err) } if !available { log.Fatalf("API not available") } if *orderid != "" { ordid, err := strconv.ParseInt(*orderid, 10, 64) if err != nil { log.Fatal(err) } os, err := c.Orders.OrderTrades(bitfinex.TradingPrefix+bitfinex.BTCUSD, ordid) if err != nil { log.Fatalf("getting order trades: %s", err) } log.Printf("order trades: %#v\n", os) } else { os, err := c.Orders.AllHistory() if err != nil { log.Fatalf("getting orders: %s", err) } log.Printf("orders: %#v\n", os) } } bitfinex-api-go-2.2.9/examples/v2/rest-positions/000077500000000000000000000000001371275744700216765ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-positions/main.go000066400000000000000000000013021371275744700231450ustar00rootroot00000000000000package main import ( "fmt" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "log" "os" ) func main() { key := os.Getenv("BFX_KEY") secret := os.Getenv("BFX_SECRET") uri := "https://api.bitfinex.com/v2/" c := rest.NewClientWithURL(uri).Credentials(key, secret) // get active positions positions, err := c.Positions.All() if err != nil { log.Fatalf("getting wallet %s", err) } for _, p := range positions.Snapshot { fmt.Println(p) } // claim active position pClaim, err := c.Positions.Claim(&bitfinex.ClaimPositionRequest{ Id: 36228736, }) if err != nil { panic(err) } fmt.Println(pClaim.NotifyInfo.(*bitfinex.PositionCancel)) } bitfinex-api-go-2.2.9/examples/v2/rest-private-pulse-history/000077500000000000000000000000001371275744700241465ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-private-pulse-history/main.go000066400000000000000000000011011371275744700254120ustar00rootroot00000000000000package main import ( "log" "os" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" ) // Set BFX_API_KEY and BFX_API_SECRET: // // export BFX_API_KEY= // export BFX_API_SECRET= // // you can obtain it from https://www.bitfinex.com/api func main() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") c := rest. NewClient(). Credentials(key, secret) pulseHist, err := c.Pulse.PulseHistory(true) if err != nil { log.Fatalf("PulseHistory: %s", err) } spew.Dump(pulseHist) } bitfinex-api-go-2.2.9/examples/v2/rest-public-trades/000077500000000000000000000000001371275744700224055ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-public-trades/main.go000066400000000000000000000012211371275744700236540ustar00rootroot00000000000000package main import ( "fmt" "log" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" bitfinex "github.com/bitfinexcom/bitfinex-api-go/v2" "time" ) func main() { c := rest.NewClient() // calculate start and end now := time.Now() millis := now.UnixNano() / 1000000 prior := now.Add(time.Duration(-24) * time.Hour) millisStart := prior.UnixNano() / 1000000 start := bitfinex.Mts(millisStart) end := bitfinex.Mts(millis) // send request trades, err := c.Trades.PublicHistoryWithQuery("tBTCUSD", start, end, 10, bitfinex.OldestFirst) if err != nil { log.Fatalf("%v", err) } for _, trade := range trades.Snapshot { fmt.Println(trade) } } bitfinex-api-go-2.2.9/examples/v2/rest-pulse-add/000077500000000000000000000000001371275744700215255ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-pulse-add/main.go000066400000000000000000000013341371275744700230010ustar00rootroot00000000000000package main import ( "log" "os" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulse" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" ) // Set BFX_API_KEY and BFX_API_SECRET: // // export BFX_API_KEY= // export BFX_API_SECRET= // // you can obtain it from https://www.bitfinex.com/api func main() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") c := rest. NewClient(). Credentials(key, secret) p := &pulse.Pulse{ Title: "GO GO GO GO GO GO TITLE", Content: "GO GO GO GO GO GO Content", IsPublic: 0, IsPin: 1, } resp, err := c.Pulse.AddPulse(p) if err != nil { log.Fatal(err) } spew.Dump(resp) } bitfinex-api-go-2.2.9/examples/v2/rest-pulse-history/000077500000000000000000000000001371275744700224765ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-pulse-history/main.go000066400000000000000000000006661371275744700237610ustar00rootroot00000000000000package main import ( "log" "time" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" ) func main() { c := rest.NewClient() now := time.Now() millis := now.UnixNano() / 1000000 end := bitfinex.Mts(millis) pulseHist, err := c.Pulse.PublicPulseHistory(0, end) if err != nil { log.Fatalf("PublicPulseHistory: %s", err) } spew.Dump(pulseHist) } bitfinex-api-go-2.2.9/examples/v2/rest-pulse-profile/000077500000000000000000000000001371275744700224355ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-pulse-profile/main.go000066400000000000000000000004611371275744700237110ustar00rootroot00000000000000package main import ( "log" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" ) func main() { c := rest.NewClient() nn := rest.Nickname("Bitfinex") profile, err := c.Pulse.PublicPulseProfile(nn) if err != nil { log.Fatalf("%s", err) } spew.Dump(profile) } bitfinex-api-go-2.2.9/examples/v2/rest-stats/000077500000000000000000000000001371275744700210055ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-stats/main.go000066400000000000000000000022261371275744700222620ustar00rootroot00000000000000package main import ( "fmt" "log" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" bitfinex "github.com/bitfinexcom/bitfinex-api-go/v2" ) func main() { c := rest.NewClient() pLStats, err := c.Stats.PositionLast("tBTCUSD", bitfinex.Long) if err != nil { log.Fatalf("getting getting last position stats: %s", err) } fmt.Println(pLStats) pHStats, err := c.Stats.PositionHistory("tBTCUSD", bitfinex.Long) if err != nil { log.Fatalf("getting getting last position stats: %s", err) } fmt.Println(pHStats) scsStats, err := c.Stats.SymbolCreditSizeLast("fUSD", "tBTCUSD") if err != nil { log.Fatalf("getting getting last position stats: %s", err) } fmt.Println(scsStats) scsHistStats, err := c.Stats.SymbolCreditSizeHistory("fUSD", "tBTCUSD") if err != nil { log.Fatalf("getting getting last position stats: %s", err) } fmt.Println(scsHistStats) fStats, err := c.Stats.FundingLast("fUSD") if err != nil { log.Fatalf("getting getting last position stats: %s", err) } fmt.Println(fStats) fhStats, err := c.Stats.FundingHistory("fUSD") if err != nil { log.Fatalf("getting getting last position stats: %s", err) } fmt.Println(fhStats) } bitfinex-api-go-2.2.9/examples/v2/rest-submit-order/000077500000000000000000000000001371275744700222635ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-submit-order/main.go000066400000000000000000000022771371275744700235460ustar00rootroot00000000000000package main import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "os" "time" ) // Set BFX_APIKEY and BFX_SECRET as : // // export BFX_API_KEY=YOUR_API_KEY // export BFX_API_SECRET=YOUR_API_SECRET // // you can obtain it from https://www.bitfinex.com/api func main() { key := os.Getenv("BFX_KEY") secret := os.Getenv("BFX_SECRET") c := rest.NewClientWithURL("https://test.bitfinex.com/v2/").Credentials(key, secret) // create order response, err := c.Orders.SubmitOrder(&bitfinex.OrderNewRequest{ Symbol: "tBTCUSD", CID: time.Now().Unix() / 1000, Amount: 0.02, Type: "EXCHANGE LIMIT", Price: 5000, }) if err != nil { panic(err) } time.Sleep(time.Second * 5) orders := response.NotifyInfo.(*bitfinex.OrderSnapshot) // update orders for _, o := range orders.Snapshot { response, err = c.Orders.SubmitUpdateOrder(&bitfinex.OrderUpdateRequest{ ID: o.ID, Price: 6000, }) if err != nil { panic(err) } // cancel orders updatedOrder := response.NotifyInfo.(*bitfinex.OrderUpdate) err := c.Orders.SubmitCancelOrder(&bitfinex.OrderCancelRequest{ ID: updatedOrder.ID, }) if err != nil { panic(err) } } } bitfinex-api-go-2.2.9/examples/v2/rest-ticker/000077500000000000000000000000001371275744700211305ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-ticker/main.go000066400000000000000000000005741371275744700224110ustar00rootroot00000000000000package main import ( "log" bfx "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" ) func main() { c := rest.NewClient() symbols := []string{bfx.TradingPrefix+bfx.BTCUSD, bfx.TradingPrefix+bfx.EOSBTC} tickers, err := c.Tickers.GetMulti(symbols) if err != nil { log.Fatalf("getting ticker: %s", err) } log.Print(tickers) } bitfinex-api-go-2.2.9/examples/v2/rest-wallet/000077500000000000000000000000001371275744700211375ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/rest-wallet/main.go000066400000000000000000000015331371275744700224140ustar00rootroot00000000000000package main import ( "fmt" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/davecgh/go-spew/spew" "log" "os" ) // Set BFX_APIKEY and BFX_SECRET as : // // export BFX_API_KEY=YOUR_API_KEY // export BFX_API_SECRET=YOUR_API_SECRET // // you can obtain it from https://www.bitfinex.com/api func main() { key := os.Getenv("BFX_KEY") secret := os.Getenv("BFX_SECRET") c := rest.NewClientWithURL("https://test.bitfinex.com/v2/").Credentials(key, secret) wallets, err := c.Wallet.Wallet() if err != nil { log.Fatalf("getting wallet %s", err) } spew.Dump(wallets) notfication, err := c.Wallet.Transfer("exchange", "margin", "BTC", "BTC", 0.1) if err != nil { panic(err) } fmt.Println(notfication) notfication2, err := c.Wallet.DepositAddress("exchange", "bitcoin") if err != nil { panic(err) } fmt.Println(notfication2) } bitfinex-api-go-2.2.9/examples/v2/trade-feed-multi/000077500000000000000000000000001371275744700220245ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/trade-feed-multi/main.go000066400000000000000000000016521371275744700233030ustar00rootroot00000000000000package main import ( "context" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" "log" _ "net/http/pprof" ) var tickers = []string{"tBTCUSD", "tETHUSD", "tBTCUSD", "tVETUSD", "tDGBUSD", "tEOSUSD", "tTRXUSD"} func main() { client := websocket.New() err := client.Connect() if err != nil { log.Printf("could not connect: %s", err.Error()) return } for obj := range client.Listen() { switch obj.(type) { case error: log.Printf("channel closed: %s", obj) return case *bitfinex.Trade: log.Printf("New trade: %s", obj) case *websocket.InfoEvent: // Info event confirms connection to the bfx websocket for _, ticker := range tickers { _, err := client.SubscribeTrades(context.Background(), ticker) if err != nil { log.Printf("could not subscribe to trades: %s", err.Error()) } } default: log.Printf("MSG RECV: %#v", obj) } } } bitfinex-api-go-2.2.9/examples/v2/trade-feed/000077500000000000000000000000001371275744700206745ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/trade-feed/main.go000066400000000000000000000015031371275744700221460ustar00rootroot00000000000000package main import ( "context" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" "log" _ "net/http/pprof" ) func main() { client := websocket.New() err := client.Connect() if err != nil { log.Printf("could not connect: %s", err.Error()) return } for obj := range client.Listen() { switch obj.(type) { case error: log.Printf("channel closed: %s", obj) return case *bitfinex.Trade: log.Printf("New trade: %s", obj) case *websocket.InfoEvent: // Info event confirms connection to the bfx websocket log.Printf("Subscribing to tBTCUSD") _, err := client.SubscribeTrades(context.Background(), "tBTCUSD") if err != nil { log.Printf("could not subscribe to trades: %s", err.Error()) } default: log.Printf("MSG RECV: %#v", obj) } } } bitfinex-api-go-2.2.9/examples/v2/ws-book/000077500000000000000000000000001371275744700202555ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/ws-book/README.md000066400000000000000000000002321371275744700215310ustar00rootroot00000000000000# Websocket public book This is a simple example showing off how to connect to the websocket service and subscribe to one of the public ticker channels. bitfinex-api-go-2.2.9/examples/v2/ws-book/main.go000066400000000000000000000024031371275744700215270ustar00rootroot00000000000000package main import ( "context" "log" "time" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) func main() { p := websocket.NewDefaultParameters() // Enable orderbook checksum verification p.ManageOrderbook = true c := websocket.NewWithParams(p) err := c.Connect() if err != nil { log.Fatal("Error connecting to web socket : ", err) } // subscribe to BTCUSD book ctx, cxl2 := context.WithTimeout(context.Background(), time.Second*5) defer cxl2() _, err = c.SubscribeBook(ctx, bitfinex.TradingPrefix+bitfinex.BTCUSD, bitfinex.Precision0, bitfinex.FrequencyRealtime, 25) if err != nil { log.Fatal(err) } // subscribe to BTCUSD trades ctx, cxl3 := context.WithTimeout(context.Background(), time.Second*5) defer cxl3() _, err = c.SubscribeTrades(ctx, bitfinex.TradingPrefix+bitfinex.BTCUSD) if err != nil { log.Fatal(err) } for obj := range c.Listen() { switch obj.(type) { case error: log.Printf("channel closed: %s", obj) default: } log.Printf("MSG RECV: %#v", obj) // Load the latest orderbook ob, _ := c.GetOrderbook(bitfinex.TradingPrefix+bitfinex.BTCUSD) if ob != nil { log.Printf("Orderbook asks: %v", ob.Asks()) log.Printf("Orderbook bids: %v", ob.Bids()) } } } bitfinex-api-go-2.2.9/examples/v2/ws-custom-logger/000077500000000000000000000000001371275744700221125ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/ws-custom-logger/main.go000066400000000000000000000026051371275744700233700ustar00rootroot00000000000000package main import ( "context" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" "github.com/op/go-logging" _ "net/http/pprof" "os" ) func main() { // create a new go-logger instance var log = logging.MustGetLogger("bfx-websocket") // create string formatter var format = logging.MustStringFormatter( `%{color}%{time:15:04:05.000} %{shortfunc} â–¶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, ) // apply to logging instance backend := logging.NewLogBackend(os.Stderr, "", 0) backendFormatter := logging.NewBackendFormatter(backend, format) logging.SetBackend(backendFormatter) // create websocket client and pass logger p := websocket.NewDefaultParameters() p.Logger = log client := websocket.NewWithParams(p) err := client.Connect() if err != nil { log.Errorf("could not connect: %s", err.Error()) return } for obj := range client.Listen() { switch obj.(type) { case error: log.Errorf("channel closed: %s", obj) return case *bitfinex.Trade: log.Infof("New trade: %s", obj) case *websocket.InfoEvent: // Info event confirms connection to the bfx websocket log.Info("Subscribing to tBTCUSD") _, err := client.SubscribeTrades(context.Background(), "tBTCUSD") if err != nil { log.Infof("could not subscribe to trades: %s", err.Error()) } default: log.Infof("MSG RECV: %#v", obj) } } } bitfinex-api-go-2.2.9/examples/v2/ws-private/000077500000000000000000000000001371275744700207755ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/ws-private/main.go000066400000000000000000000014731371275744700222550ustar00rootroot00000000000000package main import ( "log" "os" "time" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) // Set BFX_APIKEY and BFX_SECRET as : // // export BFX_API_KEY=YOUR_API_KEY // export BFX_API_SECRET=YOUR_API_SECRET // // you can obtain it from https://www.bitfinex.com/api func main() { uri := os.Getenv("BFX_API_URI") key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") p := websocket.NewDefaultParameters() p.URL = uri c := websocket.NewWithParams(p).Credentials(key, secret) err := c.Connect() if err != nil { log.Fatalf("connecting authenticated websocket: %s", err) } go func() { for msg := range c.Listen() { log.Printf("MSG RECV: %#v", msg) } }() //ctx, _ := context.WithTimeout(context.Background(), time.Second*1) //c.Authenticate(ctx) time.Sleep(time.Second * 10) } bitfinex-api-go-2.2.9/examples/v2/ws-update-order/000077500000000000000000000000001371275744700217165ustar00rootroot00000000000000bitfinex-api-go-2.2.9/examples/v2/ws-update-order/main.go000066400000000000000000000027451371275744700232010ustar00rootroot00000000000000package main import ( "log" "os" "time" "context" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) func SubmitTestOrder(c *websocket.Client) { log.Printf("Submitting new order") err := c.SubmitOrder(context.Background(), &bitfinex.OrderNewRequest{ Symbol: "tBTCUSD", CID: time.Now().Unix() / 1000, Amount: 0.02, Type: "EXCHANGE LIMIT", Price: 5000, }) if err != nil { log.Fatal(err) } } func UpdateTestOrder(orderId int64, c *websocket.Client) { log.Printf("Updating order") err := c.SubmitUpdateOrder(context.Background(), &bitfinex.OrderUpdateRequest{ ID: orderId, Amount: 0.04, }) if err != nil { log.Fatal(err) } } func main() { key := os.Getenv("BFX_KEY") secret := os.Getenv("BFX_SECRET") p := websocket.NewDefaultParameters() p.URL = "wss://test.bitfinex.com/ws/2" c := websocket.NewWithParams(p).Credentials(key, secret) err := c.Connect() if err != nil { log.Fatalf("connecting authenticated websocket: %s", err) } defer c.Close() // Begin listening to incoming messages for obj := range c.Listen() { switch obj.(type) { case error: log.Fatalf("channel closed: %s", obj) break case *websocket.AuthEvent: // on authorize create new order SubmitTestOrder(c) case *bitfinex.OrderNew: // new order received so update it id := obj.(*bitfinex.OrderNew).ID UpdateTestOrder(id, c) default: log.Printf("MSG RECV: %#v", obj) } } time.Sleep(time.Second * 10) } bitfinex-api-go-2.2.9/go.mod000066400000000000000000000003551371275744700156400ustar00rootroot00000000000000module github.com/bitfinexcom/bitfinex-api-go go 1.14 require ( github.com/davecgh/go-spew v1.1.1 github.com/gorilla/websocket v1.4.2 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/stretchr/testify v1.6.1 ) bitfinex-api-go-2.2.9/go.sum000066400000000000000000000027471371275744700156740ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= bitfinex-api-go-2.2.9/pkg/000077500000000000000000000000001371275744700153105ustar00rootroot00000000000000bitfinex-api-go-2.2.9/pkg/convert/000077500000000000000000000000001371275744700167705ustar00rootroot00000000000000bitfinex-api-go-2.2.9/pkg/convert/convert.go000066400000000000000000000042721371275744700210040ustar00rootroot00000000000000package convert import ( "encoding/json" "fmt" "strconv" ) func F64Slice(in []interface{}) ([]float64, error) { var ret []float64 for _, e := range in { if item, ok := e.(float64); ok { ret = append(ret, item) } else { return nil, fmt.Errorf("expected slice of float64 but got: %v", in) } } return ret, nil } func ItfToStrSlice(in interface{}) ([]string, error) { ret := []string{} raw, ok := in.([]interface{}) if !ok { return ret, nil } for _, e := range raw { item, ok := e.(string) if !ok { return nil, fmt.Errorf("expected slice of strings, got: %v", in) } ret = append(ret, item) } return ret, nil } // ToInt converts various types to integer. If fails, returns 0 func ToInt(in interface{}) int { var out int switch v := in.(type) { case string: if i, err := strconv.Atoi(v); err == nil { out = i } case float64: out = int(v) default: if val, ok := in.(int); ok { out = val } } return out } func ToInterfaceArray(i []interface{}) [][]interface{} { newArr := make([][]interface{}, len(i)) for index, item := range i { newArr[index] = item.([]interface{}) } return newArr } func ToFloat64Array(i [][]interface{}) ([][]float64, error) { newArr := make([][]float64, len(i)) for index, item := range i { s, err := F64Slice(item) if err != nil { return nil, err } newArr[index] = s } return newArr, nil } func FloatToJsonNumber(i interface{}) json.Number { if r, ok := i.(json.Number); ok { return r } return json.Number(strconv.FormatFloat(i.(float64), 'f', -1, 64)) } func I64ValOrZero(i interface{}) int64 { if r, ok := i.(float64); ok { return int64(r) } return 0 } func IValOrZero(i interface{}) int { if r, ok := i.(float64); ok { return int(r) } return 0 } func F64ValOrZero(i interface{}) float64 { if r, ok := i.(float64); ok { return r } return 0.0 } func SiMapOrEmpty(i interface{}) map[string]interface{} { if m, ok := i.(map[string]interface{}); ok { return m } return make(map[string]interface{}) } func BValOrFalse(i interface{}) bool { if r, ok := i.(bool); ok { return r } return false } func SValOrEmpty(i interface{}) string { if r, ok := i.(string); ok { return r } return "" } bitfinex-api-go-2.2.9/pkg/convert/convert_test.go000066400000000000000000000027151371275744700220430ustar00rootroot00000000000000package convert_test import ( "testing" "github.com/bitfinexcom/bitfinex-api-go/pkg/convert" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestItfToStrSlice(t *testing.T) { t.Run("invalid slice arguments", func(t *testing.T) { payload := []interface{}{123, 234, 345} got, err := convert.ItfToStrSlice(payload) require.NotNil(t, err) require.Nil(t, got) }) t.Run("non slice arguments", func(t *testing.T) { payload := "123" got, err := convert.ItfToStrSlice(payload) require.Nil(t, err) assert.Equal(t, []string{}, got) }) t.Run("valid arguments", func(t *testing.T) { payload := []interface{}{"foo", "bar", "baz"} got, err := convert.ItfToStrSlice(payload) expected := []string{"foo", "bar", "baz"} require.Nil(t, err) assert.Equal(t, expected, got) }) } func TestToInt(t *testing.T) { t.Run("valid int argument", func(t *testing.T) { payload := 1234 expected := 1234 got := convert.ToInt(payload) assert.Equal(t, expected, got) }) t.Run("valid string int", func(t *testing.T) { payload := "1" expected := 1 got := convert.ToInt(payload) assert.Equal(t, expected, got) }) t.Run("float64", func(t *testing.T) { var payload float64 = 1234 expected := 1234 got := convert.ToInt(payload) assert.Equal(t, expected, got) }) t.Run("invalid string int", func(t *testing.T) { payload := "foo" expected := 0 got := convert.ToInt(payload) assert.Equal(t, expected, got) }) } bitfinex-api-go-2.2.9/pkg/models/000077500000000000000000000000001371275744700165735ustar00rootroot00000000000000bitfinex-api-go-2.2.9/pkg/models/invoice/000077500000000000000000000000001371275744700202275ustar00rootroot00000000000000bitfinex-api-go-2.2.9/pkg/models/invoice/invoice.go000066400000000000000000000014161371275744700222140ustar00rootroot00000000000000package invoice import ( "fmt" "github.com/bitfinexcom/bitfinex-api-go/pkg/convert" ) // Invoice data structure type Invoice struct { InvoiceHash string Invoice string Amount string } var invoiceFields = map[string]int{ "InvoiceHash": 0, "Invoice": 1, "Amount": 4, } // NewFromRaw takes in slice of interfaces and converts them to // pointer to Invoice func NewFromRaw(raw []interface{}) (*Invoice, error) { if len(raw) < 5 { return nil, fmt.Errorf("data slice too short for Invoice: %#v", raw) } invc := &Invoice{} invc.InvoiceHash = convert.SValOrEmpty(raw[invoiceFields["InvoiceHash"]]) invc.Invoice = convert.SValOrEmpty(raw[invoiceFields["Invoice"]]) invc.Amount = convert.SValOrEmpty(raw[invoiceFields["Amount"]]) return invc, nil } bitfinex-api-go-2.2.9/pkg/models/invoice/invoice_test.go000066400000000000000000000014261371275744700232540ustar00rootroot00000000000000package invoice_test import ( "testing" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/invoice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewInvoiceFromRaw(t *testing.T) { t.Run("insufficient arguments", func(t *testing.T) { payload := []interface{}{"invoicehash"} invc, err := invoice.NewFromRaw(payload) require.NotNil(t, err) require.Nil(t, invc) }) t.Run("valid arguments", func(t *testing.T) { payload := []interface{}{ "invoicehash", "invoice", nil, nil, "0.00016", } invc, err := invoice.NewFromRaw(payload) require.Nil(t, err) expected := &invoice.Invoice{ InvoiceHash: "invoicehash", Invoice: "invoice", Amount: "0.00016", } assert.Equal(t, expected, invc) }) } bitfinex-api-go-2.2.9/pkg/models/pulse/000077500000000000000000000000001371275744700177235ustar00rootroot00000000000000bitfinex-api-go-2.2.9/pkg/models/pulse/pulse.go000066400000000000000000000054741371275744700214140ustar00rootroot00000000000000package pulse import ( "fmt" "github.com/bitfinexcom/bitfinex-api-go/pkg/convert" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulseprofile" ) // Pulse message data structure type Pulse struct { ID string `json:"id,omitempty"` MTS int64 `json:"mts,omitempty"` UserID string `json:"userId,omitempty"` Title string `json:"title,omitempty"` Content string `json:"content,omitempty"` IsPin int `json:"isPin"` IsPublic int `json:"isPublic"` Tags []string `json:"tags,omitempty"` Attachments []string `json:"attachments,omitempty"` Likes int `json:"likes,omitempty"` PulseProfile *pulseprofile.PulseProfile `json:"pulseProfile,omitempty"` } var pulseFields = map[string]int{ "ID": 0, "Mts": 1, "UserID": 3, "Title": 5, "Content": 6, "IsPin": 9, "IsPublic": 10, "Tags": 12, "Attachments": 13, "Likes": 15, "PulseProfile": 18, } // NewSingleFromRaw returns pointer to Pulse message func NewSingleFromRaw(raw []interface{}) (*Pulse, error) { if len(raw) < 19 { return nil, fmt.Errorf("data slice too short for Pulse Message: %#v", raw) } p := &Pulse{} var err error p.ID = convert.SValOrEmpty(raw[pulseFields["ID"]]) p.MTS = convert.I64ValOrZero(raw[pulseFields["Mts"]]) p.UserID = convert.SValOrEmpty(raw[pulseFields["UserID"]]) p.Title = convert.SValOrEmpty(raw[pulseFields["Title"]]) p.Content = convert.SValOrEmpty(raw[pulseFields["Content"]]) p.IsPin = convert.ToInt(raw[pulseFields["IsPin"]]) p.IsPublic = convert.ToInt(raw[pulseFields["IsPublic"]]) p.Likes = convert.ToInt(raw[pulseFields["Likes"]]) p.Tags, err = convert.ItfToStrSlice(raw[pulseFields["Tags"]]) if err != nil { return nil, err } p.Attachments, err = convert.ItfToStrSlice(raw[pulseFields["Attachments"]]) if err != nil { return nil, err } rawProfile := raw[pulseFields["PulseProfile"]] rawProfileItf, ok := rawProfile.([]interface{}) if !ok { return p, nil } profilePayload := convert.ToInterfaceArray(rawProfileItf) if len(profilePayload) < 1 { return p, nil } p.PulseProfile, err = pulseprofile.NewFromRaw(profilePayload[0]) if err != nil { return nil, err } return p, nil } // NewFromRaw returns slice of Pulse message pointers func NewFromRaw(raws []interface{}) ([]*Pulse, error) { if len(raws) < 1 { return nil, fmt.Errorf("data slice is too short for Pulse History: %#v", raws) } res := []*Pulse{} for _, raw := range raws { raw := raw.([]interface{}) p, err := NewSingleFromRaw(raw) if err != nil { return nil, err } res = append(res, p) } return res, nil } bitfinex-api-go-2.2.9/pkg/models/pulse/pulse_test.go000066400000000000000000000047211371275744700224450ustar00rootroot00000000000000package pulse_test import ( "testing" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulse" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulseprofile" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewPulseFromRaw(t *testing.T) { t.Run("insufficient arguments", func(t *testing.T) { payload := []interface{}{ []interface{}{"abc123"}, } pm, err := pulse.NewFromRaw(payload) require.NotNil(t, err) require.Nil(t, pm) }) t.Run("missing pulse profile", func(t *testing.T) { payload := []interface{}{ []interface{}{ "id", float64(1591614631576), nil, "uid", nil, "title", "content", nil, nil, 1, 1, nil, []interface{}{"tag1", "tag2"}, []interface{}{"attach1", "attach2"}, nil, 5, nil, nil, nil, }, } pm, err := pulse.NewFromRaw(payload) require.Nil(t, err) expected := &pulse.Pulse{ ID: "id", MTS: 1591614631576, UserID: "uid", Title: "title", Content: "content", IsPin: 1, IsPublic: 1, Tags: []string{"tag1", "tag2"}, Attachments: []string{"attach1", "attach2"}, Likes: 5, } assert.Equal(t, expected, pm[0]) }) t.Run("has pulse profile", func(t *testing.T) { payload := []interface{}{ []interface{}{ "id", float64(1591614631576), nil, "uid", nil, "title", "content", nil, nil, 1, 1, nil, []interface{}{"tag1", "tag2"}, []interface{}{"attach1", "attach2"}, nil, 5, nil, nil, []interface{}{ []interface{}{ "abc123", float64(1591614631576), nil, "nickname", nil, "picture", "text", nil, nil, "twitter", nil, 30, 5, nil, }, }, }, } pm, err := pulse.NewFromRaw(payload) require.Nil(t, err) expected := &pulse.Pulse{ ID: "id", MTS: 1591614631576, UserID: "uid", Title: "title", Content: "content", IsPin: 1, IsPublic: 1, Tags: []string{"tag1", "tag2"}, Attachments: []string{"attach1", "attach2"}, Likes: 5, PulseProfile: &pulseprofile.PulseProfile{ ID: "abc123", MTS: 1591614631576, Nickname: "nickname", Picture: "picture", Text: "text", TwitterHandle: "twitter", }, } assert.Equal(t, expected, pm[0]) }) } bitfinex-api-go-2.2.9/pkg/models/pulseprofile/000077500000000000000000000000001371275744700213045ustar00rootroot00000000000000bitfinex-api-go-2.2.9/pkg/models/pulseprofile/pulseprofile.go000066400000000000000000000022141371275744700243430ustar00rootroot00000000000000package pulseprofile import ( "fmt" "github.com/bitfinexcom/bitfinex-api-go/pkg/convert" ) // PulseProfile data structure type PulseProfile struct { ID string MTS int64 Nickname string Picture string Text string TwitterHandle string } var pulseProfileFields = map[string]int{ "ID": 0, "Mts": 1, "Nickname": 3, "Picture": 5, "Text": 6, "TwitterHandle": 9, } // NewFromRaw takes in slice of interfaces and converts them to // pointer to Pulse Profile func NewFromRaw(raw []interface{}) (*PulseProfile, error) { if len(raw) < 14 { return nil, fmt.Errorf("data slice too short for PulseProfile: %#v", raw) } pp := &PulseProfile{} pp.ID = convert.SValOrEmpty(raw[pulseProfileFields["ID"]]) pp.MTS = convert.I64ValOrZero(raw[pulseProfileFields["Mts"]]) pp.Nickname = convert.SValOrEmpty(raw[pulseProfileFields["Nickname"]]) pp.Picture = convert.SValOrEmpty(raw[pulseProfileFields["Picture"]]) pp.Text = convert.SValOrEmpty(raw[pulseProfileFields["Text"]]) pp.TwitterHandle = convert.SValOrEmpty(raw[pulseProfileFields["TwitterHandle"]]) return pp, nil } bitfinex-api-go-2.2.9/pkg/models/pulseprofile/pulseprofile_test.go000066400000000000000000000017411371275744700254060ustar00rootroot00000000000000package pulseprofile_test import ( "testing" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulseprofile" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewProfileFromRaw(t *testing.T) { t.Run("insufficient arguments", func(t *testing.T) { payload := []interface{}{"abc123"} pp, err := pulseprofile.NewFromRaw(payload) require.NotNil(t, err) require.Nil(t, pp) }) t.Run("sufficient arguments", func(t *testing.T) { payload := []interface{}{ "abc123", float64(1591614631576), nil, "nickname", nil, "picture", "text", nil, nil, "twitter", nil, 30, 5, nil, } pp, err := pulseprofile.NewFromRaw(payload) require.Nil(t, err) expected := &pulseprofile.PulseProfile{ ID: "abc123", MTS: 1591614631576, Nickname: "nickname", Picture: "picture", Text: "text", TwitterHandle: "twitter", } assert.Equal(t, expected, pp) }) } bitfinex-api-go-2.2.9/tests/000077500000000000000000000000001371275744700156715ustar00rootroot00000000000000bitfinex-api-go-2.2.9/tests/integration/000077500000000000000000000000001371275744700202145ustar00rootroot00000000000000bitfinex-api-go-2.2.9/tests/integration/v1/000077500000000000000000000000001371275744700205425ustar00rootroot00000000000000bitfinex-api-go-2.2.9/tests/integration/v1/balances_test.go000066400000000000000000000004211371275744700236750ustar00rootroot00000000000000package tests /* // This test always fails unless the caller provides a X-BFX-APIKEY. Commented in favor of mocked tests. func TestGetBalances(t *testing.T) { _, err := client.Balances.All() if err != nil { t.Fatalf("Balances.All() returned error: %v", err) } } */ bitfinex-api-go-2.2.9/tests/integration/v1/bitfinex_test.go000066400000000000000000000003721371275744700237420ustar00rootroot00000000000000package tests import ( "os" "github.com/bitfinexcom/bitfinex-api-go/v1" ) var ( client *bitfinex.Client ) func init() { key := os.Getenv("BFX_API_KEY") secret := os.Getenv("BFX_API_SECRET") client = bitfinex.NewClient().Auth(key, secret) } bitfinex-api-go-2.2.9/tests/integration/v1/order_book_test.go000066400000000000000000000006401371275744700242550ustar00rootroot00000000000000package tests import ( "testing" ) func TestOrderBook(t *testing.T) { order_book, err := client.OrderBook.Get("BTCUSD", 10, 10, true) if err != nil { t.Fatalf("OrderBook.Get() returned error: %v", err) } if len(order_book.Bids) == 0 { t.Fatal("Order book should contain Bids") } if len(order_book.Asks) == 0 { t.Fatal("Order book should contain Asks") } } bitfinex-api-go-2.2.9/tests/integration/v1/order_test.go000066400000000000000000000007551371275744700232520ustar00rootroot00000000000000package tests // Test api client return error messages. /* // This test always fails unless the caller provides a X-BFX-APIKEY. Commented in favor of mocked tests. func TestFailCreateOrder(t *testing.T) { _, err := client.Orders.Create("BTCUSD", 1, 299.0, bitfinex.OrderTypeExchangeLimit) if err.Error() != "POST https://api.bitfinex.com/v1/order/new: 400 Invalid order: not enough exchange balance for 1.0 BTCUSD at 299.0" { t.Fatalf("OrderBook.Get() returned error: %v", err) } } */ bitfinex-api-go-2.2.9/tests/integration/v2/000077500000000000000000000000001371275744700205435ustar00rootroot00000000000000bitfinex-api-go-2.2.9/tests/integration/v2/api_test.go000066400000000000000000000022361371275744700227050ustar00rootroot00000000000000package tests /* import ( "context" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" "testing" "time" ) // the following test is used to run the API against func TestAPI(t *testing.T) { // create transport & nonce mocks // create client params := websocket.NewDefaultParameters() params.URL = "wss://dev-prdn.bitfinex.com:2997/ws/2" ws := websocket.NewWithParams(params) //.Credentials("U83q9jkML2GVj1fVxFJOAXQeDGaXIzeZ6PwNPQLEXt4", "77SWIRggvw0rCOJUgk9GVcxbldjTxOJP5WLCjWBFIVc") // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options //ws.SetReadTimeout(time.Second * 2) ws.Connect() defer ws.Close() // begin test //ev, err := listener.nextAuthEvent() //if err != nil { // t.Fatal(err) //} //assert(t, &websocket.AuthEvent{Event: "auth", Status: "OK"}, ev) tradeSubID, err := ws.SubscribeTrades(context.Background(), "tBTCUSD") if err != nil { t.Fatal(err) } t.Logf("trade sub ID: %s", tradeSubID) //bookSubID, err := ws.SubscribeBook(context.Background(), "tBTCUSD", websocket.Precision0, websocket.FrequencyRealtime) //t.Logf("book sub ID: %s", bookSubID) time.Sleep(time.Second * 15) } */ bitfinex-api-go-2.2.9/tests/integration/v2/assert.go000066400000000000000000000064521371275744700224020ustar00rootroot00000000000000package tests import ( "bytes" "reflect" "testing" ) func isZeroOfUnderlyingType(x interface{}) bool { if x == nil { return true } if _, ok := x.([]string); ok { return true } return x == reflect.Zero(reflect.TypeOf(x)).Interface() } func objEq(expected, actual interface{}) bool { if expected == nil || actual == nil { return expected == actual } if exp, ok := expected.([]byte); ok { act, ok := actual.([]byte) if !ok { return false } else if exp == nil || act == nil { return exp == nil && act == nil } return bytes.Equal(exp, act) } return reflect.DeepEqual(expected, actual) } func assertSlice(t *testing.T, expected, actual interface{}) { if objEq(expected, actual) { t.Logf("%s OK", reflect.TypeOf(actual)) return } actualType := reflect.TypeOf(actual) if actualType == nil { t.Fatal() } expectedValue := reflect.ValueOf(expected) if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { // Attempt comparison after type conversion if reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) { t.Logf("%s OK", reflect.TypeOf(actual)) return } } t.Fatalf("FAIL %s: expected %#v, got %#v", reflect.TypeOf(expected), expected, actual) } func isPrimitive(exp interface{}) bool { t := reflect.TypeOf(exp) switch t.Kind() { case reflect.Interface: return false case reflect.Struct: return false case reflect.Array: return false case reflect.Func: return false case reflect.Map: return false case reflect.Ptr: return false case reflect.Slice: return false case reflect.UnsafePointer: return false default: return true } } // does not work on slices func assert(t *testing.T, expected interface{}, actual interface{}) { prexp := reflect.ValueOf(expected) pract := reflect.ValueOf(actual) if isPrimitive(actual) { if expected != actual { t.Fatalf("expected %#v, got %#v", expected, actual) } t.Logf("OK %s", reflect.TypeOf(expected).Name()) return } if pract.IsNil() { t.Errorf("nil actual value: %#v", actual) t.Fail() return } exp := prexp.Elem() act := pract.Elem() if !exp.IsValid() { t.Errorf("reflected expectation not valid (%#v)", expected) t.Fail() } if exp.Type() != act.Type() { t.Errorf("expected type %s, got %s", exp.Type(), act.Type()) t.Fail() } for i := 0; i < exp.NumField(); i++ { expValueField := exp.Field(i) expTypeField := exp.Type().Field(i) actValueField := act.Field(i) actTypeField := act.Type().Field(i) if expTypeField.Name != actTypeField.Name { t.Errorf("expected type %s, got %s", expTypeField.Name, actTypeField.Name) t.Errorf("%#v", actual) t.Fail() } if isZeroOfUnderlyingType(expValueField.Interface()) { continue } if !isZeroOfUnderlyingType(expValueField.Interface()) && isZeroOfUnderlyingType(actValueField.Interface()) { t.Errorf("expected %s, but was empty", expTypeField.Name) t.Errorf("%#v", actual) t.Fail() return } assert(t, expValueField.Interface(), actValueField.Interface()) /* if expValueField.Interface() != actValueField.Interface() { t.Errorf("expected %s %#v, got %#v", expTypeField.Name, expValueField.Interface(), actValueField.Interface()) t.Fail() } */ } if t.Failed() { t.Logf("FAIL %s", exp.Type().Name()) return } t.Logf("OK %s", exp.Type().Name()) } bitfinex-api-go-2.2.9/tests/integration/v2/bitfinex_test.go000066400000000000000000000003261371275744700237420ustar00rootroot00000000000000package tests import ( "log" "os" ) var ( auth = false key = os.Getenv("BFX_API_KEY") secret = os.Getenv("BFX_API_SECRET") ) func init() { log.Println("Authenticated tests disabled.") auth = false } bitfinex-api-go-2.2.9/tests/integration/v2/incrementing_nonce.go000066400000000000000000000005361371275744700247420ustar00rootroot00000000000000package tests import ( "fmt" ) // IncrementingNonceGenerator starts at nonce1 and increments each by +1: nonce1, nonce2, ..., nonceN type IncrementingNonceGenerator struct { nonce int } // GetNonce returns an incrementing nonce value. func (m *IncrementingNonceGenerator) GetNonce() string { m.nonce++ return fmt.Sprintf("nonce%d", m.nonce) } bitfinex-api-go-2.2.9/tests/integration/v2/listener.go000066400000000000000000000244141371275744700227240ustar00rootroot00000000000000package tests import ( "errors" "log" "time" bitfinex "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) type listener struct { infoEvents chan *websocket.InfoEvent authEvents chan *websocket.AuthEvent ticks chan *bitfinex.Ticker subscriptionEvents chan *websocket.SubscribeEvent unsubscriptionEvents chan *websocket.UnsubscribeEvent walletUpdates chan *bitfinex.WalletUpdate balanceUpdates chan *bitfinex.BalanceUpdate walletSnapshot chan *bitfinex.WalletSnapshot positionSnapshot chan *bitfinex.PositionSnapshot notifications chan *bitfinex.Notification positions chan *bitfinex.PositionUpdate tradeUpdates chan *bitfinex.TradeExecutionUpdate tradeExecutions chan *bitfinex.TradeExecution cancels chan *bitfinex.OrderCancel marginBase chan *bitfinex.MarginInfoBase marginUpdate chan *bitfinex.MarginInfoUpdate funding chan *bitfinex.FundingInfo orderNew chan *bitfinex.OrderNew orderUpdate chan *bitfinex.OrderUpdate errors chan error } func newListener() *listener { return &listener{ infoEvents: make(chan *websocket.InfoEvent, 10), authEvents: make(chan *websocket.AuthEvent, 10), ticks: make(chan *bitfinex.Ticker, 10), subscriptionEvents: make(chan *websocket.SubscribeEvent, 10), unsubscriptionEvents: make(chan *websocket.UnsubscribeEvent, 10), walletUpdates: make(chan *bitfinex.WalletUpdate, 10), balanceUpdates: make(chan *bitfinex.BalanceUpdate, 10), walletSnapshot: make(chan *bitfinex.WalletSnapshot, 10), positionSnapshot: make(chan *bitfinex.PositionSnapshot, 10), errors: make(chan error, 10), notifications: make(chan *bitfinex.Notification, 10), positions: make(chan *bitfinex.PositionUpdate, 10), tradeUpdates: make(chan *bitfinex.TradeExecutionUpdate, 10), tradeExecutions: make(chan *bitfinex.TradeExecution, 10), cancels: make(chan *bitfinex.OrderCancel, 10), marginBase: make(chan *bitfinex.MarginInfoBase, 10), marginUpdate: make(chan *bitfinex.MarginInfoUpdate, 10), orderNew: make(chan *bitfinex.OrderNew, 10), orderUpdate: make(chan *bitfinex.OrderUpdate, 10), funding: make(chan *bitfinex.FundingInfo, 10), } } func (l *listener) nextInfoEvent() (*websocket.InfoEvent, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.infoEvents: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for InfoEvent") } } func (l *listener) nextAuthEvent() (*websocket.AuthEvent, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.authEvents: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for AuthEvent") } } func (l *listener) nextWalletUpdate() (*bitfinex.WalletUpdate, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.walletUpdates: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for WalletUpdate") } } func (l *listener) nextBalanceUpdate() (*bitfinex.BalanceUpdate, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.balanceUpdates: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for BalanceUpdate") } } func (l *listener) nextWalletSnapshot() (*bitfinex.WalletSnapshot, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.walletSnapshot: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for WalletSnapshot") } } func (l *listener) nextPositionSnapshot() (*bitfinex.PositionSnapshot, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.positionSnapshot: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for PositionSnapshot") } } func (l *listener) nextSubscriptionEvent() (*websocket.SubscribeEvent, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.subscriptionEvents: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for SubscribeEvent") } } func (l *listener) nextUnsubscriptionEvent() (*websocket.UnsubscribeEvent, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.unsubscriptionEvents: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for UnsubscribeEvent") } } func (l *listener) nextTick() (*bitfinex.Ticker, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.ticks: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for Ticker") } } func (l *listener) nextNotification() (*bitfinex.Notification, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.notifications: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for Notification") } } func (l *listener) nextTradeExecution() (*bitfinex.TradeExecution, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.tradeExecutions: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for TradeExecution") } } func (l *listener) nextPositionUpdate() (*bitfinex.PositionUpdate, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.positions: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for PositionUpdate") } } func (l *listener) nextTradeUpdate() (*bitfinex.TradeExecutionUpdate, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.tradeUpdates: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for TradeUpdate") } } func (l *listener) nextOrderCancel() (*bitfinex.OrderCancel, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.cancels: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for OrderCancel") } } func (l *listener) nextMarginInfoBase() (*bitfinex.MarginInfoBase, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.marginBase: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for MarginInfoBase") } } func (l *listener) nextMarginInfoUpdate() (*bitfinex.MarginInfoUpdate, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.marginUpdate: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for MarginInfoUpdate") } } func (l *listener) nextFundingInfo() (*bitfinex.FundingInfo, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.funding: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for FundingInfo") } } func (l *listener) nextOrderNew() (*bitfinex.OrderNew, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.orderNew: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for OrderNew") } } func (l *listener) nextOrderUpdate() (*bitfinex.OrderUpdate, error) { timeout := make(chan bool) go func() { time.Sleep(time.Second * 2) close(timeout) }() select { case ev := <-l.orderUpdate: return ev, nil case <-timeout: return nil, errors.New("timed out waiting for OrderUpdate") } } // strongly types messages and places them into a channel func (l *listener) run(ch <-chan interface{}) { go func() { // nolint:megacheck for { select { case msg := <-ch: if msg == nil { return } // remove threading guarantees when mulitplexing into channels log.Printf("[DEBUG] WsService -> WsClient: %#v", msg) switch msg.(type) { case error: l.errors <- msg.(error) case *bitfinex.Ticker: l.ticks <- msg.(*bitfinex.Ticker) case *websocket.InfoEvent: l.infoEvents <- msg.(*websocket.InfoEvent) case *websocket.SubscribeEvent: l.subscriptionEvents <- msg.(*websocket.SubscribeEvent) case *websocket.UnsubscribeEvent: l.unsubscriptionEvents <- msg.(*websocket.UnsubscribeEvent) case *websocket.AuthEvent: l.authEvents <- msg.(*websocket.AuthEvent) case *bitfinex.WalletUpdate: l.walletUpdates <- msg.(*bitfinex.WalletUpdate) case *bitfinex.BalanceUpdate: l.balanceUpdates <- msg.(*bitfinex.BalanceUpdate) case *bitfinex.Notification: l.notifications <- msg.(*bitfinex.Notification) case *bitfinex.TradeExecutionUpdate: l.tradeUpdates <- msg.(*bitfinex.TradeExecutionUpdate) case *bitfinex.TradeExecution: l.tradeExecutions <- msg.(*bitfinex.TradeExecution) case *bitfinex.PositionUpdate: l.positions <- msg.(*bitfinex.PositionUpdate) case *bitfinex.OrderCancel: l.cancels <- msg.(*bitfinex.OrderCancel) case *bitfinex.MarginInfoBase: l.marginBase <- msg.(*bitfinex.MarginInfoBase) case *bitfinex.MarginInfoUpdate: l.marginUpdate <- msg.(*bitfinex.MarginInfoUpdate) case *bitfinex.OrderNew: l.orderNew <- msg.(*bitfinex.OrderNew) case *bitfinex.OrderUpdate: l.orderUpdate <- msg.(*bitfinex.OrderUpdate) case *bitfinex.FundingInfo: l.funding <- msg.(*bitfinex.FundingInfo) case *bitfinex.PositionSnapshot: l.positionSnapshot <- msg.(*bitfinex.PositionSnapshot) case *bitfinex.WalletSnapshot: l.walletSnapshot <- msg.(*bitfinex.WalletSnapshot) default: log.Printf("COULD NOT TYPE MSG ^") } } } }() } bitfinex-api-go-2.2.9/tests/integration/v2/live_websocket_private_test.go000066400000000000000000000041341371275744700266720ustar00rootroot00000000000000package tests import ( "context" "sync" "testing" "time" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) func TestWebsocketOrder(t *testing.T) { if !auth { t.Skip("no credentials, skipping order creation") } wg := sync.WaitGroup{} wg.Add(1) // 1. Authentication event c := websocket.New().Credentials(key, secret) errch := make(chan error) err := c.Connect() if err != nil { t.Fatalf("connecting to websocket service: %s", err) } defer c.Close() go func() { for ev := range c.Listen() { switch e := ev.(type) { case *bitfinex.Notification: if e.Status == "ERROR" && e.Type == "on-req" { t.Errorf("failed to create order: %s", e.Text) } case *bitfinex.OrderNew: wg.Done() case *bitfinex.OrderCancel: wg.Done() case error: t.Logf("Listen() error: %s", ev) errch <- ev.(error) wg.Done() } } }() /* err = c.Authenticate(context.Background()) if err != nil { t.Fatalf("authenticating with websocket service: %s", err) } if err := wait(&wg, errch, 2*time.Second); err != nil { t.Fatalf("failed to authenticate with websocket service: %s", err) } */ wg.Add(1) n := time.Now() cid := n.Unix() cidDate := n.Format("2006-01-02") o := &bitfinex.OrderNewRequest{ CID: cid, Type: bitfinex.OrderTypeExchangeLimit, Symbol: bitfinex.TradingPrefix + bitfinex.BTCUSD, Amount: 1.0, Price: 28.5, } ctx, cxl1 := context.WithTimeout(context.Background(), 2*time.Second) defer cxl1() err = c.SubmitOrder(ctx, o) if err != nil { t.Fatalf("failed to send OrderNewRequest: %s", err) } if err := wait(&wg, errch, 2*time.Second); err != nil { t.Fatalf("failed to create order: %s", err) } oc := &bitfinex.OrderCancelRequest{ CID: cid, CIDDate: cidDate, } wg.Add(1) ctx, cxl2 := context.WithTimeout(context.Background(), 2*time.Second) defer cxl2() err = c.SubmitCancel(ctx, oc) if err != nil { t.Fatalf("failed to send OrderCancelRequest: %s", err) } if err := wait(&wg, errch, 2*time.Second); err != nil { t.Fatalf("failed to cancel order: %s", err) } } bitfinex-api-go-2.2.9/tests/integration/v2/live_websocket_public_test.go000066400000000000000000000162031371275744700264760ustar00rootroot00000000000000package tests import ( "context" "fmt" "log" "sync" "testing" "time" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) // wait2 will wait for at least "count" messages on channel "ch" within time "t", or return an error func wait2(ch <-chan interface{}, count int, bc <-chan error, t time.Duration) error { c := make(chan interface{}) go func() { <-ch close(c) }() select { case <-bc: return fmt.Errorf("transport closed while waiting") case <-c: return nil // normal case <-time.After(t): return fmt.Errorf("timed out waiting") } } func wait(wg *sync.WaitGroup, bc <-chan error, to time.Duration) error { c := make(chan struct{}) go func() { defer close(c) wg.Wait() }() select { case <-bc: return fmt.Errorf("websocket closed while waiting") // timed out case <-c: return nil // completed normally case <-time.After(to): return fmt.Errorf("timed out waiting") // timed out } } func TestPublicTicker(t *testing.T) { c := websocket.New() err := c.Connect() if err != nil { t.Fatal("Error connecting to web socket : ", err) } defer c.Close() subs := make(chan interface{}, 10) unsubs := make(chan interface{}, 10) infos := make(chan interface{}, 10) tick := make(chan interface{}, 100) errch := make(chan error) go func() { // nolint:megacheck for { select { case msg := <-c.Listen(): if msg == nil { return } log.Printf("recv msg: %#v", msg) switch m := msg.(type) { case error: errch <- msg.(error) case *websocket.UnsubscribeEvent: unsubs <- m case *websocket.SubscribeEvent: subs <- m case *websocket.InfoEvent: infos <- m case *bitfinex.TickerSnapshot: tick <- m case *bitfinex.Ticker: tick <- m default: t.Logf("test recv: %#v", msg) } } } }() ctx, cxl := context.WithTimeout(context.Background(), time.Second*5) defer cxl() id, err := c.SubscribeTicker(ctx, bitfinex.TradingPrefix+bitfinex.BTCUSD) if err != nil { t.Fatal(err) } if err := wait2(tick, 1, errch, 2*time.Second); err != nil { t.Fatalf("failed to receive ticker message from websocket: %s", err) } err = c.Unsubscribe(ctx, id) if err != nil { t.Fatal(err) } if err := wait2(unsubs, 1, errch, 2*time.Second); err != nil { t.Errorf("failed to receive unsubscribe message from websocket: %s", err) } } func TestPublicTrades(t *testing.T) { c := websocket.New() wg := sync.WaitGroup{} wg.Add(3) // 1. Info with version, 2. Subscription event, 3. 3 x data message err := c.Connect() if err != nil { t.Fatal("Error connecting to web socket : ", err) } defer c.Close() subs := make(chan interface{}, 10) unsubs := make(chan interface{}, 10) infos := make(chan interface{}, 10) trades := make(chan interface{}, 100) errch := make(chan error) go func() { // nolint:megacheck for { select { case msg := <-c.Listen(): if msg == nil { return } log.Printf("recv msg: %#v", msg) switch m := msg.(type) { case error: errch <- msg.(error) case *websocket.UnsubscribeEvent: unsubs <- m case *websocket.SubscribeEvent: subs <- m case *websocket.InfoEvent: infos <- m case *bitfinex.TradeExecutionUpdateSnapshot: trades <- m case *bitfinex.Trade: trades <- m case *bitfinex.TradeExecutionUpdate: trades <- m case *bitfinex.TradeExecution: trades <- m case *bitfinex.TradeSnapshot: trades <- m default: t.Logf("test recv: %#v", msg) } } } }() ctx, cxl := context.WithTimeout(context.Background(), time.Second*5) defer cxl() id, err := c.SubscribeTrades(ctx, bitfinex.TradingPrefix+bitfinex.BTCUSD) if err != nil { t.Fatal(err) } if err := wait2(trades, 1, errch, 2*time.Second); err != nil { t.Errorf("failed to receive trade message from websocket: %s", err) } err = c.Unsubscribe(ctx, id) if err != nil { t.Fatal(err) } if err := wait2(unsubs, 1, errch, 2*time.Second); err != nil { t.Errorf("failed to receive unsubscribe message from websocket: %s", err) } } func TestPublicBooks(t *testing.T) { c := websocket.New() wg := sync.WaitGroup{} wg.Add(3) // 1. Info with version, 2. Subscription event, 3. data message err := c.Connect() if err != nil { t.Fatal("Error connecting to web socket : ", err) } defer c.Close() subs := make(chan interface{}, 10) unsubs := make(chan interface{}, 10) infos := make(chan interface{}, 10) books := make(chan interface{}, 100) errch := make(chan error) go func() { // nolint:megacheck for { select { case msg := <-c.Listen(): if msg == nil { return } log.Printf("recv msg: %#v", msg) switch m := msg.(type) { case error: errch <- msg.(error) case *websocket.UnsubscribeEvent: unsubs <- m case *websocket.SubscribeEvent: subs <- m case *websocket.InfoEvent: infos <- m case *bitfinex.BookUpdateSnapshot: books <- m case *bitfinex.BookUpdate: books <- m default: t.Logf("test recv: %#v", msg) } } } }() ctx, cxl := context.WithTimeout(context.Background(), time.Second*5) defer cxl() id, err := c.SubscribeBook(ctx, bitfinex.TradingPrefix+bitfinex.BTCUSD, bitfinex.Precision0, bitfinex.FrequencyRealtime, 1) if err != nil { t.Fatal(err) } if err := wait2(books, 1, errch, 5*time.Second); err != nil { t.Fatalf("failed to receive book update message from websocket: %s", err) } err = c.Unsubscribe(ctx, id) if err != nil { t.Fatal(err) } if err := wait2(unsubs, 1, errch, 5*time.Second); err != nil { t.Errorf("failed to receive unsubscribe message from websocket: %s", err) } } func TestPublicCandles(t *testing.T) { c := websocket.New() wg := sync.WaitGroup{} wg.Add(3) // 1. Info with version, 2. Subscription event, 3. data message err := c.Connect() if err != nil { t.Fatal("Error connecting to web socket : ", err) } defer c.Close() subs := make(chan interface{}, 10) unsubs := make(chan interface{}, 10) infos := make(chan interface{}, 10) candles := make(chan interface{}, 100) errch := make(chan error) go func() { // nolint:megacheck for { select { case msg := <-c.Listen(): if msg == nil { return } log.Printf("recv msg: %#v", msg) switch m := msg.(type) { case error: errch <- msg.(error) case *websocket.UnsubscribeEvent: unsubs <- m case *websocket.SubscribeEvent: subs <- m case *websocket.InfoEvent: infos <- m case *bitfinex.Candle: candles <- m case *bitfinex.CandleSnapshot: candles <- m default: t.Logf("test recv: %#v", msg) } } } }() ctx, cxl := context.WithTimeout(context.Background(), time.Second*5) defer cxl() id, err := c.SubscribeCandles(ctx, bitfinex.TradingPrefix+bitfinex.BTCUSD, bitfinex.OneMonth) if err != nil { t.Fatal(err) } if err := wait2(candles, 1, errch, 2*time.Second); err != nil { t.Errorf("failed to receive a candle message from websocket: %s", err) } err = c.Unsubscribe(ctx, id) if err != nil { t.Fatal(err) } if err := wait2(unsubs, 1, errch, 2*time.Second); err != nil { t.Errorf("failed to receive an unsubscribe message from websocket: %s", err) } } bitfinex-api-go-2.2.9/tests/integration/v2/mock_async.go000066400000000000000000000040431371275744700232210ustar00rootroot00000000000000package tests import ( "context" "errors" "fmt" "log" "sync" "time" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) // does not work for reconnect tests type TestAsyncFactory struct { Count int Async websocket.Asynchronous } func (t *TestAsyncFactory) Create() websocket.Asynchronous { t.Count += 1 // if first creation then send given async if t.Count == 1 { return t.Async } // otherwise create a new async for each new creation return newTestAsync() } func newTestAsyncFactory(async websocket.Asynchronous) websocket.AsynchronousFactory { return &TestAsyncFactory{Async: async, Count: 0} } type TestAsync struct { done chan error bridge chan []byte connected bool Sent []interface{} mutex sync.Mutex } func (t *TestAsync) SentCount() int { t.mutex.Lock() defer t.mutex.Unlock() return len(t.Sent) } func (t *TestAsync) waitForMessage(num int) error { seconds := 4 loops := 20 delay := time.Duration(float64(time.Second) * float64(seconds) / float64(loops)) for i := 0; i < loops; i++ { t.mutex.Lock() len := len(t.Sent) t.mutex.Unlock() if num+1 <= len { return nil } time.Sleep(delay) } return fmt.Errorf("did not send a message in pos %d", num) } func (t *TestAsync) Connect() error { t.connected = true return nil } func (t *TestAsync) Send(ctx context.Context, msg interface{}) error { if !t.connected { return errors.New("must connect before sending") } t.mutex.Lock() defer t.mutex.Unlock() t.Sent = append(t.Sent, msg) return nil } func (t *TestAsync) DumpSentMessages() { for i, msg := range t.Sent { log.Printf("%2d: %#v", i, msg) } } func (t *TestAsync) Listen() <-chan []byte { return t.bridge } func (t *TestAsync) Publish(raw string) { t.bridge <- []byte(raw) } func (t *TestAsync) Close() { close(t.bridge) close(t.done) } func (t *TestAsync) Done() <-chan error { return t.done } func newTestAsync() *TestAsync { return &TestAsync{ bridge: make(chan []byte), connected: false, Sent: make([]interface{}, 0), done: make(chan error), } } bitfinex-api-go-2.2.9/tests/integration/v2/mock_ws_private_test.go000066400000000000000000000641031371275744700253310ustar00rootroot00000000000000package tests import ( "context" "fmt" "testing" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) func TestAuthentication(t *testing.T) { // create transport & nonce mocks async := newTestAsync() nonce := &IncrementingNonceGenerator{} // create client ws := websocket.NewWithAsyncFactoryNonce(newTestAsyncFactory(async), nonce).Credentials("apiKeyABC", "apiSecretXYZ") // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // begin test async.Publish(`{"event":"info","version":2}`) ev, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(websocket.InfoEvent{Version: 2}), fmt.Sprint(*ev)) // assert outgoing auth request if err := async.waitForMessage(0); err != nil { t.Fatal(err.Error()) } expected := websocket.SubscriptionRequest{SubID: "nonce1", Event: "auth", APIKey: "apiKeyABC"} actual := *async.Sent[0].(*websocket.SubscriptionRequest) assert(t, expected.SubID, actual.SubID) assert(t, expected.Event, actual.Event) assert(t, expected.APIKey, actual.APIKey) // auth ack async.Publish(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce1","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) // assert incoming auth ack av, err := listener.nextAuthEvent() if err != nil { t.Fatal(err) } expected2 := websocket.AuthEvent{Status: "OK", SubID: "nonce1", ChanID: 0} actual2 := *av assert(t, expected2.SubID, actual2.SubID) assert(t, expected2.Status, actual2.Status) assert(t, expected2.ChanID, actual2.ChanID) } func TestWalletBalanceUpdates(t *testing.T) { // create transport & nonce mocks async := newTestAsync() nonce := &IncrementingNonceGenerator{} // create client ws := websocket.NewWithAsyncFactoryNonce(newTestAsyncFactory(async), nonce).Credentials("apiKeyABC", "apiSecretXYZ") // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options //ws.SetReadTimeout(time.Second * 2) err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // begin test--authentication assertions in TestAuthentication async.Publish(`{"event":"info","version":2}`) // eat event _, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } // auth ack async.Publish(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce1","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) // eat event _, err = listener.nextAuthEvent() if err != nil { t.Fatal(err) } // publish account info post auth ack async.Publish(`[0,"wu",["exchange","BTC",30,0,30]]`) async.Publish(`[0,"wu",["exchange","USD",80000,0,80000]]`) async.Publish(`[0,"wu",["exchange","ETH",100,0,100]]`) async.Publish(`[0,"wu",["margin","BTC",10,0,10]]`) async.Publish(`[0,"wu",["funding","BTC",10,0,10]]`) async.Publish(`[0,"wu",["funding","USD",10000,0,10000]]`) async.Publish(`[0,"wu",["margin","USD",10000,0,10000]]`) async.Publish(`[0,"bu",[147260,147260]]`) // assert incoming wallet & balance updates wu, err := listener.nextWalletUpdate() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "exchange", Currency: "BTC", Balance: 30, BalanceAvailable: 30}), fmt.Sprint(*wu)) wu, _ = listener.nextWalletUpdate() assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "exchange", Currency: "USD", Balance: 80000, BalanceAvailable: 80000}), fmt.Sprint(*wu)) wu, _ = listener.nextWalletUpdate() assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "exchange", Currency: "ETH", Balance: 100, BalanceAvailable: 100}), fmt.Sprint(*wu)) wu, _ = listener.nextWalletUpdate() assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "margin", Currency: "BTC", Balance: 10, BalanceAvailable: 10}), fmt.Sprint(*wu)) wu, _ = listener.nextWalletUpdate() assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "funding", Currency: "BTC", Balance: 10, BalanceAvailable: 10}), fmt.Sprint(*wu)) wu, _ = listener.nextWalletUpdate() assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "funding", Currency: "USD", Balance: 10000, BalanceAvailable: 10000}), fmt.Sprint(*wu)) wu, _ = listener.nextWalletUpdate() assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "margin", Currency: "USD", Balance: 10000, BalanceAvailable: 10000}), fmt.Sprint(*wu)) bu, err := listener.nextBalanceUpdate() if err != nil { t.Fatal(err) } // total aum, net aum assert(t, fmt.Sprint(bitfinex.BalanceUpdate{TotalAUM: 147260, NetAUM: 147260}), fmt.Sprint(*bu)) } func TestNewOrder(t *testing.T) { // create transport & nonce mocks async := newTestAsync() nonce := &IncrementingNonceGenerator{} // create client ws := websocket.NewWithAsyncFactoryNonce(newTestAsyncFactory(async), nonce).Credentials("apiKeyABC", "apiSecretXYZ") // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options //ws.SetReadTimeout(time.Second * 2) err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // begin test async.Publish(`{"event":"info","version":2}`) _, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } // initial logon info--Authentication & WalletUpdate assertions in prior tests async.Publish(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce1","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) async.Publish(`[0,"wu",["exchange","BTC",30,0,30]]`) async.Publish(`[0,"wu",["exchange","USD",80000,0,80000]]`) async.Publish(`[0,"wu",["exchange","ETH",100,0,100]]`) async.Publish(`[0,"wu",["margin","BTC",10,0,10]]`) async.Publish(`[0,"wu",["funding","BTC",10,0,10]]`) async.Publish(`[0,"wu",["funding","USD",10000,0,10000]]`) async.Publish(`[0,"wu",["margin","USD",10000,0,10000]]`) async.Publish(`[0,"bu",[147260,147260]]`) // submit order err = ws.SubmitOrder(context.Background(), &bitfinex.OrderNewRequest{ Symbol: "tBTCUSD", CID: 123, Amount: -0.456, }) if err != nil { t.Fatal(err) } // assert outgoing order request if len(async.Sent) <= 1 { t.Fatalf("expected >1 sent messages, got %d", len(async.Sent)) } expected := bitfinex.OrderNewRequest{Symbol: "tBTCUSD", CID: 123, Amount: -0.456} actual := *async.Sent[1].(*bitfinex.OrderNewRequest) assert(t, expected.Symbol, actual.Symbol) assert(t, expected.CID, actual.CID) assert(t, expected.Amount, actual.Amount) // order ack async.Publish(`[0,"n",[null,"on-req",null,null,[1234567,null,123,"tBTCUSD",null,null,1,1,"MARKET",null,null,null,null,null,null,null,915.5,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null],null,"SUCCESS","Submitting market buy order for 1.0 BTC."]]`) // assert order ack notification not, err := listener.nextNotification() if err != nil { t.Fatal(err) } expected2 := bitfinex.Notification{Type: "on-req", NotifyInfo: bitfinex.OrderNew{ID: 1234567, CID: 123, Symbol: "tBTCUSD", Amount: 1, AmountOrig: 1, Type: "MARKET", Price: 915.5}, Status: "SUCCESS", Text: "Submitting market buy order for 1.0 BTC."} not.NotifyInfo = *not.NotifyInfo.(*bitfinex.OrderNew) assert(t, fmt.Sprint(expected2), fmt.Sprint(*not)) } func TestFills(t *testing.T) { // create transport & nonce mocks async := newTestAsync() nonce := &IncrementingNonceGenerator{} // create client ws := websocket.NewWithAsyncFactoryNonce(newTestAsyncFactory(async), nonce).Credentials("apiKeyABC", "apiSecretXYZ") // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options //ws.SetReadTimeout(time.Second * 2) err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // begin test async.Publish(`{"event":"info","version":2}`) _, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } // initial logon info async.Publish(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce1","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) async.Publish(`[0,"ps",[["tBTCUSD","ACTIVE",7,916.52002351,0,0,null,null,null,null]]]`) async.Publish(`[0,"ws",[["exchange","BTC",30,0,null],["exchange","USD",80000,0,null],["exchange","ETH",100,0,null],["margin","BTC",10,0,null],["margin","USD",9987.16871968,0,null],["funding","BTC",10,0,null],["funding","USD",10000,0,null]]]`) // consume & assert snapshots ps, err := listener.nextPositionSnapshot() if err != nil { t.Fatal(err) } eps := make([]*bitfinex.Position, 1) eps[0] = &bitfinex.Position{ Symbol: "tBTCUSD", Status: "ACTIVE", Amount: 7, BasePrice: 916.52002351, } snap := &bitfinex.PositionSnapshot{ Snapshot: eps, } assertSlice(t, snap, ps) w, err := listener.nextWalletSnapshot() if err != nil { t.Fatal(err) } ews := make([]*bitfinex.Wallet, 7) ews[0] = &bitfinex.Wallet{Type: "exchange", Currency: "BTC", Balance: 30} ews[1] = &bitfinex.Wallet{Type: "exchange", Currency: "USD", Balance: 80000} ews[2] = &bitfinex.Wallet{Type: "exchange", Currency: "ETH", Balance: 100} ews[3] = &bitfinex.Wallet{Type: "margin", Currency: "BTC", Balance: 10} ews[4] = &bitfinex.Wallet{Type: "margin", Currency: "USD", Balance: 9987.16871968} ews[5] = &bitfinex.Wallet{Type: "funding", Currency: "BTC", Balance: 10} ews[6] = &bitfinex.Wallet{Type: "funding", Currency: "USD", Balance: 10000} wsnap := &bitfinex.WalletSnapshot{ Snapshot: ews, } assertSlice(t, wsnap, w) // submit order err = ws.SubmitOrder(context.Background(), &bitfinex.OrderNewRequest{ Symbol: "tBTCUSD", CID: 123, Amount: -0.456, }) if err != nil { t.Fatal(err) } // order ack async.Publish(`[0,"n",[null,"on-req",null,null,[1234567,null,123,"tBTCUSD",null,null,1,1,"MARKET",null,null,null,null,null,null,null,915.5,null,null,null,null,null,null,0,null,null],null,"SUCCESS","Submitting market buy order for 1.0 BTC."]]`) // assert order ack notification--Authentication, WalletUpdate, order acknowledgement assertions in prior tests _, err = listener.nextNotification() if err != nil { t.Fatal(err) } // <..crossing orders generates a fill..> // partial fills--position updates async.Publish(`[0,"pu",["tBTCUSD","ACTIVE",0.21679716,915.9,0,0,null,null,null,null]]`) async.Publish(`[0,"pu",["tBTCUSD","ACTIVE",1,916.13496085,0,0,null,null,null,null]]`) pu, err := listener.nextPositionUpdate() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.PositionUpdate{Symbol: "tBTCUSD", Status: "ACTIVE", Amount: 0.21679716, BasePrice: 915.9}), fmt.Sprint(*pu)) pu, _ = listener.nextPositionUpdate() assert(t, fmt.Sprint(bitfinex.PositionUpdate{Symbol: "tBTCUSD", Status: "ACTIVE", Amount: 1, BasePrice: 916.13496085}), fmt.Sprint(*pu)) // full fill--order terminal state message async.Publish(`[0,"oc",[1234567,0,123,"tBTCUSD",1514909325236,1514909325631,0,1,"MARKET",null,null,null,0,"EXECUTED @ 916.2(0.78): was PARTIALLY FILLED @ 915.9(0.22)",null,null,915.5,916.13496085,null,null,null,null,null,0,0,0]]`) oc, err := listener.nextOrderCancel() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.OrderCancel{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1514909325236, MTSUpdated: 1514909325631, Amount: 0, AmountOrig: 1, Type: "MARKET", Status: "EXECUTED @ 916.2(0.78): was PARTIALLY FILLED @ 915.9(0.22)", Price: 915.5, PriceAvg: 916.13496085}), fmt.Sprint(*oc)) // fills--trade executions async.Publish(`[0,"te",[1,"tBTCUSD",1514909325593,1234567,0.21679716,915.9,null,null,0]]`) async.Publish(`[0,"te",[2,"tBTCUSD",1514909325597,1234567,0.78320284,916.2,null,null,0]]`) te, err := listener.nextTradeExecution() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.TradeExecution{ID: 1, Pair: "tBTCUSD", OrderID: 1234567, MTS: 1514909325593, Amount: 0.21679716, Price: 915.9}), fmt.Sprint(*te)) te, err = listener.nextTradeExecution() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.TradeExecution{ID: 2, Pair: "tBTCUSD", OrderID: 1234567, MTS: 1514909325597, Amount: 0.78320284, Price: 916.2}), fmt.Sprint(*te)) // fills--trade updates async.Publish(`[0,"tu",[1,"tBTCUSD",1514909325593,1234567,0.21679716,915.9,"MARKET",915.5,-1,-0.39712904,"USD"]]`) async.Publish(`[0,"tu",[2,"tBTCUSD",1514909325597,1234567,0.78320284,916.2,"MARKET",915.5,-1,-1.43514088,"USD"]]`) tu, err := listener.nextTradeUpdate() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.TradeExecutionUpdate{ID: 1, Pair: "tBTCUSD", MTS: 1514909325593, ExecAmount: 0.21679716, ExecPrice: 915.9, OrderType: "MARKET", OrderPrice: 915.5, OrderID: 1234567, Maker: -1, Fee: -0.39712904, FeeCurrency: "USD"}), fmt.Sprint(*tu)) tu, _ = listener.nextTradeUpdate() assert(t, fmt.Sprint(bitfinex.TradeExecutionUpdate{ID: 2, Pair: "tBTCUSD", MTS: 1514909325597, ExecAmount: 0.78320284, ExecPrice: 916.2, OrderType: "MARKET", OrderPrice: 915.5, OrderID: 1234567, Maker: -1, Fee: -1.43514088, FeeCurrency: "USD"}), fmt.Sprint(*tu)) // fills--wallet updates from fee deduction async.Publish(`[0,"wu",["margin","USD",9999.60287096,0,null]]`) async.Publish(`[0,"wu",["margin","USD",9998.16773008,0,null]]`) wu, err := listener.nextWalletUpdate() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "margin", Currency: "USD", Balance: 9999.60287096}), fmt.Sprint(*wu)) wu, _ = listener.nextWalletUpdate() assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "margin", Currency: "USD", Balance: 9998.16773008}), fmt.Sprint(*wu)) // margin info update for executed trades async.Publish(`[0,"miu",["base",[-2.76536085,0,19150.16773008,19147.40236923]]]`) async.Publish(`[0,"miu",["sym","tBTCUSD",[60162.93960325,61088.2924336,60162.93960325,60162.93960325,null,null,null,null]]]`) mb, err := listener.nextMarginInfoBase() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.MarginInfoBase{UserProfitLoss: -2.76536085, MarginBalance: 19150.16773008, MarginNet: 19147.40236923}), fmt.Sprint(*mb)) mu, err := listener.nextMarginInfoUpdate() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.MarginInfoUpdate{Symbol: "tBTCUSD", TradableBalance: 60162.93960325}), fmt.Sprint(*mu)) // position update for executed trades async.Publish(`[0,"pu",["tBTCUSD","ACTIVE",1,916.13496085,0,0,-2.76536085,-0.30185082,0,43.7962]]`) pu, _ = listener.nextPositionUpdate() assert(t, fmt.Sprint(bitfinex.PositionUpdate{Symbol: "tBTCUSD", Status: "ACTIVE", Amount: 1, BasePrice: 916.13496085, ProfitLoss: -2.76536085, ProfitLossPercentage: -0.30185082, Leverage: 43.7962}), fmt.Sprint(*pu)) // wallet margin update for executed trades async.Publish(`[0,"wu",["margin","BTC",10,0,10]]`) async.Publish(`[0,"wu",["margin","USD",9998.16773008,0,9998.16773008]]`) wu, _ = listener.nextWalletUpdate() assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "margin", Currency: "BTC", Balance: 10, BalanceAvailable: 10}), fmt.Sprint(*wu)) wu, _ = listener.nextWalletUpdate() assert(t, fmt.Sprint(bitfinex.WalletUpdate{Type: "margin", Currency: "USD", Balance: 9998.16773008, BalanceAvailable: 9998.16773008}), fmt.Sprint(*wu)) // funding update for executed trades async.Publish(`[0,"fiu",["sym","ftBTCUSD",[0,0,0,0]]]`) fi, err := listener.nextFundingInfo() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.FundingInfo{Symbol: "ftBTCUSD"}), fmt.Sprint(*fi)) } func TestCancel(t *testing.T) { // create transport & nonce mocks async := newTestAsync() nonce := &IncrementingNonceGenerator{} // create client ws := websocket.NewWithAsyncFactoryNonce(newTestAsyncFactory(async), nonce).Credentials("apiKeyABC", "apiSecretXYZ") // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options //ws.SetReadTimeout(time.Second * 2) err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // begin test async.Publish(`{"event":"info","version":2}`) _, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } // initial logon info--Authentication & WalletUpdate assertions in prior tests async.Publish(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce1","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) async.Publish(`[0,"ps",[["tBTCUSD","ACTIVE",7,916.52002351,0,0,null,null,null,null]]]`) async.Publish(`[0,"ws",[["exchange","BTC",30,0,null],["exchange","USD",80000,0,null],["exchange","ETH",100,0,null],["margin","BTC",10,0,null],["margin","USD",9987.16871968,0,null],["funding","BTC",10,0,null],["funding","USD",10000,0,null]]]`) // consume & assert snapshots _, err_ps := listener.nextPositionSnapshot() if err_ps != nil { t.Fatal(err_ps) } _, err_was := listener.nextWalletSnapshot() if err_was != nil { t.Fatal(err_was) } // submit order err = ws.SubmitOrder(context.Background(), &bitfinex.OrderNewRequest{ Symbol: "tBTCUSD", CID: 123, Amount: -0.456, Type: "LIMIT", Price: 900.0, }) if err != nil { t.Fatal(err) } // assert outgoing order request if len(async.Sent) <= 1 { t.Fatalf("expected >1 sent messages, got %d", len(async.Sent)) } assert(t, fmt.Sprint(bitfinex.OrderNewRequest{Symbol: "tBTCUSD", CID: 123, Amount: -0.456, Type: "LIMIT", Price: 900.0}), fmt.Sprint(*async.Sent[1].(*bitfinex.OrderNewRequest))) // order pending new async.Publish(`[0,"n",[null,"on-req",null,null,[1234567,null,123,"tBTCUSD",null,null,1,1,"LIMIT",null,null,null,null,null,null,null,900,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null],null,"SUCCESS","Submitting limit buy order for 1.0 BTC."]]`) // order working--limit order async.Publish(`[0,"on",[1234567,0,123,"tBTCUSD",1515179518260,1515179518315,1,1,"LIMIT",null,null,null,0,"ACTIVE",null,null,900,0,null,null,null,null,null,0,0,null,null,null,null,null,null,null,null]]`) // eat order ack notification _, err_n := listener.nextNotification() if err_n != nil { t.Fatal(err_n) } on, err := listener.nextOrderNew() if err != nil { t.Fatal(err) } // assert order new update assert(t, fmt.Sprint(bitfinex.OrderNew{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1515179518260, MTSUpdated: 1515179518315, Type: "LIMIT", Amount: 1, AmountOrig: 1, Status: "ACTIVE", Price: 900.0}), fmt.Sprint(*on)) // publish cancel request req := &bitfinex.OrderCancelRequest{ID: on.ID} pre := async.SentCount() err = ws.SubmitCancel(context.Background(), req) if err != nil { t.Fatal(err) } if err := async.waitForMessage(pre); err != nil { t.Fatal(err.Error()) } // assert sent message assert(t, req, async.Sent[pre].(*bitfinex.OrderCancelRequest)) // cancel ack notify async.Publish(`[0,"n",[null,"oc-req",null,null,[1149686139,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null],null,"SUCCESS","Submitted for cancellation; waiting for confirmation (ID: 1149686139)."]]`) // cancel confirm async.Publish(`[0,"oc",[1234567,0,123,"tBTCUSD",1515179518260,1515179520203,1,1,"LIMIT",null,null,null,0,"CANCELED",null,null,900,0,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null]]`) // assert cancel ack oc, err := listener.nextOrderCancel() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.OrderCancel{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1515179518260, MTSUpdated: 1515179520203, Type: "LIMIT", Status: "CANCELED", Price: 900.0, Amount: 1, AmountOrig: 1}), fmt.Sprint(*oc)) } func TestUpdateOrder(t *testing.T) { // create transport & nonce mocks async := newTestAsync() nonce := &IncrementingNonceGenerator{} // create client ws := websocket.NewWithAsyncFactoryNonce(newTestAsyncFactory(async), nonce).Credentials("apiKeyABC", "apiSecretXYZ") // setup listener listener := newListener() listener.run(ws.Listen()) err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // begin test async.Publish(`{"event":"info","version":2}`) _, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } // initial logon info--Authentication & WalletUpdate assertions in prior tests async.Publish(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce1","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) async.Publish(`[0,"ps",[["tBTCUSD","ACTIVE",7,916.52002351,0,0,null,null,null,null]]]`) async.Publish(`[0,"ws",[["exchange","BTC",30,0,null],["exchange","USD",80000,0,null],["exchange","ETH",100,0,null],["margin","BTC",10,0,null],["margin","USD",9987.16871968,0,null],["funding","BTC",10,0,null],["funding","USD",10000,0,null]]]`) // consume & assert snapshots _, errps := listener.nextPositionSnapshot() if errps != nil { t.Fatal(errps) } _, errws := listener.nextWalletSnapshot() if errws != nil { t.Fatal(errws) } // submit order err = ws.SubmitOrder(context.Background(), &bitfinex.OrderNewRequest{ Symbol: "tBTCUSD", CID: 123, Amount: -0.456, Type: "LIMIT", Price: 900.0, }) if err != nil { t.Fatal(err) } // assert outgoing order request if len(async.Sent) <= 1 { t.Fatalf("expected >1 sent messages, got %d", len(async.Sent)) } assert(t, fmt.Sprint(bitfinex.OrderNewRequest{Symbol: "tBTCUSD", CID: 123, Amount: -0.456, Type: "LIMIT", Price: 900.0}), fmt.Sprint(*async.Sent[1].(*bitfinex.OrderNewRequest))) // order pending new async.Publish(`[0,"n",[null,"on-req",null,null,[1234567,null,123,"tBTCUSD",null,null,1,1,"LIMIT",null,null,null,null,null,null,null,900,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null],null,"SUCCESS","Submitting limit buy order for 1.0 BTC."]]`) // order working--limit order async.Publish(`[0,"on",[1234567,0,123,"tBTCUSD",1515179518260,1515179518315,1,1,"LIMIT",null,null,null,0,"ACTIVE",null,null,900,0,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null]]`) // eat order ack notification _, errn := listener.nextNotification() if errn != nil { t.Fatal(errn) } on, err := listener.nextOrderNew() if err != nil { t.Fatal(err) } // assert order new update assert(t, fmt.Sprint(bitfinex.OrderNew{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1515179518260, MTSUpdated: 1515179518315, Type: "LIMIT", Amount: 1, AmountOrig: 1, Status: "ACTIVE", Price: 900.0}), fmt.Sprint(*on)) // publish update request req := &bitfinex.OrderUpdateRequest{ ID: on.ID, Amount: 0.04, Price: 1200, } pre := async.SentCount() err = ws.SubmitUpdateOrder(context.Background(), req) if err != nil { t.Fatal(err) } if err := async.waitForMessage(pre); err != nil { t.Fatal(err.Error()) } // assert sent message assert(t, fmt.Sprint(*req), fmt.Sprint(*async.Sent[pre].(*bitfinex.OrderUpdateRequest))) // cancel ack notify async.Publish(`[0,"n",[1547469854094,"ou-req",null,null,[1234567,0,123,"tBTCUSD",1547469854025,1547469854042,0.04,0.04,"LIMIT",null,null,null,0,"ACTIVE",null,null,1200,0,0,0,null,null,null,0,0,null,null,null,"API>BFX",null,null,null],null,"SUCCESS","Submitting update to exchange limit buy order for 0.04 BTC."]]`) // cancel confirm async.Publish(`[0,"ou",[1234567,0,123,"tBTCUSD",1547469854025,1547469854121,0.04,0.04,"LIMIT",null,null,null,0,"ACTIVE",null,null,1200,0,0,0,null,null,null,0,0,null,null,null,"API>BFX",null,null,null]]`) // assert cancel ack ou, err := listener.nextOrderUpdate() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(bitfinex.OrderUpdate{ID:1234567, GID:0, CID:123, Symbol:"tBTCUSD", MTSCreated:1547469854025, MTSUpdated:1547469854121, Amount:0.04, AmountOrig:0.04, Type:"LIMIT", TypePrev:"", Flags:0, Status:"ACTIVE", Price:1200, PriceAvg:0, PriceTrailing:0, PriceAuxLimit:0, Notify:false, Hidden:false, PlacedID:0}), fmt.Sprint(*ou)) } func TestUsesAuthenticatedSocket(t *testing.T) { // create transport & nonce mocks async := newTestAsync() // create client p := websocket.NewDefaultParameters() // lock the capacity to 3 p.CapacityPerConnection = 3 ws := websocket.NewWithParamsAsyncFactory(p, newTestAsyncFactory(async)).Credentials("apiKeyABC", "apiSecretXYZ") // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // info welcome msg async.Publish(`{"event":"info","version":2}`) ev, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } assert(t, fmt.Sprint(websocket.InfoEvent{Version: 2}), fmt.Sprint(*ev)) // auth ack async.Publish(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce1","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) // force websocket to create new connections tickers := []string{"tBTCUSD", "tETHUSD", "tBTCUSD", "tVETUSD", "tDGBUSD", "tEOSUSD", "tTRXUSD", "tEOSETH", "tBTCETH", "tBTCEOS", "tXRPUSD", "tXRPBTC", "tTRXETH", "tTRXBTC", "tLTCUSD", "tLTCBTC", "tLTCETH"} for i, ticker := range tickers { // subscribe to 15m candles id, err := ws.SubscribeCandles(context.Background(), ticker, bitfinex.FifteenMinutes) if err != nil { t.Fatal(err) } async.Publish(`{"event":"subscribed","channel":"candles","chanId":`+string(i)+`,"key":"trade:15m:`+ticker+`","subId":"`+id+`"}`) } authSocket, err := ws.GetAuthenticatedSocket() if err != nil { t.Fatal(err) } fmt.Println(*authSocket) } bitfinex-api-go-2.2.9/tests/integration/v2/mock_ws_public_test.go000066400000000000000000000164521371275744700251410ustar00rootroot00000000000000package tests import ( "context" "testing" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) // method of testing with mocked endpoints func TestTicker(t *testing.T) { // create transport & nonce mocks async := newTestAsync() nonce := &IncrementingNonceGenerator{} // create client ws := websocket.NewWithAsyncFactoryNonce(newTestAsyncFactory(async), nonce) // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // info welcome msg async.Publish(`{"event":"info","version":2}`) ev, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } assert(t, &websocket.InfoEvent{Version: 2}, ev) // subscribe id, err := ws.SubscribeTicker(context.Background(), "tBTCUSD") if err != nil { t.Fatal(err) } // subscribe ack async.Publish(`{"event":"subscribed","channel":"ticker","chanId":5,"symbol":"tBTCUSD","subId":"nonce1","pair":"BTCUSD"}`) sub, err := listener.nextSubscriptionEvent() if err != nil { t.Fatal(err) } assert(t, &websocket.SubscribeEvent{ SubID: "nonce1", Channel: "ticker", ChanID: 5, Symbol: "tBTCUSD", Pair: "BTCUSD", }, sub) // tick data async.Publish(`[5,[14957,68.17328796,14958,55.29588132,-659,-0.0422,14971,53723.08813995,16494,14454]]`) tick, err := listener.nextTick() if err != nil { t.Fatal(err) } assert(t, &bitfinex.Ticker{ Symbol: "tBTCUSD", Bid: 14957, Ask: 14958, BidSize: 68.17328796, AskSize: 55.29588132, DailyChange: -659, DailyChangePerc: -0.0422, LastPrice: 14971, Volume: 53723.08813995, High: 16494, Low: 14454, }, tick) // unsubscribe err_unsub := ws.Unsubscribe(context.Background(), id) if err_unsub != nil { t.Fatal(err_unsub) } async.Publish(`{"event":"unsubscribed","chanId":5,"status":"OK"}`) unsub, err := listener.nextUnsubscriptionEvent() if err != nil { t.Fatal(err) } assert(t, &websocket.UnsubscribeEvent{ChanID: 5, Status: "OK"}, unsub) } func TestOrderbook(t *testing.T) { // create transport & nonce mocks async := newTestAsync() // create client p := websocket.NewDefaultParameters() p.ManageOrderbook = true ws := websocket.NewWithParamsAsyncFactory(p, newTestAsyncFactory(async)) // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // info welcome msg async.Publish(`{"event":"info","version":2}`) ev, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } assert(t, &websocket.InfoEvent{Version: 2}, ev) // we will use XRPBTC since that uses reallllyy small numbers bId, err_st := ws.SubscribeBook(context.Background(), bitfinex.TradingPrefix+bitfinex.XRPBTC, bitfinex.Precision0, bitfinex.FrequencyRealtime, 25) if err_st != nil { t.Fatal(err_st) } // checksum enabled ack async.Publish(`{"event":"conf","status":"OK","flags":131072}`) // subscribe ack async.Publish(`{"event":"subscribed","channel":"book","chanId":81757,"symbol":"tXRPBTC","prec":"P0","freq":"F0","len":"25","subId":"` + bId + `","pair":"XRPBTC"}`) // publish a snapshot async.Publish(`[81757,[[0.0000011,13,271510.49],[0.00000109,4,500793.10790141],[0.00000108,5,776367.43],[0.00000107,1,23329.54842056],[0.00000106,3,116868.87735849],[0.00000105,3,205000],[0.00000103,3,227308.25386407],[0.00000102,2,105000],[0.00000101,1,2970],[0.000001,2,21000],[7e-7,1,10000],[6.6e-7,1,10000],[6e-7,1,100000],[4.9e-7,1,10000],[2.5e-7,1,2000],[6e-8,1,100000],[5e-8,1,200000],[1e-8,4,640000],[0.00000111,1,-4847.13],[0.00000112,7,-528102.69042633],[0.00000113,5,-302397.07],[0.00000114,3,-339088.93],[0.00000126,4,-245944.06],[0.00000127,1,-5000],[0.0000013,1,-5000],[0.00000134,1,-8249.18938656],[0.00000136,1,-13161.25184766],[0.00000145,1,-2914],[0.0000015,3,-54448.5],[0.00000152,2,-5538.54849594],[0.00000153,1,-62691.75475079],[0.00000159,1,-2914],[0.0000016,1,-52631.10296831],[0.00000164,1,-4000],[0.00000166,1,-3831.46784605],[0.00000171,1,-14575.17730379],[0.00000174,1,-3124.81815395],[0.0000018,1,-18000],[0.00000182,1,-16000],[0.00000186,1,-4000],[0.00000189,1,-10000.686624],[0.00000191,1,-14500],[0.00000193,1,-2422]]]`) // publish new trade update async.Publish(`[81757,[0.0000011,12,266122.94]]`) // test that we can retrieve the orderbook ob, err_ob := ws.GetOrderbook("tXRPBTC") if err_ob != nil { t.Fatal(err_ob) } // test that changing the orderbook values will not invalidate the checksum // since they have been dereferenced ob.Bids()[0].Amount = 9999999 // publish new checksum pre := async.SentCount() async.Publish(`[81757,"cs",-1175357890]`) // test that the new trade has been added to the orderbook newTrade := ob.Bids()[0] // check that it has overwritten the original trade in the book at that price if newTrade.PriceJsNum.String() != "0.0000011" { t.Fatal("Newly submitted trade did not update into orderbook") } if newTrade.AmountJsNum.String() != "266122.94" { t.Fatal("Newly submitted trade did not update into orderbook") } // check that we did not send an unsubscribe message // because that would mean the checksum was incorrect if err_unsub := async.waitForMessage(pre); err_unsub != nil { // no message sent return } else { t.Fatal("A new unsubscribe message was sent") } } func TestCreateNewSocket(t *testing.T) { // create transport & nonce mocks async := newTestAsync() // create client p := websocket.NewDefaultParameters() // lock the capacity to 10 p.CapacityPerConnection = 10 p.ManageOrderbook = true ws := websocket.NewWithParamsAsyncFactory(p, newTestAsyncFactory(async)) // setup listener listener := newListener() listener.run(ws.Listen()) // set ws options err_ws := ws.Connect() if err_ws != nil { t.Fatal(err_ws) } defer ws.Close() // info welcome msg async.Publish(`{"event":"info","version":2}`) ev, err := listener.nextInfoEvent() if err != nil { t.Fatal(err) } assert(t, &websocket.InfoEvent{Version: 2}, ev) tickers := []string{"tBTCUSD", "tETHUSD", "tBTCUSD", "tVETUSD", "tDGBUSD", "tEOSUSD", "tTRXUSD", "tEOSETH", "tBTCETH", "tBTCEOS", "tXRPUSD", "tXRPBTC", "tTRXETH", "tTRXBTC", "tLTCUSD", "tLTCBTC", "tLTCETH"} for i, ticker := range tickers { id := i*10 // subscribe to 15m candles id1, err := ws.SubscribeCandles(context.Background(), ticker, bitfinex.FifteenMinutes) if err != nil { t.Fatal(err) } async.Publish(`{"event":"subscribed","channel":"candles","chanId":`+string(id)+`,"key":"trade:15m:`+ticker+`","subId":"`+id1+`"}`) // subscribe to 1hr candles id2, err := ws.SubscribeCandles(context.Background(), ticker, bitfinex.OneHour) if err != nil { t.Fatal(err) } // subscribe ack async.Publish(`{"event":"subscribed","channel":"candles","chanId":`+string(id+1)+`,"key":"trade:1hr:`+ticker+`","subId":"`+id2+`"}`) // subscribe to 30min candles id3, err := ws.SubscribeCandles(context.Background(), ticker, bitfinex.OneHour) if err != nil { t.Fatal(err) } // subscribe ack async.Publish(`{"event":"subscribed","channel":"candles","chanId":`+string(id+2)+`,"key":"trade:30m:`+ticker+`","subId":"`+id3+`"}`) } conCount := ws.ConnectionCount() if conCount != 6 { t.Fatal("Expected socket count to be 6 but got", conCount) } } bitfinex-api-go-2.2.9/tests/integration/v2/reconnect_test.go000066400000000000000000000307331371275744700241170ustar00rootroot00000000000000package tests import ( "context" "fmt" "log" "testing" "time" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" ) var ( wsPort = 4001 wsService *TestWsService apiRecv *listener apiClient *websocket.Client ) func assertDisconnect(maxWait time.Duration, client *websocket.Client) error { loops := 5 delay := maxWait / time.Duration(loops) for i := 0; i < loops; i++ { if !client.IsConnected() { return nil } time.Sleep(delay) } return fmt.Errorf("peer did not disconnect in %s", maxWait.String()) } // convienence func newTestParams(wsPort int) *websocket.Parameters { p := websocket.NewDefaultParameters() p.ShutdownTimeout = time.Second * 4 p.URL = fmt.Sprintf("ws://localhost:%d", wsPort) p.AutoReconnect = true p.CapacityPerConnection = 2000 p.HeartbeatTimeout = time.Millisecond * 10 p.ReconnectInterval = time.Millisecond * 500 // first reconnect is instant, won't need to wait on this return p } func setup(t *testing.T, hbTimeout time.Duration, autoReconnect, auth bool) { if wsService != nil { wsService.Stop() } if apiClient != nil { apiClient.Close() } time.Sleep(time.Millisecond * 250) wsService = NewTestWsService(wsPort) wsService.PublishOnConnect(`{"event":"info","version":2}`) err := wsService.Start() if err != nil { t.Fatal(err) } // create client params := newTestParams(wsPort) params.HeartbeatTimeout = hbTimeout params.AutoReconnect = autoReconnect factory := websocket.NewWebsocketAsynchronousFactory(params) nonce := &IncrementingNonceGenerator{} apiClient = websocket.NewWithParamsAsyncFactoryNonce(params, factory, nonce) if auth { apiClient.Credentials("apiKey1", "apiSecret1") } // setup listener // listener closes when apiClient is closed apiRecv = newListener() apiRecv.run(apiClient.Listen()) // set ws options err_con := apiClient.Connect() if err_con != nil { t.Fatal(err_con) } if err := wsService.WaitForClientCount(1); err != nil { t.Fatal(err) } } func TestReconnectResubscribeWithAuthBlah(t *testing.T) { // create transport & nonce mocks setup(t, time.Second*10, true, true) // begin test infoEv, err := apiRecv.nextInfoEvent() if err != nil { t.Fatal(err) } expInfoEv := websocket.InfoEvent{ Version: 2, } assert(t, &expInfoEv, infoEv) msg, err := wsService.WaitForMessage(0, 0) if err != nil { t.Fatal(err) } if `{"subId":"nonce1","event":"auth","apiKey":"apiKey1","authSig":"6e7e3ab737bac9d36b6c3170356c9483edb0079cb65a2afa81efa9a6b906e0c3aeb16b574a44073dff4c0f604adbdd7d","authPayload":"AUTHnonce1","authNonce":"nonce1"}` != msg { t.Fatalf("[1] did not expect to receive msg: %s", msg) } wsService.Broadcast(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce1","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) authEv, err := apiRecv.nextAuthEvent() if err != nil { t.Fatal(err) } expAuthEv := websocket.AuthEvent{ Event: "auth", Status: "OK", ChanID: 0, UserID: 1, SubID: "nonce1", AuthID: "valid-auth-guid", } assert(t, &expAuthEv, authEv) // subscriptions // trade sub _, err = apiClient.SubscribeTrades(context.Background(), "tBTCUSD") if err != nil { t.Fatal(err) } msg, err = wsService.WaitForMessage(0, 1) if err != nil { t.Fatal(err) } if `{"subId":"nonce2","event":"subscribe","channel":"trades","symbol":"tBTCUSD"}` != msg { t.Fatalf("[2] did not expect to receive: %s", msg) } wsService.Broadcast(`{"event":"subscribed","channel":"trades","chanId":5,"symbol":"tBTCUSD","subId":"nonce2","pair":"BTCUSD"}`) tradeSub, err := apiRecv.nextSubscriptionEvent() if err != nil { t.Fatal(err) } expTradeSub := websocket.SubscribeEvent{ Symbol: "tBTCUSD", SubID: "nonce2", Channel: "trades", } assert(t, &expTradeSub, tradeSub) // book sub _, err = apiClient.SubscribeBook(context.Background(), "tBTCUSD", bitfinex.Precision0, bitfinex.FrequencyRealtime, 25) if err != nil { t.Fatal(err) } msg, err = wsService.WaitForMessage(0, 2) if err != nil { t.Fatal(err) } if `{"subId":"nonce3","event":"subscribe","channel":"book","symbol":"tBTCUSD","prec":"P0","freq":"F0","len":"25"}` != msg { t.Fatalf("[3] did not expect to receive: %s", msg) } wsService.Broadcast(`{"event":"subscribed","channel":"book","chanId":8,"symbol":"tBTCUSD","subId":"nonce3","pair":"BTCUSD","prec":"P0","freq":"F0","len":"25"}`) bookSub, err := apiRecv.nextSubscriptionEvent() if err != nil { t.Fatal(err) } expBookSub := websocket.SubscribeEvent{ Symbol: "tBTCUSD", SubID: "nonce3", Channel: "book", Frequency: string(bitfinex.FrequencyRealtime), Precision: string(bitfinex.Precision0), } assert(t, &expBookSub, bookSub) // abrupt disconnect wsService.Stop() now := time.Now() // wait for client disconnect to start reconnect looping err = assertDisconnect(time.Second*20, apiClient) if err != nil { t.Fatal(err) } // nolint:megacheck diff := time.Now().Sub(now) t.Logf("client disconnect detected in %s", diff.String()) // recreate service wsService = NewTestWsService(wsPort) // fresh service, no clients if wsService.TotalClientCount() != 0 { t.Fatalf("total client count %d, expected non-zero", wsService.TotalClientCount()) } err_ws := wsService.Start() if err_ws != nil { t.Fatal(err_ws) } if err := wsService.WaitForClientCount(1); err != nil { t.Fatal(err) } wsService.Broadcast(`{"event":"info","version":2}`) infoEv, err = apiRecv.nextInfoEvent() if err != nil { t.Fatal(err) } assert(t, &expInfoEv, infoEv) // assert authentication again msg, err = wsService.WaitForMessage(0, 0) if err != nil { t.Fatal(err) } if `{"subId":"nonce4","event":"auth","apiKey":"apiKey1","authSig":"3e424670c0fa4dcb293eea38b9fe62cca49cacc595da01a493d6b9328517a5c940b22141fecf16f653c2662b298238f4","authPayload":"AUTHnonce4","authNonce":"nonce4"}` != msg { t.Fatalf("[4] did not expect to receive msg: %s", msg) } wsService.Broadcast(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce4","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) authEv, err = apiRecv.nextAuthEvent() if err != nil { t.Fatal(err) } expAuthEv = websocket.AuthEvent{ Event: "auth", Status: "OK", ChanID: 0, UserID: 1, SubID: "nonce4", AuthID: "valid-auth-guid", } assert(t, &expAuthEv, authEv) // ensure client automatically resubscribes msg, err = wsService.WaitForMessage(0, 1) if err != nil { t.Fatal(err) } if `{"subId":"nonce5","event":"subscribe","channel":"trades","symbol":"tBTCUSD"}` != msg { t.Fatalf("[6] did not expect to receive: %s", msg) } wsService.Broadcast(`{"event":"subscribed","channel":"trades","chanId":5,"symbol":"tBTCUSD","subId":"nonce5","pair":"BTCUSD"}`) tradeSub, err = apiRecv.nextSubscriptionEvent() if err != nil { t.Fatal(err) } expTradeSub = websocket.SubscribeEvent{ Symbol: "tBTCUSD", SubID: "nonce5", Channel: "trades", } assert(t, &expTradeSub, tradeSub) msg, err = wsService.WaitForMessage(0, 2) if err != nil { t.Fatal(err) } if `{"subId":"nonce6","event":"subscribe","channel":"book","symbol":"tBTCUSD","prec":"P0","freq":"F0","len":"25"}` != msg { t.Fatalf("[5] did not expect to receive: %s", msg) } wsService.Broadcast(`{"event":"subscribed","channel":"book","chanId":8,"symbol":"tBTCUSD","subId":"nonce6","pair":"BTCUSD","prec":"P0","freq":"F0","len":"25"}`) bookSub, err = apiRecv.nextSubscriptionEvent() if err != nil { t.Fatal(err) } expBookSub = websocket.SubscribeEvent{ Symbol: "tBTCUSD", SubID: "nonce6", Channel: "book", Frequency: string(bitfinex.FrequencyRealtime), Precision: string(bitfinex.Precision0), Len: "25", } assert(t, &expBookSub, bookSub) // API client thinks it's connected if !apiClient.IsConnected() { t.Fatal("not reconnected to websocket") } } func TestHeartbeatTimeoutNoReconnectBlah(t *testing.T) { // create transport & nonce mocks setup(t, time.Second, false, false) // begin test msg, err := apiRecv.nextInfoEvent() if err != nil { t.Fatal(err) } infoEv := websocket.InfoEvent{ Version: 2, } assert(t, &infoEv, msg) _, err = apiClient.SubscribeTicker(context.Background(), "tBTCUSD") if err != nil { t.Fatal(err) } wsService.Broadcast(`{"event":"subscribed","channel":"ticker","chanId":5,"symbol":"tBTCUSD","subId":"nonce1","pair":"BTCUSD"}`) // expect timeout channel heartbeat time.Sleep(time.Second * 2) if apiClient.IsConnected() { t.Fatal("API client still connected, expected heartbeat disconnect") } } // also tests resubscribes func TestHeartbeatTimeoutReconnectBlah(t *testing.T) { // create transport & nonce mocks setup(t, time.Second, true, false) // begin test // info msg automatically sends msg, err := apiRecv.nextInfoEvent() if err != nil { t.Fatal(err) } infoEv := websocket.InfoEvent{ Version: 2, } assert(t, &infoEv, msg) // use ticker sub to check for reconnect _, err = apiClient.SubscribeTicker(context.Background(), "tBTCUSD") if err != nil { t.Fatal(err) } m, err := wsService.WaitForMessage(0, 0) if err != nil { t.Fatal(err) } if `{"subId":"nonce1","event":"subscribe","channel":"ticker","symbol":"tBTCUSD"}` != m { t.Fatalf("[1] did not expect to receive: %s", m) } wsService.Broadcast(`{"event":"subscribed","channel":"ticker","chanId":5,"symbol":"tBTCUSD","subId":"nonce1","pair":"BTCUSD"}`) tickerSub, err := apiRecv.nextSubscriptionEvent() if err != nil { t.Fatal(err) } expTickerSub := websocket.SubscribeEvent{ Symbol: "tBTCUSD", SubID: "nonce1", Channel: "ticker", } assert(t, &expTickerSub, tickerSub) // expect timeout channel heartbeat time.Sleep(time.Second * 2) wsService.Broadcast(`{"event":"info","version":2}`) // begin test // info msg automatically sends _, err = apiRecv.nextInfoEvent() if err != nil { t.Fatal(err) } // check reconnect subscriptions m, err = wsService.WaitForMessage(0, 0) if err != nil { t.Fatal(err) } if `{"subId":"nonce2","event":"subscribe","channel":"ticker","symbol":"tBTCUSD"}` != m { t.Fatalf("[2] did not expect to receive: %s", m) } wsService.Broadcast(`{"event":"subscribed","channel":"ticker","chanId":5,"symbol":"tBTCUSD","subId":"nonce2","pair":"BTCUSD"}`) tickerSub, err = apiRecv.nextSubscriptionEvent() if err != nil { t.Fatal(err) } expTickerSub = websocket.SubscribeEvent{ Symbol: "tBTCUSD", SubID: "nonce2", Channel: "ticker", } assert(t, &expTickerSub, tickerSub) } func TestHeartbeatNoTimeoutDataBlah(t *testing.T) { // create transport & nonce mocks setup(t, time.Second, true, false) // begin test // info msg automatically sends msg, err := apiRecv.nextInfoEvent() if err != nil { t.Fatal(err) } infoEv := websocket.InfoEvent{ Version: 2, } assert(t, &infoEv, msg) // use ticker sub to check for reconnect _, err = apiClient.SubscribeTicker(context.Background(), "tBTCUSD") if err != nil { t.Fatal(err) } m, err := wsService.WaitForMessage(0, 0) if err != nil { t.Fatal(err) } if `{"subId":"nonce1","event":"subscribe","channel":"ticker","symbol":"tBTCUSD"}` != m { t.Fatalf("[1] did not expect to receive: %s", m) } wsService.Broadcast(`{"event":"subscribed","channel":"ticker","chanId":5,"symbol":"tBTCUSD","subId":"nonce1","pair":"BTCUSD"}`) tickerSub, err := apiRecv.nextSubscriptionEvent() if err != nil { t.Fatal(err) } expTickerSub := websocket.SubscribeEvent{ Symbol: "tBTCUSD", SubID: "nonce1", Channel: "ticker", } assert(t, &expTickerSub, tickerSub) // would normally timeout here, but we can publish data to prevent // the 1 second timeout for i := 0; i < 8; i++ { wsService.Broadcast(`[5,[14957,68.17328796,14958,55.29588132,-659,-0.0422,14971,53723.08813995,16494,14454]]`) time.Sleep(time.Millisecond * 250) } tick, err := apiRecv.nextTick() if err != nil { log.Fatal(err) } expTicker := bitfinex.Ticker{ Symbol: "tBTCUSD", Bid: 14957, BidSize: 68.17328796, Ask: 14958, AskSize: 55.29588132, DailyChange: -659, DailyChangePerc: -0.0422, LastPrice: 14971, Volume: 53723.08813995, High: 16494, Low: 14454, } assert(t, &expTicker, tick) if !apiClient.IsConnected() { t.Fatal("expected client connected, client has disconnected") } } bitfinex-api-go-2.2.9/tests/integration/v2/stress_test/000077500000000000000000000000001371275744700231255ustar00rootroot00000000000000bitfinex-api-go-2.2.9/tests/integration/v2/stress_test/main.go000066400000000000000000000106721371275744700244060ustar00rootroot00000000000000package main import ( "context" "flag" "fmt" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" "github.com/op/go-logging" "time" ) var symbols = []string{ "tBTCUSD", "tETHUSD", "tEOSUSD", "tVETUSD", "tDGBUSD", "tXRPBTC", "tTRXUSD", "tLEOUSD", "tLEOBTC", "tLEOUST", "tBTCUST", "tETHBTC", "tETHUST", "tXRPUSD", } func main() { useChannels := flag.Bool("channels", false, "subscribes to a lot of channels") useMultiplexor := flag.Bool("multiplexer", false, "subscribes/un-subscribes forcing ws connection re-shuffling") runTime := flag.Int64("time", 5, "runtime of the stress test in minutes") logMode := flag.String("log-level", "INFO", "level of logging. Can be INFO, DEBUG, WARN or ERROR.") flag.Parse() p := websocket.NewDefaultParameters() logger := logging.MustGetLogger("bfx-websocket") p.ManageOrderbook = true p.Logger = logger logLevel, err := logging.LogLevel(*logMode) if err != nil { panic(err) } logging.SetLevel(logLevel, "bfx-websocket") c := websocket.NewWithParams(p) err = c.Connect() if err != nil { panic(err) } quit := make(chan interface{}) if (*useMultiplexor) { go runMultiplexerStressTest(c, logger, quit) } else if (*useChannels) { go runChannelsStressTest(c, logger, quit) } else { fmt.Println("No flags set, use:") flag.PrintDefaults() } timeout := time.After(time.Minute * time.Duration(*runTime)) for { select { case msg := <- c.Listen(): switch msg.(type) { case error: logger.Error(msg) default: logger.Debugf("MSG RECV: %#v", msg) } case <- timeout: logger.Warningf("Test timeout of %d mins reached. Killing test.", *runTime) panic("time reached") case <-quit: return } } } func runChannelsStressTest(client *websocket.Client, log *logging.Logger, quit chan interface{}) { fmt.Println("Starting channel stress test...") for _, ticker := range symbols { log.Infof("Subscribing to trades (%s) (socketCount=%d)", ticker, client.ConnectionCount()) _, err := client.SubscribeTrades(context.Background(), ticker) if err != nil { panic(fmt.Sprintf("could not subscribe to trades: %s", err.Error())) } _, err = client.SubscribeCandles(context.Background(), ticker, bitfinex.FifteenMinutes) if err != nil { panic(fmt.Sprintf("could not subscribe to candles %s: %s", err.Error(), bitfinex.FifteenMinutes)) } _, err = client.SubscribeCandles(context.Background(), ticker, bitfinex.ThirtyMinutes) if err != nil { panic(fmt.Sprintf("could not subscribe to candles %s: %s", err.Error(), bitfinex.ThirtyMinutes)) } _, err = client.SubscribeCandles(context.Background(), ticker, bitfinex.OneHour) if err != nil { panic(fmt.Sprintf("could not subscribe to candles %s: %s", err.Error(), bitfinex.OneHour)) } _, err = client.SubscribeCandles(context.Background(), ticker, bitfinex.OneMinute) if err != nil { panic(fmt.Sprintf("could not subscribe to candles %s: %s", err.Error(), bitfinex.OneMinute)) } } } func runMultiplexerStressTest(client *websocket.Client, log *logging.Logger, quit chan interface{}) { fmt.Println("Starting multiplexer stress test...") for { subIds := make([]string, 0) for _, ticker := range symbols { log.Infof("Subscribing to trades (%s) (socketCount=%d)", ticker, client.ConnectionCount()) subId1, err := client.SubscribeTrades(context.Background(), ticker) if err != nil { panic(fmt.Sprintf("could not subscribe to trades: %s", err.Error())) } subIds = append(subIds, subId1) subId2, err := client.SubscribeCandles(context.Background(), ticker, bitfinex.FifteenMinutes) if err != nil { panic(fmt.Sprintf("could not subscribe to candles %s: %s", err.Error(), bitfinex.FifteenMinutes)) } subIds = append(subIds, subId2) subId3, err := client.SubscribeBook(context.Background(), ticker, bitfinex.Precision0, bitfinex.FrequencyRealtime, 25) if err != nil { panic(fmt.Sprintf("could not subscribe to candles %s: %s", err.Error(), bitfinex.FifteenMinutes)) } subIds = append(subIds, subId3) } // wait for a set amount of time before un-subscribing time.Sleep(time.Second * 10) // un-subscribe from all channels for _, subId := range subIds { log.Infof("Un-subscribing from (%s) (socketCount=%d)", subId, client.ConnectionCount()) err := client.Unsubscribe(context.Background(), subId) if err != nil { panic(fmt.Sprintf("Could not un-subscribe from channel %s", subId)) } } // wait again time.Sleep(time.Second * 10) } } bitfinex-api-go-2.2.9/tests/integration/v2/test_ws_service.go000066400000000000000000000116111371275744700243020ustar00rootroot00000000000000package tests import ( "bytes" "fmt" "log" "net" "net/http" "sync" "time" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } type client struct { parent *TestWsService *websocket.Conn send chan []byte received []string lock sync.Mutex } func (c *client) writePump() { for msg := range c.send { err := c.Conn.WriteMessage(websocket.TextMessage, msg) if err != nil { log.Printf("could not send message (%s) to client: %s", string(msg), err.Error()) continue } } } func (c *client) readPump() { defer func() { c.parent.unregister <- c c.Conn.Close() }() for { _, message, err := c.Conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { log.Printf("error: %v", err) } log.Printf("test ws service drop client: %s", err.Error()) return } message = bytes.TrimSpace(bytes.Replace(message, []byte("\n"), []byte(" "), -1)) c.lock.Lock() log.Printf("[DEBUG] WsClient -> WsService: %s", string(message)) c.received = append(c.received, string(message)) c.lock.Unlock() } } type TestWsService struct { clients map[*client]bool listener net.Listener port int register chan *client unregister chan *client broadcast chan []byte totalClients int lock *sync.RWMutex publishOnConnect string } func (s *TestWsService) WaitForClientCount(count int) error { loops := 80 delay := time.Millisecond * 50 for i := 0; i < loops; i++ { s.lock.RLock() if s.totalClients == count { return nil } s.lock.RUnlock() time.Sleep(delay) } return fmt.Errorf("client peer #%d did not connect", count) } func (s *TestWsService) TotalClientCount() int { return s.totalClients } func (s *TestWsService) PublishOnConnect(msg string) { s.publishOnConnect = msg } func NewTestWsService(port int) *TestWsService { return &TestWsService{ port: port, clients: make(map[*client]bool), register: make(chan *client), unregister: make(chan *client), broadcast: make(chan []byte), lock: &sync.RWMutex{}, } } // Broadcast sends a message to all connected clients. func (s *TestWsService) Broadcast(msg string) { s.broadcast <- []byte(msg) } // ReceivedCount starts indexing clients at position 0. func (s *TestWsService) ReceivedCount(clientNum int) int { i := 0 for client := range s.clients { if i == clientNum { client.lock.Lock() defer client.lock.Unlock() return len(client.received) } i++ } return 0 } // Received starts indexing clients and message positions at position 0. func (s *TestWsService) Received(clientNum int, msgNum int) (string, error) { var client *client i := 0 for client = range s.clients { if i == clientNum { break } i++ } if client != nil { client.lock.Lock() defer client.lock.Unlock() if len(client.received) > msgNum { return string(client.received[msgNum]), nil } return "", fmt.Errorf("could not find message index %d, %d messages exist", msgNum, len(client.received)) } return "", fmt.Errorf("could not find client %d", clientNum) } func (s *TestWsService) WaitForMessage(clientNum int, msgNum int) (string, error) { loops := 80 delay := time.Millisecond * 50 var msg string var err error for i := 0; i < loops; i++ { msg, err = s.Received(clientNum, msgNum) if err != nil { time.Sleep(delay) } else { return msg, nil } } return "", err } func (s *TestWsService) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.serveWs(w, r) } func (s *TestWsService) Stop() { //s.lock.RLock() //defer s.lock.RUnlock() s.listener.Close() // stop listening to http for c := range s.clients { c.Close() } } //nolint func (s *TestWsService) Start() error { go s.loop() l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.port)) if err != nil { return err } s.listener = l go http.Serve(s.listener, s) return nil } //nolint func (s *TestWsService) serveWs(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Print(err) return } s.totalClients++ client := &client{parent: s, Conn: conn, send: make(chan []byte, 256), received: make([]string, 0)} go client.writePump() go client.readPump() s.clients[client] = true if s.publishOnConnect != "" { s.Broadcast(s.publishOnConnect) } } func (s *TestWsService) loop() { for { select { case client := <-s.register: //s.lock.Lock() s.clients[client] = true //s.lock.Unlock() case client := <-s.unregister: if _, ok := s.clients[client]; ok { //s.lock.Lock() delete(s.clients, client) close(client.send) //s.lock.Unlock() } case msg := <-s.broadcast: for client := range s.clients { select { case client.send <- msg: default: // send failure //s.lock.Lock() close(client.send) delete(s.clients, client) //s.lock.Unlock() } } } } } bitfinex-api-go-2.2.9/utils/000077500000000000000000000000001371275744700156675ustar00rootroot00000000000000bitfinex-api-go-2.2.9/utils/nonce.go000066400000000000000000000022711371275744700173220ustar00rootroot00000000000000package utils import ( "strconv" "sync/atomic" "time" ) // v2 types type NonceGenerator interface { GetNonce() string } type EpochNonceGenerator struct { nonce uint64 } // GetNonce is a naive nonce producer that takes the current Unix nano epoch // and counts upwards. // This is a naive approach because the nonce bound to the currently used API // key and as such needs to be synchronised with other instances using the same // key in order to avoid race conditions. func (u *EpochNonceGenerator) GetNonce() string { return strconv.FormatUint(atomic.AddUint64(&u.nonce, 1), 10) } func NewEpochNonceGenerator() *EpochNonceGenerator { return &EpochNonceGenerator{ nonce: uint64(time.Now().Unix()) * 1000, } } // v1 support var nonce uint64 func init() { nonce = uint64(time.Now().UnixNano()) * 1000 } // GetNonce is a naive nonce producer that takes the current Unix nano epoch // and counts upwards. // This is a naive approach because the nonce bound to the currently used API // key and as such needs to be synchronised with other instances using the same // key in order to avoid race conditions. func GetNonce() string { return strconv.FormatUint(atomic.AddUint64(&nonce, 1), 10) } bitfinex-api-go-2.2.9/v1/000077500000000000000000000000001371275744700150555ustar00rootroot00000000000000bitfinex-api-go-2.2.9/v1/account.go000066400000000000000000000035331371275744700170440ustar00rootroot00000000000000package bitfinex type AccountService struct { client *Client } type AccountPairFee struct { Pair string MakerFees float64 `json:"maker_fees,string"` TakerFees float64 `json:"taker_fees,string"` } type AccountInfo struct { MakerFees float64 `json:"maker_fees,string"` TakerFees float64 `json:"taker_fees,string"` Fees []AccountPairFee } // GET account_infos func (a *AccountService) Info() (AccountInfo, error) { req, err := a.client.newAuthenticatedRequest("GET", "account_infos", nil) if err != nil { return AccountInfo{}, err } var v []AccountInfo _, err = a.client.do(req, &v) if err != nil { return AccountInfo{}, err } return v[0], nil } type KeyPerm struct { Read bool Write bool } type Permissions struct { Account KeyPerm History KeyPerm Orders KeyPerm Positions KeyPerm Funding KeyPerm Wallets KeyPerm Withdraw KeyPerm } func (a *AccountService) KeyPermission() (Permissions, error) { req, err := a.client.newAuthenticatedRequest("GET", "key_info", nil) if err != nil { return Permissions{}, err } var v Permissions _, err = a.client.do(req, &v) if err != nil { return Permissions{}, err } return v, nil } type SummaryVolume struct { Currency string `json:"curr"` Volume string `json:"vol"` } type SummaryProfit struct { Currency string `json:"curr"` Volume string `json:"amount"` } type Summary struct { TradeVolume SummaryVolume `json:"trade_vol_30d"` FundingProfit SummaryProfit `json:"funding_profit_30d"` MakerFee string `json:"maker_fee"` TakerFee string `json:"taker_fee"` } func (a *AccountService) Summary() (Summary, error) { req, err := a.client.newAuthenticatedRequest("GET", "summary", nil) if err != nil { return Summary{}, err } var v Summary _, err = a.client.do(req, &v) if err != nil { return Summary{}, err } return v, nil } bitfinex-api-go-2.2.9/v1/account_test.go000066400000000000000000000041601371275744700201000ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestAccountInfo(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "maker_fees":"0.1", "taker_fees":"0.2", "fees":[{ "pairs":"BTC", "maker_fees":"0.1", "taker_fees":"0.2" },{ "pairs":"LTC", "maker_fees":"0.1", "taker_fees":"0.2" },{ "pairs":"ETH", "maker_fees":"0.1", "taker_fees":"0.2" }] }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } info, err := NewClient().Account.Info() if err != nil { t.Error(err) } if len(info.Fees) != 3 { t.Error("Expected", 3) t.Error("Actual ", len(info.Fees)) } } func TestAccountKeyPermission(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{ "account":{ "read":true, "write":false }, "history":{ "read":true, "write":false }, "orders":{ "read":true, "write":true }, "positions":{ "read":true, "write":true }, "funding":{ "read":true, "write":true }, "wallets":{ "read":true, "write":true }, "withdraw":{ "read":null, "write":null } }` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } perm, err := NewClient().Account.KeyPermission() if err != nil { t.Error(err) } if !perm.Account.Read { t.Error("Expected", true) t.Error("Actual ", perm.Account.Read) } if perm.History.Write { t.Error("Expected", false) t.Error("Actual ", perm.History.Write) } } bitfinex-api-go-2.2.9/v1/balances.go000066400000000000000000000007551371275744700171630ustar00rootroot00000000000000package bitfinex type BalancesService struct { client *Client } type WalletBalance struct { Type string Currency string Amount string Available string } // GET balances func (b *BalancesService) All() ([]WalletBalance, error) { req, err := b.client.newAuthenticatedRequest("GET", "balances", nil) if err != nil { return nil, err } balances := make([]WalletBalance, 3) _, err = b.client.do(req, &balances) if err != nil { return nil, err } return balances, nil } bitfinex-api-go-2.2.9/v1/client.go000066400000000000000000000136641371275744700166740ustar00rootroot00000000000000// Package bitfinex is the official client to access to bitfinex.com API package bitfinex import ( "crypto/hmac" "crypto/sha512" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "github.com/bitfinexcom/bitfinex-api-go/utils" ) const ( // BaseURL is the v1 REST endpoint. BaseURL = "https://api.bitfinex.com/v1/" // WebSocketURL the v1 Websocket endpoint. WebSocketURL = "wss://api-pub.bitfinex.com/ws/" ) // Client manages all the communication with the Bitfinex API. type Client struct { // Base URL for API requests. BaseURL *url.URL WebSocketURL string WebSocketTLSSkipVerify bool // Auth data APIKey string APISecret string // Services Pairs *PairsService Stats *StatsService Ticker *TickerService Account *AccountService Balances *BalancesService Offers *OffersService Credits *CreditsService Deposit *DepositService Lendbook *LendbookService MarginInfo *MarginInfoService MarginFunding *MarginFundingService OrderBook *OrderBookService Orders *OrderService Trades *TradesService Positions *PositionsService History *HistoryService WebSocket *WebSocketService Wallet *WalletService } // NewClient creates new Bitfinex.com API client. func NewClient() *Client { baseURL, _ := url.Parse(BaseURL) c := &Client{BaseURL: baseURL, WebSocketURL: WebSocketURL} c.Pairs = &PairsService{client: c} c.Stats = &StatsService{client: c} c.Account = &AccountService{client: c} c.Ticker = &TickerService{client: c} c.Balances = &BalancesService{client: c} c.Offers = &OffersService{client: c} c.Credits = &CreditsService{client: c} c.Deposit = &DepositService{client: c} c.Lendbook = &LendbookService{client: c} c.MarginInfo = &MarginInfoService{client: c} c.MarginFunding = &MarginFundingService{client: c} c.OrderBook = &OrderBookService{client: c} c.Orders = &OrderService{client: c} c.History = &HistoryService{client: c} c.Trades = &TradesService{client: c} c.Positions = &PositionsService{client: c} c.Wallet = &WalletService{client: c} c.WebSocket = NewWebSocketService(c) c.WebSocketTLSSkipVerify = false return c } // NewRequest create new API request. Relative url can be provided in refURL. func (c *Client) newRequest(method string, refURL string, params url.Values) (*http.Request, error) { rel, err := url.Parse(refURL) if err != nil { return nil, err } if params != nil { rel.RawQuery = params.Encode() } var req *http.Request u := c.BaseURL.ResolveReference(rel) req, err = http.NewRequest(method, u.String(), nil) if err != nil { return nil, err } return req, nil } // newAuthenticatedRequest creates new http request for authenticated routes. func (c *Client) newAuthenticatedRequest(m string, refURL string, data map[string]interface{}) (*http.Request, error) { req, err := c.newRequest(m, refURL, nil) if err != nil { return nil, err } nonce := utils.GetNonce() payload := map[string]interface{}{ "request": "/v1/" + refURL, "nonce": nonce, } for k, v := range data { payload[k] = v } p, err := json.Marshal(payload) if err != nil { return nil, err } encoded := base64.StdEncoding.EncodeToString(p) req.Header.Add("Content-Type", "application/json") req.Header.Add("Accept", "application/json") req.Header.Add("X-BFX-APIKEY", c.APIKey) req.Header.Add("X-BFX-PAYLOAD", encoded) sig, err := c.signPayload(encoded) if err != nil { return nil, err } req.Header.Add("X-BFX-SIGNATURE", sig) return req, nil } func (c *Client) signPayload(payload string) (string, error) { sig := hmac.New(sha512.New384, []byte(c.APISecret)) _, err := sig.Write([]byte(payload)) if err != nil { return "", err } return hex.EncodeToString(sig.Sum(nil)), nil } // Auth sets api key and secret for usage is requests that requires authentication. func (c *Client) Auth(key string, secret string) *Client { c.APIKey = key c.APISecret = secret return c } var httpDo = func(req *http.Request) (*http.Response, error) { return http.DefaultClient.Do(req) } // Do executes API request created by NewRequest method or custom *http.Request. func (c *Client) do(req *http.Request, v interface{}) (*Response, error) { resp, err := httpDo(req) if err != nil { return nil, err } defer resp.Body.Close() response := newResponse(resp) err = checkResponse(response) if err != nil { // Return response in case caller need to debug it. return response, err } if v != nil { err = json.Unmarshal(response.Body, v) if err != nil { return response, err } } return response, nil } // Response is wrapper for standard http.Response and provides // more methods. type Response struct { Response *http.Response Body []byte } // newResponse creates new wrapper. func newResponse(r *http.Response) *Response { body, err := ioutil.ReadAll(r.Body) if err != nil { body = []byte(`Error reading body:` + err.Error()) } return &Response{r, body} } // String converts response body to string. // An empty string will be returned if error. func (r *Response) String() string { return string(r.Body) } // ErrorResponse is the custom error type that is returned if the API returns an // error. type ErrorResponse struct { Response *Response Message string `json:"message"` } func (r *ErrorResponse) Error() string { return fmt.Sprintf("%v %v: %d %v", r.Response.Response.Request.Method, r.Response.Response.Request.URL, r.Response.Response.StatusCode, r.Message, ) } // checkResponse checks response status code and response // for errors. func checkResponse(r *Response) error { if c := r.Response.StatusCode; 200 <= c && c <= 299 { return nil } // Try to decode error message errorResponse := &ErrorResponse{Response: r} err := json.Unmarshal(r.Body, errorResponse) if err != nil { errorResponse.Message = "Error decoding response error message. " + "Please see response body for more information." } return errorResponse } bitfinex-api-go-2.2.9/v1/credits.go000066400000000000000000000010701371275744700170370ustar00rootroot00000000000000package bitfinex type CreditsService struct { client *Client } type Credit struct { Id int Currency string Status string Rate float64 `json:",string"` Period float64 Amount float64 `json:",string"` Timestamp string } // Returns an array of Credit func (c *CreditsService) All() ([]Credit, error) { req, err := c.client.newAuthenticatedRequest("GET", "credits", nil) if err != nil { return nil, err } credits := make([]Credit, 0) _, err = c.client.do(req, &credits) if err != nil { return nil, err } return credits, nil } bitfinex-api-go-2.2.9/v1/deposit.go000066400000000000000000000014631371275744700170570ustar00rootroot00000000000000package bitfinex import "errors" type DepositService struct { client *Client } type DepositResponse struct { Result string Method string Currency string Address string } func (d *DepositResponse) Success() (bool, error) { if d.Result == "success" { return true, nil } else { err := errors.New(d.Address) return false, err } } func (s *DepositService) New(method, walletName string, renew int) (DepositResponse, error) { payload := map[string]interface{}{ "method": method, "wallet_name": walletName, "renew": renew, } req, err := s.client.newAuthenticatedRequest("POST", "deposit/new", payload) if err != nil { return DepositResponse{}, err } var v DepositResponse _, err = s.client.do(req, &v) if err != nil { return DepositResponse{}, err } return v, nil } bitfinex-api-go-2.2.9/v1/deposit_test.go000066400000000000000000000013461371275744700201160ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestDepositNew(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{ "result":"success", "method":"bitcoin", "currency":"BTC", "address":"1A2wyHKJ4KWEoahDHVxwQy3kdd6g1qiSYV" }` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } deposit, err := NewClient().Deposit.New("bitcoin", "trading", 0) if err != nil { t.Error(err) } success, err := deposit.Success() if err != nil || !success { t.Error("Expected", true) t.Error("Actual ", success) t.Error("With message", err) } } bitfinex-api-go-2.2.9/v1/doc.go000066400000000000000000000007601371275744700161540ustar00rootroot00000000000000package bitfinex // Package bitfinex-api-go provides structs and functions for accessing // bitfinex.com api version 1.0 // // Usage: // import "github.com/bitfinexcom/bitfinex-api-go" // // Create new client: // api := bitfinex.NewClient() // // For access methods that requires authentication use the next code: // api := bitfinex.NewClient().Auth(key, secret) // // Get all pairs // api.Pairs.All() // // Get account info // api.Account.Info() // // See examples dir for more info. bitfinex-api-go-2.2.9/v1/history.go000066400000000000000000000046051371275744700171120ustar00rootroot00000000000000package bitfinex import "time" type HistoryService struct { client *Client } type Balance struct { Currency string Amount string Balance string Description string Timestamp string } func (s *HistoryService) Balance(currency, wallet string, since, until time.Time, limit int) ([]Balance, error) { payload := map[string]interface{}{"currency": currency} if !since.IsZero() { payload["since"] = since.Unix() } if !until.IsZero() { payload["until"] = until.Unix() } if limit != 0 { payload["limit"] = limit } req, err := s.client.newAuthenticatedRequest("POST", "history", payload) if err != nil { return nil, err } var v []Balance _, err = s.client.do(req, &v) if err != nil { return nil, err } return v, nil } type Movement struct { ID int64 `json:",int"` Currency string Method string Type string Amount string Description string Status string Timestamp string } func (s *HistoryService) Movements(currency, method string, since, until time.Time, limit int) ([]Movement, error) { payload := map[string]interface{}{"currency": currency, "method": method} if !since.IsZero() { payload["since"] = since.Unix() } if !until.IsZero() { payload["until"] = until.Unix() } if limit != 0 { payload["limit"] = limit } req, err := s.client.newAuthenticatedRequest("POST", "history/movements", payload) if err != nil { return nil, err } var v []Movement _, err = s.client.do(req, &v) if err != nil { return nil, err } return v, nil } type PastTrade struct { Price string Amount string Timestamp string Exchange string Type string FeeCurrency string `json:"fee_currency"` FeeAmount string `json:"fee_amount"` TID int64 OrderId int64 `json:"order_id,int"` } func (s *HistoryService) Trades(pair string, since, until time.Time, limit int, reverse bool) ([]PastTrade, error) { payload := map[string]interface{}{"symbol": pair} if !since.IsZero() { payload["timestamp"] = since.Unix() } if !until.IsZero() { payload["until"] = until.Unix() } if limit != 0 { payload["limit_trades"] = limit } if reverse { payload["reverse"] = 1 } req, err := s.client.newAuthenticatedRequest("POST", "mytrades", payload) if err != nil { return nil, err } var v []PastTrade _, err = s.client.do(req, &v) if err != nil { return nil, err } return v, nil } bitfinex-api-go-2.2.9/v1/history_test.go000066400000000000000000000045471371275744700201560ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" "time" ) func TestHistoryBalance(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "currency":"USD", "amount":"-246.94", "balance":"515.4476526", "description":"Position claimed @ 245.2 on wallet trading", "timestamp":"1444277602.0" }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } balance, err := NewClient().History.Balance("BTC", "", time.Time{}, time.Time{}, 0) if err != nil { t.Error(err) } if len(balance) != 1 { t.Error("Expected", 1) t.Error("Actual ", len(balance)) } } func TestHistoryMovements(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "id":581183, "currency":"BTC", "method":"BITCOIN", "type":"WITHDRAWAL", "amount":".01", "description":"3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ, offchain transfer ", "status":"COMPLETED", "timestamp":"1443833327.0" }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } movements, err := NewClient().History.Movements("BTC", "", time.Time{}, time.Time{}, 0) if err != nil { t.Error(err) } if len(movements) != 1 { t.Error("Expected", 1) t.Error("Actual ", len(movements)) } } func TestHistoryTrades(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "price":"246.94", "amount":"1.0", "timestamp":"1444141857.0", "exchange":"", "type":"Buy", "fee_currency":"USD", "fee_amount":"-0.49388", "tid":11970839, "order_id":446913929 }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } trades, err := NewClient().History.Trades("BTC", time.Time{}, time.Time{}, 0, false) if err != nil { t.Error(err) } if len(trades) != 1 { t.Error("Expected", 1) t.Error("Actual ", len(trades)) } if trades[0].TID != 11970839 { t.Error("Expected", 11970839) t.Error("Actual ", trades[0].TID) } } bitfinex-api-go-2.2.9/v1/lendbook.go000066400000000000000000000030631371275744700172030ustar00rootroot00000000000000package bitfinex import ( "net/url" "strconv" "strings" "time" ) type LendbookService struct { client *Client } type Lend struct { Rate string Amount string Period int Timestamp string Frr string } func (el *Lend) ParseTime() (*time.Time, error) { i, err := strconv.ParseFloat(el.Timestamp, 64) if err != nil { return nil, err } t := time.Unix(int64(i), 0) return &t, nil } type Lendbook struct { Bids []Lend Asks []Lend } // GET /lendbook/:currency func (s *LendbookService) Get(currency string, limitBids, limitAsks int) (Lendbook, error) { currency = strings.ToUpper(currency) params := url.Values{} if limitBids != 0 { params.Add("limit_bids", strconv.Itoa(limitBids)) } if limitAsks != 0 { params.Add("limit_asks", strconv.Itoa(limitAsks)) } req, err := s.client.newRequest("GET", "lendbook/"+currency, params) if err != nil { return Lendbook{}, err } var v Lendbook _, err = s.client.do(req, &v) if err != nil { return Lendbook{}, err } return v, nil } type Lends struct { Rate string AmountLent string `json:"amount_lent"` AmountUsed string `json:"amount_used"` Timestamp int64 } func (el *Lends) Time() *time.Time { t := time.Unix(el.Timestamp, 0) return &t } // GET /lends/:currency func (s *LendbookService) Lends(currency string) ([]Lends, error) { currency = strings.ToUpper(currency) req, err := s.client.newRequest("GET", "lends/"+currency, nil) if err != nil { return nil, err } var v []Lends _, err = s.client.do(req, &v) if err != nil { return nil, err } return v, nil } bitfinex-api-go-2.2.9/v1/lendbook_test.go000066400000000000000000000031011371275744700202330ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestLendbookGet(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{ "bids":[{ "rate":"9.1287", "amount":"5000.0", "period":30, "timestamp":"1444257541.0", "frr":"No" }], "asks":[{ "rate":"8.3695", "amount":"407.5", "period":2, "timestamp":"1444260343.0", "frr":"No" }] }` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } book, err := NewClient().Lendbook.Get("usd", 0, 0) if err != nil { t.Error(err) } if len(book.Bids) != 1 { t.Error("Expected", 1) t.Error("Actual ", len(book.Bids)) } if book.Bids[0].Period != 30 { t.Error("Expected", 30) t.Error("Actual ", book.Bids[0].Period) } } func TestLendbookLends(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "rate":"9.8998", "amount_lent":"22528933.77950878", "amount_used":"0.0", "timestamp":1444264307 }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } lends, err := NewClient().Lendbook.Lends("usd") if err != nil { t.Error(err) } if len(lends) != 1 { t.Error("Expected", 1) t.Error("Actual ", len(lends)) } } bitfinex-api-go-2.2.9/v1/margin_funding.go000066400000000000000000000061471371275744700204030ustar00rootroot00000000000000package bitfinex import "strconv" type MarginFundingService struct { client *Client } type MarginOffer struct { ID int64 Currency string Rate string Period int Direction string Timestamp string IsLive bool `json:"is_live"` IsCancelled bool `json:"is_cancelled"` OriginalAmount string `json:"original_amount"` RemainingAmount string `json:"remaining_amount"` ExecutedAmount string `json:"executed_amount"` OfferId int } func (s *MarginFundingService) new(currency, direction string, amount, rate float64, period int) (MarginOffer, error) { payload := map[string]interface{}{ "currency": currency, "amount": strconv.FormatFloat(amount, 'f', -1, 32), "rate": strconv.FormatFloat(rate, 'f', -1, 32), "period": period, "direction": direction, } req, err := s.client.newAuthenticatedRequest("POST", "offer/new", payload) if err != nil { return MarginOffer{}, err } var v MarginOffer _, err = s.client.do(req, &v) if err != nil { return MarginOffer{}, err } return v, nil } func (s *MarginFundingService) NewLend(currency string, amount, rate float64, period int) (MarginOffer, error) { return s.new(currency, "lend", amount, rate, period) } func (s *MarginFundingService) NewLoan(currency string, amount, rate float64, period int) (MarginOffer, error) { return s.new(currency, "loan", amount, rate, period) } func (s *MarginFundingService) Cancel(offerId int64) (MarginOffer, error) { payload := map[string]interface{}{"offer_id": offerId} req, err := s.client.newAuthenticatedRequest("POST", "offer/cancel", payload) if err != nil { return MarginOffer{}, err } var v MarginOffer _, err = s.client.do(req, &v) if err != nil { return MarginOffer{}, err } return v, nil } func (s *MarginFundingService) Status(offerId int64) (MarginOffer, error) { payload := map[string]interface{}{"offer_id": offerId} req, err := s.client.newAuthenticatedRequest("POST", "offer/status", payload) if err != nil { return MarginOffer{}, err } var v MarginOffer _, err = s.client.do(req, &v) if err != nil { return MarginOffer{}, err } return v, nil } type ActiveOffer struct { ID int64 Currency string Rate string Period int Direction string Timestamp string IsLive bool `json:"is_live"` IsCancelled bool `json:"is_cancelled"` OriginalAmount string `json:"original_amount"` RemainingAmount string `json:"remaining_amount"` ExecutedAmount string `json:"executed_amount"` } func (s *MarginFundingService) Credits() ([]ActiveOffer, error) { req, err := s.client.newAuthenticatedRequest("POST", "credits", nil) if err != nil { return nil, err } var v []ActiveOffer _, err = s.client.do(req, &v) if err != nil { return nil, err } return v, nil } func (s *MarginFundingService) Offers() ([]ActiveOffer, error) { req, err := s.client.newAuthenticatedRequest("POST", "offers", nil) if err != nil { return nil, err } var v []ActiveOffer _, err = s.client.do(req, &v) if err != nil { return nil, err } return v, nil } bitfinex-api-go-2.2.9/v1/margin_funding_test.go000066400000000000000000000017771371275744700214460ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestMarginFundingNew(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{ "id":13800585, "currency":"USD", "rate":"20.0", "period":2, "direction":"lend", "timestamp":"1444279698.21175971", "is_live":true, "is_cancelled":false, "original_amount":"50.0", "remaining_amount":"50.0", "executed_amount":"0.0", "offer_id":13800585 }` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } offer, err := NewClient().MarginFunding.new("BTC", "loan", 10.0, 0.01, 10) if err != nil { t.Error(err) } if offer.ID != 13800585 { t.Error("Expected", 13800585) t.Error("Actual ", offer.ID) } if !offer.IsLive { t.Error("Expected", true) t.Error("Actual ", offer.IsLive) } } bitfinex-api-go-2.2.9/v1/margin_info.go000066400000000000000000000023201371275744700176710ustar00rootroot00000000000000package bitfinex type MarginInfoService struct { client *Client } type MarginInfo struct { MarginBalance float64 `json:"margin_balance,string"` TradableBalance float64 `json:"tradable_balance,string"` UnrealizedPl float64 `json:"unrealized_pl,string"` UnrealizedSwap float64 `json:"unrealized_swap,string"` NetValue float64 `json:"net_value,string"` RequiredMargin float64 `json:"required_margin,string"` Leverage float64 `json:"leverage,string"` MarginRequirement float64 `json:"margin_requirement,string"` MarginLimits []MarginLimit `json:"margin_limits,string"` Message string `json:"message"` } type MarginLimit struct { OnPair string `json:"on_pair"` InitialMargin float64 `json:"initial_margin,string"` MarginRequirement float64 `json:"margin_requirement,string"` TradableBalance float64 `json:"tradable_balance,string"` } // GET /margin_infos func (s *MarginInfoService) All() ([]MarginInfo, error) { req, err := s.client.newAuthenticatedRequest("GET", "margin_infos", nil) if err != nil { return nil, err } var v []MarginInfo _, err = s.client.do(req, &v) return v, err } bitfinex-api-go-2.2.9/v1/margin_info_test.go000066400000000000000000000022371371275744700207370ustar00rootroot00000000000000package bitfinex import ( "encoding/json" "testing" ) func TestMarginInfo_Unmarshal(t *testing.T) { // data from an actual Bitfinex's response data := []byte(`[{"margin_balance":"3168.43615333","tradable_balance":"-4105.85460135","unrealized_pl":"-174.0072","unrealized_swap":"-3.77879387","net_value":"2990.65015946","required_margin":"1344.0","leverage":"2.5","margin_requirement":"13.0","margin_limits":[{"on_pair":"BTCUSD","initial_margin":"30.0","margin_requirement":"15.0","tradable_balance":"-2544.520981132333333333"},{"on_pair":"LTCUSD","initial_margin":"30.0","margin_requirement":"15.0","tradable_balance":"-2544.520981132333333333"},{"on_pair":"LTCBTC","initial_margin":"30.0","margin_requirement":"15.0","tradable_balance":"-1634.516821132333333333"}],"message":"Margin requirement, leverage and tradable balance are now per pair. Values displayed in the root of the JSON message are incorrect (deprecated). You will find the correct ones under margin_limits, for each pair. Please update your code as soon as possible."}]`) var v []MarginInfo err := json.Unmarshal(data, &v) if err != nil { t.Fatalf("Failed unmarshaling MarginInfo: %s", err.Error()) } } bitfinex-api-go-2.2.9/v1/offers.go000066400000000000000000000040421371275744700166700ustar00rootroot00000000000000package bitfinex import "strconv" type OffersService struct { client *Client } const ( LEND = "lend" LOAN = "loan" ) type Offer struct { Id int64 Currency string Rate string Period int64 Direction string Timestamp string IsLive bool `json:"is_live"` IsCancelled bool `json:"is_cancelled"` OriginalAmount string `json:"original_amount:string"` RemainingAmount string `json:"remaining_amount:string"` ExecutedAmount string `json:"executed_amount:string"` OfferId int64 `json:"offer_id"` } // Create new offer for LEND or LOAN a currency, use LEND or LOAN constants as direction func (s *OffersService) New(currency string, amount, rate float64, period int64, direction string) (Offer, error) { payload := map[string]interface{}{ "currency": currency, "amount": strconv.FormatFloat(amount, 'f', -1, 32), "rate": strconv.FormatFloat(rate, 'f', -1, 32), "period": strconv.FormatInt(period, 10), "direction": direction, } req, err := s.client.newAuthenticatedRequest("POST", "offers/new", payload) if err != nil { return Offer{}, err } var offer = &Offer{} _, err = s.client.do(req, offer) if err != nil { return Offer{}, err } return *offer, nil } func (s *OffersService) Cancel(offerId int64) (Offer, error) { payload := map[string]interface{}{ "offer_id": strconv.FormatInt(offerId, 10), } req, err := s.client.newAuthenticatedRequest("POST", "offers/cancel", payload) if err != nil { return Offer{}, err } var offer = &Offer{} _, err = s.client.do(req, offer) if err != nil { return Offer{}, err } return *offer, nil } func (s *OffersService) Status(offerId int64) (Offer, error) { payload := map[string]interface{}{ "offer_id": strconv.FormatInt(offerId, 10), } req, err := s.client.newAuthenticatedRequest("POST", "offers/status", payload) if err != nil { return Offer{}, err } var offer = &Offer{} _, err = s.client.do(req, offer) if err != nil { return Offer{}, err } return *offer, nil } bitfinex-api-go-2.2.9/v1/offers_test.go000066400000000000000000000027621371275744700177360ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestOfferNew(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{ "id":13800585, "currency":"USD", "rate":"20.0", "period":2, "direction":"lend", "timestamp":"1444279698.21175971", "is_live":true, "is_cancelled":true, "original_amount":"50.0", "remaining_amount":"50.0", "executed_amount":"0.0", "offer_id":13800585 }` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } offer, err := NewClient().Offers.New("USD", 50, 20.0, 2, LEND) if err != nil { t.Error(err) } if offer.Currency != "USD" { t.Error("Expected", "USD") t.Error("Actual ", offer.Currency) } expectedId := int64(13800585) if offer.OfferId != expectedId { t.Error("Expected", expectedId) t.Error("Actual ", offer.OfferId) } if offer.Id != expectedId { t.Error("Expected", expectedId) t.Error("Actual ", offer.Id) } if !offer.IsLive { t.Error("Expected", true) t.Error("Actual ", offer.IsLive) } newOffer, err := NewClient().Offers.Cancel(offer.Id) if err != nil { t.Error(err) } if newOffer.Currency != "USD" { t.Error("Expected", "USD") t.Error("Actual ", newOffer.Currency) } if !newOffer.IsCancelled { t.Error("Expected", true) t.Error("Actual ", newOffer.IsCancelled) } } bitfinex-api-go-2.2.9/v1/order_book.go000066400000000000000000000021361371275744700175330ustar00rootroot00000000000000package bitfinex import ( "net/url" "strconv" "strings" "time" ) type OrderBookService struct { client *Client } type OrderBookEntry struct { Price string Rate string Amount string Period int Timestamp string Frr string } type OrderBook struct { Bids []OrderBookEntry Asks []OrderBookEntry } func (el *OrderBookEntry) ParseTime() (*time.Time, error) { i, err := strconv.ParseFloat(el.Timestamp, 64) if err != nil { return nil, err } t := time.Unix(int64(i), 0) return &t, nil } // GET /book func (s *OrderBookService) Get(pair string, limitBids, limitAsks int, noGroup bool) (OrderBook, error) { pair = strings.ToUpper(pair) params := url.Values{} if limitBids != 0 { params.Add("limit_bids", strconv.Itoa(limitBids)) } if limitAsks != 0 { params.Add("limit_asks", strconv.Itoa(limitAsks)) } if noGroup { params.Add("group", "0") } req, err := s.client.newRequest("GET", "book/"+pair, params) if err != nil { return OrderBook{}, err } var v OrderBook _, err = s.client.do(req, &v) if err != nil { return OrderBook{}, err } return v, nil } bitfinex-api-go-2.2.9/v1/order_book_test.go000066400000000000000000000015661371275744700206000ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestOrderBookGet(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{ "bids":[{ "rate":"9.1287", "amount":"5000.0", "period":30, "timestamp":"1444257541.0", "frr":"No" }], "asks":[{ "rate":"8.3695", "amount":"407.5", "period":2, "timestamp":"1444260343.0", "frr":"No" }] }` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } orderBook, err := NewClient().OrderBook.Get("btcusd", 0, 0, false) if err != nil { t.Error(err) } if len(orderBook.Bids) != 1 { t.Error("Expected", 1) t.Error("Actual ", len(orderBook.Bids)) } } bitfinex-api-go-2.2.9/v1/orders.go000066400000000000000000000135651371275744700167140ustar00rootroot00000000000000package bitfinex import ( "math" "strconv" ) // Order types that the API can return. const ( OrderTypeMarket = "market" OrderTypeLimit = "limit" OrderTypeStop = "stop" OrderTypeTrailingStop = "trailing-stop" OrderTypeFillOrKill = "fill-or-kill" OrderTypeExchangeMarket = "exchange market" OrderTypeExchangeLimit = "exchange limit" OrderTypeExchangeStop = "exchange stop" OrderTypeExchangeTrailingStop = "exchange trailing-stop" OrderTypeExchangeFillOrKill = "exchange fill-or-kill" ) // OrderService manages the Order endpoint. type OrderService struct { client *Client } // Order represents one order on the bitfinex platform. type Order struct { ID int64 Symbol string Exchange string Price string AvgExecutionPrice string `json:"avg_execution_price"` Side string Type string Timestamp string IsLive bool `json:"is_live"` IsCanceled bool `json:"is_cancelled"` IsHidden bool `json:"is_hidden"` WasForced bool `json:"was_forced"` OriginalAmount string `json:"original_amount"` RemainingAmount string `json:"remaining_amount"` ExecutedAmount string `json:"executed_amount"` } // All returns all orders for the authenticated account. func (s *OrderService) All() ([]Order, error) { req, err := s.client.newAuthenticatedRequest("GET", "orders", nil) if err != nil { return nil, err } v := []Order{} _, err = s.client.do(req, &v) if err != nil { return nil, err } return v, nil } // CancelAll active orders for the authenticated account. func (s *OrderService) CancelAll() error { req, err := s.client.newAuthenticatedRequest("POST", "order/cancel/all", nil) if err != nil { return err } _, err = s.client.do(req, nil) if err != nil { return err } return nil } // Create a new order. func (s *OrderService) Create(symbol string, amount float64, price float64, orderType string) (*Order, error) { var side string if amount < 0 { amount = math.Abs(amount) side = "sell" } else { side = "buy" } payload := map[string]interface{}{ "symbol": symbol, "amount": strconv.FormatFloat(amount, 'f', -1, 32), "price": strconv.FormatFloat(price, 'f', -1, 32), "side": side, "type": orderType, "exchange": "bitfinex", } req, err := s.client.newAuthenticatedRequest("POST", "order/new", payload) if err != nil { return nil, err } order := new(Order) _, err = s.client.do(req, order) if err != nil { return nil, err } return order, nil } // Cancel the order with id `orderID`. func (s *OrderService) Cancel(orderID int64) error { payload := map[string]interface{}{ "order_id": orderID, } req, err := s.client.newAuthenticatedRequest("POST", "order/cancel", payload) if err != nil { return err } _, err = s.client.do(req, nil) if err != nil { return err } return nil } // SubmitOrder is an order to be created on the bitfinex platform. type SubmitOrder struct { Symbol string Amount float64 Price float64 Type string } // MultipleOrderResponse bundles orders returned by the CreateMulti method. type MultipleOrderResponse struct { Orders []Order `json:"order_ids"` Status string } // CreateMulti allows batch creation of orders. func (s *OrderService) CreateMulti(orders []SubmitOrder) (MultipleOrderResponse, error) { ordersMap := make([]interface{}, 0) for _, order := range orders { var side string if order.Amount < 0 { order.Amount = math.Abs(order.Amount) side = "sell" } else { side = "buy" } ordersMap = append(ordersMap, map[string]interface{}{ "symbol": order.Symbol, "amount": strconv.FormatFloat(order.Amount, 'f', -1, 32), "price": strconv.FormatFloat(order.Price, 'f', -1, 32), "exchange": "bitfinex", "side": side, "type": order.Type, }) } payload := map[string]interface{}{ "orders": ordersMap, } req, err := s.client.newAuthenticatedRequest("POST", "order/new/multi", payload) if err != nil { return MultipleOrderResponse{}, err } response := new(MultipleOrderResponse) _, err = s.client.do(req, response) return *response, err } // CancelMulti allows batch cancellation of orders. func (s *OrderService) CancelMulti(orderIDS []int64) (string, error) { payload := map[string]interface{}{ "order_ids": orderIDS, } req, err := s.client.newAuthenticatedRequest("POST", "order/cancel/multi", payload) if err != nil { return "", err } response := make(map[string]string) _, err = s.client.do(req, &response) return response["result"], err } // Replace an Order func (s *OrderService) Replace(orderID int64, useRemaining bool, newOrder SubmitOrder) (Order, error) { var side string if newOrder.Amount < 0 { newOrder.Amount = math.Abs(newOrder.Amount) side = "sell" } else { side = "buy" } payload := map[string]interface{}{ "order_id": strconv.FormatInt(orderID, 10), "symbol": newOrder.Symbol, "amount": strconv.FormatFloat(newOrder.Amount, 'f', -1, 32), "price": strconv.FormatFloat(newOrder.Price, 'f', -1, 32), "exchange": "bitfinex", "side": side, "type": newOrder.Type, "use_remaining": useRemaining, } req, err := s.client.newAuthenticatedRequest("POST", "order/cancel/replace", payload) if err != nil { return Order{}, err } order := new(Order) _, err = s.client.do(req, order) if err != nil { return *order, err } return *order, nil } // Status retrieves the given order from the API. func (s *OrderService) Status(orderID int64) (Order, error) { payload := map[string]interface{}{ "order_id": orderID, } req, err := s.client.newAuthenticatedRequest("POST", "order/status", payload) if err != nil { return Order{}, err } order := new(Order) _, err = s.client.do(req, order) if err != nil { return *order, err } return *order, nil } bitfinex-api-go-2.2.9/v1/orders_test.go000066400000000000000000000063761371275744700177550ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestOrdersAll(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := ` [{ "id":448411365, "symbol":"btcusd", "exchange":"bitfinex", "price":"0.02", "avg_execution_price":"0.0", "side":"buy", "type":"exchange limit", "timestamp":"1444276597.0", "is_live":true, "is_cancelled":false, "is_hidden":false, "was_forced":false, "original_amount":"0.02", "remaining_amount":"0.02", "executed_amount":"0.0" }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } orders, err := NewClient().Orders.All() if err != nil { t.Error(err) } expectedID := int64(448411365) if orders[0].ID != expectedID { t.Error("Expected", expectedID) t.Error("Actual ", orders[0].ID) } } func TestCreateMulti(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{ "order_ids":[{ "id":448383727, "symbol":"btcusd", "exchange":"bitfinex", "price":"0.01", "avg_execution_price":"0.0", "side":"buy", "type":"exchange limit", "timestamp":"1444274013.621701916", "is_live":true, "is_cancelled":false, "is_hidden":false, "was_forced":false, "original_amount":"0.01", "remaining_amount":"0.01", "executed_amount":"0.0" },{ "id":448383729, "symbol":"btcusd", "exchange":"bitfinex", "price":"0.03", "avg_execution_price":"0.0", "side":"buy", "type":"exchange limit", "timestamp":"1444274013.661297306", "is_live":true, "is_cancelled":false, "is_hidden":false, "was_forced":false, "original_amount":"0.02", "remaining_amount":"0.02", "executed_amount":"0.0" }], "status":"success" }` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } reqOrders := []SubmitOrder{{ Symbol: "BTCUSD", Amount: 10.0, Price: 450.0, Type: OrderTypeLimit, }, { Symbol: "BTCUSD", Amount: 10.0, Price: 450.0, Type: OrderTypeLimit, }} response, err := NewClient().Orders.CreateMulti(reqOrders) if err != nil { t.Error(err) } if len(response.Orders) != 2 { t.Error("Expected", 2) t.Error("Actual ", len(response.Orders)) } } func TestCancelMulti(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{"result":"Orders cancelled"}` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } orders := []int64{1000, 1001, 1002} response, err := NewClient().Orders.CancelMulti(orders) if err != nil { t.Error(err) } if response != "Orders cancelled" { t.Error("Expected", "Orders cancelled") t.Error("Actual ", response) } } bitfinex-api-go-2.2.9/v1/pairs.go000066400000000000000000000020341371275744700165210ustar00rootroot00000000000000package bitfinex type PairsService struct { client *Client } // Get all Pair names as array of strings func (p *PairsService) All() ([]string, error) { req, err := p.client.newRequest("GET", "symbols", nil) if err != nil { return nil, err } var v []string _, err = p.client.do(req, &v) if err != nil { return nil, err } return v, nil } // Detailed Pair type Pair struct { Pair string PricePrecision int `json:"price_precision,int"` InitialMargin float64 `json:"initial_margin,string"` MinimumMargin float64 `json:"minimum_margin,string"` MaximumOrderSize float64 `json:"maximum_order_size,string"` MinimumOrderSize float64 `json:"minimum_order_size,string"` Expiration string Margin bool } // Return a list of detailed pairs func (p *PairsService) AllDetailed() ([]Pair, error) { req, err := p.client.newRequest("GET", "symbols_details", nil) if err != nil { return nil, err } var v []Pair _, err = p.client.do(req, &v) if err != nil { return nil, err } return v, nil } bitfinex-api-go-2.2.9/v1/pairs_test.go000066400000000000000000000041511371275744700175620ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestPairsGetAll(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `["btcusd","ltcusd","ltcbtc","ethusd","ethbtc"]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } pairs, err := NewClient().Pairs.All() numPairs := len(pairs) if err != nil { t.Error(err) } if numPairs != 5 { t.Error("Expected", 5) t.Error("Actual ", numPairs) } if (pairs)[0] != "btcusd" { t.Error("Expected", "btcusd") t.Error("Actual ", pairs[0]) } } func TestPairsAllDetailed(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "pair":"btcusd", "price_precision":5, "initial_margin":"30.0", "minimum_margin":"15.0", "maximum_order_size":"2000.0", "minimum_order_size":"0.01", "expiration":"NA", "margin":true },{ "pair":"ltcusd", "price_precision":5, "initial_margin":"30.0", "minimum_margin":"15.0", "maximum_order_size":"5000.0", "minimum_order_size":"0.1", "expiration":"NA", "margin":false }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } pairs, err := NewClient().Pairs.AllDetailed() if err != nil { t.Error(err) } if len(pairs) != 2 { t.Error("Expected", 2) t.Error("Actual", len(pairs)) } pairMargin := pairs[0].InitialMargin expectedMargin := 30.0 if (pairMargin-expectedMargin) > 0.1 || (expectedMargin-pairMargin) > 0.1 { t.Error("Expected", expectedMargin) t.Error("Actual", pairMargin) } if pairs[0].Pair != "btcusd" { t.Error("Expected", "btcusd") t.Error("Actual", pairs[0].Pair) } if pairs[0].Expiration != "NA" { t.Error("Expected", "NA") t.Error("Actual", pairs[0].Expiration) } if !pairs[0].Margin { t.Error("Expected", true) t.Error("Actual", pairs[0].Margin) } } bitfinex-api-go-2.2.9/v1/positions.go000066400000000000000000000024151371275744700174350ustar00rootroot00000000000000package bitfinex import ( "strconv" "time" ) // PositionsService structure type PositionsService struct { client *Client } // Position structure type Position struct { ID int Symbol string Amount string Status string Base string Timestamp string Swap string Pl string } func (p *Position) ParseTime() (*time.Time, error) { i, err := strconv.ParseFloat(p.Timestamp, 64) if err != nil { return nil, err } t := time.Unix(int64(i), 0) return &t, nil } // All - gets all positions func (b *PositionsService) All() ([]Position, error) { req, err := b.client.newAuthenticatedRequest("POST", "positions", nil) if err != nil { return nil, err } var positions []Position _, err = b.client.do(req, &positions) if err != nil { return nil, err } return positions, nil } // Claim a position func (b *PositionsService) Claim(positionId int, amount string) (Position, error) { request := map[string]interface{}{ "position_id": positionId, "amount": amount, } req, err := b.client.newAuthenticatedRequest("POST", "position/claim", request) if err != nil { return Position{}, err } var position = &Position{} _, err = b.client.do(req, position) if err != nil { return Position{}, err } return *position, nil } bitfinex-api-go-2.2.9/v1/positions_test.go000066400000000000000000000052071371275744700204760ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" "time" ) func TestPositions(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[ { "id":943715, "symbol":"btcusd", "status":"ACTIVE", "base":"246.94", "amount":"1.0", "timestamp":"1444141857.0", "swap":"0.0", "pl":"-2.22042" } ]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } client := NewClient() positions, err := client.Positions.All() if err != nil { t.Error(err) } if len(positions) != 1 { t.Error("Expected", 1) t.Error("Actual ", len(positions)) } pos := positions[0] if pos.Amount != "1.0" { t.Error("Expected", "1.0") t.Error("Actual ", pos.Amount) } } func TestPositionsWhenEmpty(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "id":943715, "symbol":"btcusd", "status":"ACTIVE", "base":"246.94", "amount":"1.0", "timestamp":"1444141857.0", "swap":"0.0", "pl":"-2.2304" }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } client := NewClient() positions, err := client.Positions.All() if err != nil { t.Error(err) } position := positions[0] if position.ID != 943715 { t.Error("Expected", 943715) t.Error("Actual ", position.ID) } parsedTime, err := position.ParseTime() loc, _ := time.LoadLocation("Europe/Berlin") expectedTime := time.Date(2015, 10, 06, 16, 30, 57, 00, loc) if err != nil { t.Error(err) } if !(*parsedTime).Equal(expectedTime) { t.Error("Expected", &expectedTime) t.Error("Actual ", parsedTime) } } func TestClaimPosition(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{ "id":943715, "symbol":"btcusd", "status":"ACTIVE", "base":"246.94", "amount":"1.0", "timestamp":"1444141857.0", "swap":"0.0", "pl":"-2.22042" }` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } position, err := NewClient().Positions.Claim(943715, "0.5") if err != nil { t.Error(err) } if position.ID != 943715 { t.Error("Expected", 943715) t.Error("Actual ", position.ID) } } bitfinex-api-go-2.2.9/v1/stats.go000066400000000000000000000012731371275744700165450ustar00rootroot00000000000000package bitfinex import ( "net/url" "strings" ) type StatsService struct { client *Client } type Stats struct { Period int64 Volume float64 `json:"volume,string"` } // All(pair) - Volume stats for specified pair func (s *StatsService) All(pair string, period, volume string) ([]Stats, error) { pair = strings.ToUpper(pair) params := url.Values{} if period != "" { params.Add("period", period) } if volume != "" { params.Add("volume", volume) } req, err := s.client.newRequest("GET", "stats/"+strings.ToUpper(pair), params) if err != nil { return nil, err } var stats []Stats _, err = s.client.do(req, &stats) if err != nil { return nil, err } return stats, nil } bitfinex-api-go-2.2.9/v1/stats_test.go000066400000000000000000000013241371275744700176010ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestStatsAll(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "period":1, "volume":"7967.96766158" },{ "period":7, "volume":"55938.67260266" },{ "period":30, "volume":"275148.09653645" }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } stats, err := NewClient().Stats.All("btcusd", "10", "") if err != nil { t.Error(err) } if len(stats) != 3 { t.Error("Expected", 3) t.Error("Actual ", len(stats)) } } bitfinex-api-go-2.2.9/v1/ticker.go000066400000000000000000000015621371275744700166710ustar00rootroot00000000000000package bitfinex import ( "strconv" "strings" "time" ) type TickerService struct { client *Client } type Tick struct { Mid string Bid string Ask string LastPrice string `json:"last_price"` Low string High string Volume string Timestamp string } // ParseTime - return Timestamp in time.Time format func (el *Tick) ParseTime() (*time.Time, error) { i, err := strconv.ParseFloat(el.Timestamp, 64) if err != nil { return nil, err } t := time.Unix(int64(i), 0) return &t, nil } // Get(pair) - return last Tick for specified pair func (s *TickerService) Get(pair string) (Tick, error) { pair = strings.ToUpper(pair) req, err := s.client.newRequest("GET", "pubticker/"+pair, nil) if err != nil { return Tick{}, err } var v = &Tick{} _, err = s.client.do(req, v) if err != nil { return Tick{}, err } return *v, nil } bitfinex-api-go-2.2.9/v1/ticker_test.go000066400000000000000000000015401371275744700177240ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestTickerGet(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `{ "mid":"244.755", "bid":"244.75", "ask":"244.76", "last_price":"244.82", "low":"244.2", "high":"248.19", "volume":"7842.11542563", "timestamp":"1444253422.348340958" }` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } tick, err := NewClient().Ticker.Get("btcusd") if err != nil { t.Error(err) } if tick.Bid != "244.75" { t.Error("Expected", "244.75") t.Error("Actual ", tick.Bid) } if tick.LastPrice != "244.82" { t.Error("Expected", "244.82") t.Error("Actual ", tick.LastPrice) } } bitfinex-api-go-2.2.9/v1/trades.go000066400000000000000000000015761371275744700166770ustar00rootroot00000000000000package bitfinex import ( "net/url" "strconv" "strings" "time" ) type TradesService struct { client *Client } type Trade struct { Price string Amount string Exchange string Type string Timestamp int64 TradeId int64 `json:"tid,int"` } func (el *Trade) Time() *time.Time { t := time.Unix(el.Timestamp, 0) return &t } func (s *TradesService) All(pair string, timestamp time.Time, limitTrades int) ([]Trade, error) { pair = strings.ToUpper(pair) params := url.Values{} if !time.Time.IsZero(timestamp) { params.Add("timestamp", strconv.FormatInt(timestamp.Unix(), 10)) } if limitTrades != 0 { params.Add("limit_trades", strconv.Itoa(limitTrades)) } req, err := s.client.newRequest("GET", "trades/"+pair, params) if err != nil { return nil, err } var v []Trade _, err = s.client.do(req, &v) if err != nil { return nil, err } return v, nil } bitfinex-api-go-2.2.9/v1/trades_test.go000066400000000000000000000013141371275744700177240ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" "time" ) func TestTradesServiceGet(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "timestamp":1444266681, "tid":11988919, "price":"244.8", "amount":"0.03297384", "exchange":"bitfinex", "type":"sell" }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } trades, err := NewClient().Trades.All("ethusd", time.Time{}, 0) if err != nil { t.Error(err) } if len(trades) != 1 { t.Error("Expected", 1) t.Error("Actual ", len(trades)) } } bitfinex-api-go-2.2.9/v1/wallet.go000066400000000000000000000062251371275744700167010ustar00rootroot00000000000000package bitfinex import "strconv" const ( WALLET_TRADING = "trading" WALLET_EXCHANGE = "exchange" WALLET_DEPOSIT = "deposit" ) type WalletService struct { client *Client } type TransferStatus struct { Status string Message string } // Transfer funds between wallets func (c *WalletService) Transfer(amount float64, currency, from, to string) ([]TransferStatus, error) { payload := map[string]interface{}{ "amount": strconv.FormatFloat(amount, 'f', -1, 32), "currency": currency, "walletfrom": from, "walletto": to, } req, err := c.client.newAuthenticatedRequest("GET", "transfer", payload) if err != nil { return nil, err } status := make([]TransferStatus, 0) _, err = c.client.do(req, &status) return status, err } type WithdrawStatus struct { Status string Message string WithdrawalID int `json:"withdrawal_id"` } // Withdraw a cryptocurrency to a digital wallet func (c *WalletService) WithdrawCrypto(amount float64, currency, wallet, destinationAddress string) ([]WithdrawStatus, error) { payload := map[string]interface{}{ "amount": strconv.FormatFloat(amount, 'f', -1, 32), "walletselected": wallet, "withdraw_type": currency, "address": destinationAddress, } req, err := c.client.newAuthenticatedRequest("POST", "withdraw", payload) if err != nil { return nil, err } status := make([]WithdrawStatus, 0) _, err = c.client.do(req, &status) return status, err } type BankAccount struct { AccountName string // Account name AccountNumber string // Account number or IBAN BankName string // Bank Name BankAddress string // Bank Address BankCity string // Bank City BankCountry string // Bank Country SwiftCode string // SWIFT Code } func (c *WalletService) WithdrawWire(amount float64, expressWire bool, wallet string, beneficiaryBank, intermediaryBank BankAccount, message string) ([]WithdrawStatus, error) { var express int if expressWire { express = 1 } else { express = 0 } payload := map[string]interface{}{ "amount": strconv.FormatFloat(amount, 'f', -1, 32), "walletselected": wallet, "withdraw_type": "wire", "expressWire": express, "account_name": beneficiaryBank.AccountName, "account_number": beneficiaryBank.AccountNumber, "bank_name": beneficiaryBank.BankName, "bank_address": beneficiaryBank.BankAddress, "bank_city": beneficiaryBank.BankCity, "bank_country": beneficiaryBank.BankCountry, "swift": beneficiaryBank.SwiftCode, "detail_payment": message, "intermediary_bank_account": intermediaryBank.AccountNumber, "intermediary_bank_address": intermediaryBank.BankAddress, "intermediary_bank_city": intermediaryBank.BankCity, "intermediary_bank_country": intermediaryBank.BankCountry, "intermediary_bank_swift": intermediaryBank.SwiftCode, } req, err := c.client.newAuthenticatedRequest("GET", "withdraw", payload) if err != nil { return nil, err } status := make([]WithdrawStatus, 0) _, err = c.client.do(req, &status) return status, err } bitfinex-api-go-2.2.9/v1/wallet_test.go000066400000000000000000000053461371275744700177430ustar00rootroot00000000000000package bitfinex import ( "bytes" "io/ioutil" "net/http" "testing" ) func TestWalletTransfer(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "status":"success", "message":"1.0 USD transfered from Exchange to Deposit" }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } response, err := NewClient().Wallet.Transfer(10.0, "BTC", "1WalletA", "1WalletB") if err != nil { t.Error(err) } if response[0].Status != "success" { t.Error("Expected", "success") t.Error("Actual ", response[0].Status) } } func TestWithdrawCrypto(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "status":"success", "message":"Your withdrawal request has been successfully submitted.", "withdrawal_id":586829 }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } response, err := NewClient().Wallet.WithdrawCrypto(10.0, "bitcoin", WALLET_DEPOSIT, "1WalletABC") if err != nil { t.Error(err) } if response[0].Status != "success" { t.Error("Expected", "success") t.Error("Actual ", response[0].Status) } if response[0].WithdrawalID != 586829 { t.Error("Expected", 586829) t.Error("Actual ", response[0].WithdrawalID) } } func TestWithdrawWire(t *testing.T) { httpDo = func(req *http.Request) (*http.Response, error) { msg := `[{ "status":"success", "message":"Your withdrawal request has been successfully submitted.", "withdrawal_id":586829 }]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } intermediaryBank := BankAccount{ AccountName: "Bank Account Name", AccountNumber: "IBAN12355678976543", BankName: "HongKong Bank", BankAddress: "Bank Address", BankCity: "Bank City", BankCountry: "Bank Country", SwiftCode: "SWIFT", } beneficiaryBank := BankAccount{ AccountName: "Bank Account Name", AccountNumber: "IBAN12355678976543", BankName: "HongKong Bank", BankAddress: "Bank Address", BankCity: "Bank City", BankCountry: "Bank Country", SwiftCode: "SWIFT", } response, err := NewClient().Wallet.WithdrawWire(10.0, true, WALLET_DEPOSIT, beneficiaryBank, intermediaryBank, "Wire MESSAGE") if err != nil { t.Error(err) } if response[0].Status != "success" { t.Error("Expected", "success") t.Error("Actual ", response[0].Status) } if response[0].WithdrawalID != 586829 { t.Error("Expected", 586829) t.Error("Actual ", response[0].WithdrawalID) } } bitfinex-api-go-2.2.9/v1/websocket.go000066400000000000000000000173541371275744700174040ustar00rootroot00000000000000package bitfinex import ( "bytes" "crypto/tls" "encoding/json" "log" "net/http" "reflect" "github.com/bitfinexcom/bitfinex-api-go/utils" "github.com/gorilla/websocket" ) // Pairs available const ( // Pairs BTCUSD = "BTCUSD" LTCUSD = "LTCUSD" LTCBTC = "LTCBTC" ETHUSD = "ETHUSD" ETHBTC = "ETHBTC" ETCUSD = "ETCUSD" ETCBTC = "ETCBTC" BFXUSD = "BFXUSD" BFXBTC = "BFXBTC" ZECUSD = "ZECUSD" ZECBTC = "ZECBTC" XMRUSD = "XMRUSD" XMRBTC = "XMRBTC" RRTUSD = "RRTUSD" RRTBTC = "RRTBTC" XRPUSD = "XRPUSD" XRPBTC = "XRPBTC" EOSETH = "EOSETH" EOSUSD = "EOSUSD" EOSBTC = "EOSBTC" IOTUSD = "IOTUSD" IOTBTC = "IOTBTC" IOTETH = "IOTETH" BCCBTC = "BCCBTC" BCUBTC = "BCUBTC" BCCUSD = "BCCUSD" BCUUSD = "BCUUSD" // Channels ChanBook = "book" ChanTrade = "trades" ChanTicker = "ticker" ) // WebSocketService allow to connect and receive stream data // from bitfinex.com ws service. // nolint:megacheck,structcheck type WebSocketService struct { // http client client *Client // websocket client ws *websocket.Conn // special web socket for private messages privateWs *websocket.Conn // map internal channels to websocket's chanMap map[float64]chan []float64 subscribes []subscribeToChannel } type subscribeMsg struct { Event string `json:"event"` Channel string `json:"channel"` Pair string `json:"pair"` ChanID float64 `json:"chanId,omitempty"` } type subscribeToChannel struct { Channel string Pair string Chan chan []float64 } // NewWebSocketService returns a WebSocketService using the given client. func NewWebSocketService(c *Client) *WebSocketService { return &WebSocketService{ client: c, chanMap: make(map[float64]chan []float64), subscribes: make([]subscribeToChannel, 0), } } // Connect create new bitfinex websocket connection func (w *WebSocketService) Connect() error { var d = websocket.Dialer{ Subprotocols: []string{"p1", "p2"}, ReadBufferSize: 1024, WriteBufferSize: 1024, Proxy: http.ProxyFromEnvironment, } if w.client.WebSocketTLSSkipVerify { d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} } ws, _, err := d.Dial(w.client.WebSocketURL, nil) if err != nil { return err } w.ws = ws return nil } // Close web socket connection func (w *WebSocketService) Close() { w.ws.Close() } func (w *WebSocketService) AddSubscribe(channel string, pair string, c chan []float64) { s := subscribeToChannel{ Channel: channel, Pair: pair, Chan: c, } w.subscribes = append(w.subscribes, s) } func (w *WebSocketService) ClearSubscriptions() { w.subscribes = make([]subscribeToChannel, 0) } func (w *WebSocketService) sendSubscribeMessages() error { for _, s := range w.subscribes { msg, _ := json.Marshal(subscribeMsg{ Event: "subscribe", Channel: s.Channel, Pair: s.Pair, }) err := w.ws.WriteMessage(websocket.TextMessage, msg) if err != nil { return err } } return nil } // Subscribe allows to subsribe to channels and watch for new updates. // This method supports next channels: book, trade, ticker. func (w *WebSocketService) Subscribe() error { // Subscribe to each channel if err := w.sendSubscribeMessages(); err != nil { return err } for { _, p, err := w.ws.ReadMessage() if err != nil { return err } if bytes.Contains(p, []byte("event")) { w.handleEventMessage(p) } else { w.handleDataMessage(p) } } // nolint return nil } func (w *WebSocketService) handleEventMessage(msg []byte) { // Check for first message(event:subscribed) event := &subscribeMsg{} err := json.Unmarshal(msg, event) // Received "subscribed" resposne. Link channels. if err == nil { for _, k := range w.subscribes { if event.Event == "subscribed" && event.Pair == k.Pair && event.Channel == k.Channel { w.chanMap[event.ChanID] = k.Chan } } } } func (w *WebSocketService) handleDataMessage(msg []byte) { // Received payload or data update var dataUpdate []float64 err := json.Unmarshal(msg, &dataUpdate) if err == nil { chanID := dataUpdate[0] // Remove chanID from data update // and send message to internal chan w.chanMap[chanID] <- dataUpdate[1:] } // Payload received var fullPayload []interface{} err = json.Unmarshal(msg, &fullPayload) if err != nil { log.Println("Error decoding fullPayload", err) } else { if len(fullPayload) > 3 { itemsSlice := fullPayload[3:] i, _ := json.Marshal(itemsSlice) var item []float64 err = json.Unmarshal(i, &item) if err == nil { chanID := fullPayload[0].(float64) w.chanMap[chanID] <- item } } else { itemsSlice := fullPayload[1] i, _ := json.Marshal(itemsSlice) var items [][]float64 err = json.Unmarshal(i, &items) if err == nil { chanID := fullPayload[0].(float64) for _, v := range items { w.chanMap[chanID] <- v } } } } } ///////////////////////////// // Private websocket messages ///////////////////////////// type privateConnect struct { Event string `json:"event"` APIKey string `json:"apiKey"` AuthSig string `json:"authSig"` AuthPayload string `json:"authPayload"` } // Private channel auth response type privateResponse struct { Event string `json:"event"` Status string `json:"status"` ChanID float64 `json:"chanId,omitempty"` UserID float64 `json:"userId"` } type TermData struct { // Data term. E.g: ps, ws, ou, etc... See official documentation for more details. Term string // Data will contain different number of elements for each term. // Examples: // Term: ws, Data: ["exchange","BTC",0.01410829,0] // Term: oc, Data: [0,"BTCUSD",0,-0.01,"","CANCELED",270,0,"2015-10-15T11:26:13Z",0] Data []interface{} Error string } func (c *TermData) HasError() bool { return len(c.Error) > 0 } func (w *WebSocketService) ConnectPrivate(ch chan TermData) { var d = websocket.Dialer{ Subprotocols: []string{"p1", "p2"}, ReadBufferSize: 1024, WriteBufferSize: 1024, Proxy: http.ProxyFromEnvironment, } if w.client.WebSocketTLSSkipVerify { d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} } ws, _, err := d.Dial(w.client.WebSocketURL, nil) if err != nil { ch <- TermData{ Error: err.Error(), } return } nonce := utils.GetNonce() payload := "AUTH" + nonce sig, err_sig := w.client.signPayload(payload) if err_sig != nil { return } connectMsg, _ := json.Marshal(&privateConnect{ Event: "auth", APIKey: w.client.APIKey, AuthSig: sig, AuthPayload: payload, }) // Send auth message err = ws.WriteMessage(websocket.TextMessage, connectMsg) if err != nil { ch <- TermData{ Error: err.Error(), } ws.Close() return } for { _, p, err := ws.ReadMessage() if err != nil { ch <- TermData{ Error: err.Error(), } ws.Close() return } event := &privateResponse{} err = json.Unmarshal(p, &event) if err != nil { // received data update var data []interface{} err = json.Unmarshal(p, &data) if err == nil { if len(data) == 2 { // Heartbeat // XXX: Consider adding a switch to enable/disable passing these along. ch <- TermData{Term: data[1].(string)} return } dataTerm := data[1].(string) dataList := data[2].([]interface{}) // check for empty data if len(dataList) > 0 { if reflect.TypeOf(dataList[0]) == reflect.TypeOf([]interface{}{}) { // received list of lists for _, v := range dataList { ch <- TermData{ Term: dataTerm, Data: v.([]interface{}), } } } else { // received flat list ch <- TermData{ Term: dataTerm, Data: dataList, } } } } } else { // received auth response if event.Event == "auth" && event.Status != "OK" { ch <- TermData{ Error: "Error connecting to private web socket channel.", } ws.Close() } } } } bitfinex-api-go-2.2.9/v2/000077500000000000000000000000001371275744700150565ustar00rootroot00000000000000bitfinex-api-go-2.2.9/v2/pairs.go000066400000000000000000000017731371275744700165330ustar00rootroot00000000000000package bitfinex // Some of the more available paris. See https://api.bitfinex.com/v1/symbols for // an up to date list const ( BTCUSD = "BTCUSD" BTCFUSTF = "BTCF0:USTF0" BTCEUR = "BTCEUR" BTCJPY = "BTCJPY" LTCUSD = "LTCUSD" LTCBTC = "LTCBTC" ETHUSD = "ETHUSD" ETHFUSTF ="ETHF0:USTF0" ETHBTC = "ETHBTC" ETHEUR = "ETHEUR" ETHJPY = "ETHJPY" ETHGBP = "ETHGBP" ETHUST = "ETHUST" ETCUSD = "ETCUSD" ETCBTC = "ETCBTC" ZECUSD = "ZECUSD" ZECBTC = "ZECBTC" XMRUSD = "XMRUSD" XMRBTC = "XMRBTC" RRTUSD = "RRTUSD" RRTBTC = "RRTBTC" XRPUSD = "XRPUSD" XRPBTC = "XRPBTC" EOSETH = "EOSETH" EOSUSD = "EOSUSD" EOSBTC = "EOSBTC" EOSEUR = "EOSEUR" EOSJPY = "EOSJPY" EOSGBP = "EOSGBP" NEOUSD = "NEOUSD" NEOBTC = "NEOBTC" NEOETH = "NEOETH" TRXUSD = "TRXUSD" TRXBTC = "TRXBTC" TRXETH = "TRXETH" IOTUSD = "IOTUSD" IOTBTC = "IOTBTC" IOTETH = "IOTETH" IOTEUR = "IOTEUR" IOTJPY = "IOTJPY" IOTGBP = "IOTGBP" LEOUSD = "LEOUSD" LEOBTC = "LEOBTC" LEOUST = "LEOUST" LEOEOS = "LEOEOS" LEOETH = "LEOETH" ) bitfinex-api-go-2.2.9/v2/rest/000077500000000000000000000000001371275744700160335ustar00rootroot00000000000000bitfinex-api-go-2.2.9/v2/rest/book.go000066400000000000000000000021471371275744700173200ustar00rootroot00000000000000package rest import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "net/url" "path" "strconv" ) type BookService struct { Synchronous } // Retrieve all books for the given symbol with the given precision at the given price level // see https://docs.bitfinex.com/reference#rest-public-books for more info func (b *BookService) All(symbol string, precision bitfinex.BookPrecision, priceLevels int) (*bitfinex.BookUpdateSnapshot, error) { req := NewRequestWithMethod(path.Join("book", symbol, string(precision)), "GET") req.Params = make(url.Values) req.Params.Add("len", strconv.Itoa(priceLevels)) raw, err := b.Request(req) if err != nil { return nil, err } data := make([][]float64, 0, len(raw)) for _, ifacearr := range raw { if arr, ok := ifacearr.([]interface{}); ok { sub := make([]float64, 0, len(arr)) for _, iface := range arr { if flt, ok := iface.(float64); ok { sub = append(sub, flt) } } data = append(data, sub) } } book, err := bitfinex.NewBookUpdateSnapshotFromRaw(symbol, string(precision), data, raw) if err != nil { return nil, err } return book, nil } bitfinex-api-go-2.2.9/v2/rest/book_test.go000066400000000000000000000030231371275744700203510ustar00rootroot00000000000000package rest import ( "bytes" "github.com/bitfinexcom/bitfinex-api-go/v2" "io/ioutil" "net/http" "testing" ) func TestBookAll(t *testing.T) { httpDo := func(_ *http.Client, req *http.Request) (*http.Response, error) { msg := `[[10579,1,0.0329596],[10578,1,0.11030234],[10577,2,0.11890895],[10576,2,1.0427],[10574,2,0.98962806],[10573,1,0.9443],[10572,1,0.06824617],[10571,1,0.42609023],[10570,1,0.002],[10569,2,0.99085269],[10568,3,2.1616],[10567,1,0.49990559],[10566,1,0.5],[10565,1,0.5413],[10564,2,0.99990599],[10563,2,0.28270321],[10561,2,0.99896343],[10560,1,0.498983],[10559,3,1.43741793],[10558,4,1.17],[10557,2,2.42],[10556,3,4.25833255],[10555,4,6.472],[10554,1,0.2],[10553,2,0.06940968],[10580,3,-4.5235],[10581,1,-0.9452],[10584,2,-0.46850263],[10585,1,-0.01],[10586,2,-0.93153],[10587,3,-0.82382839],[10589,2,-0.56565545],[10590,2,-0.43420271],[10592,1,-0.1],[10593,3,-2.1],[10594,3,-19.47635006],[10595,4,-7.352],[10596,1,-1.5],[10597,1,-4.5],[10598,1,-2.96],[10600,3,-0.41500001],[10601,1,-0.02835606],[10606,3,-0.28310301],[10607,2,-0.99729895],[10608,3,-0.25],[10609,3,-2.04831264],[10610,1,-0.05],[10613,2,-2],[10614,1,-0.3],[10615,1,-0.002]]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } book, err := NewClientWithHttpDo(httpDo).Book.All("tBTCUSD", bitfinex.Precision0, 25) if err != nil { t.Fatal(err) } if len(book.Snapshot) != 50 { t.Fatalf("expected 50 book update entries in snapshot, but got %d", len(book.Snapshot)) } } bitfinex-api-go-2.2.9/v2/rest/candles.go000066400000000000000000000064031371275744700177760ustar00rootroot00000000000000package rest import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "path" "strconv" "strings" "net/url" "fmt" ) // CandleService manages the Candles endpoint. type CandleService struct { Synchronous } // Retrieve the last candle for the given symbol with the given resolution // See https://docs.bitfinex.com/reference#rest-public-candles for more info func (c *CandleService) Last(symbol string, resolution bitfinex.CandleResolution) (*bitfinex.Candle, error) { if symbol == "" { return nil, fmt.Errorf("symbol cannot be empty") } segments := []string{ "trade", string(resolution), symbol } req := NewRequestWithMethod(path.Join("candles", strings.Join(segments,":"), "LAST"), "GET") req.Params = make(url.Values) raw, err := c.Request(req) if err != nil { return nil, err } cs, err := bitfinex.NewCandleFromRaw(symbol, resolution, raw) if err != nil { return nil, err } return cs, nil } // Retrieves all candles (Max=1000) with the given symbol and the given candle resolution // See https://docs.bitfinex.com/reference#rest-public-candles for more info func (c *CandleService) History(symbol string, resolution bitfinex.CandleResolution) (*bitfinex.CandleSnapshot, error) { if symbol == "" { return nil, fmt.Errorf("symbol cannot be empty") } segments := []string{ "trade", string(resolution), symbol } req := NewRequestWithMethod(path.Join("candles", strings.Join(segments,":"), "HIST"), "GET") raw, err := c.Request(req) if err != nil { return nil, err } data := make([][]float64, 0, len(raw)) for _, ifacearr := range raw { if arr, ok := ifacearr.([]interface{}); ok { sub := make([]float64, 0, len(arr)) for _, iface := range arr { if flt, ok := iface.(float64); ok { sub = append(sub, flt) } } data = append(data, sub) } } cs, err := bitfinex.NewCandleSnapshotFromRaw(symbol, resolution, data) if err != nil { return nil, err } return cs, nil } // Retrieves all candles (Max=1000) that fit the given query criteria // See https://docs.bitfinex.com/reference#rest-public-candles for more info func (c *CandleService) HistoryWithQuery( symbol string, resolution bitfinex.CandleResolution, start bitfinex.Mts, end bitfinex.Mts, limit bitfinex.QueryLimit, sort bitfinex.SortOrder, ) (*bitfinex.CandleSnapshot, error) { if symbol == "" { return nil, fmt.Errorf("symbol cannot be empty") } segments := []string{ "trade", string(resolution), symbol } req := NewRequestWithMethod(path.Join("candles", strings.Join(segments,":"), "HIST"), "GET") req.Params = make(url.Values) req.Params.Add("end", strconv.FormatInt(int64(end), 10)) req.Params.Add("start", strconv.FormatInt(int64(start), 10)) req.Params.Add("limit", strconv.FormatInt(int64(limit), 10)) req.Params.Add("sort", strconv.FormatInt(int64(sort), 10)) raw, err := c.Request(req) if err != nil { return nil, err } data := make([][]float64, 0, len(raw)) for _, ifacearr := range raw { if arr, ok := ifacearr.([]interface{}); ok { sub := make([]float64, 0, len(arr)) for _, iface := range arr { if flt, ok := iface.(float64); ok { sub = append(sub, flt) } } data = append(data, sub) } } cs, err := bitfinex.NewCandleSnapshotFromRaw(symbol, resolution, data) if err != nil { return nil, err } return cs, nil } bitfinex-api-go-2.2.9/v2/rest/client.go000066400000000000000000000244661371275744700176540ustar00rootroot00000000000000package rest import ( "crypto/hmac" "crypto/sha512" "encoding/hex" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/url" "github.com/bitfinexcom/bitfinex-api-go/utils" "github.com/bitfinexcom/bitfinex-api-go/v2" ) var productionBaseURL = "https://api-pub.bitfinex.com/v2/" type requestFactory interface { NewAuthenticatedRequestWithData(permissionType bitfinex.PermissionType, refURL string, data map[string]interface{}) (Request, error) NewAuthenticatedRequestWithBytes(permissionType bitfinex.PermissionType, refURL string, data []byte) (Request, error) NewAuthenticatedRequest(permissionType bitfinex.PermissionType, refURL string) (Request, error) } type Synchronous interface { Request(request Request) ([]interface{}, error) } type Client struct { // base members for synchronous API apiKey string apiSecret string nonce utils.NonceGenerator // service providers Candles CandleService Orders OrderService Positions PositionService Trades TradeService Tickers TickerService Currencies CurrenciesService Platform PlatformService Book BookService Wallet WalletService Ledgers LedgerService Stats StatsService Status StatusService Derivatives DerivativesService Funding FundingService Pulse PulseService Invoice InvoiceService Market MarketService Synchronous } // Create a new Rest client func NewClient() *Client { return NewClientWithURLNonce(productionBaseURL, utils.NewEpochNonceGenerator()) } // Create a new Rest client with a custom nonce generator func NewClientWithURLNonce(url string, nonce utils.NonceGenerator) *Client { httpDo := func(c *http.Client, req *http.Request) (*http.Response, error) { return c.Do(req) } return NewClientWithURLHttpDoNonce(url, httpDo, nonce) } // Create a new Rest client with a custom http handler func NewClientWithHttpDo(httpDo func(c *http.Client, r *http.Request) (*http.Response, error)) *Client { return NewClientWithURLHttpDo(productionBaseURL, httpDo) } // Create a new Rest client with a custom base url and HTTP handler func NewClientWithURLHttpDo(base string, httpDo func(c *http.Client, r *http.Request) (*http.Response, error)) *Client { return NewClientWithURLHttpDoNonce(base, httpDo, utils.NewEpochNonceGenerator()) } // Create a new Rest client with a custom base url, HTTP handler and none generator func NewClientWithURLHttpDoNonce(base string, httpDo func(c *http.Client, r *http.Request) (*http.Response, error), nonce utils.NonceGenerator) *Client { url, _ := url.Parse(base) sync := &HttpTransport{ BaseURL: url, httpDo: httpDo, HTTPClient: http.DefaultClient, } return NewClientWithSynchronousNonce(sync, nonce) } // Create a new Rest client with a custom base url func NewClientWithURL(url string) *Client { httpDo := func(c *http.Client, req *http.Request) (*http.Response, error) { return c.Do(req) } return NewClientWithURLHttpDo(url, httpDo) } // Create a new Rest client with a synchronous HTTP handler and a custom nonce generaotr func NewClientWithSynchronousNonce(sync Synchronous, nonce utils.NonceGenerator) *Client { return NewClientWithSynchronousURLNonce(sync, productionBaseURL, nonce) } // Create a new Rest client with a synchronous HTTP handler and a custom base url and nonce generator func NewClientWithSynchronousURLNonce(sync Synchronous, url string, nonce utils.NonceGenerator) *Client { c := &Client{ Synchronous: sync, nonce: nonce, } c.Orders = OrderService{Synchronous: c, requestFactory: c} c.Book = BookService{Synchronous: c} c.Candles = CandleService{Synchronous: c} c.Trades = TradeService{Synchronous: c, requestFactory: c} c.Tickers = TickerService{Synchronous: c, requestFactory: c} c.Currencies = CurrenciesService{Synchronous: c, requestFactory: c} c.Platform = PlatformService{Synchronous: c} c.Positions = PositionService{Synchronous: c, requestFactory: c} c.Wallet = WalletService{Synchronous: c, requestFactory: c} c.Ledgers = LedgerService{Synchronous: c, requestFactory: c} c.Stats = StatsService{Synchronous: c, requestFactory: c} c.Status = StatusService{Synchronous: c, requestFactory: c} c.Derivatives = DerivativesService{Synchronous: c, requestFactory: c} c.Funding = FundingService{Synchronous: c, requestFactory: c} c.Pulse = PulseService{Synchronous: c, requestFactory: c} c.Invoice = InvoiceService{Synchronous: c, requestFactory: c} c.Market = MarketService{Synchronous: c, requestFactory: c} return c } // Set the clients credentials in order to make authenticated requests func (c *Client) Credentials(key string, secret string) *Client { c.apiKey = key c.apiSecret = secret return c } // Request is a wrapper for standard http.Request. Default method is POST with no data. type Request struct { RefURL string // ref url Data []byte // body data Method string // http method Params url.Values // query parameters Headers map[string]string } // Response is a wrapper for standard http.Response and provides more methods. type Response struct { Response *http.Response Body []byte } func (c *Client) sign(msg string) (string, error) { sig := hmac.New(sha512.New384, []byte(c.apiSecret)) _, err := sig.Write([]byte(msg)) if err != nil { return "", nil } return hex.EncodeToString(sig.Sum(nil)), nil } // Create a new authenticated GET request with the given permission type and endpoint url // For example permissionType = "r" and refUrl = "/orders" then the target endpoint will be // https://api.bitfinex.com/v2/auth/r/orders/:Symbol func (c *Client) NewAuthenticatedRequest(permissionType bitfinex.PermissionType, refURL string) (Request, error) { return c.NewAuthenticatedRequestWithBytes(permissionType, refURL, []byte("{}")) } // Create a new authenticated POST request with the given permission type,endpoint url and data (bytes) as the body // For example permissionType = "r" and refUrl = "/orders" then the target endpoint will be // https://api.bitfinex.com/v2/auth/r/orders/:Symbol func (c *Client) NewAuthenticatedRequestWithBytes(permissionType bitfinex.PermissionType, refURL string, data []byte) (Request, error) { authURL := fmt.Sprintf("auth/%s/%s", string(permissionType), refURL) req := NewRequestWithBytes(authURL, data) nonce := c.nonce.GetNonce() msg := "/api/v2/" + authURL + nonce + string(data) sig, err := c.sign(msg) if err != nil { return Request{}, err } req.Headers["Content-Type"] = "application/json" req.Headers["Accept"] = "application/json" req.Headers["bfx-nonce"] = nonce req.Headers["bfx-signature"] = sig req.Headers["bfx-apikey"] = c.apiKey return req, nil } // Create a new authenticated POST request with the given permission type,endpoint url and data (map[string]interface{}) as the body // For example permissionType = "r" and refUrl = "/orders" then the target endpoint will be // https://api.bitfinex.com/v2/auth/r/orders/:Symbol func (c *Client) NewAuthenticatedRequestWithData(permissionType bitfinex.PermissionType, refURL string, data map[string]interface{}) (Request, error) { b, err := json.Marshal(data) if err != nil { return Request{}, err } return c.NewAuthenticatedRequestWithBytes(permissionType, refURL, b) } // Create new POST request with an empty body as payload func NewRequest(refURL string) Request { return NewRequestWithDataMethod(refURL, []byte("{}"), "POST") } // Create a new request with the given method (POST | GET) func NewRequestWithMethod(refURL string, method string) Request { return NewRequestWithDataMethod(refURL, []byte("{}"), method) } // Create a new POST request with the given bytes as body func NewRequestWithBytes(refURL string, data []byte) Request { return NewRequestWithDataMethod(refURL, data, "POST") } // Create a new POST request with the given data (map[string]interface{}) as body func NewRequestWithData(refURL string, data map[string]interface{}) (Request, error) { b, err := json.Marshal(data) if err != nil { return Request{}, err } return NewRequestWithDataMethod(refURL, b, "POST"), nil } // Create a new request with a given method (POST | GET) with bytes as body func NewRequestWithDataMethod(refURL string, data []byte, method string) Request { return Request{ RefURL: refURL, Data: data, Method: method, Headers: make(map[string]string), } } // newResponse creates new wrapper. func newResponse(r *http.Response) *Response { // Use a LimitReader of arbitrary size (here ~8.39MB) to prevent us from // reading overly large response bodies. lr := io.LimitReader(r.Body, 8388608) body, err := ioutil.ReadAll(lr) if err != nil { body = []byte(`Error reading body:` + err.Error()) } return &Response{r, body} } // String converts response body to string. // An empty string will be returned if error. func (r *Response) String() string { return string(r.Body) } // checkResponse checks response status code and response // for errors. func checkResponse(r *Response) error { if c := r.Response.StatusCode; c >= 200 && c <= 299 { return nil } var raw []interface{} // Try to decode error message errorResponse := &ErrorResponse{Response: r} err := json.Unmarshal(r.Body, &raw) if err != nil { errorResponse.Message = "Error decoding response error message. " + "Please see response body for more information." return errorResponse } if len(raw) < 3 { errorResponse.Message = fmt.Sprintf("Expected response to have three elements but got %#v", raw) return errorResponse } if str, ok := raw[0].(string); !ok || str != "error" { errorResponse.Message = fmt.Sprintf("Expected first element to be \"error\" but got %#v", raw) return errorResponse } code, ok := raw[1].(float64) if !ok { errorResponse.Message = fmt.Sprintf("Expected second element to be error code but got %#v", raw) return errorResponse } errorResponse.Code = int(code) msg, ok := raw[2].(string) if !ok { errorResponse.Message = fmt.Sprintf("Expected third element to be error message but got %#v", raw) return errorResponse } errorResponse.Message = msg return errorResponse } // In case if API will wrong response code // ErrorResponse will be returned to caller type ErrorResponse struct { Response *Response Message string `json:"message"` Code int `json:"code"` } func (r *ErrorResponse) Error() string { return fmt.Sprintf("%v %v: %d %v (%d)", r.Response.Response.Request.Method, r.Response.Response.Request.URL, r.Response.Response.StatusCode, r.Message, r.Code, ) } bitfinex-api-go-2.2.9/v2/rest/currencies.go000066400000000000000000000025571371275744700205350ustar00rootroot00000000000000package rest import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "path" "strings" ) // TradeService manages the Trade endpoint. type CurrenciesService struct { requestFactory Synchronous } // Retreive currency and symbol service configuration data // see https://docs.bitfinex.com/reference#rest-public-conf for more info func (cs *CurrenciesService) Conf(label, symbol, unit, explorer, pairs bool) ([]bitfinex.CurrencyConf, error) { segments := make([]string, 0) if label { segments = append(segments, string(bitfinex.CurrencyLabelMap)) } if symbol { segments = append(segments, string(bitfinex.CurrencySymbolMap)) } if unit { segments = append(segments, string(bitfinex.CurrencyUnitMap)) } if explorer { segments = append(segments, string(bitfinex.CurrencyExplorerMap)) } if pairs { segments = append(segments, string(bitfinex.CurrencyExchangeMap)) } req := NewRequestWithMethod(path.Join("conf", strings.Join(segments,",")), "GET") raw, err := cs.Request(req) if err != nil { return nil, err } // add mapping to raw data parsedRaw := make([]bitfinex.RawCurrencyConf, len(raw)) for index, d := range raw { parsedRaw = append(parsedRaw, bitfinex.RawCurrencyConf{Mapping: segments[index], Data: d}) } // parse to config object configs, err := bitfinex.NewCurrencyConfFromRaw(parsedRaw) if err != nil { return nil, err } return configs, nil } bitfinex-api-go-2.2.9/v2/rest/derivatives.go000066400000000000000000000020031371275744700207020ustar00rootroot00000000000000package rest import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "path" ) // OrderService manages data flow for the Order API endpoint type DerivativesService struct { requestFactory Synchronous } // Update the amount of collateral for a Derivative position // see https://docs.bitfinex.com/reference#rest-auth-deriv-pos-collateral-set for more info func (s *WalletService) SetCollateral(symbol string, amount float64) (bool, error) { urlPath := path.Join("deriv", "collateral", "set") data := map[string]interface{}{ "symbol": symbol, "collateral": amount, } req, err := s.requestFactory.NewAuthenticatedRequestWithData(bitfinex.PermissionRead, urlPath, data) if err != nil { return false, err } raw, err := s.Request(req) if err != nil { return false, err } // [[1]] == success, [] || [[0]] == false if len(raw) <= 0 { return false, nil } item := raw[0].([]interface{}) // [1] == success, [] || [0] == false if len(item) > 0 && item[0].(int) == 1 { return true, nil } return false, nil } bitfinex-api-go-2.2.9/v2/rest/funding.go000066400000000000000000000143331371275744700200200ustar00rootroot00000000000000package rest import ( "encoding/json" "fmt" "path" "github.com/bitfinexcom/bitfinex-api-go/v2" ) // KeepFundingRequest - data structure for constructing keep funding request payload type KeepFundingRequest struct { Type string `json:"type"` ID int `json:"id"` } // FundingService manages the Funding endpoint. type FundingService struct { requestFactory Synchronous } // Retreive all of the active fundign offers // see https://docs.bitfinex.com/reference#rest-auth-funding-offers for more info func (fs *FundingService) Offers(symbol string) (*bitfinex.FundingOfferSnapshot, error) { req, err := fs.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("funding/offers", symbol)) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } offers, err := bitfinex.NewFundingOfferSnapshotFromRaw(raw) if err != nil { return nil, err } return offers, nil } // Retreive all of the past in-active funding offers // see https://docs.bitfinex.com/reference#rest-auth-funding-offers-hist for more info func (fs *FundingService) OfferHistory(symbol string) (*bitfinex.FundingOfferSnapshot, error) { req, err := fs.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("funding/offers", symbol, "hist")) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } offers, err := bitfinex.NewFundingOfferSnapshotFromRaw(raw) if err != nil { return nil, err } return offers, nil } // Retreive all of the active funding loans // see https://docs.bitfinex.com/reference#rest-auth-funding-loans for more info func (fs *FundingService) Loans(symbol string) (*bitfinex.FundingLoanSnapshot, error) { req, err := fs.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("funding/loans", symbol)) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } loans, err := bitfinex.NewFundingLoanSnapshotFromRaw(raw) if err != nil { return nil, err } return loans, nil } // Retreive all of the past in-active funding loans // see https://docs.bitfinex.com/reference#rest-auth-funding-loans-hist for more info func (fs *FundingService) LoansHistory(symbol string) (*bitfinex.FundingLoanSnapshot, error) { req, err := fs.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("funding/loans", symbol, "hist")) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } loans, err := bitfinex.NewFundingLoanSnapshotFromRaw(raw) if err != nil { return nil, err } return loans, nil } // Retreive all of the active credits used in positions // see https://docs.bitfinex.com/reference#rest-auth-funding-credits for more info func (fs *FundingService) Credits(symbol string) (*bitfinex.FundingCreditSnapshot, error) { req, err := fs.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("funding/credits", symbol)) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } loans, err := bitfinex.NewFundingCreditSnapshotFromRaw(raw) if err != nil { return nil, err } return loans, nil } // Retreive all of the past in-active credits used in positions // see https://docs.bitfinex.com/reference#rest-auth-funding-credits-hist for more info func (fs *FundingService) CreditsHistory(symbol string) (*bitfinex.FundingCreditSnapshot, error) { req, err := fs.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("funding/credits", symbol, "hist")) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } loans, err := bitfinex.NewFundingCreditSnapshotFromRaw(raw) if err != nil { return nil, err } return loans, nil } // Retreive all of the matched funding trades // see https://docs.bitfinex.com/reference#rest-auth-funding-trades-hist for more info func (fs *FundingService) Trades(symbol string) (*bitfinex.FundingTradeSnapshot, error) { req, err := fs.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("funding/trades", symbol, "hist")) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } loans, err := bitfinex.NewFundingTradeSnapshotFromRaw(raw) if err != nil { return nil, err } return loans, nil } // Submits a request to create a new funding offer // see https://docs.bitfinex.com/reference#submit-funding-offer for more info func (fs *FundingService) SubmitOffer(fo *bitfinex.FundingOfferRequest) (*bitfinex.Notification, error) { bytes, err := fo.ToJSON() if err != nil { return nil, err } req, err := fs.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("funding/offer/submit"), bytes) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // Submits a request to cancel the given offer // see https://docs.bitfinex.com/reference#cancel-funding-offer for more info func (fs *FundingService) CancelOffer(fc *bitfinex.FundingOfferCancelRequest) (*bitfinex.Notification, error) { bytes, err := fc.ToJSON() if err != nil { return nil, err } req, err := fs.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, "funding/offer/cancel", bytes) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // KeepFunding - toggle to keep funding taken. Specify loan for unused funding and credit for used funding. // see https://docs.bitfinex.com/reference#rest-auth-keep-funding for more info func (fs *FundingService) KeepFunding(args KeepFundingRequest) (*bitfinex.Notification, error) { if args.Type != "credit" && args.Type != "loan" { return nil, fmt.Errorf("Expected type: credit or loan, got: %s", args.Type) } bytes, err := json.Marshal(args) if err != nil { return nil, err } req, err := fs.requestFactory.NewAuthenticatedRequestWithBytes( bitfinex.PermissionWrite, path.Join("funding", "keep"), bytes, ) if err != nil { return nil, err } raw, err := fs.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } bitfinex-api-go-2.2.9/v2/rest/funding_test.go000066400000000000000000000025501371275744700210550ustar00rootroot00000000000000package rest_test import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestKeepFunding(t *testing.T) { t.Run("wrong type", func(t *testing.T) { c := rest.NewClient() kf, err := c.Funding.KeepFunding(rest.KeepFundingRequest{Type: "foo"}) require.NotNil(t, err) require.Nil(t, kf) }) t.Run("calls correct resource with correct payload", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/funding/keep", r.RequestURI) assert.Equal(t, "POST", r.Method) gotReqPld := rest.KeepFundingRequest{} err := json.NewDecoder(r.Body).Decode(&gotReqPld) require.Nil(t, err) expectedReqPld := rest.KeepFundingRequest{Type: "loan", ID: 123} assert.Equal(t, expectedReqPld, gotReqPld) respMock := []interface{}{1568711312683, nil, nil, nil, nil, nil, nil, nil} payload, _ := json.Marshal(respMock) _, err = w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := rest.NewClientWithURL(server.URL) pld := rest.KeepFundingRequest{Type: "loan", ID: 123} rsp, err := c.Funding.KeepFunding(pld) require.Nil(t, err) assert.Equal(t, int64(1568711312683), rsp.MTS) }) } bitfinex-api-go-2.2.9/v2/rest/invoice.go000066400000000000000000000046571371275744700200320ustar00rootroot00000000000000package rest import ( "encoding/json" "fmt" "path" "strconv" "strings" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/invoice" "github.com/bitfinexcom/bitfinex-api-go/v2" ) // InvoiceService manages Invoice endpoint type InvoiceService struct { requestFactory Synchronous } // DepositInvoiceRequest - data structure for constructing deposit invoice request payload type DepositInvoiceRequest struct { Currency string `json:"currency,omitempty"` Wallet string `json:"wallet,omitempty"` Amount string `json:"amount,omitempty"` } var validCurrencies = map[string]struct { name string min float64 max float64 }{ "LNX": { name: "Bitcoin Lightning Network", min: 0.000001, max: 0.02, }, } func validCurrency(currency string) error { if _, ok := validCurrencies[currency]; !ok { var sb strings.Builder sb.WriteString(currency) sb.WriteString(" is not supported currency. Supported currencies: [") for sc := range validCurrencies { sb.WriteString(fmt.Sprintf(" %s(%s) ", sc, validCurrencies[sc].name)) } sb.WriteString("]") return fmt.Errorf(sb.String()) } return nil } func validAmount(currency, amount string) error { f, err := strconv.ParseFloat(amount, 64) if err != nil { return err } if f < validCurrencies[currency].min { return fmt.Errorf( "Minimum allowed amount for %s is %f. Got: %f", currency, validCurrencies[currency].min, f, ) } if f > validCurrencies[currency].max { return fmt.Errorf( "Maximum allowed amount for %s is %f. Got: %f", currency, validCurrencies[currency].max, f, ) } return nil } // GenerateInvoice generates a Lightning Network deposit invoice // Accepts DepositInvoiceRequest type as argument // https://docs.bitfinex.com/reference#rest-auth-deposit-invoice func (is *InvoiceService) GenerateInvoice(payload DepositInvoiceRequest) (*invoice.Invoice, error) { if err := validCurrency(payload.Currency); err != nil { return nil, err } if err := validAmount(payload.Currency, payload.Amount); err != nil { return nil, err } pldBytes, err := json.Marshal(payload) if err != nil { return nil, err } req, err := is.NewAuthenticatedRequestWithBytes( bitfinex.PermissionWrite, path.Join("deposit", "invoice"), pldBytes, ) if err != nil { return nil, err } raw, err := is.Request(req) if err != nil { return nil, err } invc, err := invoice.NewFromRaw(raw) if err != nil { return nil, err } return invc, nil } bitfinex-api-go-2.2.9/v2/rest/invoice_test.go000066400000000000000000000051221371275744700210550ustar00rootroot00000000000000package rest_test import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/invoice" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGenerateInvoice(t *testing.T) { t.Run("unsupported currency", func(t *testing.T) { c := rest.NewClient() args := rest.DepositInvoiceRequest{ Currency: "ETH", Wallet: "exchange", Amount: "0.0001", } invc, err := c.Invoice.GenerateInvoice(args) require.NotNil(t, err) require.Nil(t, invc) }) t.Run("amount too small", func(t *testing.T) { c := rest.NewClient() args := rest.DepositInvoiceRequest{ Currency: "LNX", Wallet: "exchange", Amount: "0.0000001", } invc, err := c.Invoice.GenerateInvoice(args) require.NotNil(t, err) require.Nil(t, invc) }) t.Run("amount too large", func(t *testing.T) { c := rest.NewClient() args := rest.DepositInvoiceRequest{ Currency: "LNX", Wallet: "exchange", Amount: "0.03", } invc, err := c.Invoice.GenerateInvoice(args) require.NotNil(t, err) require.Nil(t, invc) }) t.Run("response data slice too short", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { respMock := []interface{}{"invoicehash"} payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := rest.NewClientWithURL(server.URL) pld := rest.DepositInvoiceRequest{ Currency: "LNX", Wallet: "exchange", Amount: "0.0001", } invc, err := c.Invoice.GenerateInvoice(pld) require.NotNil(t, err) require.Nil(t, invc) }) t.Run("valid response data", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/deposit/invoice", r.RequestURI) assert.Equal(t, "POST", r.Method) respMock := []interface{}{ "invoicehash", "invoice", nil, nil, "0.002", } payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := rest.NewClientWithURL(server.URL) pld := rest.DepositInvoiceRequest{ Currency: "LNX", Wallet: "exchange", Amount: "0.002", } invc, err := c.Invoice.GenerateInvoice(pld) require.Nil(t, err) expected := &invoice.Invoice{ InvoiceHash: "invoicehash", Invoice: "invoice", Amount: "0.002", } assert.Equal(t, expected, invc) }) } bitfinex-api-go-2.2.9/v2/rest/ledgers.go000066400000000000000000000016461371275744700200160ustar00rootroot00000000000000package rest import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "path" "fmt" ) // LedgerService manages the Ledgers endpoint. type LedgerService struct { requestFactory Synchronous } // Retrieves all of the past ledger entreies // see https://docs.bitfinex.com/reference#ledgers for more info func (s *LedgerService) Ledgers(currency string, start int64, end int64, max int32) (*bitfinex.LedgerSnapshot, error) { if max > 500 { return nil, fmt.Errorf("Max request limit is higher then 500 : %#v", max) } req, err := s.requestFactory.NewAuthenticatedRequestWithData(bitfinex.PermissionRead, path.Join("ledgers", currency, "hist"), map[string]interface{}{"start": start, "end": end, "limit": max}) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } os, err := bitfinex.NewLedgerSnapshotFromRaw(raw) if err != nil { return nil, err } return os, nil } bitfinex-api-go-2.2.9/v2/rest/market.go000066400000000000000000000040161371275744700176460ustar00rootroot00000000000000package rest import ( "encoding/json" "fmt" "net/url" "path" "strconv" "github.com/bitfinexcom/bitfinex-api-go/pkg/convert" ) type MarketService struct { requestFactory Synchronous } // AveragePriceRequest data structure for constructing average price query params type AveragePriceRequest struct { Symbol string Amount string RateLimit string Period int } // ForeignExchangeRateRequest data structure for constructing foreign // exchange rate request payload type ForeignExchangeRateRequest struct { FirstCurrency string `json:"ccy1"` SecondCurrency string `json:"ccy2"` } // AveragePrice Calculate the average execution price for Trading or rate for Margin funding. // See: https://docs.bitfinex.com/reference#rest-public-calc-market-average-price func (ms *MarketService) AveragePrice(pld AveragePriceRequest) ([]float64, error) { req := NewRequestWithMethod(path.Join("calc", "trade", "avg"), "POST") req.Params = make(url.Values) req.Params.Add("symbol", pld.Symbol) req.Params.Add("amount", pld.Amount) req.Params.Add("rate_limit", pld.RateLimit) req.Params.Add("period", strconv.Itoa(pld.Period)) raw, err := ms.Request(req) if err != nil { return nil, err } resp, err := convert.F64Slice(raw) if err != nil { return nil, err } return resp, nil } // ForeignExchangeRate - Calculate the exchange rate between two currencies // See: https://docs.bitfinex.com/reference#rest-public-calc-foreign-exchange-rate func (ms *MarketService) ForeignExchangeRate(pld ForeignExchangeRateRequest) ([]float64, error) { if len(pld.FirstCurrency) == 0 || len(pld.SecondCurrency) == 0 { return nil, fmt.Errorf("FirstCurrency and SecondCurrency are required arguments") } bytes, err := json.Marshal(pld) if err != nil { return nil, err } req := NewRequestWithBytes(path.Join("calc", "fx"), bytes) req.Headers["Content-Type"] = "application/json" raw, err := ms.Request(req) if err != nil { return nil, err } resp, err := convert.F64Slice(raw) if err != nil { return nil, err } return resp, nil } bitfinex-api-go-2.2.9/v2/rest/market_test.go000066400000000000000000000044371371275744700207140ustar00rootroot00000000000000package rest_test import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/bitfinexcom/bitfinex-api-go/v2/rest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMarketAveragePrice(t *testing.T) { t.Run("calls with valid query params", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/calc/trade/avg?amount=100&period=2&rate_limit=1000.5&symbol=fUSD", r.RequestURI) assert.Equal(t, "POST", r.Method) respMock := []interface{}{ 123.123, 100, } payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := rest.NewClientWithURL(server.URL) args := rest.AveragePriceRequest{ Symbol: "fUSD", Amount: "100", RateLimit: "1000.5", Period: 2, } avgPrice, err := c.Market.AveragePrice(args) require.Nil(t, err) expected := []float64{ 123.123, 100, } assert.Equal(t, expected, avgPrice) }) } func TestForeignExchangeRate(t *testing.T) { t.Run("missing arguments", func(t *testing.T) { c := rest.NewClient() rsp, err := c.Market.ForeignExchangeRate(rest.ForeignExchangeRateRequest{ FirstCurrency: "BTC", }) require.NotNil(t, err) require.Nil(t, rsp) }) t.Run("calls correct resource with correct payload", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/calc/fx", r.RequestURI) assert.Equal(t, "POST", r.Method) gotReqPld := rest.ForeignExchangeRateRequest{} err := json.NewDecoder(r.Body).Decode(&gotReqPld) require.Nil(t, err) expectedReqPld := rest.ForeignExchangeRateRequest{ FirstCurrency: "BTC", SecondCurrency: "USD", } assert.Equal(t, expectedReqPld, gotReqPld) respMock := []interface{}{9151.5} payload, _ := json.Marshal(respMock) _, err = w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := rest.NewClientWithURL(server.URL) rsp, err := c.Market.ForeignExchangeRate(rest.ForeignExchangeRateRequest{ FirstCurrency: "BTC", SecondCurrency: "USD", }) require.Nil(t, err) assert.Equal(t, float64(9151.5), rsp[0]) }) } bitfinex-api-go-2.2.9/v2/rest/orders.go000066400000000000000000000254041371275744700176650ustar00rootroot00000000000000package rest import ( "encoding/json" "fmt" "path" "github.com/bitfinexcom/bitfinex-api-go/v2" ) // OrderService manages data flow for the Order API endpoint type OrderService struct { requestFactory Synchronous } type OrderIDs []int type GroupOrderIDs []int type ClientOrderIDs [][]interface{} type OrderOps [][]interface{} // OrderMultiOpsRequest - data structure for constructing order multi ops request payload type OrderMultiOpsRequest struct { Ops OrderOps `json:"ops"` } // CancelOrderMultiRequest - data structure for constructing cancel order multi request payload type CancelOrderMultiRequest struct { OrderIDs OrderIDs `json:"id,omitempty"` GroupOrderIDs GroupOrderIDs `json:"gid,omitempty"` ClientOrderIDs ClientOrderIDs `json:"cid,omitempty"` All int `json:"all,omitempty"` } // Retrieves all of the active orders // See https://docs.bitfinex.com/reference#rest-auth-orders for more info func (s *OrderService) All() (*bitfinex.OrderSnapshot, error) { // use no symbol, this will get all orders return s.getActiveOrders("") } // Retrieves all of the active orders with for the given symbol // See https://docs.bitfinex.com/reference#rest-auth-orders for more info func (s *OrderService) GetBySymbol(symbol string) (*bitfinex.OrderSnapshot, error) { // use no symbol, this will get all orders return s.getActiveOrders(symbol) } // Retrieve an active order by the given ID // See https://docs.bitfinex.com/reference#rest-auth-orders for more info func (s *OrderService) GetByOrderId(orderID int64) (o *bitfinex.Order, err error) { os, err := s.All() if err != nil { return nil, err } for _, order := range os.Snapshot { if order.ID == orderID { return order, nil } } return nil, bitfinex.ErrNotFound } // Retrieves all past orders // See https://docs.bitfinex.com/reference#orders-history for more info func (s *OrderService) AllHistory() (*bitfinex.OrderSnapshot, error) { // use no symbol, this will get all orders return s.getHistoricalOrders("") } // Retrieves all past orders with the given symbol // See https://docs.bitfinex.com/reference#orders-history for more info func (s *OrderService) GetHistoryBySymbol(symbol string) (*bitfinex.OrderSnapshot, error) { // use no symbol, this will get all orders return s.getHistoricalOrders(symbol) } // Retrieve a single order in history with the given id // See https://docs.bitfinex.com/reference#orders-history for more info func (s *OrderService) GetHistoryByOrderId(orderID int64) (o *bitfinex.Order, err error) { os, err := s.AllHistory() if err != nil { return nil, err } for _, order := range os.Snapshot { if order.ID == orderID { return order, nil } } return nil, bitfinex.ErrNotFound } // Retrieves the trades generated by an order // See https://docs.bitfinex.com/reference#orders-history for more info func (s *OrderService) OrderTrades(symbol string, orderID int64) (*bitfinex.TradeExecutionUpdateSnapshot, error) { key := fmt.Sprintf("%s:%d", symbol, orderID) req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("order", key, "trades")) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewTradeExecutionUpdateSnapshotFromRaw(raw) } func (s *OrderService) getActiveOrders(symbol string) (*bitfinex.OrderSnapshot, error) { req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("orders", symbol)) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } os, err := bitfinex.NewOrderSnapshotFromRaw(raw) if err != nil { return nil, err } if os == nil { return &bitfinex.OrderSnapshot{}, nil } return os, nil } func (s *OrderService) getHistoricalOrders(symbol string) (*bitfinex.OrderSnapshot, error) { req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("orders", symbol, "hist")) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } os, err := bitfinex.NewOrderSnapshotFromRaw(raw) if err != nil { return nil, err } if os == nil { return &bitfinex.OrderSnapshot{}, nil } return os, nil } // Submit a request to create a new order // see https://docs.bitfinex.com/reference#submit-order for more info func (s *OrderService) SubmitOrder(order *bitfinex.OrderNewRequest) (*bitfinex.Notification, error) { bytes, err := order.ToJSON() if err != nil { return nil, err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("order", "submit"), bytes) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // Submit a request to update an order with the given id with the given changes // see https://docs.bitfinex.com/reference#order-update for more info func (s *OrderService) SubmitUpdateOrder(order *bitfinex.OrderUpdateRequest) (*bitfinex.Notification, error) { bytes, err := order.ToJSON() if err != nil { return nil, err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("order", "update"), bytes) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // Submit a request to cancel an order with the given Id // see https://docs.bitfinex.com/reference#cancel-order for more info func (s *OrderService) SubmitCancelOrder(oc *bitfinex.OrderCancelRequest) error { bytes, err := oc.ToJSON() if err != nil { return err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("order", "cancel"), bytes) if err != nil { return err } _, err = s.Request(req) if err != nil { return err } return nil } // CancelOrderMulti cancels multiple orders simultaneously. Orders can be canceled based on the Order ID, // the combination of Client Order ID and Client Order Date, or the Group Order ID. Alternatively, the body // param 'all' can be used with a value of 1 to cancel all orders. // see https://docs.bitfinex.com/reference#rest-auth-order-cancel-multi for more info func (s *OrderService) CancelOrderMulti(args CancelOrderMultiRequest) (*bitfinex.Notification, error) { bytes, err := json.Marshal(args) if err != nil { return nil, err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes( bitfinex.PermissionWrite, path.Join("order", "cancel", "multi"), bytes, ) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // CancelOrdersMultiOp cancels multiple orders simultaneously. Accepts a slice of order ID's to be canceled. // see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info func (s *OrderService) CancelOrdersMultiOp(ids OrderIDs) (*bitfinex.Notification, error) { pld := OrderMultiOpsRequest{ Ops: OrderOps{ { "oc_multi", map[string][]int{"id": ids}, }, }, } bytes, err := json.Marshal(pld) if err != nil { return nil, err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes( bitfinex.PermissionWrite, path.Join("order", "multi"), bytes, ) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // CancelOrderMultiOp cancels order. Accepts orderID to be canceled. // see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info func (s *OrderService) CancelOrderMultiOp(orderID int) (*bitfinex.Notification, error) { pld := OrderMultiOpsRequest{ Ops: OrderOps{ { "oc", map[string]int{"id": orderID}, }, }, } bytes, err := json.Marshal(pld) if err != nil { return nil, err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes( bitfinex.PermissionWrite, path.Join("order", "multi"), bytes, ) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // OrderNewMultiOp creates new order. Accepts instance of bitfinex.OrderNewRequest // see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info func (s *OrderService) OrderNewMultiOp(order bitfinex.OrderNewRequest) (*bitfinex.Notification, error) { pld := OrderMultiOpsRequest{ Ops: OrderOps{ { "on", order.EnrichedPayload(), }, }, } bytes, err := json.Marshal(pld) if err != nil { return nil, err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes( bitfinex.PermissionWrite, path.Join("order", "multi"), bytes, ) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // OrderUpdateMultiOp updates order. Accepts instance of bitfinex.OrderUpdateRequest // see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info func (s *OrderService) OrderUpdateMultiOp(order bitfinex.OrderUpdateRequest) (*bitfinex.Notification, error) { pld := OrderMultiOpsRequest{ Ops: OrderOps{ { "ou", order.EnrichedPayload(), }, }, } bytes, err := json.Marshal(pld) if err != nil { return nil, err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes( bitfinex.PermissionWrite, path.Join("order", "multi"), bytes, ) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // OrderMultiOp - send Multiple order-related operations. Please note the sent object has // only one property with a value of a slice of slices detailing each order operation. // see https://docs.bitfinex.com/reference#rest-auth-order-multi for more info func (s *OrderService) OrderMultiOp(ops OrderOps) (*bitfinex.Notification, error) { enrichedOrderOps := OrderOps{} for _, v := range ops { if v[0] == "on" { o, ok := v[1].(bitfinex.OrderNewRequest) if !ok { return nil, fmt.Errorf("Invalid type for `on` operation. Expected: bitfinex.OrderNewRequest") } v[1] = o.EnrichedPayload() } if v[0] == "ou" { o, ok := v[1].(bitfinex.OrderUpdateRequest) if !ok { return nil, fmt.Errorf("Invalid type for `ou` operation. Expected: bitfinex.OrderUpdateRequest") } v[1] = o.EnrichedPayload() } enrichedOrderOps = append(enrichedOrderOps, v) } pld := OrderMultiOpsRequest{Ops: enrichedOrderOps} bytes, err := json.Marshal(pld) if err != nil { return nil, err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes( bitfinex.PermissionWrite, path.Join("order", "multi"), bytes, ) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } bitfinex-api-go-2.2.9/v2/rest/orders_test.go000066400000000000000000000243521371275744700207250ustar00rootroot00000000000000package rest import ( "bytes" "encoding/json" "io/ioutil" "net/http" "net/http/httptest" "testing" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestOrdersAll(t *testing.T) { httpDo := func(_ *http.Client, req *http.Request) (*http.Response, error) { msg := ` [ [4419360502,null,83283216761,"tIOTBTC",1508281683000,1508281731000,63938,63938,"EXCHANGE LIMIT",null,null,null,null,"CANCELED",null,null,0.0000843,0,0,0,null,null,null,0,0,null], [4419354239,null,83265164211,"tIOTBTC",1508281665000,1508281674000,63976,63976,"EXCHANGE LIMIT",null,null,null,null,"CANCELED",null,null,0.00008425,0,0,0,null,null,null,0,0,null], [4419339620,null,83217673277,"tIOTBTC",1508281618000,1508281653000,64014,64014,"EXCHANGE LIMIT",null,null,null,null,"CANCELED",null,null,0.0000842,0,0,0,null,null,null,0,0,null] ]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } orders, err := NewClientWithHttpDo(httpDo).Orders.All() if err != nil { t.Error(err) } if len(orders.Snapshot) != 3 { t.Fatalf("expected three orders but got %d", len(orders.Snapshot)) } } func TestOrdersHistory(t *testing.T) { httpDo := func(_ *http.Client, req *http.Request) (*http.Response, error) { msg := ` [ [4419360502,null,83283216761,"tIOTBTC",1508281683000,1508281731000,63938,63938,"EXCHANGE LIMIT",null,null,null,null,"CANCELED",null,null,0.0000843,0,0,0,null,null,null,0,0,null], [4419354239,null,83265164211,"tIOTBTC",1508281665000,1508281674000,63976,63976,"EXCHANGE LIMIT",null,null,null,null,"CANCELED",null,null,0.00008425,0,0,0,null,null,null,0,0,null], [4419339620,null,83217673277,"tIOTBTC",1508281618000,1508281653000,64014,64014,"EXCHANGE LIMIT",null,null,null,null,"CANCELED",null,null,0.0000842,0,0,0,null,null,null,0,0,null] ]` resp := http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString(msg)), StatusCode: 200, } return &resp, nil } orders, err := NewClientWithHttpDo(httpDo).Orders.GetHistoryBySymbol(bitfinex.TradingPrefix + bitfinex.IOTBTC) if err != nil { t.Error(err) } if len(orders.Snapshot) != 3 { t.Errorf("expected three orders but got %d", len(orders.Snapshot)) } } func TestCancelOrderMulti(t *testing.T) { t.Run("calls correct resource with correct payload", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/order/cancel/multi", r.RequestURI) assert.Equal(t, "POST", r.Method) gotReqPld := CancelOrderMultiRequest{} err := json.NewDecoder(r.Body).Decode(&gotReqPld) require.Nil(t, err) expectedReqPld := CancelOrderMultiRequest{ OrderIDs: OrderIDs{123}, GroupOrderIDs: GroupOrderIDs{234}, All: 1, } assert.Equal(t, expectedReqPld, gotReqPld) respMock := []interface{}{1568711312683, nil, nil, nil, nil, nil, nil, nil} payload, _ := json.Marshal(respMock) _, err = w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) pld := CancelOrderMultiRequest{ OrderIDs: OrderIDs{123}, GroupOrderIDs: GroupOrderIDs{234}, All: 1, } rsp, err := c.Orders.CancelOrderMulti(pld) require.Nil(t, err) assert.Equal(t, int64(1568711312683), rsp.MTS) }) } func TestCancelOrdersMultiOp(t *testing.T) { t.Run("calls correct resource with correct payload", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/order/multi", r.RequestURI) assert.Equal(t, "POST", r.Method) gotReqPld := OrderMultiOpsRequest{} err := json.NewDecoder(r.Body).Decode(&gotReqPld) require.Nil(t, err) expectedReqPld := []interface{}{ "oc_multi", map[string]interface{}{ "id": []interface{}{ float64(1189428429), float64(1189428430), }, }, } assert.Equal(t, expectedReqPld, gotReqPld.Ops[0]) respMock := []interface{}{1568711312683, nil, nil, nil, nil, nil, nil, nil} payload, _ := json.Marshal(respMock) _, err = w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) rsp, err := c.Orders.CancelOrdersMultiOp(OrderIDs{1189428429, 1189428430}) require.Nil(t, err) assert.Equal(t, int64(1568711312683), rsp.MTS) }) } func TestCancelOrderMultiOp(t *testing.T) { t.Run("calls correct resource with correct payload", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/order/multi", r.RequestURI) assert.Equal(t, "POST", r.Method) gotReqPld := OrderMultiOpsRequest{} err := json.NewDecoder(r.Body).Decode(&gotReqPld) require.Nil(t, err) expectedReqPld := []interface{}{ "oc", map[string]interface{}{"id": float64(1189428429)}, } assert.Equal(t, expectedReqPld, gotReqPld.Ops[0]) respMock := []interface{}{1568711312683, nil, nil, nil, nil, nil, nil, nil} payload, _ := json.Marshal(respMock) _, err = w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) rsp, err := c.Orders.CancelOrderMultiOp(1189428429) require.Nil(t, err) assert.Equal(t, int64(1568711312683), rsp.MTS) }) } func TestOrderNewMultiOp(t *testing.T) { t.Run("calls correct resource with correct payload", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/order/multi", r.RequestURI) assert.Equal(t, "POST", r.Method) gotReqPld := OrderMultiOpsRequest{} err := json.NewDecoder(r.Body).Decode(&gotReqPld) require.Nil(t, err) expectedReqPld := []interface{}{ "on", map[string]interface{}{ "amount": "0.002", "cid": float64(119), "gid": float64(118), "price": "12", "symbol": "tBTCUSD", "type": "EXCHANGE LIMIT", "flags": float64(512), "meta": map[string]interface{}{ "aff_code": "abc", }, }, } assert.Equal(t, expectedReqPld, gotReqPld.Ops[0]) respMock := []interface{}{1568711312683, nil, nil, nil, nil, nil, nil, nil} payload, _ := json.Marshal(respMock) _, err = w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) o := bitfinex.OrderNewRequest{ CID: 119, GID: 118, Type: "EXCHANGE LIMIT", Symbol: "tBTCUSD", Price: 12, Amount: 0.002, AffiliateCode: "abc", Close: true, } rsp, err := c.Orders.OrderNewMultiOp(o) require.Nil(t, err) assert.Equal(t, int64(1568711312683), rsp.MTS) }) } func TestOrderUpdateMultiOp(t *testing.T) { t.Run("calls correct resource with correct payload", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/order/multi", r.RequestURI) assert.Equal(t, "POST", r.Method) gotReqPld := OrderMultiOpsRequest{} err := json.NewDecoder(r.Body).Decode(&gotReqPld) require.Nil(t, err) expectedReqPld := []interface{}{ "ou", map[string]interface{}{ "amount": "0.002", "price": "12", "id": float64(1189503586), "flags": float64(64), }, } assert.Equal(t, expectedReqPld, gotReqPld.Ops[0]) respMock := []interface{}{1568711312683, nil, nil, nil, nil, nil, nil, nil} payload, _ := json.Marshal(respMock) _, err = w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) o := bitfinex.OrderUpdateRequest{ ID: 1189503586, Price: 12, Amount: 0.002, Hidden: true, } rsp, err := c.Orders.OrderUpdateMultiOp(o) require.Nil(t, err) assert.Equal(t, int64(1568711312683), rsp.MTS) }) } func TestOrderMultiOp(t *testing.T) { t.Run("calls correct resource with correct payload", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/order/multi", r.RequestURI) assert.Equal(t, "POST", r.Method) gotReqPld := OrderMultiOpsRequest{} err := json.NewDecoder(r.Body).Decode(&gotReqPld) require.Nil(t, err) expectedReqPld := map[string][]interface{}{ "on": { "on", map[string]interface{}{ "amount": "0.001", "cid": float64(987), "flags": float64(4096), "gid": float64(876), "meta": map[string]interface{}{ "aff_code": "abc", }, "price": "13", "symbol": "tBTCUSD", "type": "EXCHANGE LIMIT", }, }, "ou": { "ou", map[string]interface{}{ "amount": "0.002", "price": "15", "id": float64(1189503342), "flags": float64(64), }, }, "oc": { "oc", map[string]interface{}{"id": float64(1189502430)}, }, "oc_multi": { "oc_multi", map[string]interface{}{ "id": []interface{}{ float64(1189502431), float64(1189502432), }, }, }, } for _, v := range gotReqPld.Ops { key := v[0].(string) assert.Equal(t, expectedReqPld[key][1], v[1]) } respMock := []interface{}{1568711312683, nil, nil, nil, nil, nil, nil, nil} payload, _ := json.Marshal(respMock) _, err = w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) pld := OrderOps{ { "on", bitfinex.OrderNewRequest{ CID: 987, GID: 876, Type: "EXCHANGE LIMIT", Symbol: "tBTCUSD", Price: 13, Amount: 0.001, PostOnly: true, AffiliateCode: "abc", }, }, { "oc", map[string]int{"id": 1189502430}, }, { "oc_multi", map[string][]int{"id": OrderIDs{1189502431, 1189502432}}, }, { "ou", bitfinex.OrderUpdateRequest{ ID: 1189503342, Price: 15, Amount: 0.002, Hidden: true, }, }, } rsp, err := c.Orders.OrderMultiOp(pld) require.Nil(t, err) assert.Equal(t, int64(1568711312683), rsp.MTS) }) } bitfinex-api-go-2.2.9/v2/rest/platform_status.go000066400000000000000000000006301371275744700216100ustar00rootroot00000000000000package rest type PlatformService struct { Synchronous } // Retrieves the current status of the platform // see https://docs.bitfinex.com/reference#rest-public-platform-status for more info func (p *PlatformService) Status() (bool, error) { raw, err := p.Request(NewRequestWithMethod("platform/status", "GET")) if err != nil { return false, err } return len(raw) > 0 && raw[0].(float64) == 1, nil } bitfinex-api-go-2.2.9/v2/rest/positions.go000066400000000000000000000023421371275744700204120ustar00rootroot00000000000000package rest import ( "github.com/bitfinexcom/bitfinex-api-go/v2" ) // PositionService manages the Position endpoint. type PositionService struct { requestFactory Synchronous } // Retrieves all of the active positions // see https://docs.bitfinex.com/reference#rest-auth-positions for more info func (s *PositionService) All() (*bitfinex.PositionSnapshot, error) { req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, "positions") if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } os, err := bitfinex.NewPositionSnapshotFromRaw(raw) if err != nil { return nil, err } return os, nil } // Submits a request to claim an active position with the given id // see https://docs.bitfinex.com/reference#claim-position for more info func (s *PositionService) Claim(cp *bitfinex.ClaimPositionRequest) (*bitfinex.Notification, error) { bytes, err := cp.ToJSON() if err != nil { return nil, err } req, err := s.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, "position/claim", bytes) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } bitfinex-api-go-2.2.9/v2/rest/pulse.go000066400000000000000000000067131371275744700175210ustar00rootroot00000000000000package rest import ( "encoding/json" "fmt" "net/url" "path" "strconv" "github.com/bitfinexcom/bitfinex-api-go/pkg/convert" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulse" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulseprofile" "github.com/bitfinexcom/bitfinex-api-go/v2" ) type PulseService struct { requestFactory Synchronous } type Nickname string // PublicPulseProfile returns details for a specific Pulse profile // https://docs.bitfinex.com/reference#rest-public-pulse-profile func (ps *PulseService) PublicPulseProfile(nickname Nickname) (*pulseprofile.PulseProfile, error) { if (len(nickname)) == 0 { return nil, fmt.Errorf("nickname is required argument") } req := NewRequestWithMethod(path.Join("pulse", "profile", string(nickname)), "GET") raw, err := ps.Request(req) if err != nil { return nil, err } pp, err := pulseprofile.NewFromRaw(raw) if err != nil { return nil, err } return pp, nil } // PublicPulseHistory returns latest pulse messages. You can specify // an end timestamp to view older messages. // see https://docs.bitfinex.com/reference#rest-public-pulse-hist func (ps *PulseService) PublicPulseHistory(limit int, end bitfinex.Mts) ([]*pulse.Pulse, error) { req := NewRequestWithMethod(path.Join("pulse", "hist"), "GET") req.Params = make(url.Values) req.Params.Add("limit", strconv.Itoa(limit)) req.Params.Add("end", strconv.FormatInt(int64(end), 10)) raw, err := ps.Request(req) if err != nil { return nil, err } pph, err := pulse.NewFromRaw(raw) if err != nil { return nil, err } return pph, nil } // AddPulse submits pulse message // see https://docs.bitfinex.com/reference#rest-auth-pulse-add func (ps *PulseService) AddPulse(p *pulse.Pulse) (*pulse.Pulse, error) { tl := len(p.Title) if tl < 16 || tl > 120 { return nil, fmt.Errorf("Title length min 16 and max 120 characters. Got:%d", tl) } payload, err := json.Marshal(p) if err != nil { return nil, err } req, err := ps.requestFactory.NewAuthenticatedRequestWithBytes(bitfinex.PermissionWrite, path.Join("pulse", "add"), payload) if err != nil { return nil, err } raw, err := ps.Request(req) if err != nil { return nil, err } pm, err := pulse.NewSingleFromRaw(raw) if err != nil { return nil, err } return pm, nil } // PulseHistory allows you to retrieve your pulse history. Call function with // "false" boolean value for private and with "true" for public pulse history. // see https://docs.bitfinex.com/reference#rest-auth-pulse-hist func (ps *PulseService) PulseHistory(isPublic bool) ([]*pulse.Pulse, error) { req, err := ps.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("pulse", "hist")) if err != nil { return nil, err } public := "0" if isPublic { public = "1" } req.Params = make(url.Values) req.Params.Add("isPublic", public) raw, err := ps.Request(req) if err != nil { return nil, err } pph, err := pulse.NewFromRaw(raw) if err != nil { return nil, err } return pph, nil } // DeletePulse removes your pulse message. Returns 0 if no pulse was deleted and 1 if it was // see https://docs.bitfinex.com/reference#rest-auth-pulse-del func (ps *PulseService) DeletePulse(pid string) (int, error) { payload := map[string]interface{}{"pid": pid} req, err := ps.NewAuthenticatedRequestWithData(bitfinex.PermissionWrite, path.Join("pulse", "del"), payload) if err != nil { return 0, err } raw, err := ps.Request(req) if err != nil { return 0, err } return convert.ToInt(raw[0]), nil } bitfinex-api-go-2.2.9/v2/rest/pulse_test.go000066400000000000000000000230661371275744700205600ustar00rootroot00000000000000package rest import ( "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulse" "github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulseprofile" "github.com/bitfinexcom/bitfinex-api-go/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPublicPulseProfile(t *testing.T) { t.Run("missing arguments", func(t *testing.T) { c := NewClient() pp, err := c.Pulse.PublicPulseProfile("") require.NotNil(t, err) require.Nil(t, pp) }) t.Run("response data slice too short", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { respMock := []interface{}{"abc123"} payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) pp, err := c.Pulse.PublicPulseProfile("Bitfinex") require.NotNil(t, err) require.Nil(t, pp) }) t.Run("valid response data", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/pulse/profile/Bitfinex", r.RequestURI) respMock := []interface{}{ "abc123", float64(1591614631576), nil, "nickname", nil, "picture", "text", nil, nil, "twitter", nil, 30, 5, nil, } payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) pp, err := c.Pulse.PublicPulseProfile("Bitfinex") require.Nil(t, err) expected := &pulseprofile.PulseProfile{ ID: "abc123", MTS: 1591614631576, Nickname: "nickname", Picture: "picture", Text: "text", TwitterHandle: "twitter", } assert.Equal(t, expected, pp) }) } func TestPublicPulseHistory(t *testing.T) { t.Run("response data slice too short", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { respMock := []interface{}{ []interface{}{"id"}, } payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) now := time.Now() millis := now.UnixNano() / 1000000 end := bitfinex.Mts(millis) pp, err := c.Pulse.PublicPulseHistory(1, end) require.NotNil(t, err) require.Nil(t, pp) }) t.Run("valid response data no profile", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/pulse/hist?end=1591614631576&limit=1", r.RequestURI) limit := r.URL.Query().Get("limit") end := r.URL.Query().Get("end") assert.Equal(t, "1", limit) assert.Equal(t, "1591614631576", end) respMock := []interface{}{ []interface{}{ "id", float64(1591614631576), nil, "uid", nil, "title", "content", nil, nil, 1, 1, nil, []interface{}{"tag1", "tag2"}, []interface{}{"attach1", "attach2"}, nil, 5, nil, nil, nil, }, } payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) end := bitfinex.Mts(1591614631576) pph, err := c.Pulse.PublicPulseHistory(1, end) require.Nil(t, err) expected := &pulse.Pulse{ ID: "id", MTS: 1591614631576, UserID: "uid", Title: "title", Content: "content", IsPin: 1, IsPublic: 1, Tags: []string{"tag1", "tag2"}, Attachments: []string{"attach1", "attach2"}, Likes: 5, } assert.Equal(t, expected, pph[0]) }) } func TestAddPulse(t *testing.T) { t.Run("invalid payload", func(t *testing.T) { p := &pulse.Pulse{Title: "foo"} c := NewClient() pm, err := c.Pulse.AddPulse(p) require.NotNil(t, err) require.Nil(t, pm) }) t.Run("response data slice too short", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/pulse/add", r.RequestURI) respMock := []interface{}{"id"} payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) pm, err := c.Pulse.AddPulse(&pulse.Pulse{Title: "foo bar baz qux 123"}) require.NotNil(t, err) require.Nil(t, pm) }) t.Run("valid payload", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { respMock := []interface{}{ "id", float64(1591614631576), nil, "uid", nil, "title", "content", nil, nil, 1, 1, nil, []interface{}{"tag1", "tag2"}, []interface{}{"attach1", "attach2"}, nil, 5, nil, nil, nil, } payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) pm, err := c.Pulse.AddPulse(&pulse.Pulse{Title: "foo bar baz qux 123"}) require.Nil(t, err) expected := &pulse.Pulse{ ID: "id", MTS: 1591614631576, UserID: "uid", Title: "title", Content: "content", IsPin: 1, IsPublic: 1, Tags: []string{"tag1", "tag2"}, Attachments: []string{"attach1", "attach2"}, Likes: 5, } assert.Equal(t, expected, pm) }) } func TestPulseHistory(t *testing.T) { t.Run("response data slice too short", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/r/pulse/hist?isPublic=0", r.RequestURI) isPublic := r.URL.Query().Get("isPublic") assert.Equal(t, "0", isPublic) respMock := []interface{}{ []interface{}{"id"}, } payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) pp, err := c.Pulse.PulseHistory(false) require.NotNil(t, err) require.Nil(t, pp) }) t.Run("private pulse history", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { isPublic := r.URL.Query().Get("isPublic") assert.Equal(t, "0", isPublic) respMock := []interface{}{ []interface{}{ "id", float64(1591614631576), nil, "uid", nil, "title", "content", nil, nil, 1, 1, nil, []interface{}{"tag1", "tag2"}, []interface{}{"attach1", "attach2"}, nil, 5, nil, nil, nil, }, } payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) pm, err := c.Pulse.PulseHistory(false) require.Nil(t, err) expected := &pulse.Pulse{ ID: "id", MTS: 1591614631576, UserID: "uid", Title: "title", Content: "content", IsPin: 1, IsPublic: 1, Tags: []string{"tag1", "tag2"}, Attachments: []string{"attach1", "attach2"}, Likes: 5, } assert.Equal(t, expected, pm[0]) }) t.Run("public pulse history", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { isPublic := r.URL.Query().Get("isPublic") assert.Equal(t, "1", isPublic) respMock := []interface{}{ []interface{}{ "id", float64(1591614631576), nil, "uid", nil, "title", "content", nil, nil, 1, 1, nil, []interface{}{"tag1", "tag2"}, []interface{}{"attach1", "attach2"}, nil, 5, nil, nil, []interface{}{ []interface{}{ "abc123", float64(1591614631576), nil, "nickname", nil, "picture", "text", nil, nil, "twitter", nil, 30, 5, nil, }, }, }, } payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) pm, err := c.Pulse.PulseHistory(true) require.Nil(t, err) expected := &pulse.Pulse{ ID: "id", MTS: 1591614631576, UserID: "uid", Title: "title", Content: "content", IsPin: 1, IsPublic: 1, Tags: []string{"tag1", "tag2"}, Attachments: []string{"attach1", "attach2"}, Likes: 5, PulseProfile: &pulseprofile.PulseProfile{ ID: "abc123", MTS: 1591614631576, Nickname: "nickname", Picture: "picture", Text: "text", TwitterHandle: "twitter", }, } assert.Equal(t, expected, pm[0]) }) } func TestDeletePulse(t *testing.T) { t.Run("response", func(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth/w/pulse/del", r.RequestURI) respMock := []interface{}{1} payload, _ := json.Marshal(respMock) _, err := w.Write(payload) require.Nil(t, err) } server := httptest.NewServer(http.HandlerFunc(handler)) defer server.Close() c := NewClientWithURL(server.URL) deleted, err := c.Pulse.DeletePulse("abc123") require.Nil(t, err) assert.Equal(t, 1, deleted) }) } bitfinex-api-go-2.2.9/v2/rest/stats.go000066400000000000000000000103411371275744700175170ustar00rootroot00000000000000package rest import ( "fmt" "path" "github.com/bitfinexcom/bitfinex-api-go/v2" ) // TradeService manages the Trade endpoint. type StatsService struct { requestFactory Synchronous } func (ss *StatsService) get(symbol string, key bitfinex.StatKey, extra string, section string) ([]interface{}, error) { var params string if extra != "" { params = fmt.Sprintf("%s:1m:%s:%s", string(key), symbol, extra) } else { params = fmt.Sprintf("%s:1m:%s", string(key), symbol) } req := NewRequestWithMethod(path.Join("stats1", params, section), "GET") raw, err := ss.Request(req) if err != nil { return nil, err } return raw, nil } func (ss *StatsService) getHistory(symbol string, key bitfinex.StatKey, extra string) ([]bitfinex.Stat, error) { stats, err := ss.get(symbol, key, extra, "hist") if err != nil { return nil, err } res := make([]bitfinex.Stat, len(stats)) for index, stat := range stats { arr := stat.([]interface{}) period := arr[0].(float64) volume := arr[1].(float64) res[index] = bitfinex.Stat{Period: int64(period), Volume: volume} } return res, nil } func (ss *StatsService) getLast(symbol string, key bitfinex.StatKey, extra string) (*bitfinex.Stat, error) { stat, err := ss.get(symbol, key, extra, "last") if err != nil { return nil, err } if len(stat) == 0 { return nil, fmt.Errorf("Unable to get last stat for %s:%s", symbol, key) } period := stat[0].(float64) volume := stat[1].(float64) return &bitfinex.Stat{Period: int64(period), Volume: volume}, nil } // Retrieves platform statistics for funding history // see https://docs.bitfinex.com/reference#rest-public-stats for more info func (ss *StatsService) FundingHistory(symbol string) ([]bitfinex.Stat, error) { return ss.getHistory(symbol, bitfinex.FundingSizeKey, "") } // Retrieves platform statistics for funding last // see https://docs.bitfinex.com/reference#rest-public-stats for more info func (ss *StatsService) FundingLast(symbol string) (*bitfinex.Stat, error) { return ss.getLast(symbol, bitfinex.FundingSizeKey, "") } // Retrieves platform statistics for credit size history // see https://docs.bitfinex.com/reference#rest-public-stats for more info func (ss *StatsService) CreditSizeHistory(symbol string, side bitfinex.OrderSide) ([]bitfinex.Stat, error) { return ss.getHistory(symbol, bitfinex.CreditSizeKey, "") } // Retrieves platform statistics for credit size last // see https://docs.bitfinex.com/reference#rest-public-stats for more info func (ss *StatsService) CreditSizeLast(symbol string, side bitfinex.OrderSide) (*bitfinex.Stat, error) { return ss.getLast(symbol, bitfinex.CreditSizeKey, "") } // Retrieves platform statistics for credit size history // see https://docs.bitfinex.com/reference#rest-public-stats for more info func (ss *StatsService) SymbolCreditSizeHistory(fundingSymbol string, tradingSymbol string) ([]bitfinex.Stat, error) { return ss.getHistory(fundingSymbol, bitfinex.CreditSizeSymKey, tradingSymbol) } // Retrieves platform statistics for credit size last // see https://docs.bitfinex.com/reference#rest-public-stats for more info func (ss *StatsService) SymbolCreditSizeLast(fundingSymbol string, tradingSymbol string) (*bitfinex.Stat, error) { return ss.getLast(fundingSymbol, bitfinex.CreditSizeSymKey, tradingSymbol) } // Retrieves platform statistics for position history // see https://docs.bitfinex.com/reference#rest-public-stats for more info func (ss *StatsService) PositionHistory(symbol string, side bitfinex.OrderSide) ([]bitfinex.Stat, error) { var strSide string if side == bitfinex.Long { strSide = "long" } else if side == bitfinex.Short { strSide = "short" } else { return nil, fmt.Errorf("Unrecognized side %v in PositionHistory", side) } return ss.getHistory(symbol, bitfinex.PositionSizeKey, strSide) } // Retrieves platform statistics for position last // see https://docs.bitfinex.com/reference#rest-public-stats for more info func (ss *StatsService) PositionLast(symbol string, side bitfinex.OrderSide) (*bitfinex.Stat, error) { var strSide string if side == bitfinex.Long { strSide = "long" } else if side == bitfinex.Short { strSide = "short" } else { return nil, fmt.Errorf("Unrecognized side %v in PositionHistory", side) } return ss.getLast(symbol, bitfinex.PositionSizeKey, strSide) } bitfinex-api-go-2.2.9/v2/rest/status.go000066400000000000000000000036651371275744700177170ustar00rootroot00000000000000package rest import ( "fmt" "github.com/bitfinexcom/bitfinex-api-go/v2" "net/url" "path" "strings" ) // TradeService manages the Trade endpoint. type StatusService struct { requestFactory Synchronous } const ( DERIV_TYPE = "deriv" ) func (ss *StatusService) get(sType string, key string) (*bitfinex.DerivativeStatusSnapshot, error) { req := NewRequestWithMethod(path.Join("status", sType), "GET") req.Params = make(url.Values) req.Params.Add("keys", key) raw, err := ss.Request(req) if err != nil { return nil, err } trueRaw := make([][]interface{}, len(raw)) for i, r := range raw { trueRaw[i] = r.([]interface{}) } s, err := bitfinex.NewDerivativeSnapshotFromRaw(trueRaw) if err != nil { return nil, err } return s, nil } // Retrieves derivative status information for the given symbol from the platform // see https://docs.bitfinex.com/reference#rest-public-status for more info func (ss *StatusService) DerivativeStatus(symbol string) (*bitfinex.DerivativeStatus, error) { data, err := ss.get(DERIV_TYPE, symbol) if err != nil { return nil, err } if len(data.Snapshot) == 0 { return nil, fmt.Errorf("no status found for symbol %s", symbol) } return data.Snapshot[0], err } // Retrieves derivative status information for the given symbols from the platform // see https://docs.bitfinex.com/reference#rest-public-status for more info func (ss *StatusService) DerivativeStatusMulti(symbols []string) ([]*bitfinex.DerivativeStatus, error) { key := strings.Join(symbols, ",") data, err := ss.get(DERIV_TYPE, key) if err != nil { return nil, err } return data.Snapshot, err } // Retrieves derivative status information for all symbols from the platform // see https://docs.bitfinex.com/reference#rest-public-status for more info func (ss *StatusService) DerivativeStatusAll() ([]*bitfinex.DerivativeStatus, error) { data, err := ss.get(DERIV_TYPE, "ALL") if err != nil { return nil, err } return data.Snapshot, err } bitfinex-api-go-2.2.9/v2/rest/tickers.go000066400000000000000000000036131371275744700200310ustar00rootroot00000000000000package rest import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "net/url" "strings" ) // TradeService manages the Trade endpoint. type TickerService struct { requestFactory Synchronous } // Retrieves the ticker for the given symbol // see https://docs.bitfinex.com/reference#rest-public-ticker for more info func (s *TickerService) Get(symbol string) (*bitfinex.Ticker, error) { req := NewRequestWithMethod("tickers", "GET") req.Params = make(url.Values) req.Params.Add("symbols", symbol) raw, err := s.Request(req) if err != nil { return nil, err } ticker, err := bitfinex.NewTickerFromRestRaw(raw[0].([]interface{})) if err != nil { return nil, err } return ticker, nil } // Retrieves the tickers for the given symbols // see https://docs.bitfinex.com/reference#rest-public-ticker for more info func (s *TickerService) GetMulti(symbols []string) (*[]bitfinex.Ticker, error) { req := NewRequestWithMethod("tickers", "GET") req.Params = make(url.Values) req.Params.Add("symbols", strings.Join(symbols, ",")) raw, err := s.Request(req) if err != nil { return nil, err } tickers := make([]bitfinex.Ticker, 0) for _, ticker := range raw { t, err := bitfinex.NewTickerFromRestRaw(ticker.([]interface{})) if err != nil { return nil, err } tickers = append(tickers, *t) } return &tickers, nil } // Retrieves all tickers for all symbols // see https://docs.bitfinex.com/reference#rest-public-ticker for more info func (s *TickerService) All() (*[]bitfinex.Ticker, error) { req := NewRequestWithMethod("tickers", "GET") req.Params = make(url.Values) req.Params.Add("symbols", "ALL") raw, err := s.Request(req) if err != nil { return nil, err } tickers := make([]bitfinex.Ticker, 0) for _, ticker := range raw { t, err := bitfinex.NewTickerFromRestRaw(ticker.([]interface{})) if err != nil { return nil, err } tickers = append(tickers, *t) } return &tickers, nil } bitfinex-api-go-2.2.9/v2/rest/trades.go000066400000000000000000000077621371275744700176600ustar00rootroot00000000000000package rest import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "net/url" "path" "strconv" ) // TradeService manages the Trade endpoint. type TradeService struct { requestFactory Synchronous } // All returns all orders for the authenticated account. // left this in her func (s *TradeService) allAccountWithSymbol(symbol string) (*bitfinex.TradeExecutionUpdateSnapshot, error) { req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("trades", symbol, "hist")) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return parseRawPrivateToSnapshot(raw) } func (s *TradeService) allAccount() (*bitfinex.TradeExecutionUpdateSnapshot, error) { req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("trades", "hist")) if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } return parseRawPrivateToSnapshot(raw) } // Retrieves all matched trades for the account // see https://docs.bitfinex.com/reference#rest-auth-trades-hist for more info func (s *TradeService) AccountAll() (*bitfinex.TradeExecutionUpdateSnapshot, error) { return s.allAccount() } // Retrieves all matched trades with the given symbol for the account // see https://docs.bitfinex.com/reference#rest-auth-trades-hist for more info func (s *TradeService) AccountAllWithSymbol(symbol string) (*bitfinex.TradeExecutionUpdateSnapshot, error) { return s.allAccountWithSymbol(symbol) } // Queries all matched trades with group of optional parameters // see https://docs.bitfinex.com/reference#rest-auth-trades-hist for more info func (s *TradeService) AccountHistoryWithQuery( symbol string, start bitfinex.Mts, end bitfinex.Mts, limit bitfinex.QueryLimit, sort bitfinex.SortOrder, ) (*bitfinex.TradeExecutionUpdateSnapshot, error) { req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, path.Join("trades", symbol, "hist")) if err != nil { return nil, err } req.Params = make(url.Values) req.Params.Add("end", strconv.FormatInt(int64(end), 10)) req.Params.Add("start", strconv.FormatInt(int64(start), 10)) req.Params.Add("limit", strconv.FormatInt(int64(limit), 10)) req.Params.Add("sort", strconv.FormatInt(int64(sort), 10)) raw, err := s.Request(req) if err != nil { return nil, err } return parseRawPrivateToSnapshot(raw) } // Queries all public trades with a group of optional paramters // see https://docs.bitfinex.com/reference#rest-public-trades for more info func (s *TradeService) PublicHistoryWithQuery( symbol string, start bitfinex.Mts, end bitfinex.Mts, limit bitfinex.QueryLimit, sort bitfinex.SortOrder, ) (*bitfinex.TradeSnapshot, error) { req := NewRequestWithMethod(path.Join("trades", symbol, "hist"), "GET") req.Params = make(url.Values) req.Params.Add("end", strconv.FormatInt(int64(end), 10)) req.Params.Add("start", strconv.FormatInt(int64(start), 10)) req.Params.Add("limit", strconv.FormatInt(int64(limit), 10)) req.Params.Add("sort", strconv.FormatInt(int64(sort), 10)) raw, err := s.Request(req) if err != nil { return nil, err } return parseRawPublicToSnapshot(symbol, raw) } func parseRawPublicToSnapshot(symbol string, raw []interface{}) (*bitfinex.TradeSnapshot, error) { if len(raw) <= 0 { // return empty return &bitfinex.TradeSnapshot{Snapshot: make([]*bitfinex.Trade, 0)}, nil } // convert to array of floats dat := make([][]float64, 0) for _, r := range raw { t := []float64{} for _, r2 := range r.([]interface{}) { t = append(t, r2.(float64)) } dat = append(dat, t) } return bitfinex.NewTradeSnapshotFromRaw(symbol, dat) } func parseRawPrivateToSnapshot(raw []interface{}) (*bitfinex.TradeExecutionUpdateSnapshot, error) { if len(raw) <= 0 { return &bitfinex.TradeExecutionUpdateSnapshot{Snapshot: make([]*bitfinex.TradeExecutionUpdate, 0)}, nil } tradeExecutions, err := bitfinex.NewTradeExecutionUpdateSnapshotFromRaw(raw) if err != nil { return nil, err } return tradeExecutions, nil } bitfinex-api-go-2.2.9/v2/rest/transport.go000066400000000000000000000024131371275744700204160ustar00rootroot00000000000000package rest import ( "bytes" "encoding/json" "net/http" "net/url" ) type HttpTransport struct { BaseURL *url.URL HTTPClient *http.Client httpDo func(c *http.Client, req *http.Request) (*http.Response, error) } func (h HttpTransport) Request(req Request) ([]interface{}, error) { var raw []interface{} rel, err := url.Parse(req.RefURL) if err != nil { return nil, err } if req.Params != nil { rel.RawQuery = req.Params.Encode() } if req.Data == nil { req.Data = []byte("{}") } body := bytes.NewReader(req.Data) u := h.BaseURL.ResolveReference(rel) httpReq, err := http.NewRequest(req.Method, u.String(), body) for k, v := range req.Headers { httpReq.Header.Add(k, v) } if err != nil { return nil, err } err = h.do(httpReq, &raw) if err != nil { return nil, err } return raw, nil } // Do executes API request created by NewRequest method or custom *http.Request. func (h HttpTransport) do(req *http.Request, v interface{}) (error) { resp, err := h.httpDo(h.HTTPClient, req) if err != nil { return err } defer resp.Body.Close() response := newResponse(resp) err = checkResponse(response) if err != nil { return err } if v != nil { err = json.Unmarshal(response.Body, v) if err != nil { return err } } return nil } bitfinex-api-go-2.2.9/v2/rest/wallet.go000066400000000000000000000062011371275744700176510ustar00rootroot00000000000000package rest import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "strconv" ) // WalletService manages data flow for the Wallet API endpoint type WalletService struct { requestFactory Synchronous } // Retrieves all of the wallets for the account // see https://docs.bitfinex.com/reference#rest-auth-wallets for more info func (s *WalletService) Wallet() (*bitfinex.WalletSnapshot, error) { req, err := s.requestFactory.NewAuthenticatedRequest(bitfinex.PermissionRead, "wallets") if err != nil { return nil, err } raw, err := s.Request(req) if err != nil { return nil, err } os, err := bitfinex.NewWalletSnapshotFromRaw(raw) if err != nil { return nil, err } return os, nil } // Submits a request to transfer funds from one Bitfinex wallet to another // see https://docs.bitfinex.com/reference#transfer-between-wallets for more info func (ws *WalletService) Transfer(from, to, currency, currencyTo string, amount float64) (*bitfinex.Notification, error) { body := map[string]interface{}{ "from": from, "to": to, "currency": currency, "currency_to": currencyTo, "amount": strconv.FormatFloat(amount, 'f', -1, 64), } req, err := ws.requestFactory.NewAuthenticatedRequestWithData(bitfinex.PermissionWrite, "transfer", body) if err != nil { return nil, err } raw, err := ws.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } func (ws *WalletService) depositAddress(wallet string, method string, renew int) (*bitfinex.Notification, error) { body := map[string]interface{}{ "wallet": wallet, "method": method, "op_renew": renew, } req, err := ws.requestFactory.NewAuthenticatedRequestWithData(bitfinex.PermissionWrite, "deposit/address", body) if err != nil { return nil, err } raw, err := ws.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } // Retrieves the deposit address for the given Bitfinex wallet // see https://docs.bitfinex.com/reference#deposit-address for more info func (ws *WalletService) DepositAddress(wallet, method string) (*bitfinex.Notification, error) { return ws.depositAddress(wallet, method, 0) } // Submits a request to create a new deposit address for the give Bitfinex wallet. Old addresses are still valid. // See https://docs.bitfinex.com/reference#deposit-address for more info func (ws *WalletService) CreateDepositAddress(wallet, method string) (*bitfinex.Notification, error) { return ws.depositAddress(wallet, method, 1) } // Submits a request to withdraw funds from the given Bitfinex wallet to the given address // See https://docs.bitfinex.com/reference#withdraw for more info func (ws *WalletService) Withdraw(wallet, method string, amount float64, address string) (*bitfinex.Notification, error) { body := map[string]interface{}{ "wallet": wallet, "method": method, "amount": strconv.FormatFloat(amount, 'f', -1, 64), "address": address, } req, err := ws.requestFactory.NewAuthenticatedRequestWithData(bitfinex.PermissionWrite, "withdraw", body) if err != nil { return nil, err } raw, err := ws.Request(req) if err != nil { return nil, err } return bitfinex.NewNotificationFromRaw(raw) } bitfinex-api-go-2.2.9/v2/types.go000066400000000000000000001551551371275744700165650ustar00rootroot00000000000000package bitfinex import ( "encoding/json" "errors" "fmt" "log" "math" "strings" "github.com/bitfinexcom/bitfinex-api-go/pkg/convert" ) // Prefixes for available pairs const ( FundingPrefix = "f" TradingPrefix = "t" ) var ( ErrNotFound = errors.New("not found") ) // Candle resolutions const ( OneMinute CandleResolution = "1m" FiveMinutes CandleResolution = "5m" FifteenMinutes CandleResolution = "15m" ThirtyMinutes CandleResolution = "30m" OneHour CandleResolution = "1h" ThreeHours CandleResolution = "3h" SixHours CandleResolution = "6h" TwelveHours CandleResolution = "12h" OneDay CandleResolution = "1D" OneWeek CandleResolution = "7D" TwoWeeks CandleResolution = "14D" OneMonth CandleResolution = "1M" ) type Mts int64 type SortOrder int const ( OldestFirst SortOrder = 1 NewestFirst SortOrder = -1 ) type QueryLimit int const QueryLimitMax QueryLimit = 1000 func CandleResolutionFromString(str string) (CandleResolution, error) { switch str { case string(OneMinute): return OneMinute, nil case string(FiveMinutes): return FiveMinutes, nil case string(FifteenMinutes): return FifteenMinutes, nil case string(ThirtyMinutes): return ThirtyMinutes, nil case string(OneHour): return OneHour, nil case string(ThreeHours): return ThreeHours, nil case string(SixHours): return SixHours, nil case string(TwelveHours): return TwelveHours, nil case string(OneDay): return OneDay, nil case string(OneWeek): return OneWeek, nil case string(TwoWeeks): return TwoWeeks, nil case string(OneMonth): return OneMonth, nil } return OneMinute, fmt.Errorf("could not convert string to resolution: %s", str) } type PermissionType string const ( PermissionRead = "r" PermissionWrite = "w" ) // private type--cannot instantiate. type candleResolution string // CandleResolution provides a typed set of resolutions for candle subscriptions. type CandleResolution candleResolution // Order sides const ( Bid OrderSide = 1 Ask OrderSide = 2 Long OrderSide = 1 Short OrderSide = 2 ) // Settings flags const ( Dec_s int = 9 Time_s int = 32 Timestamp int = 32768 Seq_all int = 65536 Checksum int = 131072 ) type orderSide byte // OrderSide provides a typed set of order sides. type OrderSide orderSide // Book precision levels const ( // Aggregate precision levels Precision0 BookPrecision = "P0" Precision2 BookPrecision = "P2" Precision1 BookPrecision = "P1" Precision3 BookPrecision = "P3" // Raw precision PrecisionRawBook BookPrecision = "R0" ) // private type type bookPrecision string // BookPrecision provides a typed book precision level. type BookPrecision bookPrecision const ( // FrequencyRealtime book frequency gives updates as they occur in real-time. FrequencyRealtime BookFrequency = "F0" // FrequencyTwoPerSecond delivers two book updates per second. FrequencyTwoPerSecond BookFrequency = "F1" // PriceLevelDefault provides a constant default price level for book subscriptions. PriceLevelDefault int = 25 ) type bookFrequency string // BookFrequency provides a typed book frequency. type BookFrequency bookFrequency const ( OrderFlagHidden int = 64 OrderFlagClose int = 512 OrderFlagPostOnly int = 4096 OrderFlagOCO int = 16384 ) // OrderNewRequest represents an order to be posted to the bitfinex websocket // service. type OrderNewRequest struct { GID int64 `json:"gid"` CID int64 `json:"cid"` Type string `json:"type"` Symbol string `json:"symbol"` Amount float64 `json:"amount,string"` Price float64 `json:"price,string"` Leverage int64 `json:"lev,omitempty"` PriceTrailing float64 `json:"price_trailing,string,omitempty"` PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` PriceOcoStop float64 `json:"price_oco_stop,string,omitempty"` Hidden bool `json:"hidden,omitempty"` PostOnly bool `json:"postonly,omitempty"` Close bool `json:"close,omitempty"` OcoOrder bool `json:"oco_order,omitempty"` TimeInForce string `json:"tif,omitempty"` AffiliateCode string `json:"-"` Meta map[string]interface{} `json:"meta,omitempty"` } type OrderMeta struct { AffiliateCode string `json:"aff_code,string,omitempty"` } // MarshalJSON converts the order object into the format required by the bitfinex // websocket service. func (o *OrderNewRequest) MarshalJSON() ([]byte, error) { jsonOrder, err := o.ToJSON() if err != nil { return nil, err } return []byte(fmt.Sprintf("[0, \"on\", null, %s]", string(jsonOrder))), nil } // EnrichedPayload returns enriched representation of order struct for submission func (o *OrderNewRequest) EnrichedPayload() interface{} { pld := struct { GID int64 `json:"gid"` CID int64 `json:"cid"` Type string `json:"type"` Symbol string `json:"symbol"` Amount float64 `json:"amount,string"` Price float64 `json:"price,string"` Leverage int64 `json:"lev,omitempty"` PriceTrailing float64 `json:"price_trailing,string,omitempty"` PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` PriceOcoStop float64 `json:"price_oco_stop,string,omitempty"` TimeInForce string `json:"tif,omitempty"` Flags int `json:"flags,omitempty"` Meta map[string]interface{} `json:"meta,omitempty"` }{ GID: o.GID, CID: o.CID, Type: o.Type, Symbol: o.Symbol, Amount: o.Amount, Price: o.Price, Leverage: o.Leverage, PriceTrailing: o.PriceTrailing, PriceAuxLimit: o.PriceAuxLimit, PriceOcoStop: o.PriceOcoStop, TimeInForce: o.TimeInForce, } if o.Hidden { pld.Flags = pld.Flags + OrderFlagHidden } if o.PostOnly { pld.Flags = pld.Flags + OrderFlagPostOnly } if o.OcoOrder { pld.Flags = pld.Flags + OrderFlagOCO } if o.Close { pld.Flags = pld.Flags + OrderFlagClose } if o.Meta == nil { pld.Meta = make(map[string]interface{}) } if o.AffiliateCode != "" { pld.Meta["aff_code"] = o.AffiliateCode } return pld } func (o *OrderNewRequest) ToJSON() ([]byte, error) { return json.Marshal(o.EnrichedPayload()) } type OrderUpdateRequest struct { ID int64 `json:"id"` GID int64 `json:"gid,omitempty"` Price float64 `json:"price,string,omitempty"` Amount float64 `json:"amount,string,omitempty"` Leverage int64 `json:"lev,omitempty"` Delta float64 `json:"delta,string,omitempty"` PriceTrailing float64 `json:"price_trailing,string,omitempty"` PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` Hidden bool `json:"hidden,omitempty"` PostOnly bool `json:"postonly,omitempty"` TimeInForce string `json:"tif,omitempty"` Meta map[string]interface{} `json:"meta,omitempty"` } // MarshalJSON converts the order object into the format required by the bitfinex // websocket service. func (o *OrderUpdateRequest) MarshalJSON() ([]byte, error) { aux, err := o.ToJSON() if err != nil { return nil, err } return []byte(fmt.Sprintf("[0, \"ou\", null, %s]", string(aux))), nil } func (o *OrderUpdateRequest) EnrichedPayload() interface{} { pld := struct { ID int64 `json:"id"` GID int64 `json:"gid,omitempty"` Price float64 `json:"price,string,omitempty"` Amount float64 `json:"amount,string,omitempty"` Leverage int64 `json:"lev,omitempty"` Delta float64 `json:"delta,string,omitempty"` PriceTrailing float64 `json:"price_trailing,string,omitempty"` PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` Hidden bool `json:"hidden,omitempty"` PostOnly bool `json:"postonly,omitempty"` TimeInForce string `json:"tif,omitempty"` Flags int `json:"flags,omitempty"` Meta map[string]interface{} `json:"meta,omitempty"` }{ ID: o.ID, GID: o.GID, Amount: o.Amount, Leverage: o.Leverage, Price: o.Price, PriceTrailing: o.PriceTrailing, PriceAuxLimit: o.PriceAuxLimit, Delta: o.Delta, TimeInForce: o.TimeInForce, } if o.Meta == nil { pld.Meta = make(map[string]interface{}) } if o.Hidden { pld.Flags = pld.Flags + OrderFlagHidden } if o.PostOnly { pld.Flags = pld.Flags + OrderFlagPostOnly } return pld } func (o *OrderUpdateRequest) ToJSON() ([]byte, error) { return json.Marshal(o.EnrichedPayload()) } // OrderCancelRequest represents an order cancel request. // An order can be cancelled using the internal ID or a // combination of Client ID (CID) and the daten for the given // CID. type OrderCancelRequest struct { ID int64 `json:"id,omitempty"` CID int64 `json:"cid,omitempty"` CIDDate string `json:"cid_date,omitempty"` } func (o *OrderCancelRequest) ToJSON() ([]byte, error) { aux := struct { ID int64 `json:"id,omitempty"` CID int64 `json:"cid,omitempty"` CIDDate string `json:"cid_date,omitempty"` }{ ID: o.ID, CID: o.CID, CIDDate: o.CIDDate, } return json.Marshal(aux) } // MarshalJSON converts the order cancel object into the format required by the // bitfinex websocket service. func (o *OrderCancelRequest) MarshalJSON() ([]byte, error) { aux, err := o.ToJSON() if err != nil { return nil, err } return []byte(fmt.Sprintf("[0, \"oc\", null, %s]", string(aux))), nil } // TODO: MultiOrderCancelRequest represents an order cancel request. type Heartbeat struct { //ChannelIDs []int64 } // OrderType represents the types orders the bitfinex platform can handle. type OrderType string const ( OrderTypeMarket = "MARKET" OrderTypeExchangeMarket = "EXCHANGE MARKET" OrderTypeLimit = "LIMIT" OrderTypeExchangeLimit = "EXCHANGE LIMIT" OrderTypeStop = "STOP" OrderTypeExchangeStop = "EXCHANGE STOP" OrderTypeTrailingStop = "TRAILING STOP" OrderTypeExchangeTrailingStop = "EXCHANGE TRAILING STOP" OrderTypeFOK = "FOK" OrderTypeExchangeFOK = "EXCHANGE FOK" OrderTypeStopLimit = "STOP LIMIT" OrderTypeExchangeStopLimit = "EXCHANGE STOP LIMIT" ) // OrderStatus represents the possible statuses an order can be in. type OrderStatus string const ( OrderStatusActive OrderStatus = "ACTIVE" OrderStatusExecuted OrderStatus = "EXECUTED" OrderStatusPartiallyFilled OrderStatus = "PARTIALLY FILLED" OrderStatusCanceled OrderStatus = "CANCELED" ) // Order as returned from the bitfinex websocket service. type Order struct { ID int64 GID int64 CID int64 Symbol string MTSCreated int64 MTSUpdated int64 Amount float64 AmountOrig float64 Type string TypePrev string MTSTif int64 Flags int64 Status OrderStatus Price float64 PriceAvg float64 PriceTrailing float64 PriceAuxLimit float64 Notify bool Hidden bool PlacedID int64 Meta map[string]interface{} } // NewOrderFromRaw takes the raw list of values as returned from the websocket // service and tries to convert it into an Order. func NewOrderFromRaw(raw []interface{}) (o *Order, err error) { if len(raw) == 12 { o = &Order{ ID: int64(convert.F64ValOrZero(raw[0])), Symbol: convert.SValOrEmpty(raw[1]), Amount: convert.F64ValOrZero(raw[2]), AmountOrig: convert.F64ValOrZero(raw[3]), Type: convert.SValOrEmpty(raw[4]), Status: OrderStatus(convert.SValOrEmpty(raw[5])), Price: convert.F64ValOrZero(raw[6]), PriceAvg: convert.F64ValOrZero(raw[7]), MTSUpdated: convert.I64ValOrZero(raw[8]), // 3 trailing zeroes, what do they map to? } } else if len(raw) < 26 { return o, fmt.Errorf("data slice too short for order: %#v", raw) } else { o = &Order{ ID: int64(convert.F64ValOrZero(raw[0])), GID: int64(convert.F64ValOrZero(raw[1])), CID: int64(convert.F64ValOrZero(raw[2])), Symbol: convert.SValOrEmpty(raw[3]), MTSCreated: int64(convert.F64ValOrZero(raw[4])), MTSUpdated: int64(convert.F64ValOrZero(raw[5])), Amount: convert.F64ValOrZero(raw[6]), AmountOrig: convert.F64ValOrZero(raw[7]), Type: convert.SValOrEmpty(raw[8]), TypePrev: convert.SValOrEmpty(raw[9]), MTSTif: int64(convert.F64ValOrZero(raw[10])), Flags: convert.I64ValOrZero(raw[12]), Status: OrderStatus(convert.SValOrEmpty(raw[13])), Price: convert.F64ValOrZero(raw[16]), PriceAvg: convert.F64ValOrZero(raw[17]), PriceTrailing: convert.F64ValOrZero(raw[18]), PriceAuxLimit: convert.F64ValOrZero(raw[19]), Notify: convert.BValOrFalse(raw[23]), Hidden: convert.BValOrFalse(raw[24]), PlacedID: convert.I64ValOrZero(raw[25]), } } if len(raw) >= 31 { o.Meta = convert.SiMapOrEmpty(raw[31]) } return o, nil } // OrderSnapshotFromRaw takes a raw list of values as returned from the websocket // service and tries to convert it into an OrderSnapshot. func NewOrderSnapshotFromRaw(raw []interface{}) (s *OrderSnapshot, err error) { if len(raw) == 0 { return } os := make([]*Order, 0) switch raw[0].(type) { case []interface{}: for _, v := range raw { if l, ok := v.([]interface{}); ok { o, err := NewOrderFromRaw(l) if err != nil { return s, err } os = append(os, o) } } default: return s, fmt.Errorf("not an order snapshot") } s = &OrderSnapshot{Snapshot: os} return } // OrderSnapshot is a collection of Orders that would usually be sent on // inital connection. type OrderSnapshot struct { Snapshot []*Order } // OrderUpdate is an Order that gets sent out after every change to an // order. type OrderUpdate Order // OrderNew gets sent out after an Order was created successfully. type OrderNew Order // OrderCancel gets sent out after an Order was cancelled successfully. type OrderCancel Order type PositionStatus string const ( PositionStatusActive PositionStatus = "ACTIVE" PositionStatusClosed PositionStatus = "CLOSED" ) type Position struct { Id int64 Symbol string Status PositionStatus Amount float64 BasePrice float64 MarginFunding float64 MarginFundingType int64 ProfitLoss float64 ProfitLossPercentage float64 LiquidationPrice float64 Leverage float64 } func NewPositionFromRaw(raw []interface{}) (o *Position, err error) { if len(raw) == 6 { o = &Position{ Symbol: convert.SValOrEmpty(raw[0]), Status: PositionStatus(convert.SValOrEmpty(raw[1])), Amount: convert.F64ValOrZero(raw[2]), BasePrice: convert.F64ValOrZero(raw[3]), MarginFunding: convert.F64ValOrZero(raw[4]), MarginFundingType: convert.I64ValOrZero(raw[5]), } } else if len(raw) < 10 { return o, fmt.Errorf("data slice too short for position: %#v", raw) } else if len(raw) == 10 { o = &Position{ Symbol: convert.SValOrEmpty(raw[0]), Status: PositionStatus(convert.SValOrEmpty(raw[1])), Amount: convert.F64ValOrZero(raw[2]), BasePrice: convert.F64ValOrZero(raw[3]), MarginFunding: convert.F64ValOrZero(raw[4]), MarginFundingType: convert.I64ValOrZero(raw[5]), ProfitLoss: convert.F64ValOrZero(raw[6]), ProfitLossPercentage: convert.F64ValOrZero(raw[7]), LiquidationPrice: convert.F64ValOrZero(raw[8]), Leverage: convert.F64ValOrZero(raw[9]), } } else { o = &Position{ Symbol: convert.SValOrEmpty(raw[0]), Status: PositionStatus(convert.SValOrEmpty(raw[1])), Amount: convert.F64ValOrZero(raw[2]), BasePrice: convert.F64ValOrZero(raw[3]), MarginFunding: convert.F64ValOrZero(raw[4]), MarginFundingType: convert.I64ValOrZero(raw[5]), ProfitLoss: convert.F64ValOrZero(raw[6]), ProfitLossPercentage: convert.F64ValOrZero(raw[7]), LiquidationPrice: convert.F64ValOrZero(raw[8]), Leverage: convert.F64ValOrZero(raw[9]), Id: int64(convert.F64ValOrZero(raw[11])), } } return } type PositionSnapshot struct { Snapshot []*Position } type PositionNew Position type PositionUpdate Position type PositionCancel Position func NewPositionSnapshotFromRaw(raw []interface{}) (s *PositionSnapshot, err error) { if len(raw) == 0 { return } ps := make([]*Position, 0) switch raw[0].(type) { case []interface{}: for _, v := range raw { if l, ok := v.([]interface{}); ok { p, err := NewPositionFromRaw(l) if err != nil { return s, err } ps = append(ps, p) } } default: return s, fmt.Errorf("not a position snapshot") } s = &PositionSnapshot{Snapshot: ps} return } type ClaimPositionRequest struct { Id int64 } func (o *ClaimPositionRequest) ToJSON() ([]byte, error) { aux := struct { Id int64 `json:"id"` }{ Id: o.Id, } return json.Marshal(aux) } // Trade represents a trade on the public data feed. type Trade struct { Pair string ID int64 MTS int64 Amount float64 Price float64 Side OrderSide } func NewTradeFromRaw(pair string, raw []interface{}) (o *Trade, err error) { if len(raw) < 4 { return o, fmt.Errorf("data slice too short for trade: %#v", raw) } amt := convert.F64ValOrZero(raw[2]) var side OrderSide if amt > 0 { side = Bid } else { side = Ask } o = &Trade{ Pair: pair, ID: convert.I64ValOrZero(raw[0]), MTS: convert.I64ValOrZero(raw[1]), Amount: math.Abs(amt), Price: convert.F64ValOrZero(raw[3]), Side: side, } return } type TradeSnapshot struct { Snapshot []*Trade } func NewTradeSnapshotFromRaw(pair string, raw [][]float64) (*TradeSnapshot, error) { if len(raw) <= 0 { return nil, fmt.Errorf("data slice is too short for trade snapshot: %#v", raw) } snapshot := make([]*Trade, 0) for _, flt := range raw { t, err := NewTradeFromRaw(pair, ToInterface(flt)) if err == nil { snapshot = append(snapshot, t) } } return &TradeSnapshot{Snapshot: snapshot}, nil } // TradeExecutionUpdate represents a full update to a trade on the private data feed. Following a TradeExecution, // TradeExecutionUpdates include additional details, e.g. the trade's execution ID (TradeID). type TradeExecutionUpdate struct { ID int64 Pair string MTS int64 OrderID int64 ExecAmount float64 ExecPrice float64 OrderType string OrderPrice float64 Maker int Fee float64 FeeCurrency string } // public trade update just looks like a trade func NewTradeExecutionUpdateFromRaw(raw []interface{}) (o *TradeExecutionUpdate, err error) { if len(raw) == 4 { o = &TradeExecutionUpdate{ ID: convert.I64ValOrZero(raw[0]), MTS: convert.I64ValOrZero(raw[1]), ExecAmount: convert.F64ValOrZero(raw[2]), ExecPrice: convert.F64ValOrZero(raw[3]), } return } if len(raw) == 11 { o = &TradeExecutionUpdate{ ID: convert.I64ValOrZero(raw[0]), Pair: convert.SValOrEmpty(raw[1]), MTS: convert.I64ValOrZero(raw[2]), OrderID: convert.I64ValOrZero(raw[3]), ExecAmount: convert.F64ValOrZero(raw[4]), ExecPrice: convert.F64ValOrZero(raw[5]), OrderType: convert.SValOrEmpty(raw[6]), OrderPrice: convert.F64ValOrZero(raw[7]), Maker: convert.IValOrZero(raw[8]), Fee: convert.F64ValOrZero(raw[9]), FeeCurrency: convert.SValOrEmpty(raw[10]), } return } return o, fmt.Errorf("data slice too short for trade update: %#v", raw) } type TradeExecutionUpdateSnapshot struct { Snapshot []*TradeExecutionUpdate } type HistoricalTradeSnapshot TradeExecutionUpdateSnapshot func NewTradeExecutionUpdateSnapshotFromRaw(raw []interface{}) (s *TradeExecutionUpdateSnapshot, err error) { if len(raw) == 0 { return } ts := make([]*TradeExecutionUpdate, 0) switch raw[0].(type) { case []interface{}: for _, v := range raw { if l, ok := v.([]interface{}); ok { t, err := NewTradeExecutionUpdateFromRaw(l) if err != nil { return s, err } ts = append(ts, t) } } default: return s, fmt.Errorf("not a trade snapshot: %#v", raw) } s = &TradeExecutionUpdateSnapshot{Snapshot: ts} return } // TradeExecution represents the first message receievd for a trade on the private data feed. type TradeExecution struct { ID int64 Pair string MTS int64 OrderID int64 Amount float64 Price float64 OrderType string OrderPrice float64 Maker int } func NewTradeExecutionFromRaw(raw []interface{}) (o *TradeExecution, err error) { if len(raw) < 6 { log.Printf("[ERROR] not enough members (%d, need at least 6) for trade execution: %#v", len(raw), raw) return o, fmt.Errorf("data slice too short for trade execution: %#v", raw) } // trade executions sometimes omit order type, price, and maker flag o = &TradeExecution{ ID: convert.I64ValOrZero(raw[0]), Pair: convert.SValOrEmpty(raw[1]), MTS: convert.I64ValOrZero(raw[2]), OrderID: convert.I64ValOrZero(raw[3]), Amount: convert.F64ValOrZero(raw[4]), Price: convert.F64ValOrZero(raw[5]), } if len(raw) >= 9 { o.OrderType = convert.SValOrEmpty(raw[6]) o.OrderPrice = convert.F64ValOrZero(raw[7]) o.Maker = convert.IValOrZero(raw[8]) } return } type Wallet struct { Type string Currency string Balance float64 UnsettledInterest float64 BalanceAvailable float64 } func NewWalletFromRaw(raw []interface{}) (o *Wallet, err error) { if len(raw) == 4 { o = &Wallet{ Type: convert.SValOrEmpty(raw[0]), Currency: convert.SValOrEmpty(raw[1]), Balance: convert.F64ValOrZero(raw[2]), UnsettledInterest: convert.F64ValOrZero(raw[3]), } } else if len(raw) < 5 { return o, fmt.Errorf("data slice too short for wallet: %#v", raw) } else { o = &Wallet{ Type: convert.SValOrEmpty(raw[0]), Currency: convert.SValOrEmpty(raw[1]), Balance: convert.F64ValOrZero(raw[2]), UnsettledInterest: convert.F64ValOrZero(raw[3]), BalanceAvailable: convert.F64ValOrZero(raw[4]), } } return } type WalletUpdate Wallet type WalletSnapshot struct { Snapshot []*Wallet } func NewWalletSnapshotFromRaw(raw []interface{}) (s *WalletSnapshot, err error) { if len(raw) == 0 { return } ws := make([]*Wallet, 0) switch raw[0].(type) { case []interface{}: for _, v := range raw { if l, ok := v.([]interface{}); ok { o, err := NewWalletFromRaw(l) if err != nil { return s, err } ws = append(ws, o) } } default: return s, fmt.Errorf("not an wallet snapshot") } s = &WalletSnapshot{Snapshot: ws} return } type BalanceInfo struct { TotalAUM float64 NetAUM float64 /*WalletType string Currency string*/ } func NewBalanceInfoFromRaw(raw []interface{}) (o *BalanceInfo, err error) { if len(raw) < 2 { return o, fmt.Errorf("data slice too short for balance info: %#v", raw) } o = &BalanceInfo{ TotalAUM: convert.F64ValOrZero(raw[0]), NetAUM: convert.F64ValOrZero(raw[1]), /*WalletType: convert.SValOrEmpty(raw[2]), Currency: convert.SValOrEmpty(raw[3]),*/ } return } type BalanceUpdate BalanceInfo // marginInfoFromRaw returns either a MarginInfoBase or MarginInfoUpdate, since // the Margin Info is split up into a base and per symbol parts. func NewMarginInfoFromRaw(raw []interface{}) (o interface{}, err error) { if len(raw) < 2 { return o, fmt.Errorf("data slice too short for margin info base: %#v", raw) } typ, ok := raw[0].(string) if !ok { return o, fmt.Errorf("expected margin info type in first position for margin info but got %#v", raw) } if len(raw) == 2 && typ == "base" { // This should be ["base", [...]] data, ok := raw[1].([]interface{}) if !ok { return o, fmt.Errorf("expected margin info array in second position for margin info but got %#v", raw) } return NewMarginInfoBaseFromRaw(data) } else if len(raw) == 3 && typ == "sym" { // This should be ["sym", SYMBOL, [...]] symbol, ok := raw[1].(string) if !ok { return o, fmt.Errorf("expected margin info symbol in second position for margin info update but got %#v", raw) } data, ok := raw[2].([]interface{}) if !ok { return o, fmt.Errorf("expected margin info array in third position for margin info update but got %#v", raw) } return NewMarginInfoUpdateFromRaw(symbol, data) } return nil, fmt.Errorf("invalid margin info type in %#v", raw) } type MarginInfoUpdate struct { Symbol string TradableBalance float64 } func NewMarginInfoUpdateFromRaw(symbol string, raw []interface{}) (o *MarginInfoUpdate, err error) { if len(raw) < 1 { return o, fmt.Errorf("data slice too short for margin info update: %#v", raw) } o = &MarginInfoUpdate{ Symbol: symbol, TradableBalance: convert.F64ValOrZero(raw[0]), } return } type MarginInfoBase struct { UserProfitLoss float64 UserSwaps float64 MarginBalance float64 MarginNet float64 } func NewMarginInfoBaseFromRaw(raw []interface{}) (o *MarginInfoBase, err error) { if len(raw) < 4 { return o, fmt.Errorf("data slice too short for margin info base: %#v", raw) } o = &MarginInfoBase{ UserProfitLoss: convert.F64ValOrZero(raw[0]), UserSwaps: convert.F64ValOrZero(raw[1]), MarginBalance: convert.F64ValOrZero(raw[2]), MarginNet: convert.F64ValOrZero(raw[3]), } return } type FundingInfo struct { Symbol string YieldLoan float64 YieldLend float64 DurationLoan float64 DurationLend float64 } func NewFundingInfoFromRaw(raw []interface{}) (o *FundingInfo, err error) { if len(raw) < 3 { // "sym", symbol, data return o, fmt.Errorf("data slice too short for funding info: %#v", raw) } sym, ok := raw[1].(string) if !ok { return o, fmt.Errorf("expected symbol in second position of funding info: %v", raw) } data, ok := raw[2].([]interface{}) if !ok { return o, fmt.Errorf("expected list in third position of funding info: %v", raw) } if len(data) < 4 { return o, fmt.Errorf("data too short: %#v", data) } o = &FundingInfo{ Symbol: sym, YieldLoan: convert.F64ValOrZero(data[0]), YieldLend: convert.F64ValOrZero(data[1]), DurationLoan: convert.F64ValOrZero(data[2]), DurationLend: convert.F64ValOrZero(data[3]), } return } type OfferStatus string const ( OfferStatusActive OfferStatus = "ACTIVE" OfferStatusExecuted OfferStatus = "EXECUTED" OfferStatusPartiallyFilled OfferStatus = "PARTIALLY FILLED" OfferStatusCanceled OfferStatus = "CANCELED" ) type FundingOfferCancelRequest struct { Id int64 } func (o *FundingOfferCancelRequest) ToJSON() ([]byte, error) { aux := struct { Id int64 `json:"id"` }{ Id: o.Id, } return json.Marshal(aux) } // MarshalJSON converts the order cancel object into the format required by the // bitfinex websocket service. func (o *FundingOfferCancelRequest) MarshalJSON() ([]byte, error) { aux, err := o.ToJSON() if err != nil { return nil, err } return []byte(fmt.Sprintf("[0, \"foc\", null, %s]", string(aux))), nil } type FundingOfferRequest struct { Type string Symbol string Amount float64 Rate float64 Period int64 Hidden bool } func (o *FundingOfferRequest) ToJSON() ([]byte, error) { aux := struct { Type string `json:"type"` Symbol string `json:"symbol"` Amount float64 `json:"amount,string"` Rate float64 `json:"rate,string"` Period int64 `json:"period"` Flags int `json:"flags,omitempty"` }{ Type: o.Type, Symbol: o.Symbol, Amount: o.Amount, Rate: o.Rate, Period: o.Period, } if o.Hidden { aux.Flags = aux.Flags + OrderFlagHidden } return json.Marshal(aux) } // MarshalJSON converts the order cancel object into the format required by the // bitfinex websocket service. func (o *FundingOfferRequest) MarshalJSON() ([]byte, error) { aux, err := o.ToJSON() if err != nil { return nil, err } return []byte(fmt.Sprintf("[0, \"fon\", null, %s]", string(aux))), nil } type Offer struct { ID int64 Symbol string MTSCreated int64 MTSUpdated int64 Amount float64 AmountOrig float64 Type string Flags interface{} Status OfferStatus Rate float64 Period int64 Notify bool Hidden bool Insure bool Renew bool RateReal float64 } func NewOfferFromRaw(raw []interface{}) (o *Offer, err error) { if len(raw) < 21 { return o, fmt.Errorf("data slice too short for offer: %#v", raw) } o = &Offer{ ID: convert.I64ValOrZero(raw[0]), Symbol: convert.SValOrEmpty(raw[1]), MTSCreated: convert.I64ValOrZero(raw[2]), MTSUpdated: convert.I64ValOrZero(raw[3]), Amount: convert.F64ValOrZero(raw[4]), AmountOrig: convert.F64ValOrZero(raw[5]), Type: convert.SValOrEmpty(raw[6]), Flags: raw[9], Status: OfferStatus(convert.SValOrEmpty(raw[10])), Rate: convert.F64ValOrZero(raw[14]), Period: convert.I64ValOrZero(raw[15]), Notify: convert.BValOrFalse(raw[16]), Hidden: convert.BValOrFalse(raw[17]), Insure: convert.BValOrFalse(raw[18]), Renew: convert.BValOrFalse(raw[19]), RateReal: convert.F64ValOrZero(raw[20]), } return } type FundingOfferNew Offer type FundingOfferUpdate Offer type FundingOfferCancel Offer type FundingOfferSnapshot struct { Snapshot []*Offer } func NewFundingOfferSnapshotFromRaw(raw []interface{}) (snap *FundingOfferSnapshot, err error) { if len(raw) == 0 { return } fos := make([]*Offer, 0) switch raw[0].(type) { case []interface{}: for _, v := range raw { if l, ok := v.([]interface{}); ok { o, err := NewOfferFromRaw(l) if err != nil { return snap, err } fos = append(fos, o) } } default: return snap, fmt.Errorf("not a funding offer snapshot") } snap = &FundingOfferSnapshot{ Snapshot: fos, } return } type HistoricalOffer Offer type CreditStatus string const ( CreditStatusActive CreditStatus = "ACTIVE" CreditStatusExecuted CreditStatus = "EXECUTED" CreditStatusPartiallyFilled CreditStatus = "PARTIALLY FILLED" CreditStatusCanceled CreditStatus = "CANCELED" ) type Credit struct { ID int64 Symbol string Side string MTSCreated int64 MTSUpdated int64 Amount float64 Flags interface{} Status CreditStatus Rate float64 Period int64 MTSOpened int64 MTSLastPayout int64 Notify bool Hidden bool Insure bool Renew bool RateReal float64 NoClose bool PositionPair string } func NewCreditFromRaw(raw []interface{}) (o *Credit, err error) { if len(raw) < 22 { return o, fmt.Errorf("data slice too short for offer: %#v", raw) } o = &Credit{ ID: convert.I64ValOrZero(raw[0]), Symbol: convert.SValOrEmpty(raw[1]), Side: convert.SValOrEmpty(raw[2]), MTSCreated: convert.I64ValOrZero(raw[3]), MTSUpdated: convert.I64ValOrZero(raw[4]), Amount: convert.F64ValOrZero(raw[5]), Flags: raw[6], Status: CreditStatus(convert.SValOrEmpty(raw[7])), Rate: convert.F64ValOrZero(raw[11]), Period: convert.I64ValOrZero(raw[12]), MTSOpened: convert.I64ValOrZero(raw[13]), MTSLastPayout: convert.I64ValOrZero(raw[14]), Notify: convert.BValOrFalse(raw[15]), Hidden: convert.BValOrFalse(raw[16]), Insure: convert.BValOrFalse(raw[17]), Renew: convert.BValOrFalse(raw[18]), RateReal: convert.F64ValOrZero(raw[19]), NoClose: convert.BValOrFalse(raw[20]), PositionPair: convert.SValOrEmpty(raw[21]), } return } type HistoricalCredit Credit type FundingCreditNew Credit type FundingCreditUpdate Credit type FundingCreditCancel Credit type FundingCreditSnapshot struct { Snapshot []*Credit } func NewFundingCreditSnapshotFromRaw(raw []interface{}) (snap *FundingCreditSnapshot, err error) { if len(raw) == 0 { return } fcs := make([]*Credit, 0) switch raw[0].(type) { case []interface{}: for _, v := range raw { if l, ok := v.([]interface{}); ok { o, err := NewCreditFromRaw(l) if err != nil { return snap, err } fcs = append(fcs, o) } } default: return snap, fmt.Errorf("not a funding credit snapshot") } snap = &FundingCreditSnapshot{ Snapshot: fcs, } return } type LoanStatus string const ( LoanStatusActive LoanStatus = "ACTIVE" LoanStatusExecuted LoanStatus = "EXECUTED" LoanStatusPartiallyFilled LoanStatus = "PARTIALLY FILLED" LoanStatusCanceled LoanStatus = "CANCELED" ) type Loan struct { ID int64 Symbol string Side string MTSCreated int64 MTSUpdated int64 Amount float64 Flags interface{} Status LoanStatus Rate float64 Period int64 MTSOpened int64 MTSLastPayout int64 Notify bool Hidden bool Insure bool Renew bool RateReal float64 NoClose bool } func NewLoanFromRaw(raw []interface{}) (o *Loan, err error) { if len(raw) < 21 { return o, fmt.Errorf("data slice too short (len=%d) for loan: %#v", len(raw), raw) } o = &Loan{ ID: convert.I64ValOrZero(raw[0]), Symbol: convert.SValOrEmpty(raw[1]), Side: convert.SValOrEmpty(raw[2]), MTSCreated: convert.I64ValOrZero(raw[3]), MTSUpdated: convert.I64ValOrZero(raw[4]), Amount: convert.F64ValOrZero(raw[5]), Flags: raw[6], Status: LoanStatus(convert.SValOrEmpty(raw[7])), Rate: convert.F64ValOrZero(raw[11]), Period: convert.I64ValOrZero(raw[12]), MTSOpened: convert.I64ValOrZero(raw[13]), MTSLastPayout: convert.I64ValOrZero(raw[14]), Notify: convert.BValOrFalse(raw[15]), Hidden: convert.BValOrFalse(raw[16]), Insure: convert.BValOrFalse(raw[17]), Renew: convert.BValOrFalse(raw[18]), RateReal: convert.F64ValOrZero(raw[19]), NoClose: convert.BValOrFalse(raw[20]), } return o, nil } type HistoricalLoan Loan type FundingLoanNew Loan type FundingLoanUpdate Loan type FundingLoanCancel Loan type FundingLoanSnapshot struct { Snapshot []*Loan } func NewFundingLoanSnapshotFromRaw(raw []interface{}) (snap *FundingLoanSnapshot, err error) { if len(raw) == 0 { return } fls := make([]*Loan, 0) switch raw[0].(type) { case []interface{}: for _, v := range raw { if l, ok := v.([]interface{}); ok { o, err := NewLoanFromRaw(l) if err != nil { return snap, err } fls = append(fls, o) } } default: return snap, fmt.Errorf("not a funding loan snapshot") } snap = &FundingLoanSnapshot{ Snapshot: fls, } return } type FundingTrade struct { ID int64 Symbol string MTSCreated int64 OfferID int64 Amount float64 Rate float64 Period int64 Maker int64 } func NewFundingTradeFromRaw(raw []interface{}) (o *FundingTrade, err error) { if len(raw) < 8 { return o, fmt.Errorf("data slice too short for funding trade: %#v", raw) } o = &FundingTrade{ ID: convert.I64ValOrZero(raw[0]), Symbol: convert.SValOrEmpty(raw[1]), MTSCreated: convert.I64ValOrZero(raw[2]), OfferID: convert.I64ValOrZero(raw[3]), Amount: convert.F64ValOrZero(raw[4]), Rate: convert.F64ValOrZero(raw[5]), Period: convert.I64ValOrZero(raw[6]), Maker: convert.I64ValOrZero(raw[7]), } return } type FundingTradeExecution FundingTrade type FundingTradeUpdate FundingTrade type FundingTradeSnapshot struct { Snapshot []*FundingTrade } type HistoricalFundingTradeSnapshot FundingTradeSnapshot func NewFundingTradeSnapshotFromRaw(raw []interface{}) (snap *FundingTradeSnapshot, err error) { if len(raw) == 0 { return } fts := make([]*FundingTrade, 0) switch raw[0].(type) { case []interface{}: for _, v := range raw { if l, ok := v.([]interface{}); ok { o, err := NewFundingTradeFromRaw(l) if err != nil { return snap, err } fts = append(fts, o) } } default: return snap, fmt.Errorf("not a funding trade snapshot") } snap = &FundingTradeSnapshot{ Snapshot: fts, } return } type Notification struct { MTS int64 Type string MessageID int64 NotifyInfo interface{} Code int64 Status string Text string } func NewNotificationFromRaw(raw []interface{}) (o *Notification, err error) { if len(raw) < 8 { return o, fmt.Errorf("data slice too short for notification: %#v", raw) } o = &Notification{ MTS: convert.I64ValOrZero(raw[0]), Type: convert.SValOrEmpty(raw[1]), MessageID: convert.I64ValOrZero(raw[2]), //NotifyInfo: raw[4], Code: convert.I64ValOrZero(raw[5]), Status: convert.SValOrEmpty(raw[6]), Text: convert.SValOrEmpty(raw[7]), } // raw[4] = notify info var nraw []interface{} if raw[4] != nil { nraw = raw[4].([]interface{}) switch o.Type { case "on-req": if len(nraw) <= 0 { o.NotifyInfo = nil break } // will be a set of orders if created via rest // this is to accommodate OCO orders if _, ok := nraw[0].([]interface{}); ok { o.NotifyInfo, err = NewOrderSnapshotFromRaw(nraw) if err != nil { return nil, err } } else { on, err := NewOrderFromRaw(nraw) if err != nil { return nil, err } oNew := OrderNew(*on) o.NotifyInfo = &oNew } case "ou-req": on, err := NewOrderFromRaw(nraw) if err != nil { return nil, err } oNew := OrderUpdate(*on) o.NotifyInfo = &oNew case "oc-req": // if list of list then parse to order snapshot oc, err := NewOrderFromRaw(nraw) if err != nil { return o, err } orderCancel := OrderCancel(*oc) o.NotifyInfo = &orderCancel case "fon-req": fon, err := NewOfferFromRaw(nraw) if err != nil { return o, err } fundingOffer := FundingOfferNew(*fon) o.NotifyInfo = &fundingOffer case "foc-req": foc, err := NewOfferFromRaw(nraw) if err != nil { return o, err } fundingOffer := FundingOfferCancel(*foc) o.NotifyInfo = &fundingOffer case "uca": o.NotifyInfo = raw[4] case "acc_tf": o.NotifyInfo = raw[4] case "pm-req": p, err := NewPositionFromRaw(nraw) if err != nil { return o, err } cp := PositionCancel(*p) o.NotifyInfo = &cp default: o.NotifyInfo = raw[4] } } return } type Ticker struct { Symbol string Frr float64 Bid float64 BidPeriod int64 BidSize float64 Ask float64 AskPeriod int64 AskSize float64 DailyChange float64 DailyChangePerc float64 LastPrice float64 Volume float64 High float64 Low float64 } type TickerUpdate Ticker type TickerSnapshot struct { Snapshot []*Ticker } func NewTickerSnapshotFromRaw(symbol string, raw [][]float64) (*TickerSnapshot, error) { if len(raw) <= 0 { return nil, fmt.Errorf("data slice too short for ticker snapshot: %#v", raw) } snap := make([]*Ticker, 0) for _, f := range raw { c, err := NewTickerFromRaw(symbol, ToInterface(f)) if err == nil { snap = append(snap, c) } } return &TickerSnapshot{Snapshot: snap}, nil } func NewTickerFromRaw(symbol string, raw []interface{}) (t *Ticker, err error) { if len(raw) < 10 { return t, fmt.Errorf("data slice too short for ticker, expected %d got %d: %#v", 10, len(raw), raw) } // funding currency ticker // ignore bid/ask period for now if len(raw) == 13 { t = &Ticker{ Symbol: symbol, Bid: convert.F64ValOrZero(raw[1]), BidSize: convert.F64ValOrZero(raw[2]), Ask: convert.F64ValOrZero(raw[4]), AskSize: convert.F64ValOrZero(raw[5]), DailyChange: convert.F64ValOrZero(raw[7]), DailyChangePerc: convert.F64ValOrZero(raw[8]), LastPrice: convert.F64ValOrZero(raw[9]), Volume: convert.F64ValOrZero(raw[10]), High: convert.F64ValOrZero(raw[11]), Low: convert.F64ValOrZero(raw[12]), } return t, nil } else if len(raw) == 16 { // on funding currencies (ex. fUSD) // SYMBOL, FRR, BID, BID_PERIOD, BID_SIZE, ASK, ASK_PERIOD, ASK_SIZE, DAILY_CHANGE, DAILY_CHANGE_RELATIVE, // LAST_PRICE, VOLUME, HIGH, LOW, _PLACEHOLDER, _PLACEHOLDER, FRR_AMOUNT_AVAILABLE t = &Ticker{ Symbol: symbol, Frr: convert.F64ValOrZero(raw[0]), Bid: convert.F64ValOrZero(raw[1]), BidPeriod: convert.I64ValOrZero(raw[2]), BidSize: convert.F64ValOrZero(raw[3]), Ask: convert.F64ValOrZero(raw[4]), AskPeriod: convert.I64ValOrZero(raw[5]), AskSize: convert.F64ValOrZero(raw[6]), DailyChange: convert.F64ValOrZero(raw[7]), DailyChangePerc: convert.F64ValOrZero(raw[8]), LastPrice: convert.F64ValOrZero(raw[9]), Volume: convert.F64ValOrZero(raw[10]), High: convert.F64ValOrZero(raw[11]), Low: convert.F64ValOrZero(raw[12]), } return t, nil } // all other tickers // on trading pairs (ex. tBTCUSD) // SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE, DAILY_CHANGE_RELATIVE, LAST_PRICE, VOLUME, HIGH, LOW t = &Ticker{ Symbol: symbol, Bid: convert.F64ValOrZero(raw[0]), BidSize: convert.F64ValOrZero(raw[1]), Ask: convert.F64ValOrZero(raw[2]), AskSize: convert.F64ValOrZero(raw[3]), DailyChange: convert.F64ValOrZero(raw[4]), DailyChangePerc: convert.F64ValOrZero(raw[5]), LastPrice: convert.F64ValOrZero(raw[6]), Volume: convert.F64ValOrZero(raw[7]), High: convert.F64ValOrZero(raw[8]), Low: convert.F64ValOrZero(raw[9]), } return t, nil } func NewTickerFromRestRaw(raw []interface{}) (t *Ticker, err error) { return NewTickerFromRaw(raw[0].(string), raw[1:]) } type bookAction byte // BookAction represents a new/update or removal for a book entry. type BookAction bookAction const ( //BookUpdateEntry represents a new or updated book entry. BookUpdateEntry BookAction = 0 //BookRemoveEntry represents a removal of a book entry. BookRemoveEntry BookAction = 1 ) // BookUpdate represents an order book price update. type BookUpdate struct { ID int64 // the book update ID, optional Symbol string // book symbol Price float64 // updated price PriceJsNum json.Number // update price as json.Number Count int64 // updated count, optional Amount float64 // updated amount AmountJsNum json.Number // update amount as json.Number Side OrderSide // side Action BookAction // action (add/remove) } type BookUpdateSnapshot struct { Snapshot []*BookUpdate } func NewBookUpdateSnapshotFromRaw(symbol, precision string, raw [][]float64, raw_numbers interface{}) (*BookUpdateSnapshot, error) { if len(raw) <= 0 { return nil, fmt.Errorf("data slice too short for book snapshot: %#v", raw) } snap := make([]*BookUpdate, len(raw)) for i, f := range raw { b, err := NewBookUpdateFromRaw(symbol, precision, ToInterface(f), raw_numbers.([]interface{})[i]) if err != nil { return nil, err } snap[i] = b } return &BookUpdateSnapshot{Snapshot: snap}, nil } func IsRawBook(precision string) bool { return precision == "R0" } // NewBookUpdateFromRaw creates a new book update object from raw data. Precision determines how // to interpret the side (baked into Count versus Amount) // raw book updates [ID, price, qty], aggregated book updates [price, amount, count] func NewBookUpdateFromRaw(symbol, precision string, data []interface{}, raw_numbers interface{}) (b *BookUpdate, err error) { if len(data) < 3 { return b, fmt.Errorf("data slice too short for book update, expected %d got %d: %#v", 5, len(data), data) } var px float64 var px_num json.Number var id, cnt int64 raw_num_array := raw_numbers.([]interface{}) amt := convert.F64ValOrZero(data[2]) amt_num := convert.FloatToJsonNumber(raw_num_array[2]) var side OrderSide var actionCtrl float64 if IsRawBook(precision) { // [ID, price, amount] id = convert.I64ValOrZero(data[0]) px = convert.F64ValOrZero(data[1]) px_num = convert.FloatToJsonNumber(raw_num_array[1]) actionCtrl = px } else { // [price, amount, count] px = convert.F64ValOrZero(data[0]) px_num = convert.FloatToJsonNumber(raw_num_array[0]) cnt = convert.I64ValOrZero(data[1]) actionCtrl = float64(cnt) } if amt > 0 { side = Bid } else { side = Ask } var action BookAction if actionCtrl <= 0 { action = BookRemoveEntry } else { action = BookUpdateEntry } b = &BookUpdate{ Symbol: symbol, Price: math.Abs(px), PriceJsNum: px_num, Count: cnt, Amount: math.Abs(amt), AmountJsNum: amt_num, Side: side, Action: action, ID: id, } return } type Candle struct { Symbol string Resolution CandleResolution MTS int64 Open float64 Close float64 High float64 Low float64 Volume float64 } type CandleSnapshot struct { Snapshot []*Candle } func ToFloat64Slice(slice []interface{}) []float64 { data := make([]float64, 0, len(slice)) for _, i := range slice { if f, ok := i.(float64); ok { data = append(data, f) } } return data } func ToInterface(flt []float64) []interface{} { data := make([]interface{}, len(flt)) for j, f := range flt { data[j] = f } return data } func NewCandleSnapshotFromRaw(symbol string, resolution CandleResolution, raw [][]float64) (*CandleSnapshot, error) { if len(raw) <= 0 { return nil, fmt.Errorf("data slice too short for candle snapshot: %#v", raw) } snap := make([]*Candle, 0) for _, f := range raw { c, err := NewCandleFromRaw(symbol, resolution, ToInterface(f)) if err == nil { snap = append(snap, c) } } return &CandleSnapshot{Snapshot: snap}, nil } func NewCandleFromRaw(symbol string, resolution CandleResolution, raw []interface{}) (c *Candle, err error) { if len(raw) < 6 { return c, fmt.Errorf("data slice too short for candle, expected %d got %d: %#v", 6, len(raw), raw) } c = &Candle{ Symbol: symbol, Resolution: resolution, MTS: convert.I64ValOrZero(raw[0]), Open: convert.F64ValOrZero(raw[1]), Close: convert.F64ValOrZero(raw[2]), High: convert.F64ValOrZero(raw[3]), Low: convert.F64ValOrZero(raw[4]), Volume: convert.F64ValOrZero(raw[5]), } return } type Ledger struct { ID int64 Currency string Nil1 float64 MTS int64 Nil2 float64 Amount float64 Balance float64 Nil3 float64 Description string } // NewLedgerFromRaw takes the raw list of values as returned from the websocket // service and tries to convert it into an Ledger. func NewLedgerFromRaw(raw []interface{}) (o *Ledger, err error) { if len(raw) == 9 { o = &Ledger{ ID: int64(convert.F64ValOrZero(raw[0])), Currency: convert.SValOrEmpty(raw[1]), Nil1: convert.F64ValOrZero(raw[2]), MTS: convert.I64ValOrZero(raw[3]), Nil2: convert.F64ValOrZero(raw[4]), Amount: convert.F64ValOrZero(raw[5]), Balance: convert.F64ValOrZero(raw[6]), Nil3: convert.F64ValOrZero(raw[7]), Description: convert.SValOrEmpty(raw[8]), // API returns 3 Nil values, what do they map to? // API documentation says ID is type integer but api returns a string } } else { return o, fmt.Errorf("data slice too short for ledger: %#v", raw) } return } type LedgerSnapshot struct { Snapshot []*Ledger } // LedgerSnapshotFromRaw takes a raw list of values as returned from the websocket // service and tries to convert it into an LedgerSnapshot. func NewLedgerSnapshotFromRaw(raw []interface{}) (s *LedgerSnapshot, err error) { if len(raw) == 0 { return s, fmt.Errorf("data slice too short for ledgers: %#v", raw) } os := make([]*Ledger, 0) switch raw[0].(type) { case []interface{}: for _, v := range raw { if l, ok := v.([]interface{}); ok { o, err := NewLedgerFromRaw(l) if err != nil { return s, err } os = append(os, o) } } default: return s, fmt.Errorf("not an ledger snapshot") } s = &LedgerSnapshot{Snapshot: os} return } type CurrencyConf struct { Currency string Label string Symbol string Pairs []string Pools []string Explorers ExplorerConf Unit string } type ExplorerConf struct { BaseUri string AddressUri string TransactionUri string } type CurrencyConfigMapping string const ( CurrencyLabelMap CurrencyConfigMapping = "pub:map:currency:label" CurrencySymbolMap CurrencyConfigMapping = "pub:map:currency:sym" CurrencyUnitMap CurrencyConfigMapping = "pub:map:currency:unit" CurrencyExplorerMap CurrencyConfigMapping = "pub:map:currency:explorer" CurrencyExchangeMap CurrencyConfigMapping = "pub:list:pair:exchange" ) type RawCurrencyConf struct { Mapping string Data interface{} } func parseCurrencyLabelMap(config map[string]CurrencyConf, raw []interface{}) { for _, rawLabel := range raw { data := rawLabel.([]interface{}) cur := data[0].(string) if val, ok := config[cur]; ok { // add value val.Label = data[1].(string) config[cur] = val } else { // create new empty config instance cfg := CurrencyConf{} cfg.Label = data[1].(string) cfg.Currency = cur config[cur] = cfg } } } func parseCurrencySymbMap(config map[string]CurrencyConf, raw []interface{}) { for _, rawLabel := range raw { data := rawLabel.([]interface{}) cur := data[0].(string) if val, ok := config[cur]; ok { // add value val.Symbol = data[1].(string) config[cur] = val } else { // create new empty config instance cfg := CurrencyConf{} cfg.Symbol = data[1].(string) cfg.Currency = cur config[cur] = cfg } } } func parseCurrencyUnitMap(config map[string]CurrencyConf, raw []interface{}) { for _, rawLabel := range raw { data := rawLabel.([]interface{}) cur := data[0].(string) if val, ok := config[cur]; ok { // add value val.Unit = data[1].(string) config[cur] = val } else { // create new empty config instance cfg := CurrencyConf{} cfg.Unit = data[1].(string) cfg.Currency = cur config[cur] = cfg } } } func parseCurrencyExplorerMap(config map[string]CurrencyConf, raw []interface{}) { for _, rawLabel := range raw { data := rawLabel.([]interface{}) cur := data[0].(string) explorers := data[1].([]interface{}) var cfg CurrencyConf if val, ok := config[cur]; ok { cfg = val } else { // create new empty config instance cc := CurrencyConf{} cc.Currency = cur cfg = cc } ec := ExplorerConf{ explorers[0].(string), explorers[1].(string), explorers[2].(string), } cfg.Explorers = ec config[cur] = cfg } } func parseCurrencyExchangeMap(config map[string]CurrencyConf, raw []interface{}) { for _, rs := range raw { symbol := rs.(string) var base, quote string if len(symbol) > 6 { base = strings.Split(symbol, ":")[0] quote = strings.Split(symbol, ":")[1] } else { base = symbol[3:] quote = symbol[:3] } // append if base exists in configs if val, ok := config[base]; ok { val.Pairs = append(val.Pairs, symbol) config[base] = val } // append if quote exists in configs if val, ok := config[quote]; ok { val.Pairs = append(val.Pairs, symbol) config[quote] = val } } } func NewCurrencyConfFromRaw(raw []RawCurrencyConf) ([]CurrencyConf, error) { configMap := make(map[string]CurrencyConf) for _, r := range raw { switch CurrencyConfigMapping(r.Mapping) { case CurrencyLabelMap: data := r.Data.([]interface{}) parseCurrencyLabelMap(configMap, data) case CurrencySymbolMap: data := r.Data.([]interface{}) parseCurrencySymbMap(configMap, data) case CurrencyUnitMap: data := r.Data.([]interface{}) parseCurrencyUnitMap(configMap, data) case CurrencyExplorerMap: data := r.Data.([]interface{}) parseCurrencyExplorerMap(configMap, data) case CurrencyExchangeMap: data := r.Data.([]interface{}) parseCurrencyExchangeMap(configMap, data) } } // convert map to array configs := make([]CurrencyConf, 0) for _, v := range configMap { configs = append(configs, v) } return configs, nil } type StatKey string const ( FundingSizeKey StatKey = "funding.size" CreditSizeKey StatKey = "credits.size" CreditSizeSymKey StatKey = "credits.size.sym" PositionSizeKey StatKey = "pos.size" ) type Stat struct { Period int64 Volume float64 } type DerivativeStatusSnapshot struct { Snapshot []*DerivativeStatus } type StatusType string const ( DerivativeStatusType StatusType = "deriv" ) type DerivativeStatus struct { Symbol string MTS int64 Price float64 SpotPrice float64 InsuranceFundBalance float64 FundingAccrued float64 FundingStep float64 } func NewDerivativeStatusFromWsRaw(symbol string, raw []interface{}) (*DerivativeStatus, error) { if len(raw) == 11 { ds := &DerivativeStatus{ Symbol: symbol, MTS: convert.I64ValOrZero(raw[0]), // placeholder Price: convert.F64ValOrZero(raw[2]), SpotPrice: convert.F64ValOrZero(raw[3]), // placeholder InsuranceFundBalance: convert.F64ValOrZero(raw[5]), // placeholder // placeholder FundingAccrued: convert.F64ValOrZero(raw[8]), FundingStep: convert.F64ValOrZero(raw[9]), // placeholder } return ds, nil } else { return nil, fmt.Errorf("data slice too short for derivative status: %#v", raw) } } func NewDerivativeStatusFromRaw(raw []interface{}) (*DerivativeStatus, error) { if len(raw) == 12 { ds := &DerivativeStatus{ Symbol: convert.SValOrEmpty(raw[0]), MTS: convert.I64ValOrZero(raw[1]), // placeholder Price: convert.F64ValOrZero(raw[3]), SpotPrice: convert.F64ValOrZero(raw[4]), // placeholder InsuranceFundBalance: convert.F64ValOrZero(raw[6]), // placeholder // placeholder FundingAccrued: convert.F64ValOrZero(raw[9]), FundingStep: convert.F64ValOrZero(raw[10]), // placeholder } return ds, nil } else { return nil, fmt.Errorf("data slice too short for derivative status: %#v", raw) } } func NewDerivativeSnapshotFromRaw(raw [][]interface{}) (*DerivativeStatusSnapshot, error) { snapshot := make([]*DerivativeStatus, len(raw)) for i, rStatus := range raw { pStatus, err := NewDerivativeStatusFromRaw(rStatus) if err != nil { return nil, err } snapshot[i] = pStatus } return &DerivativeStatusSnapshot{Snapshot: snapshot}, nil } bitfinex-api-go-2.2.9/v2/websocket/000077500000000000000000000000001371275744700170445ustar00rootroot00000000000000bitfinex-api-go-2.2.9/v2/websocket/api.go000066400000000000000000000150341371275744700201470ustar00rootroot00000000000000package websocket import ( "context" "fmt" "github.com/bitfinexcom/bitfinex-api-go/v2" ) type FlagRequest struct { Event string `json:"event"` Flags int `json:"flags"` } // API for end-users to interact with Bitfinex. // Send publishes a generic message to the Bitfinex API. func (c *Client) Send(ctx context.Context, msg interface{}) error { socket, err := c.getSocket() if err != nil { return err } return socket.Asynchronous.Send(ctx, msg) } // Submit a request to enable the given flag func (c *Client) EnableFlag(ctx context.Context, flag int) (string, error) { req := &FlagRequest{ Event: "conf", Flags: flag, } // TODO enable flag on reconnect? // create sublist to stop concurrent map read socks := make([]*Socket, len(c.sockets)) c.mtx.RLock() for i, socket := range c.sockets { socks[i] = socket } c.mtx.RUnlock() for _, socket := range socks { err := socket.Asynchronous.Send(ctx, req) if err != nil { return "", err } } return "", nil } // Gen the count of currently active websocket connections func (c *Client) ConnectionCount() int { c.mtx.RLock() defer c.mtx.RUnlock() return len(c.sockets) } // Get the available capacity of the current // websocket connections func (c *Client) AvailableCapacity() int { return c.getTotalAvailableSocketCapacity() } // Start a new websocket connection. This function is only exposed in case you want to // implicitly add new connections otherwise connection management is already handled for you. func (c *Client) StartNewConnection() error { return c.connectSocket(SocketId(c.ConnectionCount())) } func (c *Client) subscribeBySocket(ctx context.Context, socket *Socket, req *SubscriptionRequest) (string, error) { c.subscriptions.add(socket.Id, req) err := socket.Asynchronous.Send(ctx, req) if err != nil { // propagate send error return "", err } return req.SubID, nil } // Submit a request to subscribe to the given SubscriptionRequuest func (c *Client) Subscribe(ctx context.Context, req *SubscriptionRequest) (string, error) { if c.getTotalAvailableSocketCapacity() <= 1 { err := c.StartNewConnection() if err != nil { return "", err } } // get socket with the highest available capacity socket, err := c.getMostAvailableSocket() if err != nil { return "", err } return c.subscribeBySocket(ctx, socket, req) } // Submit a request to receive ticker updates func (c *Client) SubscribeTicker(ctx context.Context, symbol string) (string, error) { req := &SubscriptionRequest{ SubID: c.nonce.GetNonce(), Event: EventSubscribe, Channel: ChanTicker, Symbol: symbol, } return c.Subscribe(ctx, req) } // Submit a request to receive trade updates func (c *Client) SubscribeTrades(ctx context.Context, symbol string) (string, error) { req := &SubscriptionRequest{ SubID: c.nonce.GetNonce(), Event: EventSubscribe, Channel: ChanTrades, Symbol: symbol, } return c.Subscribe(ctx, req) } // Submit a subscription request for market data for the given symbol, at the given frequency, with the given precision, returning no more than priceLevels price entries. // Default values are Precision0, Frequency0, and priceLevels=25. func (c *Client) SubscribeBook(ctx context.Context, symbol string, precision bitfinex.BookPrecision, frequency bitfinex.BookFrequency, priceLevel int) (string, error) { if priceLevel < 0 { return "", fmt.Errorf("negative price levels not supported: %d", priceLevel) } req := &SubscriptionRequest{ SubID: c.nonce.GetNonce(), Event: EventSubscribe, Channel: ChanBook, Symbol: symbol, Precision: string(precision), Len: fmt.Sprintf("%d", priceLevel), // needed for R0? } if !bitfinex.IsRawBook(string(precision)) { req.Frequency = string(frequency) } return c.Subscribe(ctx, req) } // Submit a subscription request to receive candle updates func (c *Client) SubscribeCandles(ctx context.Context, symbol string, resolution bitfinex.CandleResolution) (string, error) { req := &SubscriptionRequest{ SubID: c.nonce.GetNonce(), Event: EventSubscribe, Channel: ChanCandles, Key: fmt.Sprintf("trade:%s:%s", resolution, symbol), } return c.Subscribe(ctx, req) } // Submit a subscription request for status updates func (c *Client) SubscribeStatus(ctx context.Context, symbol string, sType bitfinex.StatusType) (string, error) { req := &SubscriptionRequest{ SubID: c.nonce.GetNonce(), Event: EventSubscribe, Channel: ChanStatus, Key: fmt.Sprintf("%s:%s", string(sType), symbol), } return c.Subscribe(ctx, req) } // Retrieve the Orderbook for the given symbol which is managed locally. // This requires ManageOrderbook=True and an active chanel subscribed to the given // symbols orderbook func (c *Client) GetOrderbook(symbol string) (*Orderbook, error) { c.mtx.RLock() defer c.mtx.RUnlock() if val, ok := c.orderbooks[symbol]; ok { // take dereferenced copy of orderbook return val, nil } return nil, fmt.Errorf("Orderbook %s does not exist", symbol) } // Submit a request to create a new order func (c *Client) SubmitOrder(ctx context.Context, order *bitfinex.OrderNewRequest) error { socket, err := c.GetAuthenticatedSocket() if err != nil { return err } return socket.Asynchronous.Send(ctx, order) } // Submit and update request to change an existing orders values func (c *Client) SubmitUpdateOrder(ctx context.Context, orderUpdate *bitfinex.OrderUpdateRequest) error { socket, err := c.GetAuthenticatedSocket() if err != nil { return err } return socket.Asynchronous.Send(ctx, orderUpdate) } // Submit a cancel request for an existing order func (c *Client) SubmitCancel(ctx context.Context, cancel *bitfinex.OrderCancelRequest) error { socket, err := c.GetAuthenticatedSocket() if err != nil { return err } return socket.Asynchronous.Send(ctx, cancel) } // Get a subscription request using a subscription ID func (c *Client) LookupSubscription(subID string) (*SubscriptionRequest, error) { s, err := c.subscriptions.lookupBySubscriptionID(subID) if err != nil { return nil, err } return s.Request, nil } // Submit a new funding offer request func (c *Client) SubmitFundingOffer(ctx context.Context, fundingOffer *bitfinex.FundingOfferRequest) error { socket, err := c.GetAuthenticatedSocket() if err != nil { return err } return socket.Asynchronous.Send(ctx, fundingOffer) } // Submit a request to cancel and existing funding offer func (c *Client) SubmitFundingCancel(ctx context.Context, fundingOffer *bitfinex.FundingOfferCancelRequest) error { socket, err := c.GetAuthenticatedSocket() if err != nil { return err } return socket.Asynchronous.Send(ctx, fundingOffer) } bitfinex-api-go-2.2.9/v2/websocket/channels.go000066400000000000000000000244321371275744700211730ustar00rootroot00000000000000package websocket import ( "context" "encoding/json" "fmt" "github.com/bitfinexcom/bitfinex-api-go/pkg/convert" "github.com/bitfinexcom/bitfinex-api-go/v2" ) func (c *Client) handleChannel(socketId SocketId, msg []byte) error { if c.terminal { return fmt.Errorf("received a message after close") } var raw []interface{} err := json.Unmarshal(msg, &raw) if err != nil { return err } else if len(raw) < 2 { return nil } chID, ok := raw[0].(float64) if !ok { return fmt.Errorf("expected message to start with a channel id but got %#v instead", raw[0]) } chanID := int64(chID) sub, err := c.subscriptions.lookupBySocketChannelID(chanID, socketId) if err != nil { // no subscribed channel for message return err } c.subscriptions.heartbeat(chanID) if sub.Public { switch data := raw[1].(type) { case string: switch data { case "hb": // no-op, already updated heartbeat timeout from this event return nil case "cs": if checksum, ok := raw[2].(float64); ok { return c.handleChecksumChannel(sub, int(checksum)) } else { c.log.Error("Unable to parse checksum") } default: body := raw[2].([]interface{}) return c.handlePublicChannel(sub, sub.Request.Channel, data, body, msg) } case []interface{}: return c.handlePublicChannel(sub, sub.Request.Channel, "", data, msg) } } else { return c.handlePrivateChannel(raw) } return nil } func (c *Client) handleChecksumChannel(sub *subscription, checksum int) error { symbol := sub.Request.Symbol // force to signed integer bChecksum := uint32(checksum) var orderbook *Orderbook c.mtx.Lock() if ob, ok := c.orderbooks[symbol]; ok { orderbook = ob } c.mtx.Unlock() if orderbook != nil { oChecksum := orderbook.Checksum() // compare bitfinex checksum with local checksum if bChecksum == oChecksum { c.log.Debugf("Orderbook '%s' checksum verification successful.", symbol) } else { c.log.Warningf("Orderbook '%s' checksum is invalid got %d bot got %d. Data Out of sync, reconnecting.", symbol, bChecksum, oChecksum) err := c.sendUnsubscribeMessage(context.Background(), sub) if err != nil { return err } newSub := &SubscriptionRequest{ SubID: c.nonce.GetNonce(), // generate new subID Event: sub.Request.Event, Channel: sub.Request.Channel, Symbol: sub.Request.Symbol, } _, err_sub := c.Subscribe(context.Background(), newSub) if err_sub != nil { c.log.Warningf("could not resubscribe: %s", err_sub.Error()) return err_sub } } } return nil } func (c *Client) handlePublicChannel(sub *subscription, channel, objType string, data []interface{}, raw_msg []byte) error { // unauthenticated data slice // public data is returned as raw interface arrays, use a factory to convert to raw type & publish if factory, ok := c.factories[channel]; ok { // convert to type array of interfaces if len(data) > 0 { if _, ok := data[0].([]interface{}); ok { interfaceArray := convert.ToInterfaceArray(data) // snapshot item c.mtx.Lock() // lock mutex since its mutates client struct msg, err := factory.BuildSnapshot(sub, interfaceArray, raw_msg) c.mtx.Unlock() if err != nil { return err } if msg != nil { c.listener <- msg } } else { // single item msg, err := factory.Build(sub, objType, data, raw_msg) if err != nil { return err } if msg != nil { c.listener <- msg } } } } else { // factory lookup error return fmt.Errorf("could not find public factory for %s channel", channel) } return nil } func (c *Client) handlePrivateChannel(raw []interface{}) error { // authenticated data slice, or a heartbeat if val, ok := raw[1].(string); ok && val == "hb" { chanID, ok := raw[0].(float64) if !ok { c.log.Warningf("could not find chanID: %#v", raw) return nil } c.handleHeartbeat(int64(chanID)) } else { // raw[2] is data slice // authenticated snapshots? if len(raw) > 2 { if arr, ok := raw[2].([]interface{}); ok { obj, err := c.handlePrivateDataMessage(raw[1].(string), arr) if err != nil { return err } // private data is returned as strongly typed data, publish directly if obj != nil { c.listener <- obj } } } } return nil } func (c *Client) handleHeartbeat(chanID int64) { c.subscriptions.heartbeat(chanID) } type unsubscribeMsg struct { Event string `json:"event"` ChanID int64 `json:"chanId"` } // public msg: [ChanID, [Data]] // hb (both): [ChanID, "hb"] // private update msg: [ChanID, "type", [Data]] // private snapshot msg: [ChanID, "type", [[Data]]] func (c *Client) handlePrivateDataMessage(term string, data []interface{}) (ms interface{}, err error) { if len(data) == 0 { // empty data msg return nil, nil } if term == "hb" { // Heartbeat // TODO: Consider adding a switch to enable/disable passing these along. return &bitfinex.Heartbeat{}, nil } /* list, ok := data[2].([]interface{}) if !ok { return ms, fmt.Errorf("expected data list in third position but got %#v in %#v", data[2], data) } */ ms = c.convertRaw(term, data) return } // convertRaw takes a term and the raw data attached to it to try and convert that // untyped list into a proper type. func (c *Client) convertRaw(term string, raw []interface{}) interface{} { // The things you do to get proper types. switch term { case "bu": o, err := bitfinex.NewBalanceInfoFromRaw(raw) if err != nil { return err } bu := bitfinex.BalanceUpdate(*o) return &bu case "ps": o, err := bitfinex.NewPositionSnapshotFromRaw(raw) if err != nil { return err } return o case "pn": o, err := bitfinex.NewPositionFromRaw(raw) if err != nil { return err } pn := bitfinex.PositionNew(*o) return &pn case "pu": o, err := bitfinex.NewPositionFromRaw(raw) if err != nil { return err } pu := bitfinex.PositionUpdate(*o) return &pu case "pc": o, err := bitfinex.NewPositionFromRaw(raw) if err != nil { return err } pc := bitfinex.PositionCancel(*o) return &pc case "ws": o, err := bitfinex.NewWalletSnapshotFromRaw(raw) if err != nil { return err } return o case "wu": o, err := bitfinex.NewWalletFromRaw(raw) if err != nil { return err } wu := bitfinex.WalletUpdate(*o) return &wu case "os": o, err := bitfinex.NewOrderSnapshotFromRaw(raw) if err != nil { return err } return o case "on": o, err := bitfinex.NewOrderFromRaw(raw) if err != nil { return err } on := bitfinex.OrderNew(*o) return &on case "ou": o, err := bitfinex.NewOrderFromRaw(raw) if err != nil { return err } ou := bitfinex.OrderUpdate(*o) return &ou case "oc": o, err := bitfinex.NewOrderFromRaw(raw) if err != nil { return err } oc := bitfinex.OrderCancel(*o) return &oc case "hts": o, err := bitfinex.NewTradeExecutionUpdateSnapshotFromRaw(raw) if err != nil { return err } hts := bitfinex.HistoricalTradeSnapshot(*o) return &hts case "te": o, err := bitfinex.NewTradeExecutionFromRaw(raw) if err != nil { return err } return o case "tu": tu, err := bitfinex.NewTradeExecutionUpdateFromRaw(raw) if err != nil { return err } return tu case "fte": o, err := bitfinex.NewFundingTradeFromRaw(raw) if err != nil { return err } fte := bitfinex.FundingTradeExecution(*o) return &fte case "ftu": o, err := bitfinex.NewFundingTradeFromRaw(raw) if err != nil { return err } ftu := bitfinex.FundingTradeUpdate(*o) return &ftu case "hfts": o, err := bitfinex.NewFundingTradeSnapshotFromRaw(raw) if err != nil { return err } nfts := bitfinex.HistoricalFundingTradeSnapshot(*o) return &nfts case "n": o, err := bitfinex.NewNotificationFromRaw(raw) if err != nil { return err } return o case "fos": o, err := bitfinex.NewFundingOfferSnapshotFromRaw(raw) if err != nil { return err } return o case "fon": o, err := bitfinex.NewOfferFromRaw(raw) if err != nil { return err } fon := bitfinex.FundingOfferNew(*o) return &fon case "fou": o, err := bitfinex.NewOfferFromRaw(raw) if err != nil { return err } fou := bitfinex.FundingOfferUpdate(*o) return &fou case "foc": o, err := bitfinex.NewOfferFromRaw(raw) if err != nil { return err } foc := bitfinex.FundingOfferCancel(*o) return &foc case "fiu": o, err := bitfinex.NewFundingInfoFromRaw(raw) if err != nil { return err } return o case "fcs": o, err := bitfinex.NewFundingCreditSnapshotFromRaw(raw) if err != nil { return err } return o case "fcn": o, err := bitfinex.NewCreditFromRaw(raw) if err != nil { return err } fcn := bitfinex.FundingCreditNew(*o) return &fcn case "fcu": o, err := bitfinex.NewCreditFromRaw(raw) if err != nil { return err } fcu := bitfinex.FundingCreditUpdate(*o) return &fcu case "fcc": o, err := bitfinex.NewCreditFromRaw(raw) if err != nil { return err } fcc := bitfinex.FundingCreditCancel(*o) return &fcc case "fls": o, err := bitfinex.NewFundingLoanSnapshotFromRaw(raw) if err != nil { return err } return o case "fln": o, err := bitfinex.NewLoanFromRaw(raw) if err != nil { return err } fln := bitfinex.FundingLoanNew(*o) return &fln case "flu": o, err := bitfinex.NewLoanFromRaw(raw) if err != nil { return err } flu := bitfinex.FundingLoanUpdate(*o) return &flu case "flc": o, err := bitfinex.NewLoanFromRaw(raw) if err != nil { return err } flc := bitfinex.FundingLoanCancel(*o) return &flc //case "uac": case "hb": return &bitfinex.Heartbeat{} case "ats": // TODO: Is not in documentation, so figure out what it is. return nil case "oc-req": // TODO return nil case "on-req": // TODO return nil case "mis": // Should not be sent anymore as of 2017-04-01 return nil case "miu": o, err := bitfinex.NewMarginInfoFromRaw(raw) if err != nil { return err } // return a strongly typed reference, rather than dereference a generic interface // too bad golang doesn't inherit an interface's underlying type when creating a reference to the interface if base, ok := o.(*bitfinex.MarginInfoBase); ok { return base } if update, ok := o.(*bitfinex.MarginInfoUpdate); ok { return update } return o // better than nothing default: c.log.Warningf("unhandled channel data, term: %s", term) } return fmt.Errorf("term %q not recognized", term) } bitfinex-api-go-2.2.9/v2/websocket/client.go000066400000000000000000000465631371275744700206670ustar00rootroot00000000000000package websocket import ( "bytes" "context" "fmt" "github.com/gorilla/websocket" "github.com/op/go-logging" "strings" "sync" "time" "unicode" "github.com/bitfinexcom/bitfinex-api-go/utils" "crypto/hmac" "crypto/sha512" "encoding/hex" "github.com/bitfinexcom/bitfinex-api-go/v2" ) var productionBaseURL = "wss://api-pub.bitfinex.com/ws/2" // ws-specific errors var ( ErrWSNotConnected = fmt.Errorf("websocket connection not established") ErrWSAlreadyConnected = fmt.Errorf("websocket connection already established") ) // Available channels const ( ChanBook = "book" ChanTrades = "trades" ChanTicker = "ticker" ChanCandles = "candles" ChanStatus = "status" ) // Events const ( EventSubscribe = "subscribe" EventUnsubscribe = "unsubscribe" EventPing = "ping" ) // Authentication states const ( NoAuthentication AuthState = 0 PendingAuthentication AuthState = 1 SuccessfulAuthentication AuthState = 2 RejectedAuthentication AuthState = 3 ) // private type--cannot instantiate. type authState byte // AuthState provides a typed authentication state. type AuthState authState // prevent user construction of authStates // DMSCancelOnDisconnect cancels session orders on disconnect. const DMSCancelOnDisconnect int = 4 // Asynchronous interface decouples the underlying transport from API logic. type Asynchronous interface { Connect() error Send(ctx context.Context, msg interface{}) error Listen() <-chan []byte Close() Done() <-chan error } type SocketId int type Socket struct { Id SocketId Asynchronous IsConnected bool ResetSubscriptions []*subscription IsAuthenticated bool } // AsynchronousFactory provides an interface to re-create asynchronous transports during reconnect events. type AsynchronousFactory interface { Create() Asynchronous } // WebsocketAsynchronousFactory creates a websocket-based asynchronous transport. type WebsocketAsynchronousFactory struct { parameters *Parameters } // NewWebsocketAsynchronousFactory creates a new websocket factory with a given URL. func NewWebsocketAsynchronousFactory(parameters *Parameters) AsynchronousFactory { return &WebsocketAsynchronousFactory{ parameters: parameters, } } // Create returns a new websocket transport. func (w *WebsocketAsynchronousFactory) Create() Asynchronous { return newWs(w.parameters.URL, w.parameters.LogTransport, w.parameters.Logger) } // Client provides a unified interface for users to interact with the Bitfinex V2 Websocket API. // nolint:megacheck,structcheck type Client struct { asyncFactory AsynchronousFactory // for re-creating transport during reconnects timeout int64 // read timeout apiKey string apiSecret string cancelOnDisconnect bool Authentication AuthState sockets map[SocketId]*Socket nonce utils.NonceGenerator terminal bool init bool log *logging.Logger // connection & operational behavior parameters *Parameters // subscription manager subscriptions *subscriptions factories map[string]messageFactory orderbooks map[string]*Orderbook // close signal sent to user on shutdown shutdown chan bool // downstream listener channel to deliver API objects listener chan interface{} // race management mtx *sync.RWMutex waitGroup sync.WaitGroup } // Credentials assigns authentication credentials to a connection request. func (c *Client) Credentials(key string, secret string) *Client { c.apiKey = key c.apiSecret = secret return c } // CancelOnDisconnect ensures all orders will be canceled if this API session is disconnected. func (c *Client) CancelOnDisconnect(cxl bool) *Client { c.cancelOnDisconnect = cxl return c } func (c *Client) sign(msg string) (string, error) { sig := hmac.New(sha512.New384, []byte(c.apiSecret)) _, err := sig.Write([]byte(msg)) if err != nil { return "", err } return hex.EncodeToString(sig.Sum(nil)), nil } func (c *Client) registerFactory(channel string, factory messageFactory) { c.factories[channel] = factory } // New creates a default client. func New() *Client { return NewWithParams(NewDefaultParameters()) } // NewWithAsyncFactory creates a new default client with a given asynchronous transport factory interface. func NewWithAsyncFactory(async AsynchronousFactory) *Client { return NewWithParamsAsyncFactory(NewDefaultParameters(), async) } // NewWithParams creates a new default client with a given set of parameters. func NewWithParams(params *Parameters) *Client { return NewWithParamsAsyncFactory(params, NewWebsocketAsynchronousFactory(params)) } // NewWithAsyncFactoryNonce creates a new default client with a given asynchronous transport factory and nonce generator. func NewWithAsyncFactoryNonce(async AsynchronousFactory, nonce utils.NonceGenerator) *Client { return NewWithParamsAsyncFactoryNonce(NewDefaultParameters(), async, nonce) } // NewWithParamsNonce creates a new default client with a given set of parameters and nonce generator. func NewWithParamsNonce(params *Parameters, nonce utils.NonceGenerator) *Client { return NewWithParamsAsyncFactoryNonce(params, NewWebsocketAsynchronousFactory(params), nonce) } // NewWithParamsAsyncFactory creates a new default client with a given set of parameters and asynchronous transport factory interface. func NewWithParamsAsyncFactory(params *Parameters, async AsynchronousFactory) *Client { return NewWithParamsAsyncFactoryNonce(params, async, utils.NewEpochNonceGenerator()) } // NewWithParamsAsyncFactoryNonce creates a new client with a given set of parameters, asynchronous transport factory, and nonce generator interfaces. func NewWithParamsAsyncFactoryNonce(params *Parameters, async AsynchronousFactory, nonce utils.NonceGenerator) *Client { c := &Client{ asyncFactory: async, Authentication: NoAuthentication, factories: make(map[string]messageFactory), subscriptions: newSubscriptions(params.HeartbeatTimeout, params.Logger), orderbooks: make(map[string]*Orderbook), nonce: nonce, parameters: params, listener: make(chan interface{}), terminal: false, shutdown: nil, sockets: make(map[SocketId]*Socket), mtx: &sync.RWMutex{}, log: params.Logger, } c.registerPublicFactories() return c } // Connect to the Bitfinex API, this should only be called once. func (c *Client) Connect() error { c.dumpParams() c.terminal = false go c.listenDisconnect() return c.connectSocket(SocketId(len(c.sockets))) } // Returns true if the underlying asynchronous transport is connected to an endpoint. func (c *Client) IsConnected() bool { c.mtx.RLock() defer c.mtx.RUnlock() for _, socket := range c.sockets { if socket.IsConnected { return true } } return false } // Listen for all incoming api websocket messages // When a websocket connection is terminated, the publisher channel will close. func (c *Client) Listen() <-chan interface{} { return c.listener } // Close the websocket client which will cause for all // active sockets to be exited and the Done() function // to be called func (c *Client) Close() { c.terminal = true var wg sync.WaitGroup socketCount := len(c.sockets) if socketCount > 0 { for _, socket := range c.sockets { if socket.IsConnected { wg.Add(1) socket.IsConnected = false go func(s *Socket) { c.closeAsyncAndWait(s, c.parameters.ShutdownTimeout) wg.Done() }(socket) } } wg.Wait() } c.subscriptions.Close() close(c.listener) } // Unsubscribe from the existing subscription with the given id func (c *Client) Unsubscribe(ctx context.Context, id string) error { sub, err := c.subscriptions.lookupBySubscriptionID(id) if err != nil { return err } // sub is removed from manager on ack from API return c.sendUnsubscribeMessage(ctx, sub) } func (c *Client) listenDisconnect() { for { select { case <- c.shutdown: return case hbErr := <- c.subscriptions.ListenDisconnect(): // subscription heartbeat timeout c.log.Warningf("heartbeat disconnect: %s", hbErr.Error.Error()) c.mtx.Lock() if socket, ok := c.sockets[hbErr.Subscription.SocketId]; ok { if socket.IsConnected { c.log.Infof("restarting socket (id=%d) connection", socket.Id) socket.IsConnected = false // reconnect to the socket go func() { c.closeAsyncAndWait(socket, c.parameters.ShutdownTimeout) err := c.reconnect(socket, hbErr.Error) if err != nil { c.log.Warningf("socket disconnect: %s", err.Error()) return } }() } } c.mtx.Unlock() } } } func extractSymbolResolutionFromKey(subscription string) (symbol string, resolution bitfinex.CandleResolution, err error) { var res, sym string str := strings.Split(subscription, ":") if len(str) < 3 { return "", resolution, fmt.Errorf("could not convert symbol resolution for %s: len %d", subscription, len(str)) } res = str[1] sym = str[2] resolution, err = bitfinex.CandleResolutionFromString(res) if err != nil { return "", resolution, err } return sym, resolution, nil } func (c *Client) registerPublicFactories() { c.registerFactory(ChanTicker, newTickerFactory(c.subscriptions)) c.registerFactory(ChanTrades, newTradeFactory(c.subscriptions)) c.registerFactory(ChanBook, newBookFactory(c.subscriptions, c.orderbooks, c.parameters.ManageOrderbook)) c.registerFactory(ChanCandles, newCandlesFactory(c.subscriptions)) c.registerFactory(ChanStatus, newStatsFactory(c.subscriptions)) } func (c *Client) reconnect(socket *Socket, err error) error { c.mtx.RLock() if c.terminal { // dont attempt to reconnect if terminal c.mtx.RUnlock() return err } if !c.parameters.AutoReconnect { err := fmt.Errorf("AutoReconnect setting is disabled, do not reconnect: %s", err.Error()) c.mtx.RUnlock() return err } c.mtx.RUnlock() reconnectTry := 0 for ; reconnectTry < c.parameters.ReconnectAttempts; reconnectTry++ { c.log.Debugf("socket (id=%d) waiting %s until reconnect...", socket.Id, c.parameters.ReconnectInterval) time.Sleep(c.parameters.ReconnectInterval) c.log.Infof("socket (id=%d) reconnect attempt %d/%d", socket.Id, reconnectTry+1, c.parameters.ReconnectAttempts) err := c.reconnectSocket(socket) if err == nil { c.log.Debugf("reconnect OK") return nil } c.log.Warningf("socket (id=%d) reconnect failed: %s", socket.Id, err.Error()) } if err != nil { c.log.Errorf("socket (id=%d) could not reconnect: %s", socket.Id, err.Error()) } return err } func (c *Client) dumpParams() { c.log.Debug("----Bitfinex Client Parameters----") c.log.Debugf("AutoReconnect=%t", c.parameters.AutoReconnect) c.log.Debugf("CapacityPerConnection=%t", c.parameters.CapacityPerConnection) c.log.Debugf("ReconnectInterval=%s", c.parameters.ReconnectInterval) c.log.Debugf("ReconnectAttempts=%d", c.parameters.ReconnectAttempts) c.log.Debugf("ShutdownTimeout=%s", c.parameters.ShutdownTimeout) c.log.Debugf("ResubscribeOnReconnect=%t", c.parameters.ResubscribeOnReconnect) c.log.Debugf("HeartbeatTimeout=%s", c.parameters.HeartbeatTimeout) c.log.Debugf("URL=%s", c.parameters.URL) c.log.Debugf("ManageOrderbook=%t", c.parameters.ManageOrderbook) } func (c *Client) connectSocket(socketId SocketId) error { async := c.asyncFactory.Create() // create new socket instance socket := &Socket{ Id: socketId, Asynchronous: async, IsConnected: false, ResetSubscriptions: nil, IsAuthenticated: false, } oldSocket, _ := c.socketById(socketId) if oldSocket != nil { // socket exists so use its state socket.IsAuthenticated = oldSocket.IsAuthenticated socket.ResetSubscriptions = oldSocket.ResetSubscriptions } c.mtx.Lock() // add socket to managed map c.sockets[socket.Id] = socket c.mtx.Unlock() // connect socket err := socket.Asynchronous.Connect() if err != nil { // unable to establish connection return err } socket.IsConnected = true go c.listenUpstream(socket) return nil } func (c *Client) reconnectSocket(socket *Socket) error { // remove subscriptions from manager but keep a copy so we can resubscribe once // a new connection is established if socket.ResetSubscriptions == nil && c.parameters.ResubscribeOnReconnect { socket.ResetSubscriptions = c.subscriptions.ResetSocketSubscriptions(socket.Id) } // establish a new connection err := c.connectSocket(socket.Id) if err != nil { return err } return nil } // start this goroutine before connecting, but this should die during a connection failure func (c *Client) listenUpstream(socket *Socket) { for { select { case err := <- socket.Asynchronous.Done(): if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { err := c.reconnect(socket, err) if err != nil { c.log.Errorf("Unable to reconnect socket (id=%d) after err: %s", socket.Id, err.Error()) return } } return case msg := <- socket.Asynchronous.Listen(): if msg != nil { err := c.handleMessage(socket.Id, msg) if err != nil { c.log.Warningf("upstream listen error: %s", err.Error()) } } } } } func (c *Client) closeAsyncAndWait(socket *Socket, t time.Duration) { timeout := make(chan bool) wg := sync.WaitGroup{} wg.Add(1) go func() { select { case <-socket.Asynchronous.Done(): wg.Done() case <-timeout: c.log.Errorf("socket (id=%d) took too long to close.", socket.Id) wg.Done() } }() go func() { time.Sleep(t) close(timeout) }() socket.Asynchronous.Close() wg.Wait() } func (c *Client) handleMessage(socketId SocketId, msg []byte) error { t := bytes.TrimLeftFunc(msg, unicode.IsSpace) var err error // either a channel data array or an event object, raw json encoding if bytes.HasPrefix(t, []byte("[")) { err = c.handleChannel(socketId, msg) } else if bytes.HasPrefix(t, []byte("{")) { err = c.handleEvent(socketId, msg) } else { return fmt.Errorf("unexpected message in socket (id=%d): %s", socketId, msg) } return err } func (c *Client) sendUnsubscribeMessage(ctx context.Context, sub *subscription) error { socket, err := c.socketById(sub.SocketId) if err != nil { return err } return socket.Asynchronous.Send(ctx, unsubscribeMsg{Event: "unsubscribe", ChanID: sub.ChanID}) } func (c *Client) checkResubscription(socketId SocketId) { socket, err := c.socketById(socketId) if err != nil { panic(err) } if c.parameters.ManageOrderbook { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() _, err_flag := c.EnableFlag(ctx, bitfinex.Checksum) if err_flag != nil { c.log.Errorf("could not enable checksum flag %s ", err_flag) } } if c.parameters.ResubscribeOnReconnect && socket.ResetSubscriptions != nil { for _, sub := range socket.ResetSubscriptions { if sub.Request.Event == "auth" { continue } ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() sub.Request.SubID = c.nonce.GetNonce() // new nonce c.log.Infof("socket (id=%d) resubscribing to %s with nonce %s", socket.Id, sub.Request.String(), sub.Request.SubID) _, err := c.subscribeBySocket(ctx, socket, sub.Request) if err != nil { c.log.Errorf("could not resubscribe: %s", err.Error()) } } socket.ResetSubscriptions = nil } } // called when an info event is received func (c *Client) handleOpen(socketId SocketId) error { authSocket, _ := c.GetAuthenticatedSocket() // if we have auth credentials and there is currently no authenticated // sockets (we are only allowed one) if c.hasCredentials() && authSocket == nil { err_auth := c.authenticate(context.Background(), socketId) if err_auth != nil { return err_auth } return nil } // if the opening socket (triggered by reconnect) is authenticated then re-authenticate if authSocket != nil && authSocket.Id == socketId { err_auth := c.authenticate(context.Background(), socketId) if err_auth != nil { return err_auth } return nil } // resubscribe public channels (we will handle authenticated in authAck) c.checkResubscription(socketId) return nil } // called when an auth event is received func (c *Client) handleAuthAck(socketId SocketId, auth *AuthEvent) { if c.Authentication == SuccessfulAuthentication { // set socket to authenticated socket, err := c.socketById(socketId) if err != nil { panic(err) } socket.IsAuthenticated = true err = c.subscriptions.activate(auth.SubID, auth.ChanID) if err != nil { c.log.Errorf("could not activate auth subscription: %s", err.Error()) } c.checkResubscription(socketId) } else { c.log.Error("authentication failed") } } func (c *Client) hasCredentials() bool { return c.apiKey != "" && c.apiSecret != "" } // Authenticate creates the payload for the authentication request and sends it // to the API. The filters will be applied to the authenticated channel, i.e. // only subscribe to the filtered messages. func (c *Client) authenticate(ctx context.Context, socketId SocketId, filter ...string) error { nonce := c.nonce.GetNonce() payload := "AUTH" + nonce sig, err := c.sign(payload) if err != nil { return err } s := &SubscriptionRequest{ Event: "auth", APIKey: c.apiKey, AuthSig: sig, AuthPayload: payload, AuthNonce: nonce, Filter: filter, SubID: nonce, } if c.cancelOnDisconnect { s.DMS = DMSCancelOnDisconnect } c.subscriptions.add(socketId, s) socket, err := c.socketById(socketId) if err != nil { return err } if err = socket.Asynchronous.Send(ctx, s); err != nil { return err } c.Authentication = PendingAuthentication return nil } // get a random socket func (c *Client) getSocket() (*Socket, error) { if len(c.sockets) <= 0 { return nil, fmt.Errorf("no socket found") } return c.sockets[0], nil } func (c *Client) getMostAvailableSocket() (*Socket, error) { var retSocket *Socket bestCapacity := 0 for _, socket := range c.sockets { capac := c.getAvailableSocketCapacity(socket.Id) if retSocket == nil { retSocket = socket bestCapacity = capac continue } if capac > bestCapacity { retSocket = socket bestCapacity = capac } } if retSocket == nil { return nil, fmt.Errorf("no socket found") } return retSocket, nil } // lookup the socket with the given Id, throw error if not found func (c *Client) socketById(socketId SocketId) (*Socket, error) { c.mtx.RLock() defer c.mtx.RUnlock() if socket, ok := c.sockets[socketId]; ok { return socket, nil } return nil, fmt.Errorf("could not find socket with ID %d", socketId) } // calculates how many free channels are available across all of the sockets func (c *Client) getTotalAvailableSocketCapacity() int { freeCapacity := 0 c.mtx.RLock() ids := make([]SocketId, len(c.sockets)) for i, socket := range c.sockets { ids[i] = socket.Id } c.mtx.RUnlock() for _, id := range ids { freeCapacity += c.getAvailableSocketCapacity(id) } return freeCapacity } // calculates how many free channels are available on the given socket func (c *Client) getAvailableSocketCapacity(socketId SocketId) int { c.mtx.RLock() defer c.mtx.RUnlock() subs, err := c.subscriptions.lookupBySocketId(socketId) if err == nil { return c.parameters.CapacityPerConnection - subs.Len() } return c.parameters.CapacityPerConnection } // Get the authenticated socket. Due to rate limitations // there can only be one authenticated socket active at a time func (c *Client) GetAuthenticatedSocket() (*Socket, error) { c.mtx.RLock() defer c.mtx.RUnlock() for _, socket := range c.sockets { if socket.IsAuthenticated { return socket, nil } } return nil, fmt.Errorf("no authenticated socket found") } bitfinex-api-go-2.2.9/v2/websocket/events.go000066400000000000000000000103351371275744700207010ustar00rootroot00000000000000package websocket import ( "encoding/json" ) type eventType struct { Event string `json:"event"` } type InfoEvent struct { Version float64 `json:"version"` ServerId string `json:"serverId"` Platform PlatformInfo `json:"platform"` Code int `json:"code"` Msg string `json:"msg"` } type PlatformInfo struct { Status int `json:"status"` } type RawEvent struct { Data interface{} } type AuthEvent struct { Event string `json:"event"` Status string `json:"status"` ChanID int64 `json:"chanId,omitempty"` UserID int64 `json:"userId,omitempty"` SubID string `json:"subId"` AuthID string `json:"auth_id,omitempty"` Message string `json:"msg,omitempty"` Caps Capabilities `json:"caps"` } type Capability struct { Read int `json:"read"` Write int `json:"write"` } type Capabilities struct { Orders Capability `json:"orders"` Account Capability `json:"account"` Funding Capability `json:"funding"` History Capability `json:"history"` Wallets Capability `json:"wallets"` Withdraw Capability `json:"withdraw"` Positions Capability `json:"positions"` } // error codes pulled from v2 docs & API usage const ( ErrorCodeUnknownEvent int = 10000 ErrorCodeUnknownPair int = 10001 ErrorCodeUnknownBookPrecision int = 10011 ErrorCodeUnknownBookLength int = 10012 ErrorCodeSubscriptionFailed int = 10300 ErrorCodeAlreadySubscribed int = 10301 ErrorCodeUnknownChannel int = 10302 ErrorCodeUnsubscribeFailed int = 10400 ErrorCodeNotSubscribed int = 10401 ) type ErrorEvent struct { Code int `json:"code"` Message string `json:"msg"` // also contain members related to subscription reject SubID string `json:"subId"` Channel string `json:"channel"` ChanID int64 `json:"chanId"` Symbol string `json:"symbol"` Precision string `json:"prec,omitempty"` Frequency string `json:"freq,omitempty"` Key string `json:"key,omitempty"` Len string `json:"len,omitempty"` Pair string `json:"pair"` } type UnsubscribeEvent struct { Status string `json:"status"` ChanID int64 `json:"chanId"` } type SubscribeEvent struct { SubID string `json:"subId"` Channel string `json:"channel"` ChanID int64 `json:"chanId"` Symbol string `json:"symbol"` Precision string `json:"prec,omitempty"` Frequency string `json:"freq,omitempty"` Key string `json:"key,omitempty"` Len string `json:"len,omitempty"` Pair string `json:"pair"` } type ConfEvent struct { Flags int `json:"flags"` } // onEvent handles all the event messages and connects SubID and ChannelID. func (c *Client) handleEvent(socketId SocketId, msg []byte) error { event := &eventType{} err := json.Unmarshal(msg, event) if err != nil { return err } //var e interface{} switch event.Event { case "info": i := InfoEvent{} err = json.Unmarshal(msg, &i) if err != nil { return err } if i.Code == 0 && i.Version != 0 { err_open := c.handleOpen(socketId) if err_open != nil { return err_open } } c.listener <- &i case "auth": a := AuthEvent{} err = json.Unmarshal(msg, &a) if err != nil { return err } if a.Status != "" && a.Status == "OK" { c.Authentication = SuccessfulAuthentication } else { c.Authentication = RejectedAuthentication } c.handleAuthAck(socketId, &a) c.listener <- &a return nil case "subscribed": s := SubscribeEvent{} err = json.Unmarshal(msg, &s) if err != nil { return err } err = c.subscriptions.activate(s.SubID, s.ChanID) if err != nil { return err } c.listener <- &s return nil case "unsubscribed": s := UnsubscribeEvent{} err = json.Unmarshal(msg, &s) if err != nil { return err } err_rem := c.subscriptions.removeByChannelID(s.ChanID) if err_rem != nil { return err_rem } c.listener <- &s case "error": er := ErrorEvent{} err = json.Unmarshal(msg, &er) if err != nil { return err } c.listener <- &er case "conf": ec := ConfEvent{} err = json.Unmarshal(msg, &ec) if err != nil { return err } c.listener <- &ec default: c.log.Warningf("unknown event: %s", msg) } //err = json.Unmarshal(msg, &e) //TODO raw message isn't ever published return err } bitfinex-api-go-2.2.9/v2/websocket/factories.go000066400000000000000000000130071371275744700213530ustar00rootroot00000000000000package websocket import ( "encoding/json" "fmt" "strings" "sync" "github.com/bitfinexcom/bitfinex-api-go/pkg/convert" "github.com/bitfinexcom/bitfinex-api-go/v2" ) type messageFactory interface { Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) } type TickerFactory struct { *subscriptions } func newTickerFactory(subs *subscriptions) *TickerFactory { return &TickerFactory{ subscriptions: subs, } } func (f *TickerFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) { return bitfinex.NewTickerFromRaw(sub.Request.Symbol, raw) } func (f *TickerFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) { converted, err := convert.ToFloat64Array(raw) if err != nil { return nil, err } return bitfinex.NewTickerSnapshotFromRaw(sub.Request.Symbol, converted) } type TradeFactory struct { *subscriptions } func newTradeFactory(subs *subscriptions) *TradeFactory { return &TradeFactory{ subscriptions: subs, } } func (f *TradeFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) { if "tu" == objType { return nil, nil // do not process TradeUpdate messages on public feed, only need to process TradeExecution (first copy seen) } return bitfinex.NewTradeFromRaw(sub.Request.Symbol, raw) } func (f *TradeFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) { converted, err := convert.ToFloat64Array(raw) if err != nil { return nil, err } return bitfinex.NewTradeSnapshotFromRaw(sub.Request.Symbol, converted) } type BookFactory struct { *subscriptions orderbooks map[string]*Orderbook manageBooks bool lock sync.Mutex } func newBookFactory(subs *subscriptions, obs map[string]*Orderbook, manageBooks bool) *BookFactory { return &BookFactory{ subscriptions: subs, orderbooks: obs, manageBooks: manageBooks, } } func ConvertBytesToJsonNumberArray(raw_bytes []byte) ([]interface{}, error) { var raw_json_number []interface{} d := json.NewDecoder(strings.NewReader(string(raw_bytes))) d.UseNumber() str_conv_err := d.Decode(&raw_json_number) if str_conv_err != nil { return nil, str_conv_err } return raw_json_number, nil } func (f *BookFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) { // we need ot parse the bytes using json numbers since they store the exact string value // and not a float64 representation raw_json_number, str_conv_err := ConvertBytesToJsonNumberArray(raw_bytes) if str_conv_err != nil { return nil, str_conv_err } update, err := bitfinex.NewBookUpdateFromRaw(sub.Request.Symbol, sub.Request.Precision, raw, raw_json_number[1]) if f.manageBooks { f.lock.Lock() defer f.lock.Unlock() if orderbook, ok := f.orderbooks[sub.Request.Symbol]; ok { orderbook.UpdateWith(update) } } return update, err } func (f *BookFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) { converted, err := convert.ToFloat64Array(raw) if err != nil { return nil, err } // parse the bytes using the json number value to store the exact string value raw_json_number, str_conv_err := ConvertBytesToJsonNumberArray(raw_bytes) if str_conv_err != nil { return nil, str_conv_err } update, err2 := bitfinex.NewBookUpdateSnapshotFromRaw(sub.Request.Symbol, sub.Request.Precision, converted, raw_json_number[1]) if err2 != nil { return nil, err2 } if f.manageBooks { f.lock.Lock() defer f.lock.Unlock() // create new orderbook f.orderbooks[sub.Request.Symbol] = &Orderbook{ symbol: sub.Request.Symbol, bids: make([]*bitfinex.BookUpdate, 0), asks: make([]*bitfinex.BookUpdate, 0), } f.orderbooks[sub.Request.Symbol].SetWithSnapshot(update) } return update, err } type CandlesFactory struct { *subscriptions } func newCandlesFactory(subs *subscriptions) *CandlesFactory { return &CandlesFactory{ subscriptions: subs, } } func (f *CandlesFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) { sym, res, err := extractSymbolResolutionFromKey(sub.Request.Key) if err != nil { return nil, err } candle, err := bitfinex.NewCandleFromRaw(sym, res, raw) return candle, err } func (f *CandlesFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) { converted, err := convert.ToFloat64Array(raw) if err != nil { return nil, err } sym, res, err := extractSymbolResolutionFromKey(sub.Request.Key) if err != nil { return nil, err } snap, err := bitfinex.NewCandleSnapshotFromRaw(sym, res, converted) return snap, err } type StatsFactory struct { *subscriptions } func newStatsFactory(subs *subscriptions) *StatsFactory { return &StatsFactory{ subscriptions: subs, } } func (f *StatsFactory) Build(sub *subscription, objType string, raw []interface{}, raw_bytes []byte) (interface{}, error) { splits := strings.Split(sub.Request.Key, ":") if len(splits) != 3 { return nil, fmt.Errorf("unable to parse key to symbol %s", sub.Request.Key) } symbol := splits[1] + ":" + splits[2] candle, err := bitfinex.NewDerivativeStatusFromWsRaw(symbol, raw) return candle, err } func (f *StatsFactory) BuildSnapshot(sub *subscription, raw [][]interface{}, raw_bytes []byte) (interface{}, error) { // no snapshots return nil, nil } bitfinex-api-go-2.2.9/v2/websocket/orderbook.go000066400000000000000000000055611371275744700213700ustar00rootroot00000000000000package websocket import ( "github.com/bitfinexcom/bitfinex-api-go/v2" "sort" "sync" "strings" "hash/crc32" ) type Orderbook struct { lock sync.RWMutex symbol string bids []*bitfinex.BookUpdate asks []*bitfinex.BookUpdate } // return a dereferenced copy of an orderbook side. This is so consumers can access // the book but not change the values that are used to generate the crc32 checksum func (ob *Orderbook) copySide(side []*bitfinex.BookUpdate) []bitfinex.BookUpdate { var cpy []bitfinex.BookUpdate for i := 0; i < len(side); i++ { cpy = append(cpy, *side[i]) } return cpy } func (ob *Orderbook) Symbol() string { return ob.symbol } func (ob *Orderbook) Asks() []bitfinex.BookUpdate { ob.lock.RLock() defer ob.lock.RUnlock() return ob.copySide(ob.asks) } func (ob *Orderbook) Bids() []bitfinex.BookUpdate { ob.lock.RLock() defer ob.lock.RUnlock() return ob.copySide(ob.bids) } func (ob *Orderbook) SetWithSnapshot(bs *bitfinex.BookUpdateSnapshot) { ob.lock.Lock() defer ob.lock.Unlock() ob.bids = make([]*bitfinex.BookUpdate, 0) ob.asks = make([]*bitfinex.BookUpdate, 0) for _, order := range bs.Snapshot { if (order.Side == bitfinex.Bid) { ob.bids = append(ob.bids, order) } else { ob.asks = append(ob.asks, order) } } } func (ob *Orderbook) UpdateWith(bu *bitfinex.BookUpdate) { ob.lock.Lock() defer ob.lock.Unlock() side := &ob.asks if (bu.Side == bitfinex.Bid) { side = &ob.bids } // check if first in book if (len(*side) == 0) { *side = append(*side, bu) return } // match price level for index, sOrder := range *side { if (sOrder.Price == bu.Price) { if (index+1 > len(*(side))) { return } if (bu.Count <= 0) { // delete if count is equal to zero *side = append((*side)[:index], (*side)[index+1:]...) return } else { // remove now and we will add in the code below *side = append((*side)[:index], (*side)[index+1:]...) } } } *side = append(*side, bu) // add to the orderbook and sort lowest to highest sort.Slice(*side, func(i, j int) bool { if (i >= len(*(side)) || j >= len(*(side))) { return false } if bu.Side == bitfinex.Ask { return (*side)[i].Price < (*side)[j].Price } else { return (*side)[i].Price > (*side)[j].Price } }) } func (ob *Orderbook) Checksum() (uint32) { ob.lock.Lock() defer ob.lock.Unlock() var checksumItems []string for i := 0; i < 25; i++ { if len(ob.bids) > i { // append bid checksumItems = append(checksumItems, (ob.bids)[i].PriceJsNum.String()) checksumItems = append(checksumItems, (ob.bids)[i].AmountJsNum.String()) } if len(ob.asks) > i { // append ask checksumItems = append(checksumItems, (ob.asks)[i].PriceJsNum.String()) checksumItems = append(checksumItems, (ob.asks)[i].AmountJsNum.String()) } } checksumStrings := strings.Join(checksumItems, ":") return crc32.ChecksumIEEE([]byte(checksumStrings)) } bitfinex-api-go-2.2.9/v2/websocket/parameters.go000066400000000000000000000020761371275744700215430ustar00rootroot00000000000000package websocket import ( "github.com/op/go-logging" "time" ) // Parameters defines adapter behavior. type Parameters struct { AutoReconnect bool ReconnectInterval time.Duration ReconnectAttempts int reconnectTry int ShutdownTimeout time.Duration CapacityPerConnection int Logger *logging.Logger ResubscribeOnReconnect bool HeartbeatTimeout time.Duration LogTransport bool URL string ManageOrderbook bool } func NewDefaultParameters() *Parameters { return &Parameters{ AutoReconnect: true, CapacityPerConnection: 25, ReconnectInterval: time.Second * 3, reconnectTry: 0, ReconnectAttempts: 15, URL: productionBaseURL, ManageOrderbook: false, ShutdownTimeout: time.Second * 5, ResubscribeOnReconnect: true, HeartbeatTimeout: time.Second * 30, LogTransport: false, // log transport send/recv Logger: logging.MustGetLogger("bitfinex-ws"), } } bitfinex-api-go-2.2.9/v2/websocket/subscriptions.go000066400000000000000000000222461371275744700223100ustar00rootroot00000000000000package websocket import ( "fmt" "github.com/op/go-logging" "strings" "sync" "time" ) type SubscriptionRequest struct { SubID string `json:"subId"` Event string `json:"event"` // authenticated APIKey string `json:"apiKey,omitempty"` AuthSig string `json:"authSig,omitempty"` AuthPayload string `json:"authPayload,omitempty"` AuthNonce string `json:"authNonce,omitempty"` Filter []string `json:"filter,omitempty"` DMS int `json:"dms,omitempty"` // dead man switch // unauthenticated Channel string `json:"channel,omitempty"` Symbol string `json:"symbol,omitempty"` Precision string `json:"prec,omitempty"` Frequency string `json:"freq,omitempty"` Key string `json:"key,omitempty"` Len string `json:"len,omitempty"` Pair string `json:"pair,omitempty"` } const MaxChannels = 25 func (s *SubscriptionRequest) String() string { if s.Key == "" && s.Channel != "" && s.Symbol != "" { return fmt.Sprintf("%s %s", s.Channel, s.Symbol) } if s.Channel != "" && s.Symbol != "" && s.Precision != "" && s.Frequency != "" { return fmt.Sprintf("%s %s %s %s", s.Channel, s.Symbol, s.Precision, s.Frequency) } if s.Channel != "" && s.Symbol != "" { return fmt.Sprintf("%s %s", s.Channel, s.Symbol) } return "" } type HeartbeatDisconnect struct { Subscription *subscription Error error } type UnsubscribeRequest struct { Event string `json:"event"` ChanID int64 `json:"chanId"` } type subscription struct { ChanID int64 SocketId SocketId pending bool Public bool Request *SubscriptionRequest hbDeadline time.Time } func isPublic(request *SubscriptionRequest) bool { switch request.Channel { case ChanBook: return true case ChanCandles: return true case ChanTicker: return true case ChanTrades: return true case ChanStatus: return true } return false } func newSubscription(socketId SocketId, request *SubscriptionRequest) *subscription { return &subscription{ ChanID: -1, SocketId: socketId, Request: request, pending: true, Public: isPublic(request), } } func (s subscription) SubID() string { return s.Request.SubID } func (s subscription) Pending() bool { return s.pending } func newSubscriptions(heartbeatTimeout time.Duration, log *logging.Logger) *subscriptions { subs := &subscriptions{ subsBySubID: make(map[string]*subscription), subsByChanID: make(map[int64]*subscription), subsBySocketId: make(map[SocketId]SubscriptionSet), hbTimeout: heartbeatTimeout, hbShutdown: make(chan struct{}), hbDisconnect: make(chan HeartbeatDisconnect), hbSleep: heartbeatTimeout / time.Duration(4), log: log, lock: &sync.RWMutex{}, } go subs.control() return subs } // nolint type heartbeat struct { ChanID int64 *time.Time } type subscriptions struct { lock *sync.RWMutex log *logging.Logger subsBySocketId map[SocketId]SubscriptionSet // subscripts map indexed by socket id subsBySubID map[string]*subscription // subscription map indexed by subscription ID subsByChanID map[int64]*subscription // subscription map indexed by channel ID hbActive bool hbDisconnect chan HeartbeatDisconnect // disconnect parent due to heartbeat timeout hbTimeout time.Duration hbSleep time.Duration hbShutdown chan struct{} } // SubscriptionSet is a typed version of an array of subscription pointers, intended to meet the sortable interface. // We need to sort Reset()'s return values for tests with more than 1 subscription (range map order is undefined) type SubscriptionSet []*subscription func (s SubscriptionSet) Len() int { return len(s) } func (s SubscriptionSet) Less(i, j int) bool { return strings.Compare(s[i].SubID(), s[j].SubID()) < 0 } func (s SubscriptionSet) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s SubscriptionSet) RemoveByChannelId(chanId int64) SubscriptionSet { rIndex := -1 for i, sub := range s { if sub.ChanID == chanId { rIndex = i break } } if rIndex >= 0 { return append(s[:rIndex], s[rIndex+1:]...) } return s } func (s SubscriptionSet) RemoveBySubscriptionId(subID string) SubscriptionSet { rIndex := -1 for i, sub := range s { if sub.SubID() == subID { rIndex = i break } } if rIndex >= 0 { return append(s[:rIndex], s[rIndex+1:]...) } return s } func (s *subscriptions) heartbeat(chanID int64) { s.lock.Lock() defer s.lock.Unlock() if sub, ok := s.subsByChanID[chanID]; ok { sub.hbDeadline = time.Now().Add(s.hbTimeout) } } func (s *subscriptions) sweep(exp time.Time) { s.lock.RLock() if !s.hbActive { s.lock.RUnlock() return } disconnects := make([]HeartbeatDisconnect, 0) for _, sub := range s.subsByChanID { if exp.After(sub.hbDeadline) { s.hbActive = false hbErr := HeartbeatDisconnect{ Subscription: sub, Error: fmt.Errorf("heartbeat disconnect on channel %d expired at %s (%s timeout)", sub.ChanID, sub.hbDeadline, s.hbTimeout), } disconnects = append(disconnects, hbErr) } } s.lock.RUnlock() for _, dis := range disconnects { s.hbDisconnect <- dis } } func (s *subscriptions) control() { for { select { case <-s.hbShutdown: return default: } s.sweep(time.Now()) time.Sleep(s.hbSleep) } } // Close is terminal. Do not call heartbeat after close. func (s *subscriptions) Close() { s.ResetAll() close(s.hbShutdown) } // Reset clears all subscriptions assigned to the given socket ID, and returns // a slice of the existing subscriptions prior to reset func (s *subscriptions) ResetSocketSubscriptions(socketId SocketId) []*subscription { var retSubs []*subscription s.lock.Lock() if set, ok := s.subsBySocketId[socketId]; ok { for _, sub := range set { retSubs = append(retSubs, sub) // remove from chanId array delete(s.subsByChanID, sub.ChanID) // remove from subId array delete(s.subsBySubID, sub.SubID()) } } s.subsBySocketId[socketId] = make(SubscriptionSet, 0) s.lock.Unlock() return retSubs } // Removes all tracked subscriptions func (s *subscriptions) ResetAll() { s.lock.Lock() s.subsBySubID = make(map[string]*subscription) s.subsByChanID = make(map[int64]*subscription) s.subsBySocketId = make(map[SocketId]SubscriptionSet) s.lock.Unlock() } // ListenDisconnect returns an error channel which receives a message when a heartbeat has expired a channel. func (s *subscriptions) ListenDisconnect() <-chan HeartbeatDisconnect { return s.hbDisconnect } func (s *subscriptions) add(socketId SocketId, sub *SubscriptionRequest) *subscription { s.lock.Lock() defer s.lock.Unlock() subscription := newSubscription(socketId, sub) s.subsBySubID[sub.SubID] = subscription if _, ok := s.subsBySocketId[socketId]; !ok { s.subsBySocketId[socketId] = make(SubscriptionSet, 0) } s.subsBySocketId[socketId] = append(s.subsBySocketId[socketId], subscription) return subscription } func (s *subscriptions) removeByChannelID(chanID int64) error { s.lock.Lock() defer s.lock.Unlock() // remove from socketId map sub, ok := s.subsByChanID[chanID] if !ok { return fmt.Errorf("could not find channel ID %d", chanID) } delete(s.subsByChanID, chanID) delete(s.subsBySubID, sub.SubID()) // remove from socket map if _, ok := s.subsBySocketId[sub.SocketId]; ok { s.subsBySocketId[sub.SocketId] = s.subsBySocketId[sub.SocketId].RemoveByChannelId(chanID) } return nil } // nolint:megacheck func (s *subscriptions) removeBySubscriptionID(subID string) error { s.lock.Lock() defer s.lock.Unlock() sub, ok := s.subsBySubID[subID] if !ok { return fmt.Errorf("could not find subscription ID %s to remove", subID) } // exists, remove both indices delete(s.subsBySubID, subID) delete(s.subsByChanID, sub.ChanID) // remove from socket map if _, ok := s.subsBySocketId[sub.SocketId]; ok { s.subsBySocketId[sub.SocketId] = s.subsBySocketId[sub.SocketId].RemoveBySubscriptionId(subID) } return nil } func (s *subscriptions) activate(subID string, chanID int64) error { s.lock.Lock() defer s.lock.Unlock() if sub, ok := s.subsBySubID[subID]; ok { if chanID != 0 { s.log.Infof("activated subscription %s %s for channel %d", sub.Request.Channel, sub.Request.Symbol, chanID) } sub.pending = false sub.ChanID = chanID sub.hbDeadline = time.Now().Add(s.hbTimeout) s.subsByChanID[chanID] = sub s.hbActive = true return nil } return fmt.Errorf("could not find subscription ID %s to activate", subID) } func (s *subscriptions) lookupBySocketChannelID(chanID int64, sId SocketId) (*subscription, error) { s.lock.RLock() defer s.lock.RUnlock() if subs, ok := s.subsBySocketId[sId]; ok { for _, s := range subs { if s.ChanID == chanID { return s, nil } } } return nil, fmt.Errorf("could not find subscription for channel ID %d and socket sId %d", chanID, sId) } func (s *subscriptions) lookupBySubscriptionID(subID string) (*subscription, error) { s.lock.RLock() defer s.lock.RUnlock() if sub, ok := s.subsBySubID[subID]; ok { return sub, nil } return nil, fmt.Errorf("could not find subscription ID %s", subID) } func (s *subscriptions) lookupBySocketId(socketId SocketId) (*SubscriptionSet, error) { s.lock.RLock() defer s.lock.RUnlock() if set, ok := s.subsBySocketId[socketId]; ok { return &set, nil } return nil, fmt.Errorf("could not find subscription with socketId %d", socketId) } bitfinex-api-go-2.2.9/v2/websocket/transport.go000066400000000000000000000113251371275744700214310ustar00rootroot00000000000000package websocket import ( "context" "crypto/tls" "encoding/json" "fmt" "github.com/op/go-logging" "net" "net/http" "sync" "time" "github.com/gorilla/websocket" ) // size of channel that the websocket writer // routine pulls from const WS_WRITE_CAPACITY = 5000 // size of channel that the websocket reader // routine pushes websocket updates into const WS_READ_CAPACITY = 10 // seconds to wait in between re-sending // the keep alive ping const KEEP_ALIVE_TIMEOUT = 10 func newWs(baseURL string, logTransport bool, log *logging.Logger) *ws { return &ws{ BaseURL: baseURL, downstream: make(chan []byte, WS_READ_CAPACITY), quit: make(chan error), kill: make(chan interface{}), logTransport: logTransport, log: log, lock: &sync.RWMutex{}, createTime: time.Now(), writeChan: make(chan []byte, WS_WRITE_CAPACITY), } } type ws struct { ws *websocket.Conn lock *sync.RWMutex BaseURL string TLSSkipVerify bool downstream chan []byte logTransport bool log *logging.Logger createTime time.Time writeChan chan []byte kill chan interface{} // signal to routines to kill quit chan error // signal to parent with error, if applicable } func (w *ws) Connect() error { if w.ws != nil { return nil // no op } var d = websocket.Dialer{ Subprotocols: []string{"p1", "p2"}, ReadBufferSize: 1024, WriteBufferSize: 1024, Proxy: http.ProxyFromEnvironment, HandshakeTimeout: time.Second * 10, } d.TLSClientConfig = &tls.Config{InsecureSkipVerify: w.TLSSkipVerify} w.log.Info("connecting ws to %s", w.BaseURL) ws, resp, err := d.Dial(w.BaseURL, nil) if err != nil { if err == websocket.ErrBadHandshake { w.log.Errorf("bad handshake: status code %d", resp.StatusCode) } return err } w.ws = ws go w.listenWriteChannel() go w.listenWs() // Gorilla/go dont natively support keep alive pinging // so we need to keep sending a message down the channel to stop // tcp killing the connection go w.keepAlivePinger() return nil } func (w *ws) keepAlivePinger() { for { pingTimer := time.After(time.Second * KEEP_ALIVE_TIMEOUT) select { case <-w.kill: return case <-pingTimer: w.writeChan <- []byte("ping") } } } // Send marshals the given interface and then sends it to the API. This method // can block so specify a context with timeout if you don't want to wait for too // long. func (w *ws) Send(ctx context.Context, msg interface{}) error { bs, err := json.Marshal(msg) if err != nil { return err } select { case <- ctx.Done(): return ctx.Err() case <- w.kill: // ws closed return fmt.Errorf("websocket connection closed") default: } w.log.Debug("ws->srv: %s", string(bs)) // push request into writer channel w.writeChan <- bs return nil } func (w *ws) Done() <-chan error { return w.quit } // listen for write requests and perform them func (w *ws) listenWriteChannel() { for { select { case <-w.kill: // ws closed return case message := <- w.writeChan: wsWriter, err := w.ws.NextWriter(websocket.TextMessage) if err != nil { w.log.Error("Unable to provision ws connection writer: ", err) w.stop(err) return } _, err = wsWriter.Write(message) if err != nil { w.log.Error("Unable to write to ws: ", err) w.stop(err) return } if err := wsWriter.Close(); err != nil { w.log.Error("Unable to close ws connection writer: ", err) w.stop(err) return } } } } // listen on ws & fwd to listen() func (w *ws) listenWs() { for { if w.ws == nil { return } select { case <-w.kill: // ws connection ended return default: _, msg, err := w.ws.ReadMessage() if err != nil { if cl, ok := err.(*websocket.CloseError); ok { w.log.Errorf("close error code: %d", cl.Code) } // a read during normal shutdown results in an OpError: op on closed connection if _, ok := err.(*net.OpError); ok { // general read error on a closed network connection, OK return } w.stop(err) return } w.log.Debugf("srv->ws: %s", string(msg)) w.lock.RLock() if w.downstream == nil { w.lock.RUnlock() return } w.downstream <- msg w.lock.RUnlock() } } } func (w *ws) Listen() <-chan []byte { return w.downstream } func (w *ws) stop(err error) { w.lock.Lock() defer w.lock.Unlock() if w.ws != nil { close(w.kill) w.quit <- err // pass error back close(w.quit) // signal to parent listeners close(w.downstream) w.downstream = nil if err := w.ws.Close(); err != nil { w.log.Error(fmt.Errorf("error closing websocket: %s", err)) } w.ws = nil } } // Close the websocket connection func (w *ws) Close() { w.stop(fmt.Errorf("transport connection Close called")) }