mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-21 23:16:49 +00:00
* 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
296 lines
7.2 KiB
Go
296 lines
7.2 KiB
Go
package kline
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
|
)
|
|
|
|
// CreateKline creates candles out of trade history data for a set time interval
|
|
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)
|
|
}
|
|
|
|
err := validateData(trades)
|
|
if err != nil {
|
|
return Item{}, err
|
|
}
|
|
|
|
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.Duration()) {
|
|
timeBufferEnd := t.Add(interval.Duration())
|
|
insertionCount := 0
|
|
|
|
var zonedTradeHistory []order.TradeHistory
|
|
for i := 0; i < len(trades); i++ {
|
|
if (trades[i].Timestamp.After(t) ||
|
|
trades[i].Timestamp.Equal(t)) &&
|
|
(trades[i].Timestamp.Before(timeBufferEnd) ||
|
|
trades[i].Timestamp.Equal(timeBufferEnd)) {
|
|
zonedTradeHistory = append(zonedTradeHistory, trades[i])
|
|
insertionCount++
|
|
continue
|
|
}
|
|
trades = trades[i:]
|
|
break
|
|
}
|
|
|
|
candleStart = append(candleStart, t)
|
|
|
|
// Insert dummy in time period when there is no price action
|
|
if insertionCount == 0 {
|
|
timeIntervalCache = append(timeIntervalCache, []order.TradeHistory{})
|
|
continue
|
|
}
|
|
timeIntervalCache = append(timeIntervalCache, zonedTradeHistory)
|
|
}
|
|
|
|
if candleStart == nil {
|
|
return Item{}, errors.New("candle start cannot be nil")
|
|
}
|
|
|
|
var candles = Item{
|
|
Exchange: exchange,
|
|
Pair: p,
|
|
Asset: a,
|
|
Interval: interval,
|
|
}
|
|
|
|
var closePriceOfLast float64
|
|
for x := range timeIntervalCache {
|
|
if len(timeIntervalCache[x]) == 0 {
|
|
candles.Candles = append(candles.Candles, Candle{
|
|
Time: candleStart[x],
|
|
High: closePriceOfLast,
|
|
Low: closePriceOfLast,
|
|
Close: closePriceOfLast,
|
|
Open: closePriceOfLast})
|
|
continue
|
|
}
|
|
|
|
var newCandle = Candle{
|
|
Open: timeIntervalCache[x][0].Price,
|
|
Time: candleStart[x],
|
|
}
|
|
|
|
for y := range timeIntervalCache[x] {
|
|
if y == len(timeIntervalCache[x])-1 {
|
|
newCandle.Close = timeIntervalCache[x][y].Price
|
|
closePriceOfLast = timeIntervalCache[x][y].Price
|
|
}
|
|
if newCandle.High < timeIntervalCache[x][y].Price {
|
|
newCandle.High = timeIntervalCache[x][y].Price
|
|
}
|
|
if newCandle.Low > timeIntervalCache[x][y].Price || newCandle.Low == 0 {
|
|
newCandle.Low = timeIntervalCache[x][y].Price
|
|
}
|
|
newCandle.Volume += timeIntervalCache[x][y].Amount
|
|
}
|
|
candles.Candles = append(candles.Candles, newCandle)
|
|
}
|
|
return candles, nil
|
|
}
|
|
|
|
// validatData checks for zero values on data and sorts before turning
|
|
// converting into OHLC
|
|
func validateData(trades []order.TradeHistory) error {
|
|
if len(trades) < 2 {
|
|
return errors.New("insufficient data")
|
|
}
|
|
|
|
for i := range trades {
|
|
if trades[i].Timestamp.IsZero() ||
|
|
trades[i].Timestamp.Unix() == 0 {
|
|
return fmt.Errorf("timestamp not set for element %d", i)
|
|
}
|
|
|
|
if trades[i].Amount == 0 {
|
|
return fmt.Errorf("amount not set for element %d", i)
|
|
}
|
|
|
|
if trades[i].Price == 0 {
|
|
return fmt.Errorf("price not set for element %d", i)
|
|
}
|
|
}
|
|
|
|
sort.Slice(trades, func(i, j int) bool {
|
|
return trades[i].Timestamp.Before(trades[j].Timestamp)
|
|
})
|
|
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)
|
|
})
|
|
}
|