mirror of
https://github.com/c9s/bbgo.git
synced 2024-09-20 08:11:08 +00:00
fix max trades query for pnl calulcation
This commit is contained in:
parent
ce73979713
commit
7f6a478d4f
|
@ -2,6 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/c9s/bbgo/cmd"
|
"github.com/c9s/bbgo/cmd"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
)
|
|
||||||
|
|
42
cmd/pnl.go
42
cmd/pnl.go
|
@ -13,17 +13,37 @@ import (
|
||||||
|
|
||||||
"github.com/c9s/bbgo/accounting"
|
"github.com/c9s/bbgo/accounting"
|
||||||
"github.com/c9s/bbgo/bbgo"
|
"github.com/c9s/bbgo/bbgo"
|
||||||
binance2 "github.com/c9s/bbgo/exchange/binance"
|
"github.com/c9s/bbgo/exchange/binance"
|
||||||
|
"github.com/c9s/bbgo/exchange/max"
|
||||||
"github.com/c9s/bbgo/service"
|
"github.com/c9s/bbgo/service"
|
||||||
"github.com/c9s/bbgo/types"
|
"github.com/c9s/bbgo/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
PnLCmd.Flags().String("exchange", "", "target exchange")
|
||||||
PnLCmd.Flags().String("symbol", "BTCUSDT", "trading symbol")
|
PnLCmd.Flags().String("symbol", "BTCUSDT", "trading symbol")
|
||||||
PnLCmd.Flags().String("since", "", "pnl since time")
|
PnLCmd.Flags().String("since", "", "pnl since time")
|
||||||
RootCmd.AddCommand(PnLCmd)
|
RootCmd.AddCommand(PnLCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newExchangeFromViper(n types.ExchangeName) types.Exchange {
|
||||||
|
switch n {
|
||||||
|
|
||||||
|
case types.ExchangeBinance:
|
||||||
|
key := viper.GetString("binance-api-key")
|
||||||
|
secret := viper.GetString("binance-api-secret")
|
||||||
|
return binance.New(key, secret)
|
||||||
|
|
||||||
|
case types.ExchangeMax:
|
||||||
|
key := viper.GetString("max-api-key")
|
||||||
|
secret := viper.GetString("max-api-secret")
|
||||||
|
return max.New(key, secret)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var PnLCmd = &cobra.Command{
|
var PnLCmd = &cobra.Command{
|
||||||
Use: "pnl",
|
Use: "pnl",
|
||||||
Short: "pnl calculator",
|
Short: "pnl calculator",
|
||||||
|
@ -31,14 +51,22 @@ var PnLCmd = &cobra.Command{
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
exchangeNameStr, err := cmd.Flags().GetString("exchange")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
exchangeName, err := types.ValidExchangeName(exchangeNameStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
symbol, err := cmd.Flags().GetString("symbol")
|
symbol, err := cmd.Flags().GetString("symbol")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
binanceKey := viper.GetString("binance-api-key")
|
exchange := newExchangeFromViper(exchangeName)
|
||||||
binanceSecret := viper.GetString("binance-api-secret")
|
|
||||||
binanceExchange := binance2.New(binanceKey, binanceSecret)
|
|
||||||
|
|
||||||
mysqlURL := viper.GetString("mysql-url")
|
mysqlURL := viper.GetString("mysql-url")
|
||||||
mysqlURL = fmt.Sprintf("%s?parseTime=true", mysqlURL)
|
mysqlURL = fmt.Sprintf("%s?parseTime=true", mysqlURL)
|
||||||
|
@ -69,12 +97,12 @@ var PnLCmd = &cobra.Command{
|
||||||
tradeSync := &service.TradeSync{Service: tradeService}
|
tradeSync := &service.TradeSync{Service: tradeService}
|
||||||
|
|
||||||
logrus.Info("syncing trades...")
|
logrus.Info("syncing trades...")
|
||||||
if err := tradeSync.Sync(ctx, binanceExchange, symbol, startTime); err != nil {
|
if err := tradeSync.Sync(ctx, exchange, symbol, startTime); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var trades []types.Trade
|
var trades []types.Trade
|
||||||
tradingFeeCurrency := binanceExchange.PlatformFeeCurrency()
|
tradingFeeCurrency := exchange.PlatformFeeCurrency()
|
||||||
if strings.HasPrefix(symbol, tradingFeeCurrency) {
|
if strings.HasPrefix(symbol, tradingFeeCurrency) {
|
||||||
logrus.Infof("loading all trading fee currency related trades: %s", symbol)
|
logrus.Infof("loading all trading fee currency related trades: %s", symbol)
|
||||||
trades, err = tradeService.QueryForTradingFeeCurrency(symbol, tradingFeeCurrency)
|
trades, err = tradeService.QueryForTradingFeeCurrency(symbol, tradingFeeCurrency)
|
||||||
|
@ -101,7 +129,7 @@ var PnLCmd = &cobra.Command{
|
||||||
logrus.Infof("found checkpoints: %+v", checkpoints)
|
logrus.Infof("found checkpoints: %+v", checkpoints)
|
||||||
logrus.Infof("stock: %f", stockManager.Stocks.Quantity())
|
logrus.Infof("stock: %f", stockManager.Stocks.Quantity())
|
||||||
|
|
||||||
currentPrice, err := binanceExchange.QueryAveragePrice(ctx, symbol)
|
currentPrice, err := exchange.QueryAveragePrice(ctx, symbol)
|
||||||
|
|
||||||
calculator := &accounting.ProfitAndLossCalculator{
|
calculator := &accounting.ProfitAndLossCalculator{
|
||||||
TradingFeeCurrency: tradingFeeCurrency,
|
TradingFeeCurrency: tradingFeeCurrency,
|
||||||
|
|
|
@ -7,6 +7,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
maxapi "github.com/c9s/bbgo/exchange/max/maxapi"
|
maxapi "github.com/c9s/bbgo/exchange/max/maxapi"
|
||||||
"github.com/c9s/bbgo/types"
|
"github.com/c9s/bbgo/types"
|
||||||
"github.com/c9s/bbgo/util"
|
"github.com/c9s/bbgo/util"
|
||||||
|
@ -101,7 +104,7 @@ func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap,
|
||||||
|
|
||||||
func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) (trades []types.Trade, err error) {
|
func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) (trades []types.Trade, err error) {
|
||||||
req := e.client.TradeService.NewPrivateTradeRequest()
|
req := e.client.TradeService.NewPrivateTradeRequest()
|
||||||
req.Market(symbol)
|
req.Market(toLocalSymbol(symbol))
|
||||||
|
|
||||||
if options.Limit > 0 {
|
if options.Limit > 0 {
|
||||||
req.Limit(options.Limit)
|
req.Limit(options.Limit)
|
||||||
|
@ -111,6 +114,9 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
req.From(options.LastTradeID)
|
req.From(options.LastTradeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make it compatible with binance, we need the last trade id for the next page.
|
||||||
|
req.OrderBy("asc")
|
||||||
|
|
||||||
remoteTrades, err := req.Do(ctx)
|
remoteTrades, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -119,15 +125,60 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
for _, t := range remoteTrades {
|
for _, t := range remoteTrades {
|
||||||
localTrade, err := convertRemoteTrade(t)
|
localTrade, err := convertRemoteTrade(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Errorf("can not convert binance trade: %+v", t)
|
logger.WithError(err).Errorf("can not convert trade: %+v", t)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Infof("T: id=%d % 4s %s P=%f Q=%f %s", localTrade.ID, localTrade.Symbol, localTrade.Side, localTrade.Price, localTrade.Quantity, localTrade.Time)
|
||||||
|
|
||||||
trades = append(trades, *localTrade)
|
trades = append(trades, *localTrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
return trades, nil
|
return trades, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) QueryKLines(ctx context.Context, symbol, interval string, options types.KLineQueryOptions) ([]types.KLine, error) {
|
||||||
|
var limit = 5000
|
||||||
|
if options.Limit > 0 {
|
||||||
|
// default limit == 500
|
||||||
|
limit = options.Limit
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.StartTime == nil {
|
||||||
|
return nil, errors.New("start time can not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.EndTime != nil {
|
||||||
|
return nil, errors.New("end time is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("querying kline %s %s %v", symbol, interval, options)
|
||||||
|
|
||||||
|
// avoid rate limit
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
localKlines, err := e.client.PublicService.KLines(toLocalSymbol(symbol), interval, *options.StartTime, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var kLines []types.KLine
|
||||||
|
for _, k := range localKlines {
|
||||||
|
kLines = append(kLines, k.KLine())
|
||||||
|
}
|
||||||
|
|
||||||
|
return kLines, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) QueryAveragePrice(ctx context.Context, symbol string) (float64, error) {
|
||||||
|
ticker, err := e.client.PublicService.Ticker(toLocalSymbol(symbol))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return (util.MustParseFloat(ticker.Sell) + util.MustParseFloat(ticker.Buy)) / 2, nil
|
||||||
|
}
|
||||||
|
|
||||||
func toGlobalCurrency(currency string) string {
|
func toGlobalCurrency(currency string) string {
|
||||||
return strings.ToUpper(currency)
|
return strings.ToUpper(currency)
|
||||||
}
|
}
|
||||||
|
@ -136,6 +187,14 @@ func toLocalCurrency(currency string) string {
|
||||||
return strings.ToLower(currency)
|
return strings.ToLower(currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toLocalSymbol(symbol string) string {
|
||||||
|
return strings.ToLower(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalSymbol(symbol string) string {
|
||||||
|
return strings.ToLower(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
func toLocalSideType(side types.SideType) string {
|
func toLocalSideType(side types.SideType) string {
|
||||||
return strings.ToLower(string(side))
|
return strings.ToLower(string(side))
|
||||||
}
|
}
|
||||||
|
@ -148,6 +207,9 @@ func toGlobalSideType(v string) string {
|
||||||
case "ask":
|
case "ask":
|
||||||
return "SELL"
|
return "SELL"
|
||||||
|
|
||||||
|
case "self-trade":
|
||||||
|
return "SELF"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.ToUpper(v)
|
return strings.ToUpper(v)
|
||||||
|
@ -195,7 +257,7 @@ func convertRemoteTrade(t maxapi.Trade) (*types.Trade, error) {
|
||||||
return &types.Trade{
|
return &types.Trade{
|
||||||
ID: int64(t.ID),
|
ID: int64(t.ID),
|
||||||
Price: price,
|
Price: price,
|
||||||
Symbol: t.Market,
|
Symbol: toGlobalSymbol(t.Market),
|
||||||
Exchange: "max",
|
Exchange: "max",
|
||||||
Quantity: quantity,
|
Quantity: quantity,
|
||||||
Side: side,
|
Side: side,
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
package max
|
package max
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/valyala/fastjson"
|
"github.com/valyala/fastjson"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PublicService struct {
|
type PublicService struct {
|
||||||
|
@ -140,3 +147,164 @@ func mustParseTicker(v *fastjson.Value) Ticker {
|
||||||
Low: string(v.GetStringBytes("low")),
|
Low: string(v.GetStringBytes("low")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Interval int64
|
||||||
|
|
||||||
|
func parseResolution(a string) (Interval, error) {
|
||||||
|
switch strings.ToLower(a) {
|
||||||
|
|
||||||
|
case "1m":
|
||||||
|
return 1, nil
|
||||||
|
|
||||||
|
case "5m":
|
||||||
|
return 5, nil
|
||||||
|
|
||||||
|
case "15m":
|
||||||
|
return 15, nil
|
||||||
|
|
||||||
|
case "30m":
|
||||||
|
return 30, nil
|
||||||
|
|
||||||
|
case "1h":
|
||||||
|
return 60, nil
|
||||||
|
|
||||||
|
case "3h":
|
||||||
|
return 60 * 3, nil
|
||||||
|
|
||||||
|
case "6h":
|
||||||
|
return 60 * 6, nil
|
||||||
|
|
||||||
|
case "12h":
|
||||||
|
return 60 * 12, nil
|
||||||
|
|
||||||
|
case "1d":
|
||||||
|
return 60 * 24, nil
|
||||||
|
|
||||||
|
case "3d":
|
||||||
|
return 60 * 24 * 3, nil
|
||||||
|
|
||||||
|
case "1w":
|
||||||
|
return 60 * 24 * 7, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, errors.New("incorrect resolution")
|
||||||
|
}
|
||||||
|
|
||||||
|
type KLine struct {
|
||||||
|
Symbol string
|
||||||
|
Interval string
|
||||||
|
StartTime, EndTime time.Time
|
||||||
|
Open, High, Low, Close float64
|
||||||
|
Volume float64
|
||||||
|
Closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k KLine) KLine() types.KLine {
|
||||||
|
return types.KLine{
|
||||||
|
Symbol: k.Symbol,
|
||||||
|
Interval: k.Interval,
|
||||||
|
StartTime: k.StartTime,
|
||||||
|
EndTime: k.EndTime,
|
||||||
|
Open: k.Open,
|
||||||
|
Close: k.Close,
|
||||||
|
High: k.High,
|
||||||
|
Low: k.Low,
|
||||||
|
Volume: k.Volume,
|
||||||
|
// QuoteVolume: util.MustParseFloat(k.QuoteAssetVolume),
|
||||||
|
// LastTradeID: 0,
|
||||||
|
// NumberOfTrades: k.TradeNum,
|
||||||
|
Closed: k.Closed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicService) KLines(symbol string, resolution string, start time.Time, limit int) ([]KLine, error) {
|
||||||
|
queries := url.Values{}
|
||||||
|
queries.Set("market", symbol)
|
||||||
|
|
||||||
|
interval, err := parseResolution(resolution)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
queries.Set("period", strconv.Itoa(int(interval)))
|
||||||
|
|
||||||
|
nilTime := time.Time{}
|
||||||
|
if start != nilTime {
|
||||||
|
queries.Set("timestamp", strconv.FormatInt(start.Unix(), 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit > 0 {
|
||||||
|
queries.Set("limit", strconv.Itoa(limit)) // default to 30, max limit = 10,000
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := s.client.newRequest("GET", fmt.Sprintf("%s/k", s.client.BaseURL), queries, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request build error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := resp.Body.Close(); err != nil {
|
||||||
|
logger.WithError(err).Error("failed to close resp body")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseKLines(body, symbol, resolution, interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseKLines(payload []byte, symbol, resolution string, interval Interval) (klines []KLine, err error) {
|
||||||
|
var parser fastjson.Parser
|
||||||
|
|
||||||
|
v, err := parser.ParseBytes(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to parse payload: %s", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
arr, err := v.Array()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to get array: %s", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, x := range arr {
|
||||||
|
slice, err := x.Array()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to get array: %s", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(slice) < 6 {
|
||||||
|
return nil, fmt.Errorf("unexpected length of ohlc elements: %s", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, err := slice[0].Int64()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse timestamp: %s", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime := time.Unix(ts, 0).UTC()
|
||||||
|
endTime := time.Unix(ts, 0).Add(time.Duration(interval)*time.Minute - time.Millisecond).UTC()
|
||||||
|
isClosed := time.Now().Before(endTime)
|
||||||
|
klines = append(klines, KLine{
|
||||||
|
Symbol: symbol,
|
||||||
|
Interval: resolution,
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
|
Open: slice[1].GetFloat64(),
|
||||||
|
High: slice[2].GetFloat64(),
|
||||||
|
Low: slice[3].GetFloat64(),
|
||||||
|
Close: slice[4].GetFloat64(),
|
||||||
|
Volume: slice[5].GetFloat64(),
|
||||||
|
Closed: isClosed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return klines, nil
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -141,6 +142,8 @@ func (c *RestClient) initNonce() {
|
||||||
|
|
||||||
// 1 is for the request count mod 0.000 to 0.999
|
// 1 is for the request count mod 0.000 to 0.999
|
||||||
timeOffset = serverTimestamp - clientTime.Unix() - 1
|
timeOffset = serverTimestamp - clientTime.Unix() - 1
|
||||||
|
|
||||||
|
logger.Infof("loaded max server timestamp: %d offset=%d", serverTimestamp, timeOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RestClient) getNonce() int64 {
|
func (c *RestClient) getNonce() int64 {
|
||||||
|
@ -166,6 +169,7 @@ func (c *RestClient) newRequest(method string, refURL string, params url.Values,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,13 +195,16 @@ func (c *RestClient) newAuthenticatedRequest(m string, refURL string, data inter
|
||||||
|
|
||||||
p, err = json.Marshal(payload)
|
p, err = json.Marshal(payload)
|
||||||
|
|
||||||
case PrivateRequestParams:
|
|
||||||
d.Nonce = c.getNonce()
|
|
||||||
d.Path = c.BaseURL.ResolveReference(rel).Path
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unsupported payload type")
|
params, err := getPrivateRequestParamsObject(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "unsupported payload type: %T", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
params.Nonce = c.getNonce()
|
||||||
|
params.Path = c.BaseURL.ResolveReference(rel).Path
|
||||||
|
|
||||||
|
p, err = json.Marshal(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -228,6 +235,39 @@ func (c *RestClient) newAuthenticatedRequest(m string, refURL string, data inter
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPrivateRequestParamsObject(v interface{}) (*PrivateRequestParams, error) {
|
||||||
|
vt := reflect.ValueOf(v)
|
||||||
|
|
||||||
|
if vt.Kind() == reflect.Ptr {
|
||||||
|
vt = vt.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if vt.Kind() != reflect.Struct {
|
||||||
|
return nil, errors.New("reflect error: given object is not a struct" + vt.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !vt.CanSet() {
|
||||||
|
return nil, errors.New("reflect error: can not set object")
|
||||||
|
}
|
||||||
|
|
||||||
|
field := vt.FieldByName("PrivateRequestParams")
|
||||||
|
if !field.IsValid() {
|
||||||
|
return nil, errors.New("reflect error: field PrivateRequestParams not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.IsNil() {
|
||||||
|
field.Set(reflect.ValueOf(&PrivateRequestParams{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
params, ok := field.Interface().(*PrivateRequestParams)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("reflect error: failed to cast value to *PrivateRequestParams")
|
||||||
|
}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
func signPayload(payload string, secret string) string {
|
func signPayload(payload string, secret string) string {
|
||||||
var sig = hmac.New(sha256.New, []byte(secret))
|
var sig = hmac.New(sha256.New, []byte(secret))
|
||||||
_, err := sig.Write([]byte(payload))
|
_, err := sig.Write([]byte(payload))
|
||||||
|
|
|
@ -138,7 +138,7 @@ type PrivateRequestParams struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PrivateTradeRequestParams struct {
|
type PrivateTradeRequestParams struct {
|
||||||
PrivateRequestParams
|
*PrivateRequestParams
|
||||||
|
|
||||||
Market string `json:"market"`
|
Market string `json:"market"`
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ func (r *PrivateTradeRequest) OrderBy(orderBy string) *PrivateTradeRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *PrivateTradeRequest) Do(ctx context.Context) (trades []Trade, err error) {
|
func (r *PrivateTradeRequest) Do(ctx context.Context) (trades []Trade, err error) {
|
||||||
req, err := r.client.newAuthenticatedRequest("GET", "v2/trades/my", r.params)
|
req, err := r.client.newAuthenticatedRequest("GET", "v2/trades/my", &r.params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return trades, err
|
return trades, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,31 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ExchangeName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExchangeMax = ExchangeName("max")
|
||||||
|
ExchangeBinance = ExchangeName("binance")
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidExchangeName(a string) (ExchangeName, error) {
|
||||||
|
switch strings.ToLower(a) {
|
||||||
|
case "max":
|
||||||
|
return ExchangeMax, nil
|
||||||
|
case "binance", "bn":
|
||||||
|
return ExchangeBinance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("invalid exchange name")
|
||||||
|
}
|
||||||
|
|
||||||
type Exchange interface {
|
type Exchange interface {
|
||||||
PlatformFeeCurrency() string
|
PlatformFeeCurrency() string
|
||||||
|
|
||||||
|
@ -20,6 +40,8 @@ type Exchange interface {
|
||||||
|
|
||||||
QueryTrades(ctx context.Context, symbol string, options *TradeQueryOptions) ([]Trade, error)
|
QueryTrades(ctx context.Context, symbol string, options *TradeQueryOptions) ([]Trade, error)
|
||||||
|
|
||||||
|
QueryAveragePrice(ctx context.Context, symbol string) (float64, error)
|
||||||
|
|
||||||
SubmitOrder(ctx context.Context, order *SubmitOrder) error
|
SubmitOrder(ctx context.Context, order *SubmitOrder) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,17 +80,17 @@ func (e ExchangeBatchProcessor) BatchQueryKLines(ctx context.Context, symbol, in
|
||||||
return allKLines, err
|
return allKLines, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (e ExchangeBatchProcessor) BatchQueryTrades(ctx context.Context, symbol string, options *TradeQueryOptions) (allTrades []Trade, err error) {
|
func (e ExchangeBatchProcessor) BatchQueryTrades(ctx context.Context, symbol string, options *TradeQueryOptions) (allTrades []Trade, err error) {
|
||||||
|
// last 7 days
|
||||||
var startTime = time.Now().Add(-7 * 24 * time.Hour)
|
var startTime = time.Now().Add(-7 * 24 * time.Hour)
|
||||||
if options.StartTime != nil {
|
if options.StartTime != nil {
|
||||||
startTime = *options.StartTime
|
startTime = *options.StartTime
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("querying %s trades from %s", symbol, startTime)
|
|
||||||
|
|
||||||
var lastTradeID = options.LastTradeID
|
var lastTradeID = options.LastTradeID
|
||||||
for {
|
for {
|
||||||
|
log.Infof("querying %s trades from %s, limit=%d", symbol, startTime, options.Limit)
|
||||||
|
|
||||||
trades, err := e.QueryTrades(ctx, symbol, &TradeQueryOptions{
|
trades, err := e.QueryTrades(ctx, symbol, &TradeQueryOptions{
|
||||||
StartTime: &startTime,
|
StartTime: &startTime,
|
||||||
Limit: options.Limit,
|
Limit: options.Limit,
|
||||||
|
@ -78,10 +100,18 @@ func (e ExchangeBatchProcessor) BatchQueryTrades(ctx context.Context, symbol str
|
||||||
return allTrades, err
|
return allTrades, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(trades) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
if len(trades) == 1 && trades[0].ID == lastTradeID {
|
if len(trades) == 1 && trades[0].ID == lastTradeID {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("returned %d trades", len(trades))
|
||||||
|
|
||||||
|
startTime = trades[len(trades)-1].Time
|
||||||
|
|
||||||
for _, t := range trades {
|
for _, t := range trades {
|
||||||
// ignore the first trade if last TradeID is given
|
// ignore the first trade if last TradeID is given
|
||||||
if t.ID == lastTradeID {
|
if t.ID == lastTradeID {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user