Skip to content

Commit

Permalink
poloniex: websocket update (thrasher-corp#659)
Browse files Browse the repository at this point in the history
* poloniex: websocket update with debug output and currency tracking system

* linter: fix issues

* nits: Addr

* poloniex: govet fix

* nits: addr

* Bittrex: Fix fee test
  • Loading branch information
shazbert authored Apr 27, 2021
1 parent d106d09 commit ca87ddf
Show file tree
Hide file tree
Showing 12 changed files with 4,114 additions and 426 deletions.
72 changes: 70 additions & 2 deletions engine/routines.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
Expand Down Expand Up @@ -323,6 +324,11 @@ func (bot *Engine) WebsocketDataHandler(exchName string, data interface{}) error
}
printOrderbookSummary(d, "websocket", bot, nil)
case *order.Detail:
if bot.Settings.Verbose {
printOrderSummary(d)
}
// TODO: Dont check if exists this creates two locks, on conflict update
// else insert.
if !bot.OrderManager.orderStore.exists(d) {
err := bot.OrderManager.orderStore.Add(d)
if err != nil {
Expand All @@ -335,9 +341,11 @@ func (bot *Engine) WebsocketDataHandler(exchName string, data interface{}) error
}
od.UpdateOrderFromDetail(d)
}
case *order.Cancel:
return bot.OrderManager.Cancel(d)
case *order.Modify:
if bot.Settings.Verbose {
printOrderChangeSummary(d)
}
// TODO: On conflict update or insert if not found
od, err := bot.OrderManager.orderStore.GetByExchangeAndID(d.Exchange, d.ID)
if err != nil {
return err
Expand All @@ -347,6 +355,10 @@ func (bot *Engine) WebsocketDataHandler(exchName string, data interface{}) error
return errors.New(d.Error())
case stream.UnhandledMessageWarning:
log.Warn(log.WebsocketMgr, d.Message)
case account.Change:
if bot.Settings.Verbose {
printAccountHoldingsChangeSummary(d)
}
default:
if bot.Settings.Verbose {
log.Warnf(log.WebsocketMgr,
Expand All @@ -357,3 +369,59 @@ func (bot *Engine) WebsocketDataHandler(exchName string, data interface{}) error
}
return nil
}

// printOrderChangeSummary this function will be deprecated when a order manager
// update is done.
func printOrderChangeSummary(m *order.Modify) {
if m == nil {
return
}
log.Debugf(log.WebsocketMgr,
"Order Change: %s %s %s %s %s %s OrderID:%s ClientOrderID:%s Price:%f Amount:%f Executed Amount:%f Remaining Amount:%f",
m.Exchange,
m.AssetType,
m.Pair,
m.Status,
m.Type,
m.Side,
m.ID,
m.ClientOrderID,
m.Price,
m.Amount,
m.ExecutedAmount,
m.RemainingAmount)
}

// printOrderSummary this function will be deprecated when a order manager
// update is done.
func printOrderSummary(m *order.Detail) {
if m == nil {
return
}
log.Debugf(log.WebsocketMgr,
"New Order: %s %s %s %s %s %s OrderID:%s ClientOrderID:%s Price:%f Amount:%f Executed Amount:%f Remaining Amount:%f",
m.Exchange,
m.AssetType,
m.Pair,
m.Status,
m.Type,
m.Side,
m.ID,
m.ClientOrderID,
m.Price,
m.Amount,
m.ExecutedAmount,
m.RemainingAmount)
}

// printAccountHoldingsChangeSummary this function will be deprecated when a
// account holdings update is done.
func printAccountHoldingsChangeSummary(m account.Change) {
log.Debugf(log.WebsocketMgr,
"Account Holdings Balance Changed: %s %s %s has changed balance by %f for account: %s",
m.Exchange,
m.Asset,
m.Currency,
m.Amount,
m.Account)
}
9 changes: 9 additions & 0 deletions exchanges/account/account_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,12 @@ type Balance struct {
TotalValue float64
Hold float64
}

// Change defines incoming balance change on currency holdings
type Change struct {
Exchange string
Currency currency.Code
Asset asset.Item
Amount float64
Account string
}
2 changes: 1 addition & 1 deletion exchanges/bitmex/bitmex_websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
Err: err,
}
}
b.Websocket.DataHandler <- &order.Cancel{
b.Websocket.DataHandler <- &order.Modify{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQuantity,
Exchange: b.Name,
Expand Down
4 changes: 2 additions & 2 deletions exchanges/bittrex/bittrex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,8 @@ func TestGetFee(t *testing.T) {
// CryptocurrencyWithdrawalFee Basic
feeBuilder = setFeeBuilder()
feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee
if resp, err := b.GetFee(feeBuilder); resp != float64(0.00015) || err != nil {
t.Errorf("Expected: %f, Received: %f", float64(0.00015), resp)
if resp, err := b.GetFee(feeBuilder); resp != float64(0.0003) || err != nil {
t.Errorf("Expected: %f, Received: %f", float64(0.0003), resp)
t.Error(err)
}

Expand Down
1 change: 1 addition & 0 deletions exchanges/order/order_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ const (
Open Status = "OPEN"
AutoDeleverage Status = "ADL"
Closed Status = "CLOSED"
Pending Status = "PENDING"
)

// Type enforces a standard for order types across the code base
Expand Down
235 changes: 235 additions & 0 deletions exchanges/poloniex/currency_details.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package poloniex

import (
"errors"
"strconv"
"sync"

"github.com/thrasher-corp/gocryptotrader/currency"
)

// CurrencyDetails stores a map of currencies associated with their ID
type CurrencyDetails struct {
pairs map[float64]PairSummaryInfo
codes map[float64]CodeSummaryInfo
// Mutex used for future when we will periodically update this table every
// week or so in production
m sync.RWMutex
}

// PairSummaryInfo defines currency pair information
type PairSummaryInfo struct {
Pair currency.Pair
IsFrozen bool
PostOnly bool
}

// CodeSummaryInfo defines currency information
type CodeSummaryInfo struct {
Currency currency.Code
WithdrawalTXFee float64
MinimumConfirmations int64
DepositAddress string
WithdrawalDepositDisabled bool
Frozen bool
}

var (
errCannotLoadNoData = errors.New("cannot load websocket currency data as data is nil")
errNoDepositAddress = errors.New("no public deposit address for currency")
errPairMapIsNil = errors.New("cannot get currency pair, map is nil")
errCodeMapIsNil = errors.New("cannot get currency code, map is nil")
errCurrencyNotFoundInMap = errors.New("currency not found")
)

// loadPairs loads currency pair associations with unique identifiers from
// ticker data map
func (w *CurrencyDetails) loadPairs(data map[string]Ticker) error {
if data == nil {
return errCannotLoadNoData
}
w.m.Lock()
defer w.m.Unlock()
for k, v := range data {
pair, err := currency.NewPairFromString(k)
if err != nil {
return err
}

if w.pairs == nil {
w.pairs = make(map[float64]PairSummaryInfo)
}
w.pairs[v.ID] = PairSummaryInfo{
Pair: pair,
IsFrozen: v.IsFrozen == 1,
PostOnly: v.PostOnly == 1,
}
}
return nil
}

// loadCodes loads currency codes from currency map
func (w *CurrencyDetails) loadCodes(data map[string]Currencies) error {
if data == nil {
return errCannotLoadNoData
}
w.m.Lock()
defer w.m.Unlock()
for k, v := range data {
if v.Delisted == 1 {
continue
}

if w.codes == nil {
w.codes = make(map[float64]CodeSummaryInfo)
}

w.codes[v.ID] = CodeSummaryInfo{
Currency: currency.NewCode(k),
WithdrawalTXFee: v.TxFee,
MinimumConfirmations: v.MinConfirmations,
DepositAddress: v.DepositAddress,
WithdrawalDepositDisabled: v.WithdrawalDepositDisabled == 1,
Frozen: v.Frozen == 1,
}
}
return nil
}

// GetPair returns a currency pair by its ID
func (w *CurrencyDetails) GetPair(id float64) (currency.Pair, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.pairs == nil {
return currency.Pair{}, errPairMapIsNil
}

p, ok := w.pairs[id]
if ok {
return p.Pair, nil
}

// This is here so we can still log an order with the ID as the currency
// pair which you can then cross reference later with the exchange ID list,
// rather than error out.
op, err := currency.NewPairFromString(strconv.FormatFloat(id, 'f', -1, 64))
if err != nil {
return op, err
}
return op, errIDNotFoundInPairMap
}

// GetCode returns a currency code by its ID
func (w *CurrencyDetails) GetCode(id float64) (currency.Code, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return currency.Code{}, errCodeMapIsNil
}
c, ok := w.codes[id]
if ok {
return c.Currency, nil
}
return currency.Code{}, errIDNotFoundInCodeMap
}

// GetWithdrawalTXFee returns withdrawal transaction fee for the currency
func (w *CurrencyDetails) GetWithdrawalTXFee(c currency.Code) (float64, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return 0, errCodeMapIsNil
}
for _, v := range w.codes {
if v.Currency == c {
return v.WithdrawalTXFee, nil
}
}
return 0, errCurrencyNotFoundInMap
}

// GetDepositAddress returns the public deposit address details for the currency
func (w *CurrencyDetails) GetDepositAddress(c currency.Code) (string, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return "", errCodeMapIsNil
}
for _, v := range w.codes {
if v.Currency == c {
if v.DepositAddress == "" {
return "", errNoDepositAddress
}
return v.DepositAddress, nil
}
}
return "", errCurrencyNotFoundInMap
}

// IsWithdrawAndDepositsEnabled returns if withdrawals or deposits are enabled
func (w *CurrencyDetails) IsWithdrawAndDepositsEnabled(c currency.Code) (bool, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return false, errCodeMapIsNil
}
for _, v := range w.codes {
if v.Currency == c {
return !v.WithdrawalDepositDisabled, nil
}
}
return false, errCurrencyNotFoundInMap
}

// IsTradingEnabledForCurrency returns if the currency is allowed to be traded
func (w *CurrencyDetails) IsTradingEnabledForCurrency(c currency.Code) (bool, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return false, errCodeMapIsNil
}
for _, v := range w.codes {
if v.Currency == c {
return !v.Frozen, nil
}
}
return false, errCurrencyNotFoundInMap
}

// IsTradingEnabledForPair returns if the currency pair is allowed to be traded
func (w *CurrencyDetails) IsTradingEnabledForPair(pair currency.Pair) (bool, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return false, errCodeMapIsNil
}
for _, v := range w.pairs {
if v.Pair.Equal(pair) {
return !v.IsFrozen, nil
}
}
return false, errCurrencyNotFoundInMap
}

// IsPostOnlyForPair returns if an order is allowed to take liquidity from the
// books or reduce positions
func (w *CurrencyDetails) IsPostOnlyForPair(pair currency.Pair) (bool, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return false, errCodeMapIsNil
}
for _, v := range w.pairs {
if v.Pair.Equal(pair) {
return v.PostOnly, nil
}
}
return false, errCurrencyNotFoundInMap
}

// isInitial checks state of maps to determine if they have been loaded or not
func (w *CurrencyDetails) isInitial() bool {
w.m.RLock()
defer w.m.RUnlock()
return w.codes == nil || w.pairs == nil
}
Loading

0 comments on commit ca87ddf

Please sign in to comment.