Backtester: Live trading upgrades (#1023)

* Modifications for a smoother live run

* Fixes data appending

* Successfully allows multi-currency live trading. Adds multiple currencies to live DCA strategy

* Attempting to get cash and carry working

* Poor attempts at sorting out data and appending it properly with USD in mind

* =designs new live data handler

* Updates cash and carry strat to work

* adds test coverage. begins closeallpositions function

* Updates cash and carry to work live

* New kline.Event type. Cancels orders on close. Rn types

* =Fixes USD funding issue

* =fixes tests

* fixes tests AGAIN

* adds coverage to close all orders

* crummy tests, should override

* more tests

* more tests

* more coverage

* removes scourge of currency.Pair maps. More tests

* missed currency stuff

* Fixes USD data issue & collateral issue. Needs to close ALL orders

* Now triggers updates on the very first data entry

* All my problems are solved now????

* fixes tests, extends coverage

* there is some really funky candle stuff going on

* my brain is melting

* better shutdown management, fixes freezing bug

* fixes data duplication issues, adds retries to requests

* reduces logging, adds verbose options

* expands coverage over all new functionality

* fixes fun bug from curr == curr to curr.Equal(curr)

* fixes setup issues and tests

* starts adding external wallet amounts for funding

* more setup for assets

* setup live fund calcs and placing orders

* successfully performs automated cash and carry

* merge fixes

* funding properly set at all times

* fixes some bugs, need to address currencystatistics still

* adds 'appeneded' trait, attempts to fix some stats

* fixes stat bugs, adds cool new fetchfees feature

* fixes terrible processing bugs

* tightens realorder stats, sadly loses some live stats

* this actually sets everything correctly for bothcd ..cd ..cd ..cd ..cd ..!

* fix tests

* coverage

* beautiful new test coverage

* docs

* adds new fee getter delayer

* commits from the correct directory

* Lint

* adds verbose to fund manager

* Fix bug in t2b2 strat. Update dca live config. Docs

* go mod tidy

* update buf

* buf + test improvement

* Post merge fixes

* fixes surprise offset bug

* fix sizing restrictions for cash and carry

* fix server lints

* merge fixes

* test fixesss

* lintle fixles

* slowloris

* rn run to task, bug fixes, close all on close

* rpc lint and fixes

* bugfix: order manager not processing orders properly

* somewhat addresses nits

* absolutely broken end of day commit

* absolutely massive knockon effects from nits

* massive knockon effects continue

* fixes things

* address remaining nits

* jk now fixes things

* addresses the easier nits

* more nit fixers

* more niterinos addressederinos

* refactors holdings and does some nits

* so buf

* addresses some nits, fixes holdings bugs

* cleanup

* attempts to fix alert chans to prevent many chans waiting?

* terrible code, will revert

* to be reviewed in detail tomorrow

* Fixes up channel system

* smashes those nits

* fixes extra candles, fixes collateral bug, tests

* fixes data races, introduces reflection

* more checks n tests

* Fixes cash and carry issues. Fixes more cool bugs

* fixes ~typer~ typo

* replace spot strats from ftx to binance

* fixes all the tests I just destroyed

* removes example path, rm verbose

* 1) what 2) removes FTX references from the Backtester

* renamed, non-working strategies

* Removes FTX references almost as fast as sbf removes funds

* regen docs, add contrib names,sort contrib names

* fixes merge renamings

* Addresses nits. Fixes setting API credentials. Fixes Binance limit retrieval

* Fixes live order bugs with real orders and without

* Apply suggestions from code review

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update backtester/engine/live.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update backtester/engine/live.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update backtester/config/strategyconfigbuilder/main.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* updates docs

* even better docs

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
Scott
2023-01-05 13:03:17 +11:00
committed by GitHub
parent d92ffe6e9e
commit 017cdf1384
195 changed files with 13783 additions and 8048 deletions

View File

@@ -115,7 +115,7 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding
for item, balance := range currencyHoldings {
balance.m.Lock()
currencyBalances[target] = Balance{
CurrencyName: currency.Code{Item: item, UpperCase: true},
Currency: currency.Code{Item: item, UpperCase: true},
Total: balance.total,
Hold: balance.hold,
Free: balance.free,
@@ -281,10 +281,10 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error {
}
for y := range incoming.Accounts[x].Currencies {
bal := currencyBalances[incoming.Accounts[x].Currencies[y].CurrencyName.Item]
bal := currencyBalances[incoming.Accounts[x].Currencies[y].Currency.Item]
if bal == nil {
bal = &ProtectedBalance{}
currencyBalances[incoming.Accounts[x].Currencies[y].CurrencyName.Item] = bal
currencyBalances[incoming.Accounts[x].Currencies[y].Currency.Item] = bal
}
bal.load(incoming.Accounts[x].Currencies[y])
}

View File

@@ -18,7 +18,7 @@ func TestCollectBalances(t *testing.T) {
accounts, err := CollectBalances(
map[string][]Balance{
"someAccountID": {
{CurrencyName: currency.BTC, Total: 40000, Hold: 1},
{Currency: currency.BTC, Total: 40000, Hold: 1},
},
},
asset.Spot,
@@ -31,7 +31,7 @@ func TestCollectBalances(t *testing.T) {
if subAccount.AssetType != asset.Spot {
t.Error("subAccount AssetType not set correctly")
}
if balance.CurrencyName != currency.BTC || balance.Total != 40000 || balance.Hold != 1 {
if balance.Currency != currency.BTC || balance.Total != 40000 || balance.Hold != 1 {
t.Error("subAccount currency balance not set correctly")
}
if err != nil {
@@ -112,9 +112,9 @@ func TestGetHoldings(t *testing.T) {
ID: "1337",
Currencies: []Balance{
{
CurrencyName: currency.BTC,
Total: 100,
Hold: 20,
Currency: currency.BTC,
Total: 100,
Hold: 20,
},
},
}},
@@ -132,9 +132,9 @@ func TestGetHoldings(t *testing.T) {
ID: "1337",
Currencies: []Balance{
{
CurrencyName: currency.BTC,
Total: 100,
Hold: 20,
Currency: currency.BTC,
Total: 100,
Hold: 20,
},
},
}},
@@ -182,9 +182,9 @@ func TestGetHoldings(t *testing.T) {
t.Errorf("expecting 1337 but received %s", u.Accounts[0].ID)
}
if !u.Accounts[0].Currencies[0].CurrencyName.Equal(currency.BTC) {
if !u.Accounts[0].Currencies[0].Currency.Equal(currency.BTC) {
t.Errorf("expecting BTC but received %s",
u.Accounts[0].Currencies[0].CurrencyName)
u.Accounts[0].Currencies[0].Currency)
}
if u.Accounts[0].Currencies[0].Total != 100 {
@@ -228,9 +228,9 @@ func TestGetHoldings(t *testing.T) {
AssetType: asset.MarginFunding,
Currencies: []Balance{
{
CurrencyName: currency.BTC,
Total: 100000,
Hold: 20,
Currency: currency.BTC,
Total: 100000,
Hold: 20,
},
},
}},
@@ -309,9 +309,9 @@ func TestGetBalance(t *testing.T) {
ID: "1337",
Currencies: []Balance{
{
CurrencyName: currency.BTC,
Total: 2,
Hold: 1,
Currency: currency.BTC,
Total: 2,
Hold: 1,
},
},
},
@@ -424,9 +424,9 @@ func TestUpdate(t *testing.T) {
ID: "1337",
Currencies: []Balance{
{
CurrencyName: currency.BTC,
Total: 100,
Hold: 20,
Currency: currency.BTC,
Total: 100,
Hold: 20,
},
},
},
@@ -436,9 +436,9 @@ func TestUpdate(t *testing.T) {
ID: "1337",
Currencies: []Balance{
{
CurrencyName: currency.BTC,
Total: 100,
Hold: 20,
Currency: currency.BTC,
Total: 100,
Hold: 20,
},
},
},
@@ -456,9 +456,9 @@ func TestUpdate(t *testing.T) {
ID: "1337",
Currencies: []Balance{
{
CurrencyName: currency.BTC,
Total: 100,
Hold: 20,
Currency: currency.BTC,
Total: 100,
Hold: 20,
},
},
},

View File

@@ -50,9 +50,9 @@ type SubAccount struct {
Currencies []Balance
}
// Balance is a sub type to store currency name and individual totals
// Balance is a sub-type to store currency name and individual totals
type Balance struct {
CurrencyName currency.Code
Currency currency.Code
Total float64
Hold float64
Free float64

View File

@@ -118,7 +118,7 @@ func (c *Credentials) Equal(other *Credentials) bool {
other != nil &&
c.Key == other.Key &&
c.ClientID == other.ClientID &&
c.SubAccount == other.SubAccount
(c.SubAccount == other.SubAccount || c.SubAccount == "" && other.SubAccount == "main" || c.SubAccount == "main" && other.SubAccount == "")
}
// ContextCredentialsStore protects the stored credentials for use in a context

View File

@@ -104,10 +104,10 @@ func (a *Alphapoint) UpdateAccountInfo(ctx context.Context, assetType asset.Item
balances := make([]account.Balance, len(acc.Currencies))
for i := range acc.Currencies {
balances[i] = account.Balance{
CurrencyName: currency.NewCode(acc.Currencies[i].Name),
Total: float64(acc.Currencies[i].Balance),
Hold: float64(acc.Currencies[i].Hold),
Free: float64(acc.Currencies[i].Balance) - float64(acc.Currencies[i].Hold),
Currency: currency.NewCode(acc.Currencies[i].Name),
Total: float64(acc.Currencies[i].Balance),
Hold: float64(acc.Currencies[i].Hold),
Free: float64(acc.Currencies[i].Balance) - float64(acc.Currencies[i].Hold),
}
}

View File

@@ -1189,10 +1189,9 @@ func (b *Binance) FetchSpotExchangeLimits(ctx context.Context) ([]order.MinMaxLe
assets = append(assets, asset.Spot)
case "MARGIN":
assets = append(assets, asset.Margin)
case "LEVERAGED", "TRD_GRP_003", "TRD_GRP_004", "TRD_GRP_005": // unused permissions
default:
return nil, fmt.Errorf("unhandled asset type for exchange limits loading %s",
spot.Symbols[x].Permissions[y])
// "LEVERAGED", "TRD_GRP_003", "TRD_GRP_004", "TRD_GRP_005" etc are unused permissions
// for spot exchange limits
}
}

View File

@@ -706,10 +706,10 @@ func (b *Binance) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (
locked := raw.Balances[i].Locked.InexactFloat64()
currencyBalance = append(currencyBalance, account.Balance{
CurrencyName: currency.NewCode(raw.Balances[i].Asset),
Total: free + locked,
Hold: locked,
Free: free,
Currency: currency.NewCode(raw.Balances[i].Asset),
Total: free + locked,
Hold: locked,
Free: free,
})
}
@@ -723,10 +723,10 @@ func (b *Binance) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (
var currencyDetails []account.Balance
for i := range accData.Assets {
currencyDetails = append(currencyDetails, account.Balance{
CurrencyName: currency.NewCode(accData.Assets[i].Asset),
Total: accData.Assets[i].WalletBalance,
Hold: accData.Assets[i].WalletBalance - accData.Assets[i].AvailableBalance,
Free: accData.Assets[i].AvailableBalance,
Currency: currency.NewCode(accData.Assets[i].Asset),
Total: accData.Assets[i].WalletBalance,
Hold: accData.Assets[i].WalletBalance - accData.Assets[i].AvailableBalance,
Free: accData.Assets[i].AvailableBalance,
})
}
@@ -742,10 +742,10 @@ func (b *Binance) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (
currencyDetails := accountCurrencyDetails[accData[i].AccountAlias]
accountCurrencyDetails[accData[i].AccountAlias] = append(
currencyDetails, account.Balance{
CurrencyName: currency.NewCode(accData[i].Asset),
Total: accData[i].Balance,
Hold: accData[i].Balance - accData[i].AvailableBalance,
Free: accData[i].AvailableBalance,
Currency: currency.NewCode(accData[i].Asset),
Total: accData[i].Balance,
Hold: accData[i].Balance - accData[i].AvailableBalance,
Free: accData[i].AvailableBalance,
},
)
}
@@ -761,7 +761,7 @@ func (b *Binance) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (
var currencyDetails []account.Balance
for i := range accData.UserAssets {
currencyDetails = append(currencyDetails, account.Balance{
CurrencyName: currency.NewCode(accData.UserAssets[i].Asset),
Currency: currency.NewCode(accData.UserAssets[i].Asset),
Total: accData.UserAssets[i].Free + accData.UserAssets[i].Locked,
Hold: accData.UserAssets[i].Locked,
Free: accData.UserAssets[i].Free,
@@ -1772,7 +1772,7 @@ func (b *Binance) GetHistoricCandlesExtended(ctx context.Context, pair currency.
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -441,10 +441,10 @@ func (bi *Binanceus) UpdateAccountInfo(ctx context.Context, assetType asset.Item
locked := theAccount.Balances[i].Locked.InexactFloat64()
currencyBalance[i] = account.Balance{
CurrencyName: currency.NewCode(theAccount.Balances[i].Asset),
Total: freeBalance + locked,
Hold: locked,
Free: freeBalance,
Currency: currency.NewCode(theAccount.Balances[i].Asset),
Total: freeBalance + locked,
Hold: locked,
Free: freeBalance,
}
}
acc.Currencies = currencyBalance
@@ -957,7 +957,7 @@ func (bi *Binanceus) GetHistoricCandlesExtended(ctx context.Context, pair curren
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", bi.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -516,10 +516,10 @@ func (b *Bitfinex) UpdateAccountInfo(ctx context.Context, assetType asset.Item)
if Accounts[i].ID == accountBalance[x].Type {
Accounts[i].Currencies = append(Accounts[i].Currencies,
account.Balance{
CurrencyName: currency.NewCode(accountBalance[x].Currency),
Total: accountBalance[x].Amount,
Hold: accountBalance[x].Amount - accountBalance[x].Available,
Free: accountBalance[x].Available,
Currency: currency.NewCode(accountBalance[x].Currency),
Total: accountBalance[x].Amount,
Hold: accountBalance[x].Amount - accountBalance[x].Available,
Free: accountBalance[x].Available,
})
}
}
@@ -1174,7 +1174,7 @@ func (b *Bitfinex) GetHistoricCandlesExtended(ctx context.Context, pair currency
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -380,10 +380,10 @@ func (b *Bithumb) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (
}
exchangeBalances = append(exchangeBalances, account.Balance{
CurrencyName: currency.NewCode(key),
Total: totalAmount,
Hold: hold,
Free: avail,
Currency: currency.NewCode(key),
Total: totalAmount,
Hold: hold,
Free: avail,
})
}

View File

@@ -513,8 +513,8 @@ func (b *Bitmex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
accountBalances[accountID] = append(
accountBalances[accountID], account.Balance{
CurrencyName: currency.NewCode(wallet.Currency),
Total: wallet.Amount,
Currency: currency.NewCode(wallet.Currency),
Total: wallet.Amount,
},
)
}

View File

@@ -442,10 +442,10 @@ func (b *Bitstamp) UpdateAccountInfo(ctx context.Context, assetType asset.Item)
currencies := make([]account.Balance, 0, len(accountBalance))
for k, v := range accountBalance {
currencies = append(currencies, account.Balance{
CurrencyName: currency.NewCode(k),
Total: v.Balance,
Hold: v.Reserved,
Free: v.Available,
Currency: currency.NewCode(k),
Total: v.Balance,
Hold: v.Reserved,
Free: v.Available,
})
}
response.Accounts = append(response.Accounts, account.SubAccount{
@@ -956,7 +956,7 @@ func (b *Bitstamp) GetHistoricCandlesExtended(ctx context.Context, pair currency
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -410,10 +410,10 @@ func (b *Bittrex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (
currencies := make([]account.Balance, len(balanceData))
for i := range balanceData {
currencies[i] = account.Balance{
CurrencyName: currency.NewCode(balanceData[i].CurrencySymbol),
Total: balanceData[i].Total,
Hold: balanceData[i].Total - balanceData[i].Available,
Free: balanceData[i].Available,
Currency: currency.NewCode(balanceData[i].CurrencySymbol),
Total: balanceData[i].Total,
Hold: balanceData[i].Total - balanceData[i].Available,
Free: balanceData[i].Available,
}
}
@@ -1044,7 +1044,7 @@ func (b *Bittrex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
})
}
ret.SortCandlesByTimestamp(false)
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
return ret, nil
}

View File

@@ -447,10 +447,10 @@ func (b *BTCMarkets) UpdateAccountInfo(ctx context.Context, assetType asset.Item
acc.AssetType = assetType
for x := range data {
acc.Currencies = append(acc.Currencies, account.Balance{
CurrencyName: currency.NewCode(data[x].AssetName),
Total: data[x].Balance,
Hold: data[x].Locked,
Free: data[x].Available,
Currency: currency.NewCode(data[x].AssetName),
Total: data[x].Balance,
Hold: data[x].Locked,
Free: data[x].Available,
})
}
resp.Accounts = append(resp.Accounts, acc)
@@ -1148,7 +1148,7 @@ func (b *BTCMarkets) GetHistoricCandlesExtended(ctx context.Context, p currency.
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -401,10 +401,10 @@ func (b *BTSE) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (acc
currencies := make([]account.Balance, len(balance))
for b := range balance {
currencies[b] = account.Balance{
CurrencyName: currency.NewCode(balance[b].Currency),
Total: balance[b].Total,
Hold: balance[b].Total - balance[b].Available,
Free: balance[b].Available,
Currency: currency.NewCode(balance[b].Currency),
Total: balance[b].Total,
Hold: balance[b].Total - balance[b].Available,
Free: balance[b].Available,
}
}
a.Exchange = b.Name

View File

@@ -712,10 +712,10 @@ func (by *Bybit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
currencyBalance := make([]account.Balance, len(balances))
for i := range balances {
currencyBalance[i] = account.Balance{
CurrencyName: currency.NewCode(balances[i].CoinName),
Total: balances[i].Total,
Hold: balances[i].Locked,
Free: balances[i].Total - balances[i].Locked,
Currency: currency.NewCode(balances[i].CoinName),
Total: balances[i].Total,
Hold: balances[i].Locked,
Free: balances[i].Total - balances[i].Locked,
}
}
@@ -731,10 +731,10 @@ func (by *Bybit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
currencyBalance := make([]account.Balance, len(balances))
for coinName, data := range balances {
currencyBalance[i] = account.Balance{
CurrencyName: currency.NewCode(coinName),
Total: data.WalletBalance,
Hold: data.WalletBalance - data.AvailableBalance,
Free: data.AvailableBalance,
Currency: currency.NewCode(coinName),
Total: data.WalletBalance,
Hold: data.WalletBalance - data.AvailableBalance,
Free: data.AvailableBalance,
}
i++
}
@@ -749,10 +749,10 @@ func (by *Bybit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
acc.Currencies = []account.Balance{
{
CurrencyName: currency.USD,
Total: balance.WalletBalance,
Hold: balance.WalletBalance - balance.AvailableBalance,
Free: balance.AvailableBalance,
Currency: currency.USD,
Total: balance.WalletBalance,
Hold: balance.WalletBalance - balance.AvailableBalance,
Free: balance.AvailableBalance,
},
}
@@ -2037,7 +2037,7 @@ func (by *Bybit) GetHistoricCandlesExtended(ctx context.Context, pair currency.P
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", by.Name, summary)
}
klineItem.RemoveDuplicates()
klineItem.RemoveDuplicateCandlesByTime()
klineItem.RemoveOutsideRange(start, end)
klineItem.SortCandlesByTimestamp(false)
return klineItem, nil

View File

@@ -329,7 +329,7 @@ func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Ite
profileID := accountBalance[i].ProfileID
currencies := accountCurrencies[profileID]
accountCurrencies[profileID] = append(currencies, account.Balance{
CurrencyName: currency.NewCode(accountBalance[i].Currency),
Currency: currency.NewCode(accountBalance[i].Currency),
Total: accountBalance[i].Balance,
Hold: accountBalance[i].Hold,
Free: accountBalance[i].Available,
@@ -1013,7 +1013,7 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, p currency
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", c.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -324,60 +324,60 @@ func (c *COINUT) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
var balances = []account.Balance{
{
CurrencyName: currency.BCH,
Total: bal.BCH,
Currency: currency.BCH,
Total: bal.BCH,
},
{
CurrencyName: currency.BTC,
Total: bal.BTC,
Currency: currency.BTC,
Total: bal.BTC,
},
{
CurrencyName: currency.BTG,
Total: bal.BTG,
Currency: currency.BTG,
Total: bal.BTG,
},
{
CurrencyName: currency.CAD,
Total: bal.CAD,
Currency: currency.CAD,
Total: bal.CAD,
},
{
CurrencyName: currency.ETC,
Total: bal.ETC,
Currency: currency.ETC,
Total: bal.ETC,
},
{
CurrencyName: currency.ETH,
Total: bal.ETH,
Currency: currency.ETH,
Total: bal.ETH,
},
{
CurrencyName: currency.LCH,
Total: bal.LCH,
Currency: currency.LCH,
Total: bal.LCH,
},
{
CurrencyName: currency.LTC,
Total: bal.LTC,
Currency: currency.LTC,
Total: bal.LTC,
},
{
CurrencyName: currency.MYR,
Total: bal.MYR,
Currency: currency.MYR,
Total: bal.MYR,
},
{
CurrencyName: currency.SGD,
Total: bal.SGD,
Currency: currency.SGD,
Total: bal.SGD,
},
{
CurrencyName: currency.USD,
Total: bal.USD,
Currency: currency.USD,
Total: bal.USD,
},
{
CurrencyName: currency.USDT,
Total: bal.USDT,
Currency: currency.USDT,
Total: bal.USDT,
},
{
CurrencyName: currency.XMR,
Total: bal.XMR,
Currency: currency.XMR,
Total: bal.XMR,
},
{
CurrencyName: currency.ZEC,
Total: bal.ZEC,
Currency: currency.ZEC,
Total: bal.ZEC,
},
}
info.Exchange = c.Name

View File

@@ -1444,7 +1444,7 @@ func (b *Base) CalculateTotalCollateral(ctx context.Context, calculator *order.T
}
// GetCollateralCurrencyForContract returns the collateral currency for an asset and contract pair
func (b *Base) GetCollateralCurrencyForContract(asset.Item, currency.Pair) (currency.Code, asset.Item, error) {
func (b *Base) GetCollateralCurrencyForContract(a asset.Item, cp currency.Pair) (currency.Code, asset.Item, error) {
return currency.Code{}, asset.Empty, common.ErrNotYetImplemented
}

View File

@@ -2644,3 +2644,41 @@ func TestSetRequester(t *testing.T) {
t.Fatal("requester not set correctly")
}
}
func TestGetCollateralCurrencyForContract(t *testing.T) {
t.Parallel()
b := Base{}
_, _, err := b.GetCollateralCurrencyForContract(asset.Futures, currency.NewPair(currency.XRP, currency.BABYDOGE))
if !errors.Is(err, common.ErrNotYetImplemented) {
t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrNotYetImplemented)
}
}
func TestGetCurrencyForRealisedPNL(t *testing.T) {
t.Parallel()
b := Base{}
_, _, err := b.GetCurrencyForRealisedPNL(asset.Empty, currency.EMPTYPAIR)
if !errors.Is(err, common.ErrNotYetImplemented) {
t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrNotYetImplemented)
}
}
func TestHasAssetTypeAccountSegregation(t *testing.T) {
t.Parallel()
b := Base{
Name: "RAWR",
Features: Features{
Supports: FeaturesSupported{
REST: true,
RESTCapabilities: protocol.Features{
HasAssetTypeAccountSegregation: true,
},
},
},
}
has := b.HasAssetTypeAccountSegregation()
if !has {
t.Errorf("expected '%v' received '%v'", true, false)
}
}

View File

@@ -361,7 +361,7 @@ func (e *EXMO) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (acc
currencies := make([]account.Balance, 0, len(result.Balances))
for x, y := range result.Balances {
var exchangeCurrency account.Balance
exchangeCurrency.CurrencyName = currency.NewCode(x)
exchangeCurrency.Currency = currency.NewCode(x)
for z, w := range result.Reserved {
if z != x {
continue

View File

@@ -764,7 +764,11 @@ func (f *FTX) Order(
req := make(map[string]interface{})
req["market"] = marketName
req["side"] = side
req["price"] = price
if orderType == "market" {
req["price"] = nil
} else {
req["price"] = price
}
req["type"] = orderType
req["size"] = size
if reduceOnly {
@@ -922,7 +926,7 @@ func (f *FTX) DeleteTriggerOrder(ctx context.Context, orderID string) (string, e
// GetFills gets order fills data and ensures that all
// fills are retrieved from the supplied timeframe
func (f *FTX) GetFills(ctx context.Context, market currency.Pair, item asset.Item, startTime, endTime time.Time) ([]FillsData, error) {
func (f *FTX) GetFills(ctx context.Context, market currency.Pair, item asset.Item, startTime, endTime time.Time, orderID string) ([]FillsData, error) {
var resp []FillsData
var nextEnd = endTime
limit := 200
@@ -946,6 +950,9 @@ func (f *FTX) GetFills(ctx context.Context, market currency.Pair, item asset.Ite
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(nextEnd.Unix(), 10))
}
if orderID != "" {
params.Set("orderId", orderID)
}
endpoint := common.EncodeURLValues(getFills, params)
err := f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, endpoint, nil, &data)
if err != nil {

View File

@@ -678,6 +678,7 @@ func TestSubmitOrder(t *testing.T) {
Amount: 1,
AssetType: asset.Spot,
ClientOrderID: "order12345679$$$$$",
RetrieveFees: true,
}
_, err = f.SubmitOrder(context.Background(), orderSubmission)
if err != nil {
@@ -766,25 +767,30 @@ func TestGetFills(t *testing.T) {
t.Skip()
}
_, err := f.GetFills(context.Background(),
currency.Pair{}, asset.Futures, time.Now().Add(time.Hour*24*365), time.Now())
currency.Pair{}, asset.Futures, time.Now().Add(time.Hour*24*365), time.Now(), "")
if !errors.Is(err, errStartTimeCannotBeAfterEndTime) {
t.Errorf("received '%v' expected '%v'", err, errStartTimeCannotBeAfterEndTime)
}
_, err = f.GetFills(context.Background(),
currency.Pair{}, asset.Futures, time.Time{}, time.Time{})
currency.Pair{}, asset.Futures, time.Time{}, time.Time{}, "")
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = f.GetFills(context.Background(),
currency.Pair{}, asset.Futures, time.Now().Add(-time.Hour*24*365), time.Now())
currency.Pair{}, asset.Futures, time.Now().Add(-time.Hour*24*365), time.Now(), "")
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = f.GetFills(context.Background(),
spotPair, asset.Spot, time.Now().Add(-time.Hour*24*365), time.Now())
spotPair, asset.Spot, time.Now().Add(-time.Hour*24*365), time.Now(), "")
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = f.GetFills(context.Background(),
currency.EMPTYPAIR, asset.Futures, time.Time{}, time.Time{}, "177453606715")
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
@@ -2819,3 +2825,36 @@ func TestGetReferralRebateRate(t *testing.T) {
t.Fatal(err)
}
}
func TestGetCollateralCurrencyForContract(t *testing.T) {
t.Parallel()
c, a, err := f.GetCollateralCurrencyForContract(asset.Futures, currency.NewPair(currency.XRP, currency.BABYDOGE))
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if a != asset.Futures {
t.Fatalf("received: '%v' but expected: '%v'", a, asset.Futures)
}
if !c.Equal(currency.USD) {
t.Fatalf("received: '%v' but expected: '%v'", c, currency.USD)
}
}
func TestGetCurrencyForRealisedPNL(t *testing.T) {
t.Parallel()
c, a, err := f.GetCurrencyForRealisedPNL(asset.Futures, currency.NewPair(currency.XRP, currency.BABYDOGE))
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if a != asset.Spot {
t.Fatalf("received: '%v' but expected: '%v'", a, asset.Spot)
}
if !c.Equal(currency.USD) {
t.Fatalf("received: '%v' but expected: '%v'", c, currency.USD)
}
_, _, err = f.GetCurrencyForRealisedPNL(asset.Spot, currency.NewPair(currency.SHIB, currency.DOGE))
if !errors.Is(err, order.ErrNotFuturesAsset) {
t.Fatalf("received: '%v' but expected: '%v'", err, order.ErrNotFuturesAsset)
}
}

View File

@@ -337,7 +337,7 @@ type OrderData struct {
ClientID string `json:"clientId"`
CreatedAt time.Time `json:"createdAt"`
FilledSize float64 `json:"filledSize"`
Future currency.Pair `json:"future"`
Future string `json:"future"`
ID int64 `json:"id"`
IOC bool `json:"ioc"`
Market currency.Pair `json:"market"`

View File

@@ -168,6 +168,14 @@ func (f *FTX) SetDefaults() {
f.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
f.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
f.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
err = f.LoadCollateralWeightings(context.TODO())
if err != nil {
log.Errorf(log.ExchangeSys,
"%s failed to store collateral weightings. Err: %s",
f.Name,
err)
}
}
// Setup takes in the supplied exchange configuration details and sets params
@@ -206,15 +214,6 @@ func (f *FTX) Setup(exch *config.Exchange) error {
return err
}
if err = f.CurrencyPairs.IsAssetEnabled(asset.Futures); err == nil {
err = f.LoadCollateralWeightings(context.TODO())
if err != nil {
log.Errorf(log.ExchangeSys,
"%s failed to store collateral weightings. Err: %s",
f.Name,
err)
}
}
return f.Websocket.SetupNewConnection(stream.ConnectionSetup{
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
@@ -490,7 +489,7 @@ func (f *FTX) UpdateAccountInfo(ctx context.Context, a asset.Item) (account.Hold
hold := balances[x].Total - balances[x].AvailableWithoutBorrow
acc.Currencies = append(acc.Currencies,
account.Balance{
CurrencyName: balances[x].Coin,
Currency: balances[x].Coin,
Total: balances[x].Total,
Hold: hold,
AvailableWithoutBorrow: balances[x].AvailableWithoutBorrow,
@@ -648,10 +647,10 @@ func (f *FTX) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitRe
return nil, err
}
if s.Side == order.Ask {
if s.Side.IsShort() {
s.Side = order.Sell
}
if s.Side == order.Bid {
if s.Side.IsLong() {
s.Side = order.Buy
}
@@ -674,7 +673,43 @@ func (f *FTX) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitRe
return nil, err
}
return s.DeriveSubmitResponse(strconv.FormatInt(tempResp.ID, 10))
resp, err := s.DeriveSubmitResponse(strconv.FormatInt(tempResp.ID, 10))
if err != nil {
return nil, err
}
if !s.RetrieveFees {
return resp, nil
}
time.Sleep(s.RetrieveFeeDelay)
fills, err := f.GetFills(ctx, s.Pair, s.AssetType, time.Time{}, time.Time{}, strconv.FormatInt(tempResp.ID, 10))
if err != nil {
// choosing to return with no error so that a valid order is still returned to caller
log.Errorf(log.ExchangeSys, "could not retrieve fees for order %v: %v", tempResp.ID, err)
return resp, nil
}
for i := range fills {
resp.Fee += fills[i].Fee
var side order.Side
side, err = order.StringToOrderSide(fills[i].Side)
if err != nil {
return nil, err
}
if resp.FeeAsset.IsEmpty() {
resp.FeeAsset = fills[i].FeeCurrency
}
resp.Trades = append(resp.Trades, order.TradeHistory{
Price: fills[i].Price,
Amount: fills[i].Size,
Fee: fills[i].Fee,
Exchange: f.Name,
TID: strconv.FormatInt(fills[i].TradeID, 10),
Side: side,
Timestamp: fills[i].Time,
IsMaker: fills[i].Liquidity == "maker",
FeeAsset: fills[i].FeeCurrency.String(),
})
}
return resp, nil
}
// ModifyOrder will allow of changing orderbook placement and limit to
@@ -1270,7 +1305,7 @@ func (f *FTX) GetHistoricCandlesExtended(ctx context.Context, p currency.Pair, a
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", f.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil
@@ -1696,7 +1731,7 @@ func (f *FTX) GetFuturesPositions(ctx context.Context, request *order.PositionsR
allPositions:
for {
var fills []FillsData
fills, err = f.GetFills(ctx, request.Pairs[x], request.Asset, request.StartDate, endTime)
fills, err = f.GetFills(ctx, request.Pairs[x], request.Asset, request.StartDate, endTime, "")
if err != nil {
return nil, err
}
@@ -1760,12 +1795,15 @@ func (f *FTX) GetFuturesPositions(ctx context.Context, request *order.PositionsR
}
// GetCollateralCurrencyForContract returns the collateral currency for an asset and contract pair
func (f *FTX) GetCollateralCurrencyForContract(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
func (f *FTX) GetCollateralCurrencyForContract(a asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
return currency.USD, asset.Futures, nil
}
// GetCurrencyForRealisedPNL returns where to put realised PNL
func (f *FTX) GetCurrencyForRealisedPNL(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
func (f *FTX) GetCurrencyForRealisedPNL(a asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
if !a.IsFutures() {
return currency.EMPTYCODE, asset.Empty, fmt.Errorf("%v %w", a, order.ErrNotFuturesAsset)
}
return currency.USD, asset.Spot, nil
}

View File

@@ -357,10 +357,10 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
var currData []account.Balance
for k := range resp.Result {
currData = append(currData, account.Balance{
CurrencyName: currency.NewCode(k),
Total: resp.Result[k].Available + resp.Result[k].Freeze,
Hold: resp.Result[k].Freeze,
Free: resp.Result[k].Available,
Currency: currency.NewCode(k),
Total: resp.Result[k].Available + resp.Result[k].Freeze,
Hold: resp.Result[k].Freeze,
Free: resp.Result[k].Available,
})
}
info.Accounts = append(info.Accounts, account.SubAccount{
@@ -383,8 +383,8 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
}
balances = append(balances, account.Balance{
CurrencyName: currency.NewCode(x),
Hold: lockedF,
Currency: currency.NewCode(x),
Hold: lockedF,
})
}
default:
@@ -402,7 +402,7 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
var updated bool
for i := range balances {
if !balances[i].CurrencyName.Equal(currency.NewCode(x)) {
if !balances[i].Currency.Equal(currency.NewCode(x)) {
continue
}
balances[i].Total = balances[i].Hold + availAmount
@@ -413,8 +413,8 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
}
if !updated {
balances = append(balances, account.Balance{
CurrencyName: currency.NewCode(x),
Total: availAmount,
Currency: currency.NewCode(x),
Total: availAmount,
})
}
}

View File

@@ -324,10 +324,10 @@ func (g *Gemini) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
currencies := make([]account.Balance, len(accountBalance))
for i := range accountBalance {
currencies[i] = account.Balance{
CurrencyName: currency.NewCode(accountBalance[i].Currency),
Total: accountBalance[i].Amount,
Hold: accountBalance[i].Amount - accountBalance[i].Available,
Free: accountBalance[i].Available,
Currency: currency.NewCode(accountBalance[i].Currency),
Total: accountBalance[i].Amount,
Hold: accountBalance[i].Amount - accountBalance[i].Available,
Free: accountBalance[i].Available,
}
}

View File

@@ -436,10 +436,10 @@ func (h *HitBTC) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
currencies := make([]account.Balance, 0, len(accountBalance))
for i := range accountBalance {
currencies = append(currencies, account.Balance{
CurrencyName: currency.NewCode(accountBalance[i].Currency),
Total: accountBalance[i].Available + accountBalance[i].Reserved,
Hold: accountBalance[i].Reserved,
Free: accountBalance[i].Available,
Currency: currency.NewCode(accountBalance[i].Currency),
Total: accountBalance[i].Available + accountBalance[i].Reserved,
Hold: accountBalance[i].Reserved,
Free: accountBalance[i].Available,
})
}
@@ -951,7 +951,7 @@ func (h *HitBTC) GetHistoricCandlesExtended(ctx context.Context, pair currency.P
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", h.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -639,8 +639,8 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
continue
}
currData := account.Balance{
CurrencyName: currency.NewCode(resp.Data[i].List[0].Currency),
Total: resp.Data[i].List[0].Balance,
Currency: currency.NewCode(resp.Data[i].List[0].Currency),
Total: resp.Data[i].List[0].Balance,
}
if len(resp.Data[i].List) > 1 && resp.Data[i].List[1].Type == "frozen" {
currData.Hold = resp.Data[i].List[1].Balance
@@ -668,7 +668,7 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
for j := range balances {
frozen := balances[j].Type == "frozen"
for i := range currencyDetails {
if currencyDetails[i].CurrencyName.String() == balances[j].Currency {
if currencyDetails[i].Currency.String() == balances[j].Currency {
if frozen {
currencyDetails[i].Hold = balances[j].Balance
} else {
@@ -681,14 +681,14 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
if frozen {
currencyDetails = append(currencyDetails,
account.Balance{
CurrencyName: currency.NewCode(balances[j].Currency),
Hold: balances[j].Balance,
Currency: currency.NewCode(balances[j].Currency),
Hold: balances[j].Balance,
})
} else {
currencyDetails = append(currencyDetails,
account.Balance{
CurrencyName: currency.NewCode(balances[j].Currency),
Total: balances[j].Balance,
Currency: currency.NewCode(balances[j].Currency),
Total: balances[j].Balance,
})
}
}
@@ -706,10 +706,10 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
var mainAcctBalances []account.Balance
for x := range acctInfo.Data {
mainAcctBalances = append(mainAcctBalances, account.Balance{
CurrencyName: currency.NewCode(acctInfo.Data[x].Symbol),
Total: acctInfo.Data[x].MarginBalance,
Hold: acctInfo.Data[x].MarginFrozen,
Free: acctInfo.Data[x].MarginAvailable,
Currency: currency.NewCode(acctInfo.Data[x].Symbol),
Total: acctInfo.Data[x].MarginBalance,
Hold: acctInfo.Data[x].MarginFrozen,
Free: acctInfo.Data[x].MarginAvailable,
})
}
@@ -733,10 +733,10 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
}
for y := range a.Data {
currencyDetails = append(currencyDetails, account.Balance{
CurrencyName: currency.NewCode(a.Data[y].Symbol),
Total: a.Data[y].MarginBalance,
Hold: a.Data[y].MarginFrozen,
Free: a.Data[y].MarginAvailable,
Currency: currency.NewCode(a.Data[y].Symbol),
Total: a.Data[y].MarginBalance,
Hold: a.Data[y].MarginFrozen,
Free: a.Data[y].MarginAvailable,
})
}
}
@@ -751,10 +751,10 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
var mainAcctBalances []account.Balance
for x := range mainAcctData.AccData {
mainAcctBalances = append(mainAcctBalances, account.Balance{
CurrencyName: currency.NewCode(mainAcctData.AccData[x].Symbol),
Total: mainAcctData.AccData[x].MarginBalance,
Hold: mainAcctData.AccData[x].MarginFrozen,
Free: mainAcctData.AccData[x].MarginAvailable,
Currency: currency.NewCode(mainAcctData.AccData[x].Symbol),
Total: mainAcctData.AccData[x].MarginBalance,
Hold: mainAcctData.AccData[x].MarginFrozen,
Free: mainAcctData.AccData[x].MarginAvailable,
})
}
@@ -778,10 +778,10 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
}
for y := range a.AssetsData {
currencyDetails = append(currencyDetails, account.Balance{
CurrencyName: currency.NewCode(a.AssetsData[y].Symbol),
Total: a.AssetsData[y].MarginBalance,
Hold: a.AssetsData[y].MarginFrozen,
Free: a.AssetsData[y].MarginAvailable,
Currency: currency.NewCode(a.AssetsData[y].Symbol),
Total: a.AssetsData[y].MarginBalance,
Hold: a.AssetsData[y].MarginFrozen,
Free: a.AssetsData[y].MarginAvailable,
})
}
}

View File

@@ -286,10 +286,10 @@ func (i *ItBit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
fullBalance := make([]account.Balance, 0, len(amounts))
for key := range amounts {
fullBalance = append(fullBalance, account.Balance{
CurrencyName: currency.NewCode(key),
Total: amounts[key].Total,
Hold: amounts[key].Hold,
Free: amounts[key].Free,
Currency: currency.NewCode(key),
Total: amounts[key].Total,
Hold: amounts[key].Hold,
Free: amounts[key].Free,
})
}

View File

@@ -183,17 +183,14 @@ func (k *Item) FillMissingDataWithEmptyEntries(i *IntervalRangeHolder) {
}
}
// RemoveDuplicates removes any duplicate candles
func (k *Item) RemoveDuplicates() {
// RemoveDuplicateCandlesByTime removes any duplicate candles
func (k *Item) RemoveDuplicateCandlesByTime() {
var newCandles []Candle
candleMap := make(map[int64]struct{})
for x := range k.Candles {
if x == 0 {
newCandles = append(newCandles, k.Candles[x])
continue
}
if !k.Candles[x].Time.Equal(k.Candles[x-1].Time) {
// don't add duplicate
if _, ok := candleMap[k.Candles[x].Time.UnixNano()]; !ok {
newCandles = append(newCandles, k.Candles[x])
candleMap[k.Candles[x].Time.UnixNano()] = struct{}{}
}
}
@@ -566,3 +563,20 @@ func CreateIntervalTime(tt time.Time) IntervalTime {
func (i *IntervalTime) Equal(tt time.Time) bool {
return tt.Unix() == i.Ticks
}
// EqualSource checks whether two sets of candles
// come from the same data source
func (k *Item) EqualSource(i *Item) error {
if k == nil || i == nil {
return common.ErrNilPointer
}
if k.Exchange != i.Exchange ||
k.Asset != i.Asset ||
!k.Pair.Equal(i.Pair) {
return fmt.Errorf("%v %v %v %w %v %v %v", k.Exchange, k.Asset, k.Pair, ErrItemNotEqual, i.Exchange, i.Asset, i.Pair)
}
if !k.UnderlyingPair.IsEmpty() && !i.UnderlyingPair.IsEmpty() && !k.UnderlyingPair.Equal(i.UnderlyingPair) {
return fmt.Errorf("%w %v %v", ErrItemUnderlyingNotEqual, k.UnderlyingPair, i.UnderlyingPair)
}
return nil
}

View File

@@ -879,6 +879,7 @@ func BenchmarkJustifyIntervalTimeStoringUnixValues2(b *testing.B) {
}
func TestConvertToNewInterval(t *testing.T) {
t.Parallel()
_, err := ConvertToNewInterval(nil, OneMin)
if !errors.Is(err, errNilKline) {
t.Errorf("received '%v' expected '%v'", err, errNilKline)
@@ -966,6 +967,7 @@ func TestConvertToNewInterval(t *testing.T) {
}
func TestGetClosePriceAtTime(t *testing.T) {
t.Parallel()
tt := time.Now()
k := Item{
Candles: []Candle{
@@ -991,3 +993,34 @@ func TestGetClosePriceAtTime(t *testing.T) {
t.Errorf("received '%v' expected '%v'", err, ErrNotFoundAtTime)
}
}
func TestRemoveDuplicateCandlesByTime(t *testing.T) {
t.Parallel()
tt := time.Now()
k := Item{
Candles: []Candle{
{
// out of order duplicate time
Time: tt.Add(time.Hour),
Close: 1337,
},
{
Time: tt,
Close: 1337,
},
{
Time: tt.Add(time.Hour),
Close: 1338,
},
},
}
k.RemoveDuplicateCandlesByTime()
if len(k.Candles) != 2 {
t.Errorf("received '%v' expected '%v'", len(k.Candles), 2)
}
k.Candles[0].Time = tt
k.RemoveDuplicateCandlesByTime()
if len(k.Candles) != 1 {
t.Errorf("received '%v' expected '%v'", len(k.Candles), 1)
}
}

View File

@@ -51,14 +51,18 @@ var (
ErrCanOnlyDownscaleCandles = errors.New("interval must be a longer duration to scale")
// ErrWholeNumberScaling returns when old interval data cannot neatly fit into new interval size
ErrWholeNumberScaling = errors.New("new interval must scale properly into new candle")
errNilKline = errors.New("kline item is nil")
// ErrNotFoundAtTime returned when looking up a candle at a specific time
ErrNotFoundAtTime = errors.New("candle not found at time")
// ErrItemNotEqual returns when comparison between two kline items fail
ErrItemNotEqual = errors.New("kline item not equal")
// ErrItemUnderlyingNotEqual returns when the underlying pair is not equal
ErrItemUnderlyingNotEqual = errors.New("kline item underlying pair not equal")
// ErrValidatingParams defines an error when the kline params are either not
// enabled or are invalid.
ErrValidatingParams = errors.New("kline param(s) are invalid")
errNilKline = errors.New("kline item is nil")
// SupportedIntervals is a list of all supported intervals
SupportedIntervals = []Interval{
FifteenSecond,

View File

@@ -604,8 +604,8 @@ func (k *Kraken) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
continue
}
balances = append(balances, account.Balance{
CurrencyName: currency.NewCode(translatedCurrency),
Total: bal[key],
Currency: currency.NewCode(translatedCurrency),
Total: bal[key],
})
}
info.Accounts = append(info.Accounts, account.SubAccount{
@@ -620,8 +620,8 @@ func (k *Kraken) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
for name := range bal.Accounts {
for code := range bal.Accounts[name].Balances {
balances = append(balances, account.Balance{
CurrencyName: currency.NewCode(code).Upper(),
Total: bal.Accounts[name].Balances[code],
Currency: currency.NewCode(code).Upper(),
Total: bal.Accounts[name].Balances[code],
})
}
info.Accounts = append(info.Accounts, account.SubAccount{

View File

@@ -338,10 +338,10 @@ func (l *Lbank) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
return info, parseErr
}
acc.Currencies = append(acc.Currencies, account.Balance{
CurrencyName: c,
Total: totalVal,
Hold: totalHold,
Free: totalVal - totalHold,
Currency: c,
Total: totalVal,
Hold: totalHold,
Free: totalVal - totalHold,
})
}
@@ -980,7 +980,7 @@ func (l *Lbank) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pa
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", l.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -295,10 +295,10 @@ func (l *LocalBitcoins) UpdateAccountInfo(ctx context.Context, assetType asset.I
AssetType: assetType,
Currencies: []account.Balance{
{
CurrencyName: currency.BTC,
Total: accountBalance.Total.Balance,
Hold: accountBalance.Total.Balance - accountBalance.Total.Sendable,
Free: accountBalance.Total.Sendable,
Currency: currency.BTC,
Total: accountBalance.Total.Balance,
Hold: accountBalance.Total.Balance - accountBalance.Total.Sendable,
Free: accountBalance.Total.Sendable,
}},
})

View File

@@ -562,10 +562,10 @@ func (o *OKCoin) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
}
currencyAccount.Currencies = append(currencyAccount.Currencies,
account.Balance{
CurrencyName: currency.NewCode(currencies[i].Currency),
Total: totalValue,
Hold: hold,
Free: totalValue - hold,
Currency: currency.NewCode(currencies[i].Currency),
Total: totalValue,
Hold: hold,
Free: totalValue - hold,
})
}
@@ -1048,7 +1048,7 @@ func (o *OKCoin) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a
return kline.Item{}, err
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil
@@ -1098,7 +1098,7 @@ func (o *OKCoin) GetHistoricCandlesExtended(ctx context.Context, pair currency.P
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", o.Base.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -527,10 +527,10 @@ func (ok *Okx) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (acc
free := balances[i].AvailBal
locked := balances[i].FrozenBalance
currencyBalance[i] = account.Balance{
CurrencyName: currency.NewCode(balances[i].Currency),
Total: balances[i].Balance,
Hold: locked,
Free: free,
Currency: currency.NewCode(balances[i].Currency),
Total: balances[i].Balance,
Hold: locked,
Free: free,
}
}
acc.Currencies = currencyBalance
@@ -1439,7 +1439,7 @@ func (ok *Okx) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pai
})
}
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil

View File

@@ -1945,3 +1945,16 @@ func TestGetOrdersRequest_Filter(t *testing.T) {
}
}
}
func TestIsValidOrderSubmissionSide(t *testing.T) {
t.Parallel()
if IsValidOrderSubmissionSide(UnknownSide) {
t.Error("expected false")
}
if !IsValidOrderSubmissionSide(Buy) {
t.Error("expected true")
}
if IsValidOrderSubmissionSide(CouldNotBuy) {
t.Error("expected false")
}
}

View File

@@ -32,7 +32,7 @@ var (
// Submit contains all properties of an order that may be required
// for an order to be created on an exchange
// Each exchange has their own requirements, so not all fields
// are required to be populated
// need to be populated
type Submit struct {
Exchange string
Type Type
@@ -62,6 +62,13 @@ type Submit struct {
TriggerPrice float64
ClientID string // TODO: Shift to credentials
ClientOrderID string
// RetrieveFees use if an API submit order response does not return fees
// enabling this will perform additional request(s) to retrieve them
// and set it in the SubmitResponse
RetrieveFees bool
// RetrieveFeeDelay some exchanges take time to properly save order data
// and cannot retrieve fees data immediately
RetrieveFeeDelay time.Duration
}
// SubmitResponse is what is returned after submitting an order to an exchange
@@ -90,6 +97,7 @@ type SubmitResponse struct {
OrderID string
Trades []TradeHistory
Fee float64
FeeAsset currency.Code
Cost float64
}

View File

@@ -19,9 +19,10 @@ const (
orderSubmissionValidSides = Buy | Sell | Bid | Ask | Long | Short
shortSide = Short | Sell | Ask
longSide = Long | Buy | Bid
inactiveStatuses = Filled | Cancelled | InsufficientBalance | MarketUnavailable | Rejected | PartiallyCancelled | Expired | Closed | AnyStatus | Cancelling | Liquidated
activeStatuses = Active | Open | PartiallyFilled | New | PendingCancel | Hidden | AutoDeleverage | Pending
notPlaced = InsufficientBalance | MarketUnavailable | Rejected
inactiveStatuses = Filled | Cancelled | InsufficientBalance | MarketUnavailable | Rejected | PartiallyCancelled | Expired | Closed | AnyStatus | Cancelling | Liquidated
activeStatuses = Active | Open | PartiallyFilled | New | PendingCancel | Hidden | AutoDeleverage | Pending
notPlaced = InsufficientBalance | MarketUnavailable | Rejected
)
var (
@@ -39,7 +40,12 @@ var (
errOrderDetailIsNil = errors.New("order detail is nil")
)
// Validate checks the supplied data and returns whether or not it's valid
// IsValidOrderSubmissionSide validates that the order side is a valid submission direction
func IsValidOrderSubmissionSide(s Side) bool {
return s != UnknownSide && orderSubmissionValidSides&s == s
}
// Validate checks the supplied data and returns whether it's valid
func (s *Submit) Validate(opt ...validate.Checker) error {
if s == nil {
return ErrSubmissionIsNil
@@ -61,8 +67,8 @@ func (s *Submit) Validate(opt ...validate.Checker) error {
return fmt.Errorf("'%s' %w", s.AssetType, asset.ErrNotSupported)
}
if s.Side == UnknownSide || orderSubmissionValidSides&s.Side != s.Side {
return ErrSideIsInvalid
if !IsValidOrderSubmissionSide(s.Side) {
return fmt.Errorf("%w %v", ErrSideIsInvalid, s.Side)
}
if s.Type != Market && s.Type != Limit {

View File

@@ -419,8 +419,8 @@ func (p *Poloniex) UpdateAccountInfo(ctx context.Context, assetType asset.Item)
currencies := make([]account.Balance, 0, len(accountBalance.Currency))
for x, y := range accountBalance.Currency {
currencies = append(currencies, account.Balance{
CurrencyName: currency.NewCode(x),
Total: y,
Currency: currency.NewCode(x),
Total: y,
})
}

View File

@@ -311,9 +311,8 @@ func (y *Yobit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac
currencies := make([]account.Balance, 0, len(accountBalance.FundsInclOrders))
for x, y := range accountBalance.FundsInclOrders {
var exchangeCurrency account.Balance
exchangeCurrency.CurrencyName = currency.NewCode(x)
exchangeCurrency.Currency = currency.NewCode(x)
exchangeCurrency.Total = y
exchangeCurrency.Hold = 0
for z, w := range accountBalance.Funds {
if z == x {
exchangeCurrency.Hold = y - w

View File

@@ -386,10 +386,10 @@ func (z *ZB) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (accou
}
balances[i] = account.Balance{
CurrencyName: currency.NewCode(coins[i].EnName),
Total: hold + avail,
Hold: hold,
Free: avail,
Currency: currency.NewCode(coins[i].EnName),
Total: hold + avail,
Hold: hold,
Free: avail,
}
}