pax_global_header 0000666 0000000 0000000 00000000064 13712757447 0014533 g ustar 00root root 0000000 0000000 52 comment=dbf1c3d69ddb5bf5ff3b24f63722b75a6e88f20a
bitfinex-api-go-2.2.9/ 0000775 0000000 0000000 00000000000 13712757447 0014527 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/.github/ 0000775 0000000 0000000 00000000000 13712757447 0016067 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/.github/ISSUE_TEMPLATE 0000664 0000000 0000000 00000000254 13712757447 0020176 0 ustar 00root root 0000000 0000000 #### Issue type
- [ ] bug
- [ ] missing functionality
- [ ] performance
- [ ] feature request
#### Brief description
#### Steps to reproduce
-
##### Additional Notes:
-
bitfinex-api-go-2.2.9/.github/PULL_REQUEST_TEMPLATE 0000664 0000000 0000000 00000000233 13712757447 0021267 0 ustar 00root root 0000000 0000000 ### Description:
...
### Breaking changes:
- [ ]
### New features:
- [ ]
### Fixes:
- [ ]
### PR status:
- [ ] Version bumped
- [ ] Change-log updated
bitfinex-api-go-2.2.9/.gitignore 0000664 0000000 0000000 00000000020 13712757447 0016507 0 ustar 00root root 0000000 0000000 .idea
.DS_Store
bitfinex-api-go-2.2.9/.travis.yml 0000664 0000000 0000000 00000000405 13712757447 0016637 0 ustar 00root root 0000000 0000000 language: 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/CHANGELOG 0000664 0000000 0000000 00000004406 13712757447 0015745 0 ustar 00root root 0000000 0000000 2.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.txt 0000664 0000000 0000000 00000002064 13712757447 0016354 0 ustar 00root root 0000000 0000000 The 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/Makefile 0000664 0000000 0000000 00000000344 13712757447 0016170 0 ustar 00root root 0000000 0000000 NAME = 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.md 0000664 0000000 0000000 00000013772 13712757447 0016020 0 ustar 00root root 0000000 0000000 # Bitfinex Trading Library for GoLang - Bitcoin, Ethereum, Ripple and more

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/VERSION 0000664 0000000 0000000 00000000006 13712757447 0015573 0 ustar 00root root 0000000 0000000 2.2.9
bitfinex-api-go-2.2.9/docs/ 0000775 0000000 0000000 00000000000 13712757447 0015457 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/docs/rest_v2.md 0000664 0000000 0000000 00000071100 13712757447 0017364 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000051207 13712757447 0016334 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000052001 13712757447 0017037 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 13712757447 0016345 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v1/ 0000775 0000000 0000000 00000000000 13712757447 0016673 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v1/account/ 0000775 0000000 0000000 00000000000 13712757447 0020327 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v1/account/main.go 0000664 0000000 0000000 00000000771 13712757447 0021607 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0020171 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v1/orders/main.go 0000664 0000000 0000000 00000001326 13712757447 0021446 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0020254 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v1/ws-book/main.go 0000664 0000000 0000000 00000002241 13712757447 0021526 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0020774 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v1/ws-private/main.go 0000664 0000000 0000000 00000001320 13712757447 0022243 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0016674 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/book-feed/ 0000775 0000000 0000000 00000000000 13712757447 0020527 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/book-feed/main.go 0000664 0000000 0000000 00000001711 13712757447 0022002 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0023335 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-cancel-order-multi/main.go 0000664 0000000 0000000 00000004062 13712757447 0024612 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021260 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-candles/main.go 0000664 0000000 0000000 00000002725 13712757447 0022541 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0022011 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-currencies/main.go 0000664 0000000 0000000 00000000525 13712757447 0023266 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0022237 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-delete-pulse/main.go 0000664 0000000 0000000 00000001077 13712757447 0023517 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0023332 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-derivative-status/main.go 0000664 0000000 0000000 00000000445 13712757447 0024610 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021301 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-funding/main.go 0000664 0000000 0000000 00000004562 13712757447 0022563 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0023073 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-generate-invoice/main.go 0000664 0000000 0000000 00000001220 13712757447 0024341 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021132 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-market/main.go 0000664 0000000 0000000 00000001342 13712757447 0022405 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0022112 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-order-multi/main.go 0000664 0000000 0000000 00000004355 13712757447 0023374 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021145 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-orders/main.go 0000664 0000000 0000000 00000002351 13712757447 0022421 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021676 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-positions/main.go 0000664 0000000 0000000 00000001302 13712757447 0023145 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0024146 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-private-pulse-history/main.go 0000664 0000000 0000000 00000001101 13712757447 0025412 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0022405 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-public-trades/main.go 0000664 0000000 0000000 00000001221 13712757447 0023654 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021525 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-pulse-add/main.go 0000664 0000000 0000000 00000001334 13712757447 0023001 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0022476 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-pulse-history/main.go 0000664 0000000 0000000 00000000666 13712757447 0023761 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0022435 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-pulse-profile/main.go 0000664 0000000 0000000 00000000461 13712757447 0023711 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021005 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-stats/main.go 0000664 0000000 0000000 00000002226 13712757447 0022262 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0022263 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-submit-order/main.go 0000664 0000000 0000000 00000002277 13712757447 0023546 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021130 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-ticker/main.go 0000664 0000000 0000000 00000000574 13712757447 0022411 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021137 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/rest-wallet/main.go 0000664 0000000 0000000 00000001533 13712757447 0022414 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0022024 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/trade-feed-multi/main.go 0000664 0000000 0000000 00000001652 13712757447 0023303 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0020674 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/trade-feed/main.go 0000664 0000000 0000000 00000001503 13712757447 0022146 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0020255 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/ws-book/README.md 0000664 0000000 0000000 00000000232 13712757447 0021531 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000002403 13712757447 0021527 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0022112 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/ws-custom-logger/main.go 0000664 0000000 0000000 00000002605 13712757447 0023370 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0020775 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/ws-private/main.go 0000664 0000000 0000000 00000001473 13712757447 0022255 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021716 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/examples/v2/ws-update-order/main.go 0000664 0000000 0000000 00000002745 13712757447 0023201 0 ustar 00root root 0000000 0000000 package 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.mod 0000664 0000000 0000000 00000000355 13712757447 0015640 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000002747 13712757447 0015674 0 ustar 00root root 0000000 0000000 github.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/ 0000775 0000000 0000000 00000000000 13712757447 0015310 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/pkg/convert/ 0000775 0000000 0000000 00000000000 13712757447 0016770 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/pkg/convert/convert.go 0000664 0000000 0000000 00000004272 13712757447 0021004 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002715 13712757447 0022043 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0016573 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/pkg/models/invoice/ 0000775 0000000 0000000 00000000000 13712757447 0020227 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/pkg/models/invoice/invoice.go 0000664 0000000 0000000 00000001416 13712757447 0022214 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001426 13712757447 0023254 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0017723 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/pkg/models/pulse/pulse.go 0000664 0000000 0000000 00000005474 13712757447 0021414 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004721 13712757447 0022445 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0021304 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/pkg/models/pulseprofile/pulseprofile.go 0000664 0000000 0000000 00000002214 13712757447 0024343 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001741 13712757447 0025406 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0015671 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/tests/integration/ 0000775 0000000 0000000 00000000000 13712757447 0020214 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/tests/integration/v1/ 0000775 0000000 0000000 00000000000 13712757447 0020542 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/tests/integration/v1/balances_test.go 0000664 0000000 0000000 00000000421 13712757447 0023675 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000372 13712757447 0023742 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000640 13712757447 0024255 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000755 13712757447 0023252 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0020543 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/tests/integration/v2/api_test.go 0000664 0000000 0000000 00000002236 13712757447 0022705 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000006452 13712757447 0022402 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000326 13712757447 0023742 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000536 13712757447 0024742 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000024414 13712757447 0022724 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004134 13712757447 0026672 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000016203 13712757447 0026476 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004043 13712757447 0023221 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000064103 13712757447 0025331 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000016452 13712757447 0025141 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000030733 13712757447 0024117 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0023125 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/tests/integration/v2/stress_test/main.go 0000664 0000000 0000000 00000010672 13712757447 0024406 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000011611 13712757447 0024302 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0015667 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/utils/nonce.go 0000664 0000000 0000000 00000002271 13712757447 0017322 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0015055 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/v1/account.go 0000664 0000000 0000000 00000003533 13712757447 0017044 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004160 13712757447 0020100 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000755 13712757447 0017163 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000013664 13712757447 0016674 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001070 13712757447 0017037 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001463 13712757447 0017057 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001346 13712757447 0020116 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000760 13712757447 0016154 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004605 13712757447 0017112 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004547 13712757447 0020156 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000003063 13712757447 0017203 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000003101 13712757447 0020233 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000006147 13712757447 0020403 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001777 13712757447 0021446 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002320 13712757447 0017671 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002237 13712757447 0020737 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004042 13712757447 0016670 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002762 13712757447 0017736 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002136 13712757447 0017533 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001566 13712757447 0020600 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000013565 13712757447 0016714 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000006376 13712757447 0017755 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002034 13712757447 0016521 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004151 13712757447 0017562 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002415 13712757447 0017435 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000005207 13712757447 0020476 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001273 13712757447 0016545 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001324 13712757447 0017601 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001562 13712757447 0016671 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001540 13712757447 0017724 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001576 13712757447 0016677 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001314 13712757447 0017724 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000006225 13712757447 0016701 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000005346 13712757447 0017743 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000017354 13712757447 0017404 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0015056 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/v2/pairs.go 0000664 0000000 0000000 00000001773 13712757447 0016533 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0016033 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/v2/rest/book.go 0000664 0000000 0000000 00000002147 13712757447 0017320 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000003023 13712757447 0020351 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000006403 13712757447 0017776 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000024466 13712757447 0017654 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002557 13712757447 0020535 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002003 13712757447 0020702 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000014333 13712757447 0020020 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002550 13712757447 0021055 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004657 13712757447 0020032 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000005122 13712757447 0021055 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000001646 13712757447 0020016 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004016 13712757447 0017646 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004437 13712757447 0020714 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000025404 13712757447 0017665 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000024352 13712757447 0020725 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000630 13712757447 0021610 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002342 13712757447 0020412 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000006713 13712757447 0017521 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000023066 13712757447 0020560 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000010341 13712757447 0017517 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000003665 13712757447 0017717 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000003613 13712757447 0020031 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000007762 13712757447 0017660 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002413 13712757447 0020416 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000006201 13712757447 0017651 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000155155 13712757447 0016565 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13712757447 0017044 5 ustar 00root root 0000000 0000000 bitfinex-api-go-2.2.9/v2/websocket/api.go 0000664 0000000 0000000 00000015034 13712757447 0020147 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000024432 13712757447 0021173 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000046563 13712757447 0020667 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000010335 13712757447 0020701 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000013007 13712757447 0021353 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000005561 13712757447 0021370 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000002076 13712757447 0021543 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000022246 13712757447 0022310 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000011325 13712757447 0021431 0 ustar 00root root 0000000 0000000 package 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"))
}