mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-23 23:16:49 +00:00
(Exchange) Add GetHistoricCandles() & GetHistoricCandlesEx() support to exchanges (#479)
* implemented binance and bitfinex GetHistoricCandles wrapper methods) * coinbene supported added * after and before clean up * gateio wrapper completed * merged upstream/master * Added bsaic KlineIntervalSupported() method * Converted binance fixed test * WIP * new KlineConvertToExchangeStandardString method added * end of day WIP * WIP * end of day WIP started migration of trade history * added kline support to hitbtc huobi lbank * added exchangehistory to all supported exchanges started work on coinbase 300 candles/request method * end of day WIP * removed unused ta and misc changes to flag ready for review * yobit cleanup * revert coinbase changES * general code clean up and added zb support * poloniex support added * renamed method to FormatExchangeKlineInterval other misc fixes * linter fixes * linter fixes * removed verbose * fixed poloniex test coverage * revert poloniex mock data * regenerated poloniex mock data * a very verbose clean up * binance mock clean up * removed unneeded t.Log() * setting verbose to true to debug CI issue * first pass changes addressed * common.ErrNotYetImplemented implemented :D * comments added * WIP-addressed exchange requests and reverted previous GetExchangeHistory changes * WIP-addressed exchange requests and reverted previous GetExchangeHistory changes * increased test coverage added kraken support * OKGroup support completed started work on address GetExchangeHistory feedback and migrating to own PR under https://github.com/xtda/gocryptotrader/tree/exchange_history * convert zb ratelimits * gofmt run on okcoin * increased delay on rate limit * gofmt package * fixed panic with coinbene and bithumb if conversion fails * very broken end of day WIP * added support for GetHistoricCandlesEx to coinbase and binance * gofmt package * coinbase, btcmarkets, zb ex wrapper function added * added all exchange support for ex regenerated mock data * update bithumb to return wrapper method * gofmt package * end of day started work on changes * reworked test coverage added okgroup support general fixes/change requests addressed * Added OneMonth * limit checks on supportedexchanges * reverted getexchangehistory * reworked binance tesT * added workaround for kraken panic * renamed command to extended removed interval check on non-implemented commands * added wrapperconfig back * increased test coverage for FormatExchangeKlineInterval * WIP * increased test coverage for FormatExchangeKlineInterval bitfinex/gateio/huobi * linter fixes * zb kraken lbank coinbene btcmarkets support added * removed verbose * OK group support for other asset types added * swapped margin to use spot endpoint * index support added test coverage added for asset types * added asset type to okcoin test * gofmt * add asset to extended method * removed verbose * add support for coinbene swap increase test coverage * removed verbose * small clean up of okgroup wrapper functions * verbose to troubleshoot CI issues * removed verbose * added error check reverted coinbasechanges * readme updated * removed unused start/finish started work on decoupling api requests from kline package * restructured coinbene, bithumb methods, added bitstamp support * kraken time fix * BTCMarkets restructure * typo fix * removed test for futures due to contact changing * added start/end date to extended method over range * converted to assettranslator * removed verbose * removed invalid char * reverted incorrectly removed return * added import * further template updates * macos hates my keyboard :D * misc canges * x -> i * removed verbose * updated fixCasing to allocate var before checks * removed time conversion * sort all outgoing kline candles * fixCasing fix * after/before checks added * added parallel to test * logic check on BTCmarkets * removed unused param, used correct iterator * converted HitBTC to use time.Time * add iszero false check to candle times * updated resultlimit to 5000 * new line added * added comment to exported const * use configured ratelimit * fixed pair for test * panic fixed WIP on fixCasing * fixCasing rework, started work on readme docs * enable rate limiter for wrapper issues tool * docs updated * removed err from return and formatted currency * updated Yobit supported status * Updated HitBTC to use onehour candles due to test exeuction times * added further details to gctcli output * added link to docs * added link to tempalte * disable FTX websocket in config_example * fix poloneix * regenerated poloniex mock data * removed recording flag
This commit is contained in:
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
@@ -67,8 +66,7 @@ type Binance struct {
|
||||
WebsocketConn *wshandler.WebsocketConnection
|
||||
|
||||
// Valid string list that is required by the exchange
|
||||
validLimits []int
|
||||
validIntervals []TimeInterval
|
||||
validLimits []int
|
||||
}
|
||||
|
||||
// GetExchangeInfo returns exchange information. Check binance_types for more
|
||||
@@ -178,10 +176,11 @@ func (b *Binance) GetAggregatedTrades(symbol string, limit int) ([]AggregatedTra
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", strings.ToUpper(symbol))
|
||||
params.Set("limit", strconv.Itoa(limit))
|
||||
|
||||
path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, aggregatedTrades, params.Encode())
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.Itoa(limit))
|
||||
}
|
||||
|
||||
path := b.API.Endpoints.URL + aggregatedTrades + "?" + params.Encode()
|
||||
return resp, b.SendHTTPRequest(path, limitDefault, &resp)
|
||||
}
|
||||
|
||||
@@ -199,7 +198,7 @@ func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) {
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", arg.Symbol)
|
||||
params.Set("interval", string(arg.Interval))
|
||||
params.Set("interval", arg.Interval)
|
||||
if arg.Limit != 0 {
|
||||
params.Set("limit", strconv.Itoa(arg.Limit))
|
||||
}
|
||||
@@ -581,36 +580,9 @@ func (b *Binance) CheckSymbol(symbol string, assetType asset.Item) error {
|
||||
return errors.New("incorrect symbol values - please check available pairs in configuration")
|
||||
}
|
||||
|
||||
// CheckIntervals checks value against a variable list
|
||||
func (b *Binance) CheckIntervals(interval string) error {
|
||||
for x := range b.validIntervals {
|
||||
if TimeInterval(interval) == b.validIntervals[x] {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New(`incorrect interval values - valid values are "1m","3m","5m","15m","30m","1h","2h","4h","6h","8h","12h","1d","3d","1w","1M"`)
|
||||
}
|
||||
|
||||
// SetValues sets the default valid values
|
||||
func (b *Binance) SetValues() {
|
||||
b.validLimits = []int{5, 10, 20, 50, 100, 500, 1000, 5000}
|
||||
b.validIntervals = []TimeInterval{
|
||||
TimeIntervalMinute,
|
||||
TimeIntervalThreeMinutes,
|
||||
TimeIntervalFiveMinutes,
|
||||
TimeIntervalFifteenMinutes,
|
||||
TimeIntervalThirtyMinutes,
|
||||
TimeIntervalHour,
|
||||
TimeIntervalTwoHours,
|
||||
TimeIntervalFourHours,
|
||||
TimeIntervalSixHours,
|
||||
TimeIntervalEightHours,
|
||||
TimeIntervalTwelveHours,
|
||||
TimeIntervalDay,
|
||||
TimeIntervalThreeDays,
|
||||
TimeIntervalWeek,
|
||||
TimeIntervalMonth,
|
||||
}
|
||||
}
|
||||
|
||||
// GetFee returns an estimate of fee based on type of transaction
|
||||
@@ -757,38 +729,3 @@ func (b *Binance) MaintainWsAuthStreamKey() error {
|
||||
HTTPRecording: b.HTTPRecording,
|
||||
})
|
||||
}
|
||||
|
||||
func parseInterval(in time.Duration) (TimeInterval, error) {
|
||||
switch in {
|
||||
case kline.OneMin:
|
||||
return TimeIntervalMinute, nil
|
||||
case kline.ThreeMin:
|
||||
return TimeIntervalThreeMinutes, nil
|
||||
case kline.FiveMin:
|
||||
return TimeIntervalFiveMinutes, nil
|
||||
case kline.FifteenMin:
|
||||
return TimeIntervalFifteenMinutes, nil
|
||||
case kline.ThirtyMin:
|
||||
return TimeIntervalThirtyMinutes, nil
|
||||
case kline.OneHour:
|
||||
return TimeIntervalHour, nil
|
||||
case kline.TwoHour:
|
||||
return TimeIntervalTwoHours, nil
|
||||
case kline.FourHour:
|
||||
return TimeIntervalFourHours, nil
|
||||
case kline.SixHour:
|
||||
return TimeIntervalSixHours, nil
|
||||
case kline.OneHour * 8:
|
||||
return TimeIntervalEightHours, nil
|
||||
case kline.TwelveHour:
|
||||
return TimeIntervalTwelveHours, nil
|
||||
case kline.OneDay:
|
||||
return TimeIntervalDay, nil
|
||||
case kline.ThreeDay:
|
||||
return TimeIntervalThreeDays, nil
|
||||
case kline.OneWeek:
|
||||
return TimeIntervalWeek, nil
|
||||
default:
|
||||
return TimeIntervalMinute, errInvalidInterval
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,6 @@ func TestGetHistoricalTrades(t *testing.T) {
|
||||
|
||||
func TestGetAggregatedTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := b.GetAggregatedTrades("BTCUSDT", 5)
|
||||
if err != nil {
|
||||
t.Error("Binance GetAggregatedTrades() error", err)
|
||||
@@ -102,11 +101,12 @@ func TestGetAggregatedTrades(t *testing.T) {
|
||||
|
||||
func TestGetSpotKline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := b.GetSpotKline(KlinesRequestParams{
|
||||
Symbol: "BTCUSDT",
|
||||
Interval: TimeIntervalFiveMinutes,
|
||||
Limit: 24,
|
||||
Symbol: "BTCUSDT",
|
||||
Interval: kline.FiveMin.Short(),
|
||||
Limit: 24,
|
||||
StartTime: time.Unix(1577836800, 0).Unix() * 1000,
|
||||
EndTime: time.Unix(1580515200, 0).Unix() * 1000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error("Binance GetSpotKline() error", err)
|
||||
@@ -501,7 +501,6 @@ func TestModifyOrder(t *testing.T) {
|
||||
|
||||
func TestWithdraw(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
@@ -905,130 +904,65 @@ func TestExecutionTypeToOrderStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
if mockTests {
|
||||
t.Skip("skipping test under mock as its covered by GetSpotKlines()")
|
||||
}
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
start := time.Date(2017, 8, 18, 0, 0, 0, 0, time.UTC)
|
||||
end := start.AddDate(0, 6, 0)
|
||||
|
||||
_, err := b.GetHistoricCandles(currencyPair, asset.Spot, start, end, kline.OneDay)
|
||||
startTime := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
_, err := b.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInterval(t *testing.T) {
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
startTime := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
_, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinance_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval time.Duration
|
||||
expected TimeInterval
|
||||
err error
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
TimeIntervalMinute,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"ThreeMin",
|
||||
kline.ThreeMin,
|
||||
TimeIntervalThreeMinutes,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"FiveMin",
|
||||
kline.FiveMin,
|
||||
TimeIntervalFiveMinutes,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"FifteenMin",
|
||||
kline.FifteenMin,
|
||||
TimeIntervalFifteenMinutes,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"ThirtyMin",
|
||||
kline.ThirtyMin,
|
||||
TimeIntervalThirtyMinutes,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"OneHour",
|
||||
kline.OneHour,
|
||||
TimeIntervalHour,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"TwoHour",
|
||||
kline.TwoHour,
|
||||
TimeIntervalTwoHours,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"FourHour",
|
||||
kline.FourHour,
|
||||
TimeIntervalFourHours,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"SixHour",
|
||||
kline.SixHour,
|
||||
TimeIntervalSixHours,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"EightHour",
|
||||
kline.OneHour * 8,
|
||||
TimeIntervalEightHours,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"TwelveHour",
|
||||
kline.TwelveHour,
|
||||
TimeIntervalTwelveHours,
|
||||
nil,
|
||||
"1m",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
TimeIntervalDay,
|
||||
nil,
|
||||
"1d",
|
||||
},
|
||||
{
|
||||
"ThreeDay",
|
||||
kline.ThreeDay,
|
||||
TimeIntervalThreeDays,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"OneWeek",
|
||||
kline.OneWeek,
|
||||
TimeIntervalWeek,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"default",
|
||||
time.Hour * 1337,
|
||||
TimeIntervalHour,
|
||||
errInvalidInterval,
|
||||
"OneMonth",
|
||||
kline.OneMonth,
|
||||
"1M",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
v, err := parseInterval(test.interval)
|
||||
if err != nil {
|
||||
if err != test.err {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if v != test.expected {
|
||||
t.Fatalf("%v: received %v expected %v", test.name, v, test.expected)
|
||||
}
|
||||
ret := b.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package binance
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
var errInvalidInterval = errors.New("invalid interval")
|
||||
|
||||
// Response holds basic binance api response data
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
@@ -407,35 +404,13 @@ var (
|
||||
|
||||
// KlinesRequestParams represents Klines request data.
|
||||
type KlinesRequestParams struct {
|
||||
Symbol string // Required field; example LTCBTC, BTCUSDT
|
||||
Interval TimeInterval // Time interval period
|
||||
Limit int // Default 500; max 500.
|
||||
Symbol string // Required field; example LTCBTC, BTCUSDT
|
||||
Interval string // Time interval period
|
||||
Limit int // Default 500; max 500.
|
||||
StartTime int64
|
||||
EndTime int64
|
||||
}
|
||||
|
||||
// TimeInterval represents interval enum.
|
||||
type TimeInterval string
|
||||
|
||||
// Vars related to time intervals
|
||||
var (
|
||||
TimeIntervalMinute = TimeInterval("1m")
|
||||
TimeIntervalThreeMinutes = TimeInterval("3m")
|
||||
TimeIntervalFiveMinutes = TimeInterval("5m")
|
||||
TimeIntervalFifteenMinutes = TimeInterval("15m")
|
||||
TimeIntervalThirtyMinutes = TimeInterval("30m")
|
||||
TimeIntervalHour = TimeInterval("1h")
|
||||
TimeIntervalTwoHours = TimeInterval("2h")
|
||||
TimeIntervalFourHours = TimeInterval("4h")
|
||||
TimeIntervalSixHours = TimeInterval("6h")
|
||||
TimeIntervalEightHours = TimeInterval("8h")
|
||||
TimeIntervalTwelveHours = TimeInterval("12h")
|
||||
TimeIntervalDay = TimeInterval("1d")
|
||||
TimeIntervalThreeDays = TimeInterval("3d")
|
||||
TimeIntervalWeek = TimeInterval("1w")
|
||||
TimeIntervalMonth = TimeInterval("1M")
|
||||
)
|
||||
|
||||
// WithdrawalFees the large list of predefined withdrawal fees
|
||||
// Prone to change
|
||||
var WithdrawalFees = map[currency.Code]float64{
|
||||
|
||||
@@ -110,9 +110,32 @@ func (b *Binance) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.ThreeDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
kline.OneMonth.Word(): true,
|
||||
},
|
||||
ResultLimit: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -672,22 +695,35 @@ func (b *Binance) ValidateCredentials() error {
|
||||
return b.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Binance) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
intervalToString, err := parseInterval(interval)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (b *Binance) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
if in == kline.OneDay {
|
||||
return "1d"
|
||||
}
|
||||
klineParams := KlinesRequestParams{
|
||||
Interval: intervalToString,
|
||||
if in == kline.OneMonth {
|
||||
return "1M"
|
||||
}
|
||||
return in.Short()
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Binance) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !b.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
if kline.TotalCandlesPerInterval(start, end, interval) > b.Features.Enabled.Kline.ResultLimit {
|
||||
return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits)
|
||||
}
|
||||
|
||||
req := KlinesRequestParams{
|
||||
Interval: b.FormatExchangeKlineInterval(interval),
|
||||
Symbol: b.FormatExchangeCurrency(pair, a).String(),
|
||||
StartTime: start.Unix() * 1000,
|
||||
EndTime: end.Unix() * 1000,
|
||||
}
|
||||
|
||||
candles, err := b.GetSpotKline(klineParams)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
Limit: int(b.Features.Enabled.Kline.ResultLimit),
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
@@ -697,6 +733,11 @@ func (b *Binance) GetHistoricCandles(pair currency.Pair, a asset.Item, start, en
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
candles, err := b.GetSpotKline(req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for x := range candles {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: candles[x].OpenTime,
|
||||
@@ -707,5 +748,53 @@ func (b *Binance) GetHistoricCandles(pair currency.Pair, a asset.Item, start, en
|
||||
Volume: candles[x].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *Binance) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !b.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
req := KlinesRequestParams{
|
||||
Interval: b.FormatExchangeKlineInterval(interval),
|
||||
Symbol: b.FormatExchangeCurrency(pair, a).String(),
|
||||
StartTime: dates[x].Start.UTC().Unix() * 1000,
|
||||
EndTime: dates[x].End.UTC().Unix() * 1000,
|
||||
Limit: int(b.Features.Enabled.Kline.ResultLimit),
|
||||
}
|
||||
|
||||
candles, err := b.GetSpotKline(req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range candles {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: candles[i].OpenTime,
|
||||
Open: candles[i].Open,
|
||||
High: candles[i].Close,
|
||||
Low: candles[i].Low,
|
||||
Close: candles[i].Close,
|
||||
Volume: candles[i].Volume,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ func (b *Bitfinex) GetTrades(currencyPair string, limit, timestampStart, timesta
|
||||
v.Encode()
|
||||
|
||||
var resp [][]interface{}
|
||||
err := b.SendHTTPRequest(path, &resp, trade)
|
||||
err := b.SendHTTPRequest(path, &resp, tradeRateLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -408,7 +408,7 @@ func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]Lends, error) {
|
||||
// timeFrame values: '1m', '5m', '15m', '30m', '1h', '3h', '6h', '12h', '1D',
|
||||
// '7D', '14D', '1M'
|
||||
// section values: last or hist
|
||||
func (b *Bitfinex) GetCandles(symbol, timeFrame string, start, end, limit int64, historic, ascending bool) ([]Candle, error) {
|
||||
func (b *Bitfinex) GetCandles(symbol, timeFrame string, start, end int64, limit uint32, historic bool) ([]Candle, error) {
|
||||
var fundingPeriod string
|
||||
if symbol[0] == 'f' {
|
||||
fundingPeriod = ":p30"
|
||||
@@ -434,7 +434,7 @@ func (b *Bitfinex) GetCandles(symbol, timeFrame string, start, end, limit int64,
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
v.Set("limit", strconv.FormatInt(limit, 10))
|
||||
v.Set("limit", strconv.FormatInt(int64(limit), 10))
|
||||
}
|
||||
|
||||
path += "/hist"
|
||||
@@ -451,7 +451,7 @@ func (b *Bitfinex) GetCandles(symbol, timeFrame string, start, end, limit int64,
|
||||
var c []Candle
|
||||
for i := range response {
|
||||
c = append(c, Candle{
|
||||
Timestamp: int64(response[i][0].(float64)),
|
||||
Timestamp: time.Unix(int64(response[i][0].(float64)/1000), 0),
|
||||
Open: response[i][1].(float64),
|
||||
Close: response[i][2].(float64),
|
||||
High: response[i][3].(float64),
|
||||
@@ -476,7 +476,7 @@ func (b *Bitfinex) GetCandles(symbol, timeFrame string, start, end, limit int64,
|
||||
}
|
||||
|
||||
return []Candle{{
|
||||
Timestamp: int64(response[0].(float64)),
|
||||
Timestamp: time.Unix(int64(response[0].(float64))/1000, 0),
|
||||
Open: response[1].(float64),
|
||||
Close: response[2].(float64),
|
||||
High: response[3].(float64),
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -153,7 +154,7 @@ func TestGetLends(t *testing.T) {
|
||||
|
||||
func TestGetCandles(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetCandles("fUSD", "1m", 0, 0, 10, true, false)
|
||||
_, err := b.GetCandles("fUSD", "1m", 0, 0, 10, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1210,3 +1211,112 @@ func TestWsNotifications(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSD")
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin*1337)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("TBTCUSD")
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin*1337)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixCasing(t *testing.T) {
|
||||
ret := b.fixCasing(currency.NewPairFromString("BTCUSD"), asset.Spot)
|
||||
if ret != "tBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("TBTCUSD"), asset.Spot)
|
||||
if ret != "tBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("tBTCUSD"), asset.Spot)
|
||||
if ret != "tBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("BTCUSD"), asset.Margin)
|
||||
if ret != "fBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("BTCUSD"), asset.Spot)
|
||||
if ret != "tBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("FUNETH"), asset.Spot)
|
||||
if ret != "tFUNETH" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("TNBUSD"), asset.Spot)
|
||||
if ret != "tTNBUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("tTNBUSD"), asset.Spot)
|
||||
if ret != "tTNBUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"1m",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"1D",
|
||||
},
|
||||
{
|
||||
"OneWeek",
|
||||
kline.OneWeek,
|
||||
"7D",
|
||||
},
|
||||
{
|
||||
"TwoWeeks",
|
||||
kline.OneWeek * 2,
|
||||
"14D",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := b.FormatExchangeKlineInterval(test.interval)
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package bitfinex
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
)
|
||||
|
||||
// AcceptedOrderType defines the accepted market types, exchange strings denote
|
||||
// non-contract order types.
|
||||
@@ -71,6 +75,7 @@ type Trade struct {
|
||||
Rate float64
|
||||
Period int64
|
||||
Type string
|
||||
Side order.Side
|
||||
}
|
||||
|
||||
// Lendbook holds most recent funding data for a relevant currency
|
||||
@@ -388,7 +393,7 @@ type WebsocketTrade struct {
|
||||
|
||||
// Candle holds OHLC data
|
||||
type Candle struct {
|
||||
Timestamp int64
|
||||
Timestamp time.Time
|
||||
Open float64
|
||||
Close float64
|
||||
High float64
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
@@ -119,9 +120,31 @@ func (b *Bitfinex) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission |
|
||||
exchange.AutoWithdrawFiatWithAPIPermission,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
kline.TwoWeek.Word(): true,
|
||||
},
|
||||
ResultLimit: 10000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -773,7 +796,117 @@ func (b *Bitfinex) ValidateCredentials() error {
|
||||
return b.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Bitfinex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (b *Bitfinex) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
switch in {
|
||||
case kline.OneDay:
|
||||
return "1D"
|
||||
case kline.OneWeek:
|
||||
return "7D"
|
||||
case kline.OneWeek * 2:
|
||||
return "14D"
|
||||
default:
|
||||
return in.Short()
|
||||
}
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Bitfinex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !b.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
if kline.TotalCandlesPerInterval(start, end, interval) > b.Features.Enabled.Kline.ResultLimit {
|
||||
return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits)
|
||||
}
|
||||
|
||||
candles, err := b.GetCandles(b.fixCasing(pair, a), b.FormatExchangeKlineInterval(interval),
|
||||
start.Unix()*1000, end.Unix()*1000,
|
||||
b.Features.Enabled.Kline.ResultLimit, true)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range candles {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: candles[x].Timestamp,
|
||||
Open: candles[x].Open,
|
||||
High: candles[x].Close,
|
||||
Low: candles[x].Low,
|
||||
Close: candles[x].Close,
|
||||
Volume: candles[x].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *Bitfinex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !b.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
candles, err := b.GetCandles(b.fixCasing(pair, a), b.FormatExchangeKlineInterval(interval),
|
||||
dates[x].Start.Unix()*1000, dates[x].End.Unix()*1000,
|
||||
b.Features.Enabled.Kline.ResultLimit, true)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range candles {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: candles[i].Timestamp,
|
||||
Open: candles[i].Open,
|
||||
High: candles[i].Close,
|
||||
Low: candles[i].Low,
|
||||
Close: candles[i].Close,
|
||||
Volume: candles[i].Volume,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) string {
|
||||
var checkString [2]byte
|
||||
if a == asset.Spot {
|
||||
checkString[0] = 't'
|
||||
checkString[1] = 'T'
|
||||
} else if a == asset.Margin {
|
||||
checkString[0] = 'f'
|
||||
checkString[1] = 'F'
|
||||
}
|
||||
|
||||
y := in.Base.String()
|
||||
if (y[0] != checkString[0] && y[0] != checkString[1]) ||
|
||||
(y[0] == checkString[1] && y[1] == checkString[1]) || in.Base == currency.TNB {
|
||||
return string(checkString[0]) + b.FormatExchangeCurrency(in, a).Upper().String()
|
||||
}
|
||||
|
||||
runes := []rune(b.FormatExchangeCurrency(in, a).Upper().String())
|
||||
runes[0] = unicode.ToLower(runes[0])
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ const (
|
||||
platformStatus request.EndpointLimit = iota
|
||||
tickerBatch
|
||||
tickerFunction
|
||||
trade
|
||||
tradeRateLimit
|
||||
orderbookFunction
|
||||
stats
|
||||
candle
|
||||
@@ -262,7 +262,7 @@ func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
time.Sleep(r.TickerBatch.Reserve().Delay())
|
||||
case tickerFunction:
|
||||
time.Sleep(r.Ticker.Reserve().Delay())
|
||||
case trade:
|
||||
case tradeRateLimit:
|
||||
time.Sleep(r.Trade.Reserve().Delay())
|
||||
case orderbookFunction:
|
||||
time.Sleep(r.Orderbook.Reserve().Delay())
|
||||
|
||||
@@ -392,6 +392,11 @@ func (b *Bitflyer) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Bitflyer) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (b *Bitflyer) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *Bitflyer) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
publicTicker = "/public/ticker/"
|
||||
publicOrderBook = "/public/orderbook/"
|
||||
publicTransactionHistory = "/public/transaction_history/"
|
||||
publicCandleStick = "/public/candlestick/"
|
||||
|
||||
privateAccInfo = "/info/account"
|
||||
privateAccBalance = "/info/balance"
|
||||
@@ -599,3 +600,10 @@ var errCode = map[string]string{
|
||||
"5600": "CUSTOM NOTICE (상황별 에러 메시지 출력) usually means transaction not allowed",
|
||||
"5900": "Unknown Error",
|
||||
}
|
||||
|
||||
// GetCandleStick returns candle stick data for requested pair
|
||||
func (b *Bithumb) GetCandleStick(symbol, interval string) (resp OHLCVResponse, err error) {
|
||||
path := b.API.Endpoints.URL + publicCandleStick + symbol + "/" + interval
|
||||
err = b.SendHTTPRequest(path, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
@@ -523,3 +526,28 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCandleStick(t *testing.T) {
|
||||
_, err := b.GetCandleStick("BTC_KRW", "1m")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTC_KRW")
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTC_KRW")
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,3 +284,9 @@ type FullBalance struct {
|
||||
Xcoin map[string]float64
|
||||
Available map[string]float64
|
||||
}
|
||||
|
||||
// OHLCVResponse holds returned kline data
|
||||
type OHLCVResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data [][6]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ func (b *Bithumb) SetDefaults() {
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "_",
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
@@ -95,15 +96,31 @@ func (b *Bithumb) SetDefaults() {
|
||||
FiatWithdrawalFee: true,
|
||||
CryptoDepositFee: true,
|
||||
CryptoWithdrawalFee: true,
|
||||
KlineFetching: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.AutoWithdrawFiat,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.TenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.Requester = request.New(b.Name,
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout),
|
||||
request.WithLimiter(SetRateLimit()))
|
||||
@@ -594,7 +611,94 @@ func (b *Bithumb) ValidateCredentials() error {
|
||||
return b.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Bithumb) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (b *Bithumb) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
return in.Short()
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Bithumb) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !b.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
candle, err := b.GetCandleStick(b.FormatExchangeCurrency(pair, a).String(), b.FormatExchangeKlineInterval(interval))
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: pair,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range candle.Data {
|
||||
var tempCandle kline.Candle
|
||||
|
||||
tempTime := candle.Data[x][0].(float64)
|
||||
timestamp := time.Unix(0, int64(tempTime)*int64(time.Millisecond))
|
||||
if timestamp.Before(start) {
|
||||
continue
|
||||
}
|
||||
if timestamp.After(end) {
|
||||
break
|
||||
}
|
||||
tempCandle.Time = timestamp
|
||||
|
||||
open, ok := candle.Data[x][1].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("open conversion failed")
|
||||
}
|
||||
tempCandle.Open, err = strconv.ParseFloat(open, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
high, ok := candle.Data[x][2].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("high conversion failed")
|
||||
}
|
||||
tempCandle.High, err = strconv.ParseFloat(high, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
low, ok := candle.Data[x][3].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("low conversion failed")
|
||||
}
|
||||
tempCandle.Low, err = strconv.ParseFloat(low, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
closeTemp, ok := candle.Data[x][4].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("close conversion failed")
|
||||
}
|
||||
tempCandle.Close, err = strconv.ParseFloat(closeTemp, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
vol, ok := candle.Data[x][5].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("vol conversion failed")
|
||||
}
|
||||
tempCandle.Volume, err = strconv.ParseFloat(vol, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempCandle)
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *Bithumb) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return b.GetHistoricCandles(pair, a, start, end, interval)
|
||||
}
|
||||
|
||||
@@ -371,7 +371,11 @@ func TestGetTrade(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetPreviousTrades(t *testing.T) {
|
||||
_, err := b.GetPreviousTrades(&TradeGetBucketedParams{})
|
||||
_, err := b.GetPreviousTrades(&TradeGetBucketedParams{
|
||||
Symbol: "XBTBTC",
|
||||
Start: int32(time.Now().Add(-time.Hour * 24).Unix()),
|
||||
Columns: "open,high,low,close,volume",
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("GetPreviousTrades() Expected error")
|
||||
}
|
||||
|
||||
@@ -690,6 +690,11 @@ func (b *Bitmex) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Bitmex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (b *Bitmex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *Bitmex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ const (
|
||||
bitstampAPIXrpDeposit = "xrp_address"
|
||||
bitstampAPIReturnType = "string"
|
||||
bitstampAPITradingPairsInfo = "trading-pairs-info"
|
||||
bitstampOHLC = "ohlc"
|
||||
|
||||
bitstampRateInterval = time.Minute * 10
|
||||
bitstampRequestRate = 8000
|
||||
@@ -579,6 +580,24 @@ func (b *Bitstamp) GetUnconfirmedBitcoinDeposits() ([]UnconfirmedBTCTransactions
|
||||
b.SendAuthenticatedHTTPRequest(bitstampAPIUnconfirmedBitcoin, false, nil, &response)
|
||||
}
|
||||
|
||||
// OHLC returns OHLCV data for step (interval)
|
||||
func (b *Bitstamp) OHLC(currency string, start, end time.Time, step, limit string) (resp OHLCResponse, err error) {
|
||||
var v = url.Values{}
|
||||
v.Add("limit", limit)
|
||||
v.Add("step", step)
|
||||
|
||||
if start.After(end) && !end.IsZero() {
|
||||
return resp, errors.New("start time cannot be after end time")
|
||||
}
|
||||
if !start.IsZero() {
|
||||
v.Add("start", strconv.FormatInt(start.Unix(), 10))
|
||||
}
|
||||
if !end.IsZero() {
|
||||
v.Add("end", strconv.FormatInt(end.Unix(), 10))
|
||||
}
|
||||
return resp, b.SendHTTPRequest(common.EncodeURLValues(b.API.Endpoints.URL+"/v"+bitstampAPIVersion+"/"+bitstampOHLC+"/"+currency, v), &resp)
|
||||
}
|
||||
|
||||
// TransferAccountBalance transfers funds from either a main or sub account
|
||||
// amount - to transfers
|
||||
// currency - which currency to transfer
|
||||
|
||||
@@ -3,10 +3,13 @@ package bitstamp
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
@@ -664,3 +667,33 @@ func TestWsRequestReconnect(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBitstamp_OHLC(t *testing.T) {
|
||||
start := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
_, err := b.OHLC("btcusd", start, end, "60", "10")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBitstamp_GetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("btcusd")
|
||||
start := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
|
||||
_, err := b.GetHistoricCandles(currencyPair, asset.Spot, start, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBitstamp_GetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("btcusd")
|
||||
start := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
_, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,3 +219,18 @@ type websocketOrderBook struct {
|
||||
Timestamp int64 `json:"timestamp,string"`
|
||||
Microtimestamp string `json:"microtimestamp"`
|
||||
}
|
||||
|
||||
// OHLCResponse holds returned candle data
|
||||
type OHLCResponse struct {
|
||||
Data struct {
|
||||
Pair string `json:"pair"`
|
||||
OHLCV []struct {
|
||||
Timestamp int64 `json:"timestamp,string"`
|
||||
Open float64 `json:"open,string"`
|
||||
High float64 `json:"high,string"`
|
||||
Low float64 `json:"low,string"`
|
||||
Close float64 `json:"close,string"`
|
||||
Volume float64 `json:"volume,string"`
|
||||
} `json:"ohlc"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
@@ -103,9 +103,30 @@ func (b *Bitstamp) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.AutoWithdrawFiat,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
Intervals: true,
|
||||
DateRanges: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.ThreeDay.Word(): true,
|
||||
},
|
||||
ResultLimit: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -677,6 +698,95 @@ func (b *Bitstamp) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Bitstamp) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (b *Bitstamp) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !b.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
candles, err := b.OHLC(
|
||||
b.FormatExchangeCurrency(pair, a).Lower().String(),
|
||||
start,
|
||||
end,
|
||||
b.FormatExchangeKlineInterval(interval),
|
||||
strconv.FormatInt(int64(b.Features.Enabled.Kline.ResultLimit), 10),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for x := range candles.Data.OHLCV {
|
||||
if time.Unix(candles.Data.OHLCV[x].Timestamp, 0).Before(start) ||
|
||||
time.Unix(candles.Data.OHLCV[x].Timestamp, 0).After(end) {
|
||||
continue
|
||||
}
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: time.Unix(candles.Data.OHLCV[x].Timestamp, 0),
|
||||
Open: candles.Data.OHLCV[x].Open,
|
||||
High: candles.Data.OHLCV[x].High,
|
||||
Low: candles.Data.OHLCV[x].Low,
|
||||
Close: candles.Data.OHLCV[x].Close,
|
||||
Volume: candles.Data.OHLCV[x].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *Bitstamp) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !b.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
candles, err := b.OHLC(
|
||||
b.FormatExchangeCurrency(pair, a).Lower().String(),
|
||||
dates[x].Start,
|
||||
dates[x].End,
|
||||
b.FormatExchangeKlineInterval(interval),
|
||||
strconv.FormatInt(int64(b.Features.Enabled.Kline.ResultLimit), 10),
|
||||
)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range candles.Data.OHLCV {
|
||||
if time.Unix(candles.Data.OHLCV[i].Timestamp, 0).Before(start) ||
|
||||
time.Unix(candles.Data.OHLCV[i].Timestamp, 0).After(end) {
|
||||
continue
|
||||
}
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: time.Unix(candles.Data.OHLCV[i].Timestamp, 0),
|
||||
Open: candles.Data.OHLCV[i].Open,
|
||||
High: candles.Data.OHLCV[i].High,
|
||||
Low: candles.Data.OHLCV[i].Low,
|
||||
Close: candles.Data.OHLCV[i].Close,
|
||||
Volume: candles.Data.OHLCV[i].Volume,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -591,6 +591,11 @@ func (b *Bittrex) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *Bittrex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (b *Bittrex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *Bittrex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
@@ -68,13 +66,13 @@ const (
|
||||
stop = "Stop"
|
||||
takeProfit = "Take Profit"
|
||||
|
||||
subscribe = "subscribe"
|
||||
fundChange = "fundChange"
|
||||
orderChange = "orderChange"
|
||||
heartbeat = "heartbeat"
|
||||
tick = "tick"
|
||||
wsOB = "orderbookUpdate"
|
||||
trade = "trade"
|
||||
subscribe = "subscribe"
|
||||
fundChange = "fundChange"
|
||||
orderChange = "orderChange"
|
||||
heartbeat = "heartbeat"
|
||||
tick = "tick"
|
||||
wsOB = "orderbookUpdate"
|
||||
tradeEndPoint = "tradeEndPoint"
|
||||
)
|
||||
|
||||
// BTCMarkets is the overarching type across the BTCMarkets package
|
||||
@@ -164,27 +162,15 @@ func (b *BTCMarkets) GetOrderbook(marketID string, level int64) (Orderbook, erro
|
||||
}
|
||||
|
||||
// GetMarketCandles gets candles for specified currency pair
|
||||
func (b *BTCMarkets) GetMarketCandles(marketID string, timeWindow time.Duration, from, to time.Time, before, after, limit int64) (kline.Item, error) {
|
||||
func (b *BTCMarkets) GetMarketCandles(marketID, timeWindow string, from, to time.Time, before, after, limit int64) (out CandleResponse, err error) {
|
||||
if (before > 0) && (after >= 0) {
|
||||
return kline.Item{}, errors.New("BTCMarkets only supports either before or after, not both")
|
||||
return out, errors.New("BTCMarkets only supports either before or after, not both")
|
||||
}
|
||||
var temp [][]string
|
||||
params := url.Values{}
|
||||
params.Set("timeWindow", timeWindow)
|
||||
|
||||
var intervalStr string
|
||||
switch timeWindow {
|
||||
case kline.OneMin:
|
||||
intervalStr = "1m"
|
||||
case kline.OneHour:
|
||||
intervalStr = "1h"
|
||||
case kline.OneDay:
|
||||
intervalStr = "1d"
|
||||
default:
|
||||
return kline.Item{}, errInvalidTimeInterval
|
||||
}
|
||||
|
||||
if timeWindow != 0 {
|
||||
params.Set("timeWindow", intervalStr)
|
||||
if from.After(to) && !to.IsZero() {
|
||||
return out, errors.New("start time cannot be after end time")
|
||||
}
|
||||
if !from.IsZero() {
|
||||
params.Set("from", from.UTC().Format(time.RFC3339))
|
||||
@@ -201,49 +187,7 @@ func (b *BTCMarkets) GetMarketCandles(marketID string, timeWindow time.Duration,
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
err := b.SendHTTPRequest(btcMarketsUnauthPath+marketID+btcMarketsCandles+params.Encode(),
|
||||
&temp)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: currency.NewPairFromString(marketID),
|
||||
Asset: asset.Spot,
|
||||
Interval: timeWindow,
|
||||
}
|
||||
|
||||
var tempData kline.Candle
|
||||
var tempTime time.Time
|
||||
for x := range temp {
|
||||
tempTime, err = time.Parse(time.RFC3339, temp[x][0])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Time = tempTime
|
||||
tempData.Open, err = strconv.ParseFloat(temp[x][1], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.High, err = strconv.ParseFloat(temp[x][2], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Low, err = strconv.ParseFloat(temp[x][3], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Close, err = strconv.ParseFloat(temp[x][4], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Volume, err = strconv.ParseFloat(temp[x][5], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempData)
|
||||
}
|
||||
return ret, nil
|
||||
return out, b.SendHTTPRequest(btcMarketsUnauthPath+marketID+btcMarketsCandles+params.Encode(), &out)
|
||||
}
|
||||
|
||||
// GetTickers gets multiple tickers
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestGetOrderbook(t *testing.T) {
|
||||
|
||||
func TestGetMarketCandles(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetMarketCandles(BTCAUD, kline.OneHour, time.Now().UTC().Add(-time.Hour*24), time.Now().UTC(), -1, -1, -1)
|
||||
_, err := b.GetMarketCandles(BTCAUD, "1h", time.Now().UTC().Add(-time.Hour*24), time.Now().UTC(), -1, -1, -1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -727,8 +727,49 @@ func TestBTCMarkets_GetHistoricCandles(t *testing.T) {
|
||||
}
|
||||
_, err = b.GetHistoricCandles(p, asset.Spot, time.Now().Add(-time.Hour*24).UTC(), time.Now().UTC(), kline.FifteenMin)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errInvalidTimeInterval) {
|
||||
if !errors.As(err, &kline.ErrorKline{}) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBTCMarkets_GetHistoricCandlesExtended(t *testing.T) {
|
||||
start := time.Now().AddDate(0, 0, -1001)
|
||||
end := time.Now()
|
||||
p := currency.NewPairFromString(BTCAUD)
|
||||
_, err := b.GetHistoricCandlesExtended(p, asset.Spot, start, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"1m",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"1d",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := b.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ type Trade struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Side string `json:"side"`
|
||||
}
|
||||
|
||||
// tempOrderbook stores orderbook data
|
||||
@@ -433,3 +434,6 @@ type WsError struct {
|
||||
Code int64 `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// CandleResponse holds OHLCV data for exchange
|
||||
type CandleResponse [][6]string
|
||||
|
||||
@@ -152,7 +152,7 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
case trade:
|
||||
case tradeEndPoint:
|
||||
var trade WsTrade
|
||||
err := json.Unmarshal(respRaw, &trade)
|
||||
if err != nil {
|
||||
@@ -283,7 +283,7 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
|
||||
func (b *BTCMarkets) generateDefaultSubscriptions() {
|
||||
var channels = []string{tick, trade, wsOB}
|
||||
var channels = []string{tick, tradeEndPoint, wsOB}
|
||||
enabledCurrencies := b.GetEnabledPairs(asset.Spot)
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
for i := range channels {
|
||||
@@ -299,7 +299,7 @@ func (b *BTCMarkets) generateDefaultSubscriptions() {
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (b *BTCMarkets) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
unauthChannels := []string{tick, trade, wsOB}
|
||||
unauthChannels := []string{tick, tradeEndPoint, wsOB}
|
||||
authChannels := []string{fundChange, heartbeat, orderChange}
|
||||
switch {
|
||||
case common.StringDataCompare(unauthChannels, channelToSubscribe.Channel):
|
||||
|
||||
@@ -3,6 +3,7 @@ package btcmarkets
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -107,9 +108,21 @@ func (b *BTCMarkets) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.AutoWithdrawFiat,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
},
|
||||
ResultLimit: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -743,7 +756,135 @@ func (b *BTCMarkets) ValidateCredentials() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *BTCMarkets) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return b.GetMarketCandles(pair.String(), interval, start, end, -1, -1, 0)
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (b *BTCMarkets) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
if in == kline.OneDay {
|
||||
return "1d"
|
||||
}
|
||||
return in.Short()
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *BTCMarkets) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !b.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
if kline.TotalCandlesPerInterval(start, end, interval) > b.Features.Enabled.Kline.ResultLimit {
|
||||
return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits)
|
||||
}
|
||||
|
||||
candles, err := b.GetMarketCandles(b.FormatExchangeCurrency(pair, a).String(),
|
||||
b.FormatExchangeKlineInterval(interval),
|
||||
start,
|
||||
end,
|
||||
-1,
|
||||
-1,
|
||||
-1)
|
||||
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: b.FormatExchangeCurrency(pair, a),
|
||||
Asset: asset.Spot,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range candles {
|
||||
var tempTime time.Time
|
||||
var tempData kline.Candle
|
||||
tempTime, err = time.Parse(time.RFC3339, candles[x][0])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Time = tempTime
|
||||
tempData.Open, err = strconv.ParseFloat(candles[x][1], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.High, err = strconv.ParseFloat(candles[x][2], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Low, err = strconv.ParseFloat(candles[x][3], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Close, err = strconv.ParseFloat(candles[x][4], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Volume, err = strconv.ParseFloat(candles[x][5], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempData)
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *BTCMarkets) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !b.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
candles, err := b.GetMarketCandles(p.String(),
|
||||
b.FormatExchangeKlineInterval(interval),
|
||||
dates[x].Start, dates[x].End, -1, -1, -1)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range candles {
|
||||
var tempTime time.Time
|
||||
var tempData kline.Candle
|
||||
tempTime, err = time.Parse(time.RFC3339, candles[i][0])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Time = tempTime
|
||||
tempData.Open, err = strconv.ParseFloat(candles[i][1], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.High, err = strconv.ParseFloat(candles[i][2], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Low, err = strconv.ParseFloat(candles[i][3], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Close, err = strconv.ParseFloat(candles[i][4], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempData.Volume, err = strconv.ParseFloat(candles[i][5], 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempData)
|
||||
}
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -653,6 +653,11 @@ func (b *BTSE) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *BTSE) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (b *BTSE) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *BTSE) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -79,11 +80,21 @@ func TestGetTrades(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetHistoricRatesGranularityCheck(t *testing.T) {
|
||||
end := time.Now().UTC()
|
||||
start := end.Add(-time.Hour * 24)
|
||||
end := time.Now()
|
||||
start := end.Add(-time.Hour * 2)
|
||||
p := currency.NewPair(currency.BTC, currency.USD)
|
||||
_, err := c.GetHistoricCandles(p, asset.Spot, start, end, kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := c.GetHistoricCandles(p, asset.Spot, start, end, time.Hour)
|
||||
func TestCoinbasePro_GetHistoricCandlesExtended(t *testing.T) {
|
||||
start := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
|
||||
p := currency.NewPair(currency.BTC, currency.USD)
|
||||
_, err := c.GetHistoricCandlesExtended(p, asset.Spot, start, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package coinbasepro
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -112,9 +113,24 @@ func (c *CoinbasePro) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission |
|
||||
exchange.AutoWithdrawFiatWithAPIPermission,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
},
|
||||
ResultLimit: 300,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -682,47 +698,38 @@ func (c *CoinbasePro) AuthenticateWebsocket() error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// checkInterval checks allowable interval
|
||||
func checkInterval(i time.Duration) (int64, error) {
|
||||
switch i.Seconds() {
|
||||
case 60:
|
||||
return 60, nil
|
||||
case 300:
|
||||
return 300, nil
|
||||
case 900:
|
||||
return 900, nil
|
||||
case 3600:
|
||||
return 3600, nil
|
||||
case 21600:
|
||||
return 21600, nil
|
||||
case 86400:
|
||||
return 86400, nil
|
||||
}
|
||||
return 0, fmt.Errorf("interval not allowed %v", i.Seconds())
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns a set of candle between two time periods for a
|
||||
// designated time period
|
||||
func (c *CoinbasePro) GetHistoricCandles(p currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
i, err := checkInterval(interval)
|
||||
func (c *CoinbasePro) GetHistoricCandles(p currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !c.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
if kline.TotalCandlesPerInterval(start, end, interval) > c.Features.Enabled.Kline.ResultLimit {
|
||||
return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits)
|
||||
}
|
||||
|
||||
candles := kline.Item{
|
||||
Exchange: c.Name,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
gran, err := strconv.ParseInt(c.FormatExchangeKlineInterval(interval), 10, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
history, err := c.GetHistoricRates(c.FormatExchangeCurrency(p, a).String(),
|
||||
start.Format(time.RFC3339),
|
||||
end.Format(time.RFC3339),
|
||||
i)
|
||||
gran)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
var candles kline.Item
|
||||
candles.Asset = a
|
||||
candles.Exchange = c.Name
|
||||
candles.Interval = interval
|
||||
candles.Pair = p
|
||||
|
||||
for x := range history {
|
||||
candles.Candles = append(candles.Candles, kline.Candle{
|
||||
Time: time.Unix(history[x].Time, 0),
|
||||
@@ -733,9 +740,56 @@ func (c *CoinbasePro) GetHistoricCandles(p currency.Pair, a asset.Item, start, e
|
||||
Volume: history[x].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
candles.SortCandlesByTimestamp(false)
|
||||
return candles, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (c *CoinbasePro) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !c.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: c.Name,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
gran, err := strconv.ParseInt(c.FormatExchangeKlineInterval(interval), 10, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
dates := kline.CalcDateRanges(start, end, interval, c.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
history, err := c.GetHistoricRates(c.FormatExchangeCurrency(p, a).String(),
|
||||
dates[x].Start.Format(time.RFC3339),
|
||||
dates[x].End.Format(time.RFC3339),
|
||||
gran)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range history {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: time.Unix(history[i].Time, 0),
|
||||
Low: history[i].Low,
|
||||
High: history[i].High,
|
||||
Open: history[i].Open,
|
||||
Close: history[i].Close,
|
||||
Volume: history[i].Volume,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (c *CoinbasePro) ValidateCredentials() error {
|
||||
|
||||
@@ -613,74 +613,48 @@ func (c *Coinbene) GetSwapOrderbook(symbol string, size int64) (Orderbook, error
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetKlines data returns the kline data for a specific symbol
|
||||
func (c *Coinbene) GetKlines(pair string, start, end time.Time, period string) (resp CandleResponse, err error) {
|
||||
v := url.Values{}
|
||||
v.Add("symbol", pair)
|
||||
if !start.IsZero() {
|
||||
v.Add("start", strconv.FormatInt(start.Unix(), 10))
|
||||
}
|
||||
if !end.IsZero() {
|
||||
v.Add("end", strconv.FormatInt(end.Unix(), 10))
|
||||
}
|
||||
v.Add("period", period)
|
||||
|
||||
path := common.EncodeURLValues(coinbeneAPIURL+coinbeneAPIVersion+coinbeneSpotKlines, v)
|
||||
if err = c.SendHTTPRequest(path, contractKline, &resp); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if resp.Code != 200 {
|
||||
return resp, errors.New(resp.Message)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetSwapKlines data returns the kline data for a specific symbol
|
||||
func (c *Coinbene) GetSwapKlines(symbol, startTime, endTime, resolution string) (SwapKlines, error) {
|
||||
var s SwapKlines
|
||||
func (c *Coinbene) GetSwapKlines(symbol string, start, end time.Time, resolution string) (resp CandleResponse, err error) {
|
||||
v := url.Values{}
|
||||
v.Set("symbol", symbol)
|
||||
v.Set("startTime", startTime)
|
||||
v.Set("endTime", endTime)
|
||||
if !start.IsZero() {
|
||||
v.Add("startTime", strconv.FormatInt(start.Unix(), 10))
|
||||
}
|
||||
if !end.IsZero() {
|
||||
v.Add("endTime", strconv.FormatInt(end.Unix(), 10))
|
||||
}
|
||||
v.Set("resolution", resolution)
|
||||
|
||||
type resp struct {
|
||||
Data [][]string `json:"data"`
|
||||
}
|
||||
var r resp
|
||||
path := common.EncodeURLValues(coinbeneSwapAPIURL+coinbeneAPIVersion+coinbeneGetKlines, v)
|
||||
if err := c.SendHTTPRequest(path, contractKline, &r); err != nil {
|
||||
return nil, err
|
||||
if err = c.SendHTTPRequest(path, contractKline, &resp); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for x := range r.Data {
|
||||
buyTurnover, err := strconv.ParseFloat(r.Data[x][8], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tm, err := strconv.ParseInt(r.Data[x][0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
open, err := strconv.ParseFloat(r.Data[x][1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
closePrice, err := strconv.ParseFloat(r.Data[x][2], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
high, err := strconv.ParseFloat(r.Data[x][3], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
low, err := strconv.ParseFloat(r.Data[x][4], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
volume, err := strconv.ParseFloat(r.Data[x][5], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
turnover, err := strconv.ParseFloat(r.Data[x][6], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buyVolume, err := strconv.ParseFloat(r.Data[x][7], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s = append(s, SwapKlineItem{
|
||||
Time: time.Unix(tm, 0),
|
||||
Open: open,
|
||||
Close: closePrice,
|
||||
High: high,
|
||||
Low: low,
|
||||
Volume: volume,
|
||||
Turnover: turnover,
|
||||
BuyVolume: buyVolume,
|
||||
BuyTurnover: buyTurnover,
|
||||
})
|
||||
}
|
||||
return s, nil
|
||||
return
|
||||
}
|
||||
|
||||
// GetSwapTrades returns a list of trades for a swap symbol
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
@@ -284,12 +286,19 @@ func TestGetSwapOrderbook(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetKlines(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := c.GetKlines(currency.NewPairFromString(spotTestPair).String(),
|
||||
time.Now().Add(-time.Hour*1), time.Now(), "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSwapKlines(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := c.GetSwapKlines(swapTestPair,
|
||||
"1573184608",
|
||||
"1573184808",
|
||||
"1")
|
||||
_, err := c.GetSwapKlines(currency.NewPairFromString(swapTestPair).String(),
|
||||
time.Now().Add(-time.Hour*1), time.Now(), "1")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -681,3 +690,78 @@ func TestWsUserOrder(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString(spotTestPair)
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := c.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
currencyPairSwap := currency.NewPairFromString(swapTestPair)
|
||||
_, err = c.GetHistoricCandles(currencyPairSwap, asset.PerpetualSwap, startTime, time.Now(), kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString(spotTestPair)
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := c.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"1",
|
||||
},
|
||||
{
|
||||
"OneHour",
|
||||
kline.OneHour,
|
||||
"60",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"D",
|
||||
},
|
||||
{
|
||||
"OneWeek",
|
||||
kline.OneWeek,
|
||||
"W",
|
||||
},
|
||||
{
|
||||
"OneMonth",
|
||||
kline.OneMonth,
|
||||
"M",
|
||||
},
|
||||
{
|
||||
"AllOther",
|
||||
kline.TwoWeek,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := c.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,3 +354,10 @@ type SwapFundingRate struct {
|
||||
FeeRate float64 `json:"feeRate,string"`
|
||||
Leverage int64 `json:"leverage"`
|
||||
}
|
||||
|
||||
// CandleResponse stores returned kline data
|
||||
type CandleResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data [][]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package coinbene
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -114,9 +116,30 @@ func (c *Coinbene) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.NoFiatWithdrawals |
|
||||
exchange.WithdrawCryptoViaWebsiteOnly,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.ThreeDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
c.Requester = request.New(c.Name,
|
||||
@@ -727,7 +750,111 @@ func (c *Coinbene) ValidateCredentials() error {
|
||||
return c.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (c *Coinbene) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
// FormatExchangeKlineInterval returns Interval to string
|
||||
func (c *Coinbene) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
switch in {
|
||||
case kline.OneMin, kline.ThreeMin, kline.FiveMin, kline.FifteenMin,
|
||||
kline.ThirtyMin, kline.OneHour, kline.TwoHour, kline.FourHour, kline.SixHour, kline.TwelveHour:
|
||||
return strconv.FormatFloat(in.Duration().Minutes(), 'f', 0, 64)
|
||||
case kline.OneDay:
|
||||
return "D"
|
||||
case kline.OneWeek:
|
||||
return "W"
|
||||
case kline.OneMonth:
|
||||
return "M"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (c *Coinbene) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !c.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
var candles CandleResponse
|
||||
var err error
|
||||
if a == asset.PerpetualSwap {
|
||||
candles, err = c.GetSwapKlines(c.FormatExchangeCurrency(pair, asset.PerpetualSwap).String(),
|
||||
start, end,
|
||||
c.FormatExchangeKlineInterval(interval))
|
||||
} else {
|
||||
candles, err = c.GetKlines(c.FormatExchangeCurrency(pair, asset.Spot).String(),
|
||||
start, end,
|
||||
c.FormatExchangeKlineInterval(interval))
|
||||
}
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: c.Name,
|
||||
Pair: pair,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range candles.Data {
|
||||
var tempCandle kline.Candle
|
||||
tempTime := candles.Data[x][0].(string)
|
||||
timestamp, err := time.Parse(time.RFC3339, tempTime)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
tempCandle.Time = timestamp
|
||||
open, ok := candles.Data[x][1].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("open conversion failed")
|
||||
}
|
||||
tempCandle.Open, err = strconv.ParseFloat(open, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
high, ok := candles.Data[x][2].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("high conversion failed")
|
||||
}
|
||||
tempCandle.High, err = strconv.ParseFloat(high, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
low, ok := candles.Data[x][3].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("low conversion failed")
|
||||
}
|
||||
tempCandle.Low, err = strconv.ParseFloat(low, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
closeTemp, ok := candles.Data[x][4].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("close conversion failed")
|
||||
}
|
||||
tempCandle.Close, err = strconv.ParseFloat(closeTemp, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
vol, ok := candles.Data[x][5].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("vol conversion failed")
|
||||
}
|
||||
tempCandle.Volume, err = strconv.ParseFloat(vol, 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret.Candles = append(ret.Candles, tempCandle)
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (c *Coinbene) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return c.GetHistoricCandles(pair, a, start, end, interval)
|
||||
}
|
||||
|
||||
@@ -903,6 +903,11 @@ func (c *COINUT) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (c *COINUT) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (c *COINUT) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (c *COINUT) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
@@ -801,3 +803,14 @@ func (e *Base) DisableRateLimiter() error {
|
||||
func (e *Base) EnableRateLimiter() error {
|
||||
return e.Requester.EnableRateLimiter()
|
||||
}
|
||||
|
||||
// KlineIntervalEnabled returns if requested interval is enabled on exchange
|
||||
func (e *Base) KlineIntervalEnabled(in kline.Interval) bool {
|
||||
return e.Features.Enabled.Kline.Intervals[in.Word()]
|
||||
}
|
||||
|
||||
// FormatExchangeKlineInterval returns Interval to string
|
||||
// Exchanges can override this if they require custom formatting
|
||||
func (e *Base) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
return strconv.FormatFloat(in.Duration().Seconds(), 'f', 0, 64)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -1479,3 +1480,35 @@ func TestGetFormattedPairAndAssetType(t *testing.T) {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"60",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"86400",
|
||||
},
|
||||
}
|
||||
|
||||
b := Base{}
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := b.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -148,6 +149,7 @@ type Features struct {
|
||||
// FeaturesEnabled stores the exchange enabled features
|
||||
type FeaturesEnabled struct {
|
||||
AutoPairUpdates bool
|
||||
Kline kline.ExchangeCapabilitiesEnabled
|
||||
}
|
||||
|
||||
// FeaturesSupported stores the exchanges supported features
|
||||
@@ -157,6 +159,7 @@ type FeaturesSupported struct {
|
||||
Websocket bool
|
||||
WebsocketCapabilities protocol.Features
|
||||
WithdrawPermissions uint32
|
||||
Kline kline.ExchangeCapabilitiesSupported
|
||||
}
|
||||
|
||||
// API stores the exchange API settings
|
||||
|
||||
@@ -584,6 +584,11 @@ func (e *EXMO) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (e *EXMO) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (e *EXMO) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (e *EXMO) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -870,27 +869,6 @@ func getOfflineTradeFee(feeBuilder *exchange.FeeBuilder) float64 {
|
||||
return 0.0007 * feeBuilder.PurchasePrice * feeBuilder.Amount
|
||||
}
|
||||
|
||||
func parseInterval(in time.Duration) (TimeInterval, error) {
|
||||
switch in {
|
||||
case kline.FifteenSecond:
|
||||
return TimeIntervalFifteenSeconds, nil
|
||||
case kline.OneMin:
|
||||
return TimeIntervalMinute, nil
|
||||
case kline.FiveMin:
|
||||
return TimeIntervalFiveMinutes, nil
|
||||
case kline.FifteenMin:
|
||||
return TimeIntervalFifteenMinutes, nil
|
||||
case kline.OneHour:
|
||||
return TimeIntervalHour, nil
|
||||
case kline.FourHour:
|
||||
return TimeIntervalFourHours, nil
|
||||
case kline.OneDay:
|
||||
return TimeIntervalDay, nil
|
||||
default:
|
||||
return TimeIntervalMinute, errInvalidInterval
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FTX) compatibleOrderVars(orderSide, orderStatus, orderType string, amount, filledAmount, avgFillPrice float64) (OrderVars, error) {
|
||||
var resp OrderVars
|
||||
switch orderSide {
|
||||
|
||||
@@ -864,6 +864,17 @@ func TestGetHistoricCandles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
t.Parallel()
|
||||
currencyPair := currency.NewPairFromString(spotPair)
|
||||
start := time.Date(2019, 11, 12, 0, 0, 0, 0, time.UTC)
|
||||
end := start.AddDate(0, 0, 5)
|
||||
_, err := f.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsingWSFillData(t *testing.T) {
|
||||
t.Parallel()
|
||||
data := []byte(`{
|
||||
|
||||
@@ -111,9 +111,25 @@ func (f *FTX) SetDefaults() {
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.NoAPIWithdrawalMethods,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.FifteenSecond.Word(): true,
|
||||
kline.OneMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
},
|
||||
ResultLimit: 5000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -845,29 +861,76 @@ func (f *FTX) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (f *FTX) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
intervalToString, err := parseInterval(interval)
|
||||
func (f *FTX) GetHistoricCandles(p currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !f.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ohlcData, err := f.GetHistoricalData(f.FormatExchangeCurrency(p, a).String(),
|
||||
f.FormatExchangeKlineInterval(interval),
|
||||
strconv.FormatInt(int64(f.Features.Enabled.Kline.ResultLimit), 10),
|
||||
start, end)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
var resp kline.Item
|
||||
ohlcData, err := f.GetHistoricalData(f.FormatExchangeCurrency(pair, a).String(),
|
||||
string(intervalToString), "", start, end)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: f.Name,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
resp.Exchange = f.Name
|
||||
resp.Asset = a
|
||||
resp.Pair = pair
|
||||
|
||||
for x := range ohlcData {
|
||||
var tempData kline.Candle
|
||||
tempData.Open = ohlcData[x].Open
|
||||
tempData.High = ohlcData[x].High
|
||||
tempData.Low = ohlcData[x].Low
|
||||
tempData.Close = ohlcData[x].Close
|
||||
tempData.Volume = ohlcData[x].Volume
|
||||
tempData.Time = ohlcData[x].StartTime
|
||||
resp.Candles = append(resp.Candles, tempData)
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: ohlcData[x].StartTime,
|
||||
Open: ohlcData[x].Open,
|
||||
High: ohlcData[x].High,
|
||||
Low: ohlcData[x].Low,
|
||||
Close: ohlcData[x].Close,
|
||||
Volume: ohlcData[x].Volume,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (f *FTX) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !f.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: f.Name,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, f.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
ohlcData, err := f.GetHistoricalData(f.FormatExchangeCurrency(p, a).String(),
|
||||
f.FormatExchangeKlineInterval(interval),
|
||||
strconv.FormatInt(int64(f.Features.Enabled.Kline.ResultLimit), 10),
|
||||
dates[x].Start, dates[x].End)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range ohlcData {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: ohlcData[i].StartTime,
|
||||
Open: ohlcData[i].Open,
|
||||
High: ohlcData[i].High,
|
||||
Low: ohlcData[i].Low,
|
||||
Close: ohlcData[i].Close,
|
||||
Volume: ohlcData[i].Volume,
|
||||
})
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
@@ -184,8 +185,8 @@ func (g *Gateio) GetOrderbook(symbol string) (Orderbook, error) {
|
||||
}
|
||||
|
||||
// GetSpotKline returns kline data for the most recent time period
|
||||
func (g *Gateio) GetSpotKline(arg KlinesRequestParams) ([]*KLineResponse, error) {
|
||||
urlPath := fmt.Sprintf("%s/%s/%s/%s?group_sec=%d&range_hour=%d",
|
||||
func (g *Gateio) GetSpotKline(arg KlinesRequestParams) (kline.Item, error) {
|
||||
urlPath := fmt.Sprintf("%s/%s/%s/%s?group_sec=%s&range_hour=%d",
|
||||
g.API.Endpoints.URLSecondary,
|
||||
gateioAPIVersion,
|
||||
gateioKline,
|
||||
@@ -196,58 +197,59 @@ func (g *Gateio) GetSpotKline(arg KlinesRequestParams) ([]*KLineResponse, error)
|
||||
var rawKlines map[string]interface{}
|
||||
err := g.SendHTTPRequest(urlPath, &rawKlines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
result := kline.Item{
|
||||
Exchange: g.Name,
|
||||
}
|
||||
|
||||
var result []*KLineResponse
|
||||
if rawKlines == nil || rawKlines["data"] == nil {
|
||||
return nil, fmt.Errorf("rawKlines is nil. Err: %s", err)
|
||||
return kline.Item{}, errors.New("rawKlines is nil")
|
||||
}
|
||||
|
||||
rawKlineDatasString, _ := json.Marshal(rawKlines["data"].([]interface{}))
|
||||
var rawKlineDatas [][]interface{}
|
||||
if err := json.Unmarshal(rawKlineDatasString, &rawKlineDatas); err != nil {
|
||||
return nil, fmt.Errorf("rawKlines unmarshal failed. Err: %s", err)
|
||||
return kline.Item{}, fmt.Errorf("rawKlines unmarshal failed. Err: %s", err)
|
||||
}
|
||||
|
||||
for _, k := range rawKlineDatas {
|
||||
otString, _ := strconv.ParseFloat(k[0].(string), 64)
|
||||
otString, err := strconv.ParseFloat(k[0].(string), 64)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ot, err := convert.TimeFromUnixTimestampFloat(otString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse Kline.OpenTime. Err: %s", err)
|
||||
return kline.Item{}, fmt.Errorf("cannot parse Kline.OpenTime. Err: %s", err)
|
||||
}
|
||||
_vol, err := convert.FloatFromString(k[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse Kline.Volume. Err: %s", err)
|
||||
}
|
||||
_id, err := convert.FloatFromString(k[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse Kline.Id. Err: %s", err)
|
||||
return kline.Item{}, fmt.Errorf("cannot parse Kline.Volume. Err: %s", err)
|
||||
}
|
||||
_close, err := convert.FloatFromString(k[2])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse Kline.Close. Err: %s", err)
|
||||
return kline.Item{}, fmt.Errorf("cannot parse Kline.Close. Err: %s", err)
|
||||
}
|
||||
_high, err := convert.FloatFromString(k[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse Kline.High. Err: %s", err)
|
||||
return kline.Item{}, fmt.Errorf("cannot parse Kline.High. Err: %s", err)
|
||||
}
|
||||
_low, err := convert.FloatFromString(k[4])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse Kline.Low. Err: %s", err)
|
||||
return kline.Item{}, fmt.Errorf("cannot parse Kline.Low. Err: %s", err)
|
||||
}
|
||||
_open, err := convert.FloatFromString(k[5])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse Kline.Open. Err: %s", err)
|
||||
return kline.Item{}, fmt.Errorf("cannot parse Kline.Open. Err: %s", err)
|
||||
}
|
||||
result = append(result, &KLineResponse{
|
||||
ID: _id,
|
||||
KlineTime: ot,
|
||||
Volume: _vol,
|
||||
Close: _close,
|
||||
High: _high,
|
||||
Low: _low,
|
||||
Open: _open,
|
||||
result.Candles = append(result.Candles, kline.Candle{
|
||||
Time: ot,
|
||||
Volume: _vol,
|
||||
Close: _close,
|
||||
High: _high,
|
||||
Low: _low,
|
||||
Open: _open,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
@@ -13,6 +14,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -148,11 +151,10 @@ func TestGetOrderbook(t *testing.T) {
|
||||
|
||||
func TestGetSpotKline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := g.GetSpotKline(KlinesRequestParams{
|
||||
Symbol: "btc_usdt",
|
||||
GroupSec: TimeIntervalFiveMinutes, // 5 minutes or less
|
||||
HourSize: 1, // 1 hour data
|
||||
GroupSec: "5", // 5 minutes or less
|
||||
HourSize: 1, // 1 hour data
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -578,7 +580,6 @@ func setupWSTestAuth(t *testing.T) {
|
||||
// TestWsUnsubscribe dials websocket, sends an unsubscribe request.
|
||||
func TestWsUnsubscribe(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
g.Verbose = true
|
||||
err := g.Unsubscribe(wshandler.WebsocketChannelSubscription{
|
||||
Channel: "ticker.subscribe",
|
||||
Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_"),
|
||||
@@ -762,3 +763,52 @@ func TestParseTime(t *testing.T) {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTC_USDT")
|
||||
startTime := time.Now().Add(-time.Hour * 6)
|
||||
_, err := g.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTC_USDT")
|
||||
startTime := time.Now().Add(-time.Hour * 6)
|
||||
_, err := g.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"60",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"86400",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := g.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ type BalancesResponse struct {
|
||||
type KlinesRequestParams struct {
|
||||
Symbol string // Required field; example LTCBTC,BTCUSDT
|
||||
HourSize int // How many hours of data
|
||||
GroupSec TimeInterval
|
||||
GroupSec string
|
||||
}
|
||||
|
||||
// KLineResponse holds the kline response data
|
||||
|
||||
@@ -108,12 +108,29 @@ func (g *Gateio) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g.Requester = request.New(g.Name,
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
|
||||
|
||||
@@ -710,7 +727,39 @@ func (g *Gateio) ValidateCredentials() error {
|
||||
return g.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (g *Gateio) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (g *Gateio) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
return strconv.FormatFloat(in.Duration().Seconds(), 'f', 0, 64)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (g *Gateio) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !g.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
hours := end.Sub(start).Hours()
|
||||
params := KlinesRequestParams{
|
||||
Symbol: g.FormatExchangeCurrency(pair, a).String(),
|
||||
GroupSec: g.FormatExchangeKlineInterval(interval),
|
||||
HourSize: int(hours),
|
||||
}
|
||||
|
||||
klineData, err := g.GetSpotKline(params)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
klineData.Interval = interval
|
||||
klineData.Pair = pair
|
||||
klineData.Asset = a
|
||||
|
||||
klineData.SortCandlesByTimestamp(false)
|
||||
return klineData, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (g *Gateio) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return g.GetHistoricCandles(pair, a, start, end, interval)
|
||||
}
|
||||
|
||||
@@ -575,6 +575,11 @@ func (g *Gemini) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (g *Gemini) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
func (g *Gemini) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (g *Gemini) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
@@ -201,11 +202,10 @@ func (h *HitBTC) GetOrderbook(currencyPair string, limit int) (Orderbook, error)
|
||||
|
||||
// GetCandles returns candles which is used for OHLC a specific currency.
|
||||
// Note: Result contain candles only with non zero volume.
|
||||
func (h *HitBTC) GetCandles(currencyPair, limit, period string) ([]ChartData, error) {
|
||||
func (h *HitBTC) GetCandles(currencyPair, limit, period string, start, end time.Time) ([]ChartData, error) {
|
||||
// limit Limit of candles, default 100.
|
||||
// period One of: M1 (one minute), M3, M5, M15, M30, H1, H4, D1, D7, 1M (one month). Default is M30 (30 minutes).
|
||||
vals := url.Values{}
|
||||
|
||||
if limit != "" {
|
||||
vals.Set("limit", limit)
|
||||
}
|
||||
@@ -214,6 +214,18 @@ func (h *HitBTC) GetCandles(currencyPair, limit, period string) ([]ChartData, er
|
||||
vals.Set("period", period)
|
||||
}
|
||||
|
||||
if !end.IsZero() && start.After(end) {
|
||||
return nil, errors.New("start time cannot be after end time")
|
||||
}
|
||||
|
||||
if !start.IsZero() {
|
||||
vals.Set("from", start.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
if !end.IsZero() {
|
||||
vals.Set("till", end.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
var resp []ChartData
|
||||
path := fmt.Sprintf("%s/%s/%s?%s", h.API.Endpoints.URL, apiV2Candles, currencyPair, vals.Encode())
|
||||
return resp, h.SendHTTPRequest(path, &resp)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -71,12 +72,42 @@ func TestGetTrades(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetChartCandles(t *testing.T) {
|
||||
_, err := h.GetCandles("BTCUSD", "", "")
|
||||
_, err := h.GetCandles("BTCUSD", "", "D1", time.Now().Add(-24*time.Hour), time.Now())
|
||||
if err != nil {
|
||||
t.Error("Test faild - HitBTC GetChartData() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSD")
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
end := time.Now()
|
||||
_, err := h.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = h.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSD")
|
||||
startTime := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
_, err := h.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = h.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrencies(t *testing.T) {
|
||||
_, err := h.GetCurrencies()
|
||||
if err != nil {
|
||||
@@ -918,3 +949,44 @@ func TestWsTrades(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"M1",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"D1",
|
||||
},
|
||||
{
|
||||
"SevenDay",
|
||||
kline.SevenDay,
|
||||
"D7",
|
||||
},
|
||||
{
|
||||
"AllOther",
|
||||
kline.OneMonth,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := h.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,9 +109,26 @@ func (h *HitBTC) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
Intervals: true,
|
||||
DateRanges: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.SevenDay.Word(): true,
|
||||
},
|
||||
ResultLimit: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -628,7 +645,93 @@ func (h *HitBTC) ValidateCredentials() error {
|
||||
return h.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (h *HitBTC) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (h *HitBTC) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
switch in {
|
||||
case kline.OneMin, kline.ThreeMin,
|
||||
kline.FiveMin, kline.FifteenMin, kline.ThirtyMin:
|
||||
return "M" + in.Short()[:len(in.Short())-1]
|
||||
case kline.OneDay:
|
||||
return "D1"
|
||||
case kline.SevenDay:
|
||||
return "D7"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (h *HitBTC) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !h.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
data, err := h.GetCandles(h.FormatExchangeCurrency(pair, a).String(),
|
||||
strconv.FormatInt(int64(h.Features.Enabled.Kline.ResultLimit), 10),
|
||||
h.FormatExchangeKlineInterval(interval),
|
||||
start, end)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: h.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
for x := range data {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: data[x].Timestamp,
|
||||
Open: data[x].Open,
|
||||
High: data[x].Max,
|
||||
Low: data[x].Min,
|
||||
Close: data[x].Close,
|
||||
Volume: data[x].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (h *HitBTC) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !h.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: h.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, h.Features.Enabled.Kline.ResultLimit)
|
||||
for y := range dates {
|
||||
data, err := h.GetCandles(h.FormatExchangeCurrency(pair, a).String(),
|
||||
strconv.FormatInt(int64(h.Features.Enabled.Kline.ResultLimit), 10),
|
||||
h.FormatExchangeKlineInterval(interval),
|
||||
dates[y].Start, dates[y].End)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range data {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: data[i].Timestamp,
|
||||
Open: data[i].Open,
|
||||
High: data[i].Max,
|
||||
Low: data[i].Min,
|
||||
Close: data[i].Close,
|
||||
Volume: data[i].Volume,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ type HUOBI struct {
|
||||
func (h *HUOBI) GetSpotKline(arg KlinesRequestParams) ([]KlineItem, error) {
|
||||
vals := url.Values{}
|
||||
vals.Set("symbol", arg.Symbol)
|
||||
vals.Set("period", string(arg.Period))
|
||||
vals.Set("period", arg.Period)
|
||||
|
||||
if arg.Size != 0 {
|
||||
vals.Set("size", strconv.Itoa(arg.Size))
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
@@ -12,6 +13,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -96,7 +99,7 @@ func TestGetSpotKline(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := h.GetSpotKline(KlinesRequestParams{
|
||||
Symbol: testSymbol,
|
||||
Period: TimeIntervalHour,
|
||||
Period: "1min",
|
||||
Size: 0,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -104,6 +107,39 @@ func TestGetSpotKline(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
startTime := time.Now().Add(-time.Hour * 1)
|
||||
_, err := h.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = h.GetHistoricCandles(currencyPair, asset.Spot, startTime.AddDate(0, 0, -7), time.Now(), kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = h.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
startTime := time.Now().Add(-time.Hour * 1)
|
||||
_, err := h.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = h.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarketDetailMerged(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := h.GetMarketDetailMerged(testSymbol)
|
||||
@@ -1032,3 +1068,59 @@ func TestStringToOrderType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"1min",
|
||||
},
|
||||
{
|
||||
"FourHour",
|
||||
kline.FourHour,
|
||||
"4hour",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"1day",
|
||||
},
|
||||
{
|
||||
"OneWeek",
|
||||
kline.OneWeek,
|
||||
"1week",
|
||||
},
|
||||
{
|
||||
"OneMonth",
|
||||
kline.OneMonth,
|
||||
"1mon",
|
||||
},
|
||||
{
|
||||
"OneYear",
|
||||
kline.OneYear,
|
||||
"1year",
|
||||
},
|
||||
{
|
||||
"AllOthers",
|
||||
kline.TwoWeek,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := h.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,27 +299,11 @@ var (
|
||||
|
||||
// KlinesRequestParams represents Klines request data.
|
||||
type KlinesRequestParams struct {
|
||||
Symbol string // Symbol to be used; example btcusdt, bccbtc......
|
||||
Period TimeInterval // Kline time interval; 1min, 5min, 15min......
|
||||
Size int // Size; [1-2000]
|
||||
Symbol string // Symbol to be used; example btcusdt, bccbtc......
|
||||
Period string // Kline time interval; 1min, 5min, 15min......
|
||||
Size int // Size; [1-2000]
|
||||
}
|
||||
|
||||
// TimeInterval base type
|
||||
type TimeInterval string
|
||||
|
||||
// TimeInterval vars
|
||||
var (
|
||||
TimeIntervalMinute = TimeInterval("1min")
|
||||
TimeIntervalFiveMinutes = TimeInterval("5min")
|
||||
TimeIntervalFifteenMinutes = TimeInterval("15min")
|
||||
TimeIntervalThirtyMinutes = TimeInterval("30min")
|
||||
TimeIntervalHour = TimeInterval("60min")
|
||||
TimeIntervalDay = TimeInterval("1day")
|
||||
TimeIntervalWeek = TimeInterval("1week")
|
||||
TimeIntervalMohth = TimeInterval("1mon")
|
||||
TimeIntervalYear = TimeInterval("1year")
|
||||
)
|
||||
|
||||
// WsRequest defines a request data structure
|
||||
type WsRequest struct {
|
||||
Topic string `json:"req,omitempty"`
|
||||
|
||||
@@ -107,9 +107,27 @@ func (h *HUOBI) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup |
|
||||
exchange.NoFiatWithdrawals,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
kline.OneMonth.Word(): true,
|
||||
kline.OneYear.Word(): true,
|
||||
},
|
||||
ResultLimit: 2000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -924,7 +942,70 @@ func (h *HUOBI) ValidateCredentials() error {
|
||||
return h.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (h *HUOBI) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (h *HUOBI) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
switch in {
|
||||
case kline.OneMin, kline.FiveMin, kline.FifteenMin, kline.ThirtyMin:
|
||||
return in.Short() + "in"
|
||||
case kline.FourHour:
|
||||
return "4hour"
|
||||
case kline.OneDay:
|
||||
return "1day"
|
||||
case kline.OneMonth:
|
||||
return "1mon"
|
||||
case kline.OneWeek:
|
||||
return "1week"
|
||||
case kline.OneYear:
|
||||
return "1year"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (h *HUOBI) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !h.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
klineParams := KlinesRequestParams{
|
||||
Period: h.FormatExchangeKlineInterval(interval),
|
||||
Symbol: h.FormatExchangeCurrency(pair, a).String(),
|
||||
}
|
||||
|
||||
candles, err := h.GetSpotKline(klineParams)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: h.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range candles {
|
||||
if time.Unix(candles[x].ID, 0).Before(start) ||
|
||||
time.Unix(candles[x].ID, 0).After(end) {
|
||||
continue
|
||||
}
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: time.Unix(candles[x].ID, 0),
|
||||
Open: candles[x].Open,
|
||||
High: candles[x].Close,
|
||||
Low: candles[x].Low,
|
||||
Close: candles[x].Close,
|
||||
Volume: candles[x].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (h *HUOBI) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return h.GetHistoricCandles(pair, a, start, end, interval)
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ type IBotExchange interface {
|
||||
GetDefaultConfig() (*config.ExchangeConfig, error)
|
||||
GetBase() *Base
|
||||
SupportsAsset(assetType asset.Item) bool
|
||||
GetHistoricCandles(p currency.Pair, a asset.Item, timeStart, timeEnd time.Time, interval time.Duration) (kline.Item, error)
|
||||
GetHistoricCandles(p currency.Pair, a asset.Item, timeStart, timeEnd time.Time, interval kline.Interval) (kline.Item, error)
|
||||
GetHistoricCandlesExtended(p currency.Pair, a asset.Item, timeStart, timeEnd time.Time, interval kline.Interval) (kline.Item, error)
|
||||
DisableRateLimiter() error
|
||||
EnableRateLimiter() error
|
||||
}
|
||||
|
||||
@@ -61,9 +61,9 @@ func (i *ItBit) GetOrderbook(currencyPair string) (OrderbookResponse, error) {
|
||||
//
|
||||
// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR"
|
||||
// timestamp - matchNumber, only executions after this will be returned
|
||||
func (i *ItBit) GetTradeHistory(currencyPair, timestamp string) (Trades, error) {
|
||||
func (i *ItBit) GetTradeHistory(currencyPair, tradeID string) (Trades, error) {
|
||||
response := Trades{}
|
||||
req := "trades?since=" + timestamp
|
||||
req := "trades?since=" + tradeID
|
||||
path := fmt.Sprintf("%s/%s/%s/%s", i.API.Endpoints.URL, itbitMarkets, currencyPair, req)
|
||||
|
||||
return response, i.SendHTTPRequest(path, &response)
|
||||
|
||||
@@ -571,6 +571,11 @@ func (i *ItBit) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (i *ItBit) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (i *ItBit) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (i *ItBit) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
@@ -12,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
// CreateKline creates candles out of trade history data for a set time interval
|
||||
func CreateKline(trades []order.TradeHistory, interval time.Duration, p currency.Pair, a asset.Item, exchange string) (Item, error) {
|
||||
if interval < time.Minute {
|
||||
func CreateKline(trades []order.TradeHistory, interval Interval, p currency.Pair, a asset.Item, exchange string) (Item, error) {
|
||||
if interval.Duration() < time.Minute {
|
||||
return Item{}, fmt.Errorf("invalid time interval: [%s]", interval)
|
||||
}
|
||||
|
||||
@@ -22,15 +23,15 @@ func CreateKline(trades []order.TradeHistory, interval time.Duration, p currency
|
||||
return Item{}, err
|
||||
}
|
||||
|
||||
timeIntervalStart := trades[0].Timestamp.Truncate(interval)
|
||||
timeIntervalStart := trades[0].Timestamp.Truncate(interval.Duration())
|
||||
timeIntervalEnd := trades[len(trades)-1].Timestamp
|
||||
|
||||
// Adds time interval buffer zones
|
||||
var timeIntervalCache [][]order.TradeHistory
|
||||
var candleStart []time.Time
|
||||
|
||||
for t := timeIntervalStart; t.Before(timeIntervalEnd); t = t.Add(interval) {
|
||||
timeBufferEnd := t.Add(interval)
|
||||
for t := timeIntervalStart; t.Before(timeIntervalEnd); t = t.Add(interval.Duration()) {
|
||||
timeBufferEnd := t.Add(interval.Duration())
|
||||
insertionCount := 0
|
||||
|
||||
var zonedTradeHistory []order.TradeHistory
|
||||
@@ -130,3 +131,165 @@ func validateData(trades []order.TradeHistory) error {
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns numeric string
|
||||
func (i Interval) String() string {
|
||||
return i.Duration().String()
|
||||
}
|
||||
|
||||
// Word returns text version of Interval
|
||||
func (i Interval) Word() string {
|
||||
return durationToWord(i)
|
||||
}
|
||||
|
||||
// Duration returns interval casted as time.Duration for compatibility
|
||||
func (i Interval) Duration() time.Duration {
|
||||
return time.Duration(i)
|
||||
}
|
||||
|
||||
// Short returns short string version of interval
|
||||
func (i Interval) Short() string {
|
||||
s := i.String()
|
||||
if strings.HasSuffix(s, "m0s") {
|
||||
s = s[:len(s)-2]
|
||||
}
|
||||
if strings.HasSuffix(s, "h0m") {
|
||||
s = s[:len(s)-2]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// durationToWord returns english version of interval
|
||||
func durationToWord(in Interval) string {
|
||||
switch in {
|
||||
case OneMin:
|
||||
return "onemin"
|
||||
case ThreeMin:
|
||||
return "threemin"
|
||||
case FiveMin:
|
||||
return "fivemin"
|
||||
case TenMin:
|
||||
return "tenmin"
|
||||
case FifteenMin:
|
||||
return "fifteenmin"
|
||||
case ThirtyMin:
|
||||
return "thirtymin"
|
||||
case OneHour:
|
||||
return "onehour"
|
||||
case TwoHour:
|
||||
return "twohour"
|
||||
case FourHour:
|
||||
return "fourhour"
|
||||
case SixHour:
|
||||
return "sixhour"
|
||||
case EightHour:
|
||||
return "eighthour"
|
||||
case TwelveHour:
|
||||
return "twelvehour"
|
||||
case OneDay:
|
||||
return "oneday"
|
||||
case ThreeDay:
|
||||
return "threeday"
|
||||
case FifteenDay:
|
||||
return "fifteenday"
|
||||
case OneWeek:
|
||||
return "oneweek"
|
||||
case TwoWeek:
|
||||
return "twoweek"
|
||||
case OneMonth:
|
||||
return "onemonth"
|
||||
case OneYear:
|
||||
return "oneyear"
|
||||
default:
|
||||
return "notfound"
|
||||
}
|
||||
}
|
||||
|
||||
// TotalCandlesPerInterval turns total candles per period for interval
|
||||
func TotalCandlesPerInterval(start, end time.Time, interval Interval) (out uint32) {
|
||||
switch interval {
|
||||
case OneMin:
|
||||
out = uint32(end.Sub(start).Minutes())
|
||||
case ThreeMin:
|
||||
out = uint32(end.Sub(start).Minutes() / 3)
|
||||
case FiveMin:
|
||||
out = uint32(end.Sub(start).Minutes() / 5)
|
||||
case TenMin:
|
||||
out = uint32(end.Sub(start).Minutes() / 10)
|
||||
case FifteenMin:
|
||||
out = uint32(end.Sub(start).Minutes() / 15)
|
||||
case ThirtyMin:
|
||||
out = uint32(end.Sub(start).Minutes() / 30)
|
||||
case OneHour:
|
||||
out = uint32(end.Sub(start).Hours())
|
||||
case TwoHour:
|
||||
out = uint32(end.Sub(start).Hours() / 2)
|
||||
case FourHour:
|
||||
out = uint32(end.Sub(start).Hours() / 4)
|
||||
case SixHour:
|
||||
out = uint32(end.Sub(start).Hours() / 6)
|
||||
case EightHour:
|
||||
out = uint32(end.Sub(start).Hours() / 8)
|
||||
case TwelveHour:
|
||||
out = uint32(end.Sub(start).Hours() / 12)
|
||||
case OneDay:
|
||||
out = uint32(end.Sub(start).Hours() / 24)
|
||||
case ThreeDay:
|
||||
out = uint32(end.Sub(start).Hours() / 72)
|
||||
case FifteenDay:
|
||||
out = uint32(end.Sub(start).Hours() / (24 * 15))
|
||||
case OneWeek:
|
||||
out = uint32(end.Sub(start).Hours()) / (24 * 7)
|
||||
case TwoWeek:
|
||||
out = uint32(end.Sub(start).Hours() / (24 * 14))
|
||||
case OneYear:
|
||||
out = uint32(end.Sub(start).Hours() / 8760)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// CalcDateRanges returns slice of start/end times based on start & end date
|
||||
func CalcDateRanges(start, end time.Time, interval Interval, limit uint32) (out []DateRange) {
|
||||
total := TotalCandlesPerInterval(start, end, interval)
|
||||
if total < limit {
|
||||
return []DateRange{{
|
||||
Start: start,
|
||||
End: end,
|
||||
},
|
||||
}
|
||||
}
|
||||
var allDateIntervals []time.Time
|
||||
var y uint32
|
||||
var lastNum int
|
||||
for d := start; !d.After(end); d = d.Add(interval.Duration()) {
|
||||
allDateIntervals = append(allDateIntervals, d)
|
||||
}
|
||||
for x := range allDateIntervals {
|
||||
if y == limit {
|
||||
out = append(out, DateRange{
|
||||
allDateIntervals[x-int(limit)],
|
||||
allDateIntervals[x],
|
||||
})
|
||||
y = 0
|
||||
lastNum = x
|
||||
}
|
||||
y++
|
||||
}
|
||||
if allDateIntervals != nil {
|
||||
out = append(out, DateRange{
|
||||
Start: allDateIntervals[lastNum+1],
|
||||
End: allDateIntervals[len(allDateIntervals)-1],
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// SortCandlesByTimestamp sorts candles by timestamp
|
||||
func (k *Item) SortCandlesByTimestamp(asc bool) {
|
||||
sort.Slice(k.Candles, func(i, j int) bool {
|
||||
if asc {
|
||||
return k.Candles[i].Time.After(k.Candles[j].Time)
|
||||
}
|
||||
return k.Candles[i].Time.Before(k.Candles[j].Time)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package kline
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -71,7 +72,7 @@ func TestValidateData(t *testing.T) {
|
||||
|
||||
func TestCreateKline(t *testing.T) {
|
||||
c, err := CreateKline(nil,
|
||||
time.Minute,
|
||||
OneMin,
|
||||
currency.NewPair(currency.BTC, currency.USD),
|
||||
asset.Spot,
|
||||
"Binance")
|
||||
@@ -113,3 +114,190 @@ func TestCreateKline(t *testing.T) {
|
||||
t.Fatal("no data returned, expecting a lot.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKlineWord(t *testing.T) {
|
||||
if OneDay.Word() != "oneday" {
|
||||
t.Fatalf("unexpected result: %v", OneDay.Word())
|
||||
}
|
||||
}
|
||||
|
||||
func TestKlineDuration(t *testing.T) {
|
||||
if OneDay.Duration() != time.Hour*24 {
|
||||
t.Fatalf("unexpected result: %v", OneDay.Duration())
|
||||
}
|
||||
}
|
||||
|
||||
func TestKlineShort(t *testing.T) {
|
||||
if OneDay.Short() != "24h" {
|
||||
t.Fatalf("unexpected result: %v", OneDay.Short())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDurationToWord(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval Interval
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
OneMin,
|
||||
},
|
||||
{
|
||||
"ThreeMin",
|
||||
ThreeMin,
|
||||
},
|
||||
{
|
||||
"FiveMin",
|
||||
FiveMin,
|
||||
},
|
||||
{
|
||||
"TenMin",
|
||||
TenMin,
|
||||
},
|
||||
{
|
||||
"FifteenMin",
|
||||
FifteenMin,
|
||||
},
|
||||
{
|
||||
"ThirtyMin",
|
||||
ThirtyMin,
|
||||
},
|
||||
{
|
||||
"OneHour",
|
||||
OneHour,
|
||||
},
|
||||
{
|
||||
"TwoHour",
|
||||
TwoHour,
|
||||
},
|
||||
{
|
||||
"FourHour",
|
||||
FourHour,
|
||||
},
|
||||
{
|
||||
"SixHour",
|
||||
SixHour,
|
||||
},
|
||||
{
|
||||
"EightHour",
|
||||
OneHour * 8,
|
||||
},
|
||||
{
|
||||
"TwelveHour",
|
||||
TwelveHour,
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
OneDay,
|
||||
},
|
||||
{
|
||||
"ThreeDay",
|
||||
ThreeDay,
|
||||
},
|
||||
{
|
||||
"FifteenDay",
|
||||
FifteenDay,
|
||||
},
|
||||
{
|
||||
"OneWeek",
|
||||
OneWeek,
|
||||
},
|
||||
{
|
||||
"TwoWeek",
|
||||
TwoWeek,
|
||||
},
|
||||
{
|
||||
"OneMonth",
|
||||
OneMonth,
|
||||
},
|
||||
{
|
||||
"notfound",
|
||||
Interval(time.Hour * 1337),
|
||||
},
|
||||
}
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
v := durationToWord(test.interval)
|
||||
if !strings.EqualFold(v, test.name) {
|
||||
t.Fatalf("%v: received %v expected %v", test.name, v, test.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKlineErrors(t *testing.T) {
|
||||
v := ErrorKline{
|
||||
Interval: OneYear,
|
||||
}
|
||||
|
||||
if v.Error() != "oneyear interval unsupported by exchange" {
|
||||
t.Fatal("unexpected error returned")
|
||||
}
|
||||
|
||||
if v.Unwrap().Error() != "8760h0m0s interval unsupported by exchange" {
|
||||
t.Fatal("unexpected error returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTotalCandlesPerInterval(t *testing.T) {
|
||||
end := time.Now()
|
||||
start := end.AddDate(-1, 0, 0)
|
||||
|
||||
v := TotalCandlesPerInterval(start, end, OneYear)
|
||||
if v != 1 {
|
||||
t.Fatalf("unexpected result expected 1 received %v", v)
|
||||
}
|
||||
v = TotalCandlesPerInterval(start, end, FifteenDay)
|
||||
if v != 24 {
|
||||
t.Fatalf("unexpected result expected 24 received %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcDateRanges(t *testing.T) {
|
||||
start := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
|
||||
v := CalcDateRanges(start, end, OneMin, 300)
|
||||
|
||||
if v[0].Start.Unix() != time.Unix(1546300800, 0).Unix() {
|
||||
t.Fatalf("unexpected result received %v", v[0].Start.Unix())
|
||||
}
|
||||
|
||||
v = CalcDateRanges(time.Now(), time.Now().AddDate(0, 0, 1), OneDay, 100)
|
||||
if len(v) != 1 {
|
||||
t.Fatal("expected CalcDateRanges() with a Candle count lower than limit to return 1 result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestItem_SortCandlesByTimestamp(t *testing.T) {
|
||||
var tempKline = Item{
|
||||
Exchange: "testExchange",
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
Asset: asset.Spot,
|
||||
Interval: OneDay,
|
||||
}
|
||||
|
||||
for x := 0; x < 100; x++ {
|
||||
y := rand.Float64()
|
||||
tempKline.Candles = append(tempKline.Candles,
|
||||
Candle{
|
||||
Time: time.Now().AddDate(0, 0, -x),
|
||||
Open: y,
|
||||
High: y + float64(x),
|
||||
Low: y - float64(x),
|
||||
Close: y,
|
||||
Volume: y,
|
||||
})
|
||||
}
|
||||
|
||||
tempKline.SortCandlesByTimestamp(false)
|
||||
if tempKline.Candles[0].Time.After(tempKline.Candles[1].Time) {
|
||||
t.Fatal("expected kline.Candles to be in descending order")
|
||||
}
|
||||
|
||||
tempKline.SortCandlesByTimestamp(true)
|
||||
if tempKline.Candles[0].Time.Before(tempKline.Candles[1].Time) {
|
||||
t.Fatal("expected kline.Candles to be in ascending order")
|
||||
}
|
||||
}
|
||||
|
||||
96
exchanges/kline/kline_types.go
Normal file
96
exchanges/kline/kline_types.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package kline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
// Consts here define basic time intervals
|
||||
const (
|
||||
FifteenSecond = Interval(15 * time.Second)
|
||||
OneMin = Interval(time.Minute)
|
||||
ThreeMin = 3 * OneMin
|
||||
FiveMin = 5 * OneMin
|
||||
TenMin = 10 * OneMin
|
||||
FifteenMin = 15 * OneMin
|
||||
ThirtyMin = 30 * OneMin
|
||||
OneHour = Interval(time.Hour)
|
||||
TwoHour = 2 * OneHour
|
||||
FourHour = 4 * OneHour
|
||||
SixHour = 6 * OneHour
|
||||
EightHour = 8 * OneHour
|
||||
TwelveHour = 12 * OneHour
|
||||
OneDay = 24 * OneHour
|
||||
ThreeDay = 3 * OneDay
|
||||
SevenDay = 7 * OneDay
|
||||
FifteenDay = 15 * OneDay
|
||||
OneWeek = 7 * OneDay
|
||||
TwoWeek = 2 * OneWeek
|
||||
OneMonth = 31 * OneDay
|
||||
OneYear = 365 * OneDay
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrUnsupportedInterval locale for an unsupported interval
|
||||
ErrUnsupportedInterval = "%s interval unsupported by exchange"
|
||||
// ErrRequestExceedsExchangeLimits locale for exceeding rate limits message
|
||||
ErrRequestExceedsExchangeLimits = "requested data would exceed exchange limits please lower range or use GetHistoricCandlesEx"
|
||||
)
|
||||
|
||||
// Item holds all the relevant information for internal kline elements
|
||||
type Item struct {
|
||||
Exchange string
|
||||
Pair currency.Pair
|
||||
Asset asset.Item
|
||||
Interval Interval
|
||||
Candles []Candle
|
||||
}
|
||||
|
||||
// Candle holds historic rate information.
|
||||
type Candle struct {
|
||||
Time time.Time
|
||||
Open float64
|
||||
High float64
|
||||
Low float64
|
||||
Close float64
|
||||
Volume float64
|
||||
}
|
||||
|
||||
// ExchangeCapabilitiesSupported all kline related exchange supported options
|
||||
type ExchangeCapabilitiesSupported struct {
|
||||
Intervals bool
|
||||
DateRanges bool
|
||||
}
|
||||
|
||||
// ExchangeCapabilitiesEnabled all kline related exchange enabled options
|
||||
type ExchangeCapabilitiesEnabled struct {
|
||||
Intervals map[string]bool `json:"intervals,omitempty"`
|
||||
ResultLimit uint32
|
||||
}
|
||||
|
||||
// Interval type for kline Interval usage
|
||||
type Interval time.Duration
|
||||
|
||||
// ErrorKline struct to hold kline interval errors
|
||||
type ErrorKline struct {
|
||||
Interval Interval
|
||||
}
|
||||
|
||||
// Error returns short interval unsupported message
|
||||
func (k ErrorKline) Error() string {
|
||||
return fmt.Sprintf(ErrUnsupportedInterval, k.Interval.Word())
|
||||
}
|
||||
|
||||
// Unwrap returns interval unsupported message
|
||||
func (k *ErrorKline) Unwrap() error {
|
||||
return fmt.Errorf(ErrUnsupportedInterval, k.Interval)
|
||||
}
|
||||
|
||||
// DateRange holds a start and end date for kline usage
|
||||
type DateRange struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package kline
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
// Consts here define basic time intervals
|
||||
const (
|
||||
FifteenSecond = 15 * time.Second
|
||||
OneMin = time.Minute
|
||||
ThreeMin = 3 * time.Minute
|
||||
FiveMin = 5 * time.Minute
|
||||
FifteenMin = 15 * time.Minute
|
||||
ThirtyMin = 30 * time.Minute
|
||||
OneHour = 1 * time.Hour
|
||||
TwoHour = 2 * time.Hour
|
||||
FourHour = 4 * time.Hour
|
||||
SixHour = 6 * time.Hour
|
||||
TwelveHour = 12 * time.Hour
|
||||
OneDay = 24 * time.Hour
|
||||
ThreeDay = 72 * time.Hour
|
||||
OneWeek = 168 * time.Hour
|
||||
)
|
||||
|
||||
// Item holds all the relevant information for internal kline elements
|
||||
type Item struct {
|
||||
Exchange string
|
||||
Pair currency.Pair
|
||||
Asset asset.Item
|
||||
Interval time.Duration
|
||||
Candles []Candle
|
||||
}
|
||||
|
||||
// Candle holds historic rate information.
|
||||
type Candle struct {
|
||||
Time time.Time
|
||||
Open float64
|
||||
High float64
|
||||
Low float64
|
||||
Close float64
|
||||
Volume float64
|
||||
}
|
||||
@@ -216,10 +216,10 @@ func (k *Kraken) GetTickers(pairList string) (map[string]Ticker, error) {
|
||||
}
|
||||
|
||||
// GetOHLC returns an array of open high low close values of a currency pair
|
||||
func (k *Kraken) GetOHLC(symbol string) ([]OpenHighLowClose, error) {
|
||||
func (k *Kraken) GetOHLC(symbol, interval string) ([]OpenHighLowClose, error) {
|
||||
values := url.Values{}
|
||||
values.Set("pair", symbol)
|
||||
|
||||
values.Set("interval", interval)
|
||||
type Response struct {
|
||||
Error []interface{} `json:"error"`
|
||||
Data map[string]interface{} `json:"result"`
|
||||
@@ -239,6 +239,11 @@ func (k *Kraken) GetOHLC(symbol string) ([]OpenHighLowClose, error) {
|
||||
return OHLC, fmt.Errorf("getOHLC error: %s", result.Error)
|
||||
}
|
||||
|
||||
_, ok := result.Data[symbol].([]interface{})
|
||||
if !ok {
|
||||
return nil, errors.New("invalid data returned")
|
||||
}
|
||||
|
||||
for _, y := range result.Data[symbol].([]interface{}) {
|
||||
o := OpenHighLowClose{}
|
||||
for i, x := range y.([]interface{}) {
|
||||
@@ -275,7 +280,6 @@ func (k *Kraken) GetDepth(symbol string) (Orderbook, error) {
|
||||
var orderBook Orderbook
|
||||
|
||||
path := fmt.Sprintf("%s/%s/public/%s?%s", k.API.Endpoints.URL, krakenAPIVersion, krakenDepth, values.Encode())
|
||||
|
||||
err := k.SendHTTPRequest(path, &result)
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
@@ -13,6 +14,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -160,7 +163,7 @@ func TestGetTickers(t *testing.T) {
|
||||
// TestGetOHLC API endpoint test
|
||||
func TestGetOHLC(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := k.GetOHLC("BCHEUR")
|
||||
_, err := k.GetOHLC("XXBTZUSD", "1440")
|
||||
if err != nil {
|
||||
t.Error("GetOHLC() error", err)
|
||||
}
|
||||
@@ -1421,3 +1424,60 @@ func TestParseTime(t *testing.T) {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("XBTUSD")
|
||||
_, err := k.GetHistoricCandles(currencyPair, asset.Spot, time.Now().AddDate(0, 0, -1), time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = k.GetHistoricCandles(currencyPair, asset.Spot, time.Now(), time.Now(), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("XBTUSD")
|
||||
_, err := k.GetHistoricCandlesExtended(currencyPair, asset.Spot, time.Now().AddDate(0, -6, 0), time.Now(), kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = k.GetHistoricCandlesExtended(currencyPair, asset.Spot, time.Now(), time.Now(), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"1",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"1440",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := k.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +119,27 @@ func (k *Kraken) SetDefaults() {
|
||||
exchange.WithdrawCryptoWith2FA |
|
||||
exchange.AutoWithdrawFiatWithSetup |
|
||||
exchange.WithdrawFiatWith2FA,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.FifteenDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -770,7 +788,88 @@ func (k *Kraken) ValidateCredentials() error {
|
||||
return k.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (k *Kraken) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (k *Kraken) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
return strconv.FormatFloat(in.Duration().Minutes(), 'f', -1, 64)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (k *Kraken) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !k.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: k.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
candles, err := k.GetOHLC(assetTranslator.LookupCurrency(k.FormatExchangeCurrency(pair, a).Upper().String()), k.FormatExchangeKlineInterval(interval))
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
for x := range candles {
|
||||
timeValue, err := convert.TimeFromUnixTimestampFloat(candles[x].Time * 1000)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
if timeValue.Before(start) || timeValue.After(end) {
|
||||
continue
|
||||
}
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: timeValue,
|
||||
Open: candles[x].Open,
|
||||
High: candles[x].Close,
|
||||
Low: candles[x].Low,
|
||||
Close: candles[x].Close,
|
||||
Volume: candles[x].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (k *Kraken) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !k.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: k.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
candles, err := k.GetOHLC(assetTranslator.LookupCurrency(k.FormatExchangeCurrency(pair, a).Upper().String()), k.FormatExchangeKlineInterval(interval))
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
for i := range candles {
|
||||
timeValue, err := convert.TimeFromUnixTimestampFloat(candles[i].Time * 1000)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
if timeValue.Before(start) || timeValue.After(end) {
|
||||
continue
|
||||
}
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: timeValue,
|
||||
Open: candles[i].Open,
|
||||
High: candles[i].Close,
|
||||
Low: candles[i].Low,
|
||||
Close: candles[i].Close,
|
||||
Volume: candles[i].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -140,8 +141,13 @@ func (l *LakeBTC) GetOrderBook(currency string) (Orderbook, error) {
|
||||
}
|
||||
|
||||
// GetTradeHistory returns the trade history for a given currency pair
|
||||
func (l *LakeBTC) GetTradeHistory(currency string) ([]TradeHistory, error) {
|
||||
path := fmt.Sprintf("%s/%s?symbol=%s", l.API.Endpoints.URL, lakeBTCTrades, strings.ToLower(currency))
|
||||
func (l *LakeBTC) GetTradeHistory(currency string, start int64) ([]TradeHistory, error) {
|
||||
v := url.Values{}
|
||||
v.Set("symbol", strings.ToLower(currency))
|
||||
if start != 0 {
|
||||
v.Set("at", strconv.FormatInt(start, 10))
|
||||
}
|
||||
path := fmt.Sprintf("%s/%s?%s", l.API.Endpoints.URL, lakeBTCTrades, v.Encode())
|
||||
var resp []TradeHistory
|
||||
return resp, l.SendHTTPRequest(path, &resp)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
@@ -76,7 +77,7 @@ func TestGetOrderBook(t *testing.T) {
|
||||
|
||||
func TestGetTradeHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := l.GetTradeHistory("BTCUSD")
|
||||
_, err := l.GetTradeHistory("BTCUSD", time.Now().Unix())
|
||||
if err != nil {
|
||||
t.Error("GetTradeHistory() error", err)
|
||||
}
|
||||
|
||||
@@ -560,6 +560,11 @@ func (l *LakeBTC) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (l *LakeBTC) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (l *LakeBTC) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (l *LakeBTC) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
)
|
||||
|
||||
@@ -406,3 +407,75 @@ func TestGetOrderHistory(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
t.Parallel()
|
||||
pair := currency.NewPairFromString(testCurrencyPair)
|
||||
_, err := l.GetHistoricCandles(pair, asset.Spot, time.Now().Add(-24*time.Hour), time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = l.GetHistoricCandles(pair, asset.Spot, time.Now().Add(-24*time.Hour), time.Now(), kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
startTime := time.Now().Add(-time.Hour)
|
||||
end := time.Now()
|
||||
pair := currency.NewPairFromString(testCurrencyPair)
|
||||
_, err := l.GetHistoricCandlesExtended(pair, asset.Spot, startTime, end, kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"minute1",
|
||||
},
|
||||
{
|
||||
"OneHour",
|
||||
kline.OneHour,
|
||||
"hour1",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"day1",
|
||||
},
|
||||
{
|
||||
"OneWeek",
|
||||
kline.OneWeek,
|
||||
"week1",
|
||||
},
|
||||
{
|
||||
"AllOther",
|
||||
kline.FifteenDay,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := l.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,21 @@ func (l *Lbank) SetDefaults() {
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.EightHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
},
|
||||
ResultLimit: 2880,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -718,7 +733,99 @@ func (l *Lbank) ValidateCredentials() error {
|
||||
return l.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (l *Lbank) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (l *Lbank) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
switch in {
|
||||
case kline.OneMin, kline.ThreeMin,
|
||||
kline.FiveMin, kline.FifteenMin, kline.ThirtyMin:
|
||||
return "minute" + in.Short()[:len(in.Short())-1]
|
||||
case kline.OneHour, kline.FourHour,
|
||||
kline.EightHour, kline.TwelveHour:
|
||||
return "hour" + in.Short()[:len(in.Short())-1]
|
||||
case kline.OneDay:
|
||||
return "day1"
|
||||
case kline.OneWeek:
|
||||
return "week1"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (l *Lbank) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !l.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
data, err := l.GetKlines(l.FormatExchangeCurrency(pair, a).String(),
|
||||
strconv.FormatInt(int64(l.Features.Enabled.Kline.ResultLimit), 10),
|
||||
l.FormatExchangeKlineInterval(interval),
|
||||
strconv.FormatInt(start.Unix(), 10))
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: l.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range data {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: time.Unix(data[x].TimeStamp, 0),
|
||||
Open: data[x].OpenPrice,
|
||||
High: data[x].HigestPrice,
|
||||
Low: data[x].LowestPrice,
|
||||
Close: data[x].ClosePrice,
|
||||
Volume: data[x].TradingVolume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (l *Lbank) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !l.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
ret := kline.Item{
|
||||
Exchange: l.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, l.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
data, err := l.GetKlines(l.FormatExchangeCurrency(pair, a).String(),
|
||||
strconv.FormatInt(int64(l.Features.Enabled.Kline.ResultLimit), 10),
|
||||
l.FormatExchangeKlineInterval(interval),
|
||||
strconv.FormatInt(dates[x].Start.UTC().Unix(), 10))
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
for i := range data {
|
||||
if time.Unix(data[i].TimeStamp, 0).UTC().Before(dates[x].Start.UTC()) || time.Unix(data[i].TimeStamp, 0).UTC().After(dates[x].End.UTC()) {
|
||||
continue
|
||||
}
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: time.Unix(data[i].TimeStamp, 0).UTC(),
|
||||
Open: data[i].OpenPrice,
|
||||
High: data[i].HigestPrice,
|
||||
Low: data[i].LowestPrice,
|
||||
Close: data[i].ClosePrice,
|
||||
Volume: data[i].TradingVolume,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -599,6 +599,11 @@ func (l *LocalBitcoins) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (l *LocalBitcoins) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
func (l *LocalBitcoins) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (l *LocalBitcoins) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
@@ -483,11 +484,12 @@ func TestGetSpotFilledOrdersInformation(t *testing.T) {
|
||||
|
||||
// TestGetSpotMarketData API endpoint test
|
||||
func TestGetSpotMarketData(t *testing.T) {
|
||||
request := okgroup.GetSpotMarketDataRequest{
|
||||
request := &okgroup.GetMarketDataRequest{
|
||||
Asset: asset.Spot,
|
||||
InstrumentID: spotCurrency,
|
||||
Granularity: 604800,
|
||||
Granularity: "604800",
|
||||
}
|
||||
_, err := o.GetSpotMarketData(request)
|
||||
_, err := o.GetMarketData(request)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -1095,3 +1097,26 @@ func TestGetOrderbook(t *testing.T) {
|
||||
t.Error("error cannot be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
startTime := time.Unix(1588636800, 0)
|
||||
_, err := o.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
startTime := time.Unix(1588636800, 0)
|
||||
_, err := o.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = o.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package okcoin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
@@ -111,9 +114,31 @@ func (o *OKCoin) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.ThreeDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
},
|
||||
ResultLimit: 1440,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -263,6 +288,141 @@ func (o *OKCoin) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (o *OKCoin) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
func (o *OKCoin) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !o.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
req := &okgroup.GetMarketDataRequest{
|
||||
Asset: a,
|
||||
Start: start.UTC().Format(time.RFC3339),
|
||||
End: end.UTC().Format(time.RFC3339),
|
||||
Granularity: o.FormatExchangeKlineInterval(interval),
|
||||
InstrumentID: o.FormatExchangeCurrency(pair, a).String(),
|
||||
}
|
||||
|
||||
candles, err := o.GetMarketData(req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: o.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range candles {
|
||||
t := candles[x].([]interface{})
|
||||
tempCandle := kline.Candle{}
|
||||
v, ok := t[0].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("unexpected value received")
|
||||
}
|
||||
tempCandle.Time, err = time.Parse(time.RFC3339, v)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempCandle.Open, err = convert.FloatFromString(t[1])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempCandle.High, err = convert.FloatFromString(t[2])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Low, err = convert.FloatFromString(t[3])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Close, err = convert.FloatFromString(t[4])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Volume, err = convert.FloatFromString(t[5])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempCandle)
|
||||
}
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (o *OKCoin) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !o.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: o.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, o.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
req := &okgroup.GetMarketDataRequest{
|
||||
Asset: a,
|
||||
Start: dates[x].Start.UTC().Format(time.RFC3339),
|
||||
End: dates[x].End.UTC().Format(time.RFC3339),
|
||||
Granularity: o.FormatExchangeKlineInterval(interval),
|
||||
InstrumentID: o.FormatExchangeCurrency(pair, a).String(),
|
||||
}
|
||||
|
||||
candles, err := o.GetMarketData(req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range candles {
|
||||
t := candles[i].([]interface{})
|
||||
tempCandle := kline.Candle{}
|
||||
v, ok := t[0].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("unexpected value received")
|
||||
}
|
||||
tempCandle.Time, err = time.Parse(time.RFC3339, v)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempCandle.Open, err = convert.FloatFromString(t[1])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempCandle.High, err = convert.FloatFromString(t[2])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Low, err = convert.FloatFromString(t[3])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Close, err = convert.FloatFromString(t[4])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Volume, err = convert.FloatFromString(t[5])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempCandle)
|
||||
}
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -159,12 +159,6 @@ func (o *OKEX) GetFuturesFilledOrder(request okgroup.GetFuturesFilledOrderReques
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetFuturesMarketData Get the charts of the trading pairs. Charts are returned in grouped buckets based on requested granularity.
|
||||
func (o *OKEX) GetFuturesMarketData(request okgroup.GetFuturesMarketDateRequest) (resp okgroup.GetFuturesMarketDataResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupGetSpotMarketData, okgroup.FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetFuturesHoldAmount Get the number of futures with hold.
|
||||
func (o *OKEX) GetFuturesHoldAmount(instrumentID string) (resp okgroup.GetFuturesHoldAmountResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupAccounts, instrumentID, okGroupFutureHolds)
|
||||
@@ -327,12 +321,6 @@ func (o *OKEX) GetSwapFilledOrdersData(request *okgroup.GetSwapFilledOrdersDataR
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSwapMarketData Get the charts of the trading pairs.
|
||||
func (o *OKEX) GetSwapMarketData(request okgroup.GetSwapMarketDataRequest) (resp []okgroup.GetSwapMarketDataResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupGetSpotMarketData, okgroup.FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSwapIndices Get Indices of tokens.
|
||||
func (o *OKEX) GetSwapIndices(instrumentID string) (resp okgroup.GetSwapIndecesResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okGroupIndices)
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
@@ -519,16 +520,56 @@ func TestGetSpotFilledOrdersInformation(t *testing.T) {
|
||||
// TestGetSpotMarketData API endpoint test
|
||||
func TestGetSpotMarketData(t *testing.T) {
|
||||
t.Parallel()
|
||||
request := okgroup.GetSpotMarketDataRequest{
|
||||
request := &okgroup.GetMarketDataRequest{
|
||||
Asset: asset.Spot,
|
||||
InstrumentID: spotCurrency,
|
||||
Granularity: 604800,
|
||||
Granularity: "604800",
|
||||
}
|
||||
_, err := o.GetSpotMarketData(request)
|
||||
_, err := o.GetMarketData(request)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
startTime := time.Unix(1588636800, 0)
|
||||
_, err := o.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = o.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
|
||||
_, err = o.GetHistoricCandles(currencyPair, asset.Margin, startTime, time.Now(), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
|
||||
swapPair := currency.NewPairFromString("BTC-USD_SWAP")
|
||||
_, err = o.GetHistoricCandles(swapPair, asset.PerpetualSwap, startTime, time.Now(), kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
startTime := time.Unix(1588636800, 0)
|
||||
_, err := o.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = o.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetMarginTradingAccounts API endpoint test
|
||||
func TestGetMarginTradingAccounts(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -1240,19 +1281,6 @@ func TestGetSwapFilledOrdersData(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSwapMarketData API endpoint test
|
||||
func TestGetSwapMarketData(t *testing.T) {
|
||||
t.Parallel()
|
||||
request := okgroup.GetSwapMarketDataRequest{
|
||||
InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD),
|
||||
Granularity: 604800,
|
||||
}
|
||||
_, err := o.GetSwapMarketData(request)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSwapIndeces API endpoint test
|
||||
func TestGetSwapIndeces(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
@@ -145,9 +147,31 @@ func (o *OKEX) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.ThreeDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
},
|
||||
ResultLimit: 1440,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -419,6 +443,142 @@ func (o *OKEX) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData *t
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (o *OKEX) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
func (o *OKEX) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !o.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
req := &okgroup.GetMarketDataRequest{
|
||||
Asset: a,
|
||||
Start: start.UTC().Format(time.RFC3339),
|
||||
End: end.UTC().Format(time.RFC3339),
|
||||
Granularity: o.FormatExchangeKlineInterval(interval),
|
||||
InstrumentID: o.FormatExchangeCurrency(pair, a).String(),
|
||||
}
|
||||
|
||||
candles, err := o.GetMarketData(req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: o.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range candles {
|
||||
t := candles[x].([]interface{})
|
||||
tempCandle := kline.Candle{}
|
||||
v, ok := t[0].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("unexpected value received")
|
||||
}
|
||||
tempCandle.Time, err = time.Parse(time.RFC3339, v)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempCandle.Open, err = convert.FloatFromString(t[1])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempCandle.High, err = convert.FloatFromString(t[2])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Low, err = convert.FloatFromString(t[3])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Close, err = convert.FloatFromString(t[4])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Volume, err = convert.FloatFromString(t[5])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempCandle)
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (o *OKEX) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !o.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: o.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, o.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
req := &okgroup.GetMarketDataRequest{
|
||||
Asset: a,
|
||||
Start: dates[x].Start.UTC().Format(time.RFC3339),
|
||||
End: dates[x].End.UTC().Format(time.RFC3339),
|
||||
Granularity: o.FormatExchangeKlineInterval(interval),
|
||||
InstrumentID: o.FormatExchangeCurrency(pair, a).String(),
|
||||
}
|
||||
|
||||
candles, err := o.GetMarketData(req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range candles {
|
||||
t := candles[i].([]interface{})
|
||||
tempCandle := kline.Candle{}
|
||||
v, ok := t[0].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("unexpected value received")
|
||||
}
|
||||
tempCandle.Time, err = time.Parse(time.RFC3339, v)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempCandle.Open, err = convert.FloatFromString(t[1])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
tempCandle.High, err = convert.FloatFromString(t[2])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Low, err = convert.FloatFromString(t[3])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Close, err = convert.FloatFromString(t[4])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
tempCandle.Volume, err = convert.FloatFromString(t[5])
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempCandle)
|
||||
}
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@ const (
|
||||
// OKGroupAPIPath const to help with api url formatting
|
||||
OKGroupAPIPath = "api/"
|
||||
// API subsections
|
||||
okGroupAccountSubsection = "account"
|
||||
okGroupTokenSubsection = "spot"
|
||||
okGroupMarginTradingSubsection = "margin"
|
||||
okGroupAccountSubsection = "account"
|
||||
okGroupTokenSubsection = "spot"
|
||||
okGroupMarginTradingSubsection = "margin"
|
||||
okGroupFuturesTradingSubSection = "futures"
|
||||
oKGroupSwapTradingSubSection = "swap"
|
||||
// OKGroupAccounts common api endpoint
|
||||
OKGroupAccounts = "accounts"
|
||||
// OKGroupLedger common api endpoint
|
||||
@@ -365,10 +367,21 @@ func (o *OKGroup) GetSpotFilledOrdersInformation(request GetSpotFilledOrdersInfo
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSpotMarketData Get the charts of the trading pairs. Charts are returned in grouped buckets based on requested granularity.
|
||||
func (o *OKGroup) GetSpotMarketData(request GetSpotMarketDataRequest) (resp GetSpotMarketDataResponse, _ error) {
|
||||
// GetMarketData Get the charts of the trading pairs. Charts are returned in grouped buckets based on requested granularity.
|
||||
func (o *OKGroup) GetMarketData(request *GetMarketDataRequest) (resp GetMarketDataResponse, err error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupGetSpotMarketData, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, false)
|
||||
var requestType string
|
||||
switch request.Asset {
|
||||
case asset.Spot, asset.Margin:
|
||||
requestType = okGroupTokenSubsection
|
||||
case asset.Futures:
|
||||
requestType = okGroupFuturesTradingSubSection
|
||||
case asset.PerpetualSwap:
|
||||
requestType = oKGroupSwapTradingSubSection
|
||||
default:
|
||||
return nil, errors.New("asset not supported")
|
||||
}
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, requestType, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetMarginTradingAccounts List all assets under token margin trading account, including information such as balance, amount on hold and more.
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
// Order types
|
||||
@@ -329,15 +330,16 @@ type GetSpotFilledOrdersInformationResponse struct {
|
||||
TradeID string `json:"trade_id"`
|
||||
}
|
||||
|
||||
// GetSpotMarketDataRequest request data for GetSpotMarketData
|
||||
type GetSpotMarketDataRequest struct {
|
||||
// GetMarketDataRequest request data for GetMarketData
|
||||
type GetMarketDataRequest struct {
|
||||
Asset asset.Item
|
||||
Start string `url:"start,omitempty"` // [optional] start time in ISO 8601
|
||||
End string `url:"end,omitempty"` // [optional] end time in ISO 8601
|
||||
Granularity int64 `url:"granularity"` // The granularity field must be one of the following values: {60, 180, 300, 900, 1800, 3600, 7200, 14400, 43200, 86400, 604800}.
|
||||
Granularity string `url:"granularity"` // The granularity field must be one of the following values: {60, 180, 300, 900, 1800, 3600, 7200, 14400, 43200, 86400, 604800}.
|
||||
InstrumentID string `url:"-"` // [required] trading pairs
|
||||
}
|
||||
|
||||
// GetSpotMarketDataResponse response data for GetSpotMarketData
|
||||
// GetMarketDataResponse response data for GetMarketData
|
||||
// Return Parameters
|
||||
// time string Start time
|
||||
// open string Open price
|
||||
@@ -345,7 +347,7 @@ type GetSpotMarketDataRequest struct {
|
||||
// low string Lowest price
|
||||
// close string Close price
|
||||
// volume string Trading volume
|
||||
type GetSpotMarketDataResponse []interface{}
|
||||
type GetMarketDataResponse []interface{}
|
||||
|
||||
// GetMarginAccountsResponse response data for GetMarginAccounts
|
||||
type GetMarginAccountsResponse struct {
|
||||
@@ -731,7 +733,7 @@ type GetFuturesMarketDateRequest struct {
|
||||
InstrumentID string `url:"-"` // [required] trading pairs
|
||||
}
|
||||
|
||||
// GetFuturesMarketDataResponse contains candle data from a GetSpotMarketDataRequest
|
||||
// GetFuturesMarketDataResponse contains candle data from a GetMarketDataRequest
|
||||
// Return Parameters
|
||||
// time string Start time
|
||||
// open string Open price
|
||||
|
||||
@@ -177,30 +177,44 @@ func (p *Poloniex) GetTradeHistory(currencyPair, start, end string) ([]TradeHist
|
||||
}
|
||||
|
||||
// GetChartData returns chart data for a specific currency pair
|
||||
func (p *Poloniex) GetChartData(currencyPair, start, end, period string) ([]ChartData, error) {
|
||||
func (p *Poloniex) GetChartData(currencyPair string, start, end time.Time, period string) ([]ChartData, error) {
|
||||
vals := url.Values{}
|
||||
vals.Set("currencyPair", currencyPair)
|
||||
|
||||
if start != "" {
|
||||
vals.Set("start", start)
|
||||
if !start.IsZero() {
|
||||
vals.Set("start", strconv.FormatInt(start.Unix(), 10))
|
||||
}
|
||||
|
||||
if end != "" {
|
||||
vals.Set("end", end)
|
||||
if !end.IsZero() {
|
||||
vals.Set("end", strconv.FormatInt(end.Unix(), 10))
|
||||
}
|
||||
|
||||
if period != "" {
|
||||
vals.Set("period", period)
|
||||
}
|
||||
|
||||
var temp json.RawMessage
|
||||
var resp []ChartData
|
||||
path := fmt.Sprintf("%s/public?command=returnChartData&%s", p.API.Endpoints.URL, vals.Encode())
|
||||
|
||||
err := p.SendHTTPRequest(path, &resp)
|
||||
path := p.API.Endpoints.URL + "/public?command=returnChartData&" + vals.Encode()
|
||||
err := p.SendHTTPRequest(path, &temp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempUnmarshal := json.Unmarshal(temp, &resp)
|
||||
if tempUnmarshal != nil {
|
||||
var errResp struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
errRet := json.Unmarshal(temp, &errResp)
|
||||
if errRet != nil {
|
||||
return nil, err
|
||||
}
|
||||
if errResp.Error != "" {
|
||||
return nil, errors.New(errResp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -63,7 +65,8 @@ func TestGetTradeHistory(t *testing.T) {
|
||||
|
||||
func TestGetChartData(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := p.GetChartData("BTC_XMR", "1405699200", "1405699400", "300")
|
||||
_, err := p.GetChartData("BTC_XMR",
|
||||
time.Unix(1405699200, 0), time.Unix(1405699400, 0), "300")
|
||||
if err != nil {
|
||||
t.Error("Test faild - Poloniex GetChartData() error", err)
|
||||
}
|
||||
@@ -510,7 +513,6 @@ func TestWsPriceAggregateOrderbook(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsHandleAccountData(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := p.getCurrencyIDMap()
|
||||
@@ -530,3 +532,39 @@ func TestWsHandleAccountData(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCLTC")
|
||||
_, err := p.GetHistoricCandles(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.FiveMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = p.GetHistoricCandles(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
|
||||
currencyPair.Quote = currency.NewCode("LTCC")
|
||||
_, err = p.GetHistoricCandles(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.FiveMin)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCLTC")
|
||||
_, err := p.GetHistoricCandlesExtended(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.FiveMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = p.GetHistoricCandlesExtended(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
|
||||
currencyPair.Quote = currency.NewCode("LTCC")
|
||||
_, err = p.GetHistoricCandlesExtended(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.FiveMin)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +107,22 @@ func (p *Poloniex) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission |
|
||||
exchange.NoFiatWithdrawals,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -638,6 +651,43 @@ func (p *Poloniex) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (p *Poloniex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (p *Poloniex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !p.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
candles, err := p.GetChartData(p.FormatExchangeCurrency(pair, a).String(),
|
||||
start, end,
|
||||
p.FormatExchangeKlineInterval(interval))
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: p.Name,
|
||||
Interval: interval,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
}
|
||||
|
||||
for x := range candles {
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: time.Unix(candles[x].Date, 0),
|
||||
Open: candles[x].Open,
|
||||
High: candles[x].Close,
|
||||
Low: candles[x].Low,
|
||||
Close: candles[x].Close,
|
||||
Volume: candles[x].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (p *Poloniex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return p.GetHistoricCandles(pair, a, start, end, interval)
|
||||
}
|
||||
|
||||
@@ -78,14 +78,23 @@ func (y *Yobit) GetDepth(symbol string) (Orderbook, error) {
|
||||
}
|
||||
|
||||
// GetTrades returns the trades for a specific currency
|
||||
func (y *Yobit) GetTrades(symbol string) ([]Trades, error) {
|
||||
func (y *Yobit) GetTrades(symbol string, start int64, sortAsc bool) ([]Trades, error) {
|
||||
type Response struct {
|
||||
Data map[string][]Trades
|
||||
}
|
||||
|
||||
response := Response{}
|
||||
path := fmt.Sprintf("%s/%s/%s/%s", y.API.Endpoints.URL, apiPublicVersion, publicTrades, symbol)
|
||||
v := url.Values{}
|
||||
if sortAsc {
|
||||
v.Set("order", "ASC")
|
||||
} else {
|
||||
v.Set("order", "DESC")
|
||||
}
|
||||
if start != 0 {
|
||||
v.Set("since", strconv.FormatInt(start, 10))
|
||||
}
|
||||
|
||||
var response Response
|
||||
path := y.API.Endpoints.URL + "/" + apiPublicVersion + "/" + publicTrades + "/" + symbol + "?" + v.Encode()
|
||||
return response.Data[symbol], y.SendHTTPRequest(path, &response.Data)
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ func TestGetDepth(t *testing.T) {
|
||||
|
||||
func TestGetTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := y.GetTrades("btc_usd")
|
||||
_, err := y.GetTrades("btc_usd", 0, false)
|
||||
if err != nil {
|
||||
t.Error("GetTrades() error", err)
|
||||
}
|
||||
|
||||
@@ -565,6 +565,11 @@ func (y *Yobit) ValidateCredentials() error {
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (y *Yobit) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
func (y *Yobit) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (y *Yobit) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
49
exchanges/zb/ratelimit.go
Normal file
49
exchanges/zb/ratelimit.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package zb
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const (
|
||||
zbRateInterval = time.Second
|
||||
zbAuthLimit = 60
|
||||
zbUnauthLimit = 60
|
||||
|
||||
zbKlineDataInterval = time.Second * 2
|
||||
zbKlineDataLimit = 1
|
||||
|
||||
// Used to match endpints to rate limits
|
||||
klineFunc request.EndpointLimit = iota
|
||||
)
|
||||
|
||||
// RateLimit implements the request.Limiter interface
|
||||
type RateLimit struct {
|
||||
Auth *rate.Limiter
|
||||
UnAuth *rate.Limiter
|
||||
KlineData *rate.Limiter
|
||||
}
|
||||
|
||||
// Limit limits the outbound requests
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case request.Auth:
|
||||
time.Sleep(r.Auth.Reserve().Delay())
|
||||
case klineFunc:
|
||||
time.Sleep(r.KlineData.Reserve().Delay())
|
||||
default:
|
||||
time.Sleep(r.UnAuth.Reserve().Delay())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
func SetRateLimit() *RateLimit {
|
||||
return &RateLimit{
|
||||
Auth: request.NewRateLimit(zbRateInterval, zbAuthLimit),
|
||||
UnAuth: request.NewRateLimit(zbRateInterval, zbUnauthLimit),
|
||||
KlineData: request.NewRateLimit(zbKlineDataInterval, zbKlineDataLimit),
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,6 @@ const (
|
||||
zbGetOrdersGet = "getOrders"
|
||||
zbWithdraw = "withdraw"
|
||||
zbDepositAddress = "getUserAddress"
|
||||
|
||||
zbRateInterval = time.Second
|
||||
zbReqRate = 60
|
||||
)
|
||||
|
||||
// ZB is the overarching type across this package
|
||||
@@ -61,7 +58,7 @@ func (z *ZB) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) {
|
||||
vals.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64))
|
||||
vals.Set("tradeType", string(arg.Type))
|
||||
|
||||
err := z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &result)
|
||||
err := z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &result, request.Auth)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -89,7 +86,7 @@ func (z *ZB) CancelExistingOrder(orderID int64, symbol string) error {
|
||||
vals.Set("currency", symbol)
|
||||
|
||||
var result response
|
||||
err := z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &result)
|
||||
err := z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &result, request.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -109,7 +106,7 @@ func (z *ZB) GetAccountInformation() (AccountsResponse, error) {
|
||||
vals.Set("accesskey", z.API.Credentials.Key)
|
||||
vals.Set("method", "getAccountInfo")
|
||||
|
||||
return result, z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &result)
|
||||
return result, z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &result, request.Auth)
|
||||
}
|
||||
|
||||
// GetUnfinishedOrdersIgnoreTradeType returns unfinished orders
|
||||
@@ -122,7 +119,7 @@ func (z *ZB) GetUnfinishedOrdersIgnoreTradeType(currency string, pageindex, page
|
||||
vals.Set("pageIndex", strconv.FormatInt(pageindex, 10))
|
||||
vals.Set("pageSize", strconv.FormatInt(pagesize, 10))
|
||||
|
||||
err := z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &result)
|
||||
err := z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &result, request.Auth)
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -135,7 +132,7 @@ func (z *ZB) GetOrders(currency string, pageindex, side int64) ([]Order, error)
|
||||
vals.Set("currency", currency)
|
||||
vals.Set("pageIndex", strconv.FormatInt(pageindex, 10))
|
||||
vals.Set("tradeType", strconv.FormatInt(side, 10))
|
||||
return response, z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &response)
|
||||
return response, z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &response, request.Auth)
|
||||
}
|
||||
|
||||
// GetMarkets returns market information including pricing, symbols and
|
||||
@@ -144,7 +141,7 @@ func (z *ZB) GetMarkets() (map[string]MarketResponseItem, error) {
|
||||
endpoint := fmt.Sprintf("%s/%s/%s", z.API.Endpoints.URL, zbAPIVersion, zbMarkets)
|
||||
|
||||
var res map[string]MarketResponseItem
|
||||
err := z.SendHTTPRequest(endpoint, &res)
|
||||
err := z.SendHTTPRequest(endpoint, &res, request.UnAuth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -170,7 +167,7 @@ func (z *ZB) GetLatestSpotPrice(symbol string) (float64, error) {
|
||||
func (z *ZB) GetTicker(symbol string) (TickerResponse, error) {
|
||||
urlPath := fmt.Sprintf("%s/%s/%s?market=%s", z.API.Endpoints.URL, zbAPIVersion, zbTicker, symbol)
|
||||
var res TickerResponse
|
||||
err := z.SendHTTPRequest(urlPath, &res)
|
||||
err := z.SendHTTPRequest(urlPath, &res, request.UnAuth)
|
||||
return res, err
|
||||
}
|
||||
|
||||
@@ -178,7 +175,7 @@ func (z *ZB) GetTicker(symbol string) (TickerResponse, error) {
|
||||
func (z *ZB) GetTickers() (map[string]TickerChildResponse, error) {
|
||||
urlPath := fmt.Sprintf("%s/%s/%s", z.API.Endpoints.URL, zbAPIVersion, zbTickers)
|
||||
resp := make(map[string]TickerChildResponse)
|
||||
err := z.SendHTTPRequest(urlPath, &resp)
|
||||
err := z.SendHTTPRequest(urlPath, &resp, request.UnAuth)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@@ -187,7 +184,7 @@ func (z *ZB) GetOrderbook(symbol string) (OrderbookResponse, error) {
|
||||
urlPath := fmt.Sprintf("%s/%s/%s?market=%s", z.API.Endpoints.URL, zbAPIVersion, zbDepth, symbol)
|
||||
var res OrderbookResponse
|
||||
|
||||
err := z.SendHTTPRequest(urlPath, &res)
|
||||
err := z.SendHTTPRequest(urlPath, &res, request.UnAuth)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -213,10 +210,10 @@ func (z *ZB) GetOrderbook(symbol string) (OrderbookResponse, error) {
|
||||
// GetSpotKline returns Kline data
|
||||
func (z *ZB) GetSpotKline(arg KlinesRequestParams) (KLineResponse, error) {
|
||||
vals := url.Values{}
|
||||
vals.Set("type", string(arg.Type))
|
||||
vals.Set("type", arg.Type)
|
||||
vals.Set("market", arg.Symbol)
|
||||
if arg.Since != "" {
|
||||
vals.Set("since", arg.Since)
|
||||
if arg.Since > 0 {
|
||||
vals.Set("since", strconv.FormatInt(arg.Since, 10))
|
||||
}
|
||||
if arg.Size != 0 {
|
||||
vals.Set("size", fmt.Sprintf("%d", arg.Size))
|
||||
@@ -226,7 +223,7 @@ func (z *ZB) GetSpotKline(arg KlinesRequestParams) (KLineResponse, error) {
|
||||
|
||||
var res KLineResponse
|
||||
var rawKlines map[string]interface{}
|
||||
err := z.SendHTTPRequest(urlPath, &rawKlines)
|
||||
err := z.SendHTTPRequest(urlPath, &rawKlines, klineFunc)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -273,11 +270,11 @@ func (z *ZB) GetCryptoAddress(currency currency.Code) (UserAddress, error) {
|
||||
vals.Set("currency", currency.Lower().String())
|
||||
|
||||
return resp,
|
||||
z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &resp)
|
||||
z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &resp, request.Auth)
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an unauthenticated HTTP request
|
||||
func (z *ZB) SendHTTPRequest(path string, result interface{}) error {
|
||||
func (z *ZB) SendHTTPRequest(path string, result interface{}, f request.EndpointLimit) error {
|
||||
return z.SendPayload(context.Background(), &request.Item{
|
||||
Method: http.MethodGet,
|
||||
Path: path,
|
||||
@@ -285,11 +282,12 @@ func (z *ZB) SendHTTPRequest(path string, result interface{}) error {
|
||||
Verbose: z.Verbose,
|
||||
HTTPDebugging: z.HTTPDebugging,
|
||||
HTTPRecording: z.HTTPRecording,
|
||||
Endpoint: f,
|
||||
})
|
||||
}
|
||||
|
||||
// SendAuthenticatedHTTPRequest sends authenticated requests to the zb API
|
||||
func (z *ZB) SendAuthenticatedHTTPRequest(httpMethod string, params url.Values, result interface{}) error {
|
||||
func (z *ZB) SendAuthenticatedHTTPRequest(httpMethod string, params url.Values, result interface{}, f request.EndpointLimit) error {
|
||||
if !z.AllowAuthenticatedRequest() {
|
||||
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, z.Name)
|
||||
}
|
||||
@@ -328,6 +326,7 @@ func (z *ZB) SendAuthenticatedHTTPRequest(httpMethod string, params url.Values,
|
||||
Verbose: z.Verbose,
|
||||
HTTPDebugging: z.HTTPDebugging,
|
||||
HTTPRecording: z.HTTPRecording,
|
||||
Endpoint: f,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -427,7 +426,7 @@ func (z *ZB) Withdraw(currency, address, safepassword string, amount, fees float
|
||||
vals.Set("safePwd", safepassword)
|
||||
|
||||
var resp response
|
||||
err := z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &resp)
|
||||
err := z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &resp, request.Auth)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
@@ -15,6 +16,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -153,11 +156,9 @@ func TestGetMarkets(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetSpotKline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
arg := KlinesRequestParams{
|
||||
Symbol: "btc_usdt",
|
||||
Type: TimeIntervalFiveMinutes,
|
||||
Type: kline.OneMin.Short() + "in",
|
||||
Size: 10,
|
||||
}
|
||||
_, err := z.GetSpotKline(arg)
|
||||
@@ -838,3 +839,78 @@ func TestWsCreateSubUserResponse(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("btc_usdt")
|
||||
startTime := time.Now().Add(-time.Hour * 1)
|
||||
_, err := z.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = z.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.Interval(time.Hour*7))
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("btc_usdt")
|
||||
start := time.Now().AddDate(0, -2, 0)
|
||||
end := time.Now()
|
||||
_, err := z.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
interval kline.Interval
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"OneMin",
|
||||
kline.OneMin,
|
||||
"1min",
|
||||
},
|
||||
{
|
||||
"OneHour",
|
||||
kline.OneHour,
|
||||
"1hour",
|
||||
},
|
||||
{
|
||||
"OneDay",
|
||||
kline.OneDay,
|
||||
"1day",
|
||||
},
|
||||
{
|
||||
"ThreeDay",
|
||||
kline.ThreeDay,
|
||||
"3day",
|
||||
},
|
||||
{
|
||||
"OneWeek",
|
||||
kline.OneWeek,
|
||||
"1week",
|
||||
},
|
||||
{
|
||||
"AllOther",
|
||||
kline.FifteenDay,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for x := range testCases {
|
||||
test := testCases[x]
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ret := z.FormatExchangeKlineInterval(test.interval)
|
||||
|
||||
if ret != test.output {
|
||||
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,10 +112,10 @@ type SpotNewOrderResponse struct {
|
||||
|
||||
// KlinesRequestParams represents Klines request data.
|
||||
type KlinesRequestParams struct {
|
||||
Symbol string // 交易对, zb_qc,zb_usdt,zb_btc...
|
||||
Type TimeInterval // K线类型, 1min, 3min, 15min, 30min, 1hour......
|
||||
Since string // 从这个时间戳之后的
|
||||
Size int // 返回数据的条数限制(默认为1000,如果返回数据多于1000条,那么只返回1000条)
|
||||
Symbol string // 交易对, zb_qc,zb_usdt,zb_btc...
|
||||
Type string // K线类型, 1min, 3min, 15min, 30min, 1hour......
|
||||
Since int64 // 从这个时间戳之后的
|
||||
Size int // 返回数据的条数限制(默认为1000,如果返回数据多于1000条,那么只返回1000条)
|
||||
}
|
||||
|
||||
// KLineResponseData Kline Data
|
||||
@@ -149,26 +149,6 @@ type UserAddress struct {
|
||||
} `json:"message"`
|
||||
}
|
||||
|
||||
// TimeInterval represents interval enum.
|
||||
type TimeInterval string
|
||||
|
||||
// TimeInterval vars
|
||||
var (
|
||||
TimeIntervalMinute = TimeInterval("1min")
|
||||
TimeIntervalThreeMinutes = TimeInterval("3min")
|
||||
TimeIntervalFiveMinutes = TimeInterval("5min")
|
||||
TimeIntervalFifteenMinutes = TimeInterval("15min")
|
||||
TimeIntervalThirtyMinutes = TimeInterval("30min")
|
||||
TimeIntervalHour = TimeInterval("1hour")
|
||||
TimeIntervalTwoHours = TimeInterval("2hour")
|
||||
TimeIntervalFourHours = TimeInterval("4hour")
|
||||
TimeIntervalSixHours = TimeInterval("6hour")
|
||||
TimeIntervalTwelveHours = TimeInterval("12hour")
|
||||
TimeIntervalDay = TimeInterval("1day")
|
||||
TimeIntervalThreeDays = TimeInterval("3day")
|
||||
TimeIntervalWeek = TimeInterval("1week")
|
||||
)
|
||||
|
||||
// WithdrawalFees the large list of predefined withdrawal fees
|
||||
// Prone to change, using highest value
|
||||
var WithdrawalFees = map[currency.Code]float64{
|
||||
|
||||
@@ -106,16 +106,36 @@ func (z *ZB) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.ThreeDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
},
|
||||
ResultLimit: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
z.Requester = request.New(z.Name,
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout),
|
||||
// TODO: Implement full rate limit for endpoints
|
||||
request.WithLimiter(request.NewBasicRateLimit(zbRateInterval, zbReqRate)))
|
||||
request.WithLimiter(SetRateLimit()))
|
||||
|
||||
z.API.Endpoints.URLDefault = zbTradeURL
|
||||
z.API.Endpoints.URL = z.API.Endpoints.URLDefault
|
||||
@@ -699,7 +719,68 @@ func (z *ZB) ValidateCredentials() error {
|
||||
return z.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (z *ZB) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (z *ZB) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
switch in {
|
||||
case kline.OneMin, kline.ThreeMin,
|
||||
kline.FiveMin, kline.FifteenMin, kline.ThirtyMin:
|
||||
return in.Short() + "in"
|
||||
case kline.OneHour, kline.TwoHour, kline.FourHour, kline.SixHour, kline.TwelveHour:
|
||||
return in.Short()[:len(in.Short())-1] + "hour"
|
||||
case kline.OneDay:
|
||||
return "1day"
|
||||
case kline.ThreeDay:
|
||||
return "3day"
|
||||
case kline.OneWeek:
|
||||
return "1week"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (z *ZB) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if !z.KlineIntervalEnabled(interval) {
|
||||
return kline.Item{}, kline.ErrorKline{
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
klineParams := KlinesRequestParams{
|
||||
Type: z.FormatExchangeKlineInterval(interval),
|
||||
Symbol: z.FormatExchangeCurrency(pair, a).String(),
|
||||
}
|
||||
|
||||
candles, err := z.GetSpotKline(klineParams)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: z.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range candles.Data {
|
||||
if candles.Data[x].KlineTime.Before(start) || candles.Data[x].KlineTime.After(end) {
|
||||
continue
|
||||
}
|
||||
ret.Candles = append(ret.Candles, kline.Candle{
|
||||
Time: candles.Data[x].KlineTime,
|
||||
Open: candles.Data[x].Open,
|
||||
High: candles.Data[x].Close,
|
||||
Low: candles.Data[x].Low,
|
||||
Close: candles.Data[x].Close,
|
||||
Volume: candles.Data[x].Volume,
|
||||
})
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (z *ZB) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return z.GetHistoricCandles(p, a, start, end, interval)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user