diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 53c2135e8..52fd330e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,6 @@ jobs: - name: Build run: | npm install --global yarn - (cd frontend && yarn install) make dist VERSION=${{ steps.get_version.outputs.VERSION }} shell: bash - name: Create Release diff --git a/.gitignore b/.gitignore index c0c1ccacf..efa134118 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ /pkg/server/assets.go bbgo.sqlite3 +node_modules + diff --git a/Makefile b/Makefile index 014888b05..765e19e98 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,8 @@ $(BIN_DIR): # build native bbgo -bbgo: - go build -tags web,release -o $(BIN_DIR)/$@ ./cmd/bbgo +bbgo: static + go build -tags web,release -o $(BIN_DIR)/bbgo ./cmd/bbgo # build native bbgo (slim version) bbgo-slim: @@ -124,12 +124,16 @@ dist: static dist-bbgo-linux dist-bbgo-darwin desktop pkg/version/version.go: .FORCE bash utils/generate-version-file.sh > $@ - git commit $@ -m "bump version to $(VERSION)" || true -version: pkg/version/version.go migrations +pkg/version/dev.go: .FORCE + VERSION_SUFFIX="-dev" bash utils/generate-version-file.sh > $@ + +version: pkg/version/version.go pkg/version/dev.go migrations + git commit $< $(word 2,$^) -m "bump version to $(VERSION)" || true [[ -e doc/release/$(VERSION).md ]] || (echo "file doc/release/$(VERSION).md does not exist" ; exit 1) git add -v doc/release/$(VERSION).md && git commit doc/release/$(VERSION).md -m "add release note" || true git tag -f $(VERSION) + git push origin HEAD git push origin $(VERSION) migrations: @@ -146,8 +150,11 @@ docker-push: docker push yoanlin/bbgo bash -c "[[ -n $(DOCKER_TAG) ]] && docker push yoanlin/bbgo:$(DOCKER_TAG)" -frontend/out/index.html: - (cd frontend && yarn export) +frontend/node_modules: + cd frontend && yarn install + +frontend/out/index.html: frontend/node_modules + cd frontend && yarn export pkg/server/assets.go: frontend/out/index.html go run ./util/embed -package server -output $@ $(FRONTEND_EXPORT_DIR) diff --git a/README.md b/README.md index 490d5793c..be2696f24 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,10 @@ A trading bot framework written in Go. The name bbgo comes from the BB8 bot in t Get your exchange API key and secret after you register the accounts (you can choose one or more exchanges): -- For MAX: -- For Binance: -- For FTX: +- MAX: +- Binance: +- FTX: +- OKEx: Since the exchange implementation and support are done by a small team, if you like the work they've done for you, It would be great if you can use their referral code as your support to them. :-D @@ -125,6 +126,8 @@ bbgo pnl --exchange binance --asset BTC --since "2019-01-01" ## Advanced Configuration +### Notification + - [Setting up Telegram notification](./doc/configuration/telegram.md) - [Setting up Slack notification](./doc/configuration/slack.md) @@ -184,7 +187,8 @@ bbgo sync --session binance Check out the strategy directory [strategy](pkg/strategy) for all built-in strategies: -- `pricealert` strategy demonstrates how to use the notification system [pricealert](pkg/strategy/pricealert) +- `pricealert` strategy demonstrates how to use the notification system [pricealert](pkg/strategy/pricealert). See + [document](./doc/strategy/pricealert.md). - `xpuremaker` strategy demonstrates how to maintain the orderbook and submit maker orders [xpuremaker](pkg/strategy/xpuremaker) - `buyandhold` strategy demonstrates how to subscribe kline events and submit market @@ -535,7 +539,7 @@ rockhopper --config rockhopper_sqlite.yaml create --type sql add_pnl_column rockhopper --config rockhopper_mysql.yaml create --type sql add_pnl_column ``` -or +or you can use the util script: ``` bash utils/generate-new-migration.sh add_pnl_column @@ -558,6 +562,21 @@ Then run the following command to compile the migration files into go files: make migrations ``` + +If you want to override the DSN and the Driver defined in the YAML config file, you can add some env vars in your dotenv file like this: + +```shell +ROCKHOPPER_DRIVER=mysql +ROCKHOPPER_DIALECT=mysql +ROCKHOPPER_DSN="root:123123@unix(/opt/local/var/run/mysql57/mysqld.sock)/bbgo" +``` + +And then, run: + +```shell +dotenv -f .env.local -- rockhopper --config rockhopper_mysql.yaml up +``` + ### Setup frontend development environment ```sh diff --git a/config/pricealert-tg.yaml b/config/pricealert-tg.yaml new file mode 100644 index 000000000..0ee50ccad --- /dev/null +++ b/config/pricealert-tg.yaml @@ -0,0 +1,20 @@ +--- +notifications: + # object routing rules + routing: + trade: "$symbol" + order: "$symbol" + submitOrder: "$session" # not supported yet + pnL: "bbgo-pnl" + +sessions: + binance: + exchange: binance + envVarPrefix: binance + +exchangeStrategies: +- on: binance + pricealert: + symbol: "BTCUSDT" + interval: "1m" + minChange: 300 \ No newline at end of file diff --git a/doc/build-from-source.md b/doc/build-from-source.md index 67751fe82..d8057ee48 100644 --- a/doc/build-from-source.md +++ b/doc/build-from-source.md @@ -1,5 +1,7 @@ # Build From Source +## Install Go SDK + Go to the Go official website to download the Go SDK . An example installation looks like this: @@ -28,12 +30,18 @@ Make sure your `go` is successfully installed: go version ``` +## Install go-sqlite + If you need to use go-sqlite, you will need to enable CGO first: ``` CGO_ENABLED=1 go get github.com/mattn/go-sqlite3 ``` +## Install + +### Install bbgo via go install + Install bbgo: ```sh @@ -64,6 +72,41 @@ export GOPATH=~/mygo Then your bbgo will be installed at `~/mygo/bin/bbgo`. +### Install via git clone + +Since the default GOPATH is located at `~/go`, you can clone the bbgo repo into the folder `~/go/src/github.com/c9s/bbgo`: + +```shell +mkdir -p ~/go/src/github.com/c9s +git clone git@github.com:c9s/bbgo.git ~/go/src/github.com/c9s/bbgo +cd ~/go/src/github.com/c9s/bbgo +``` + +Download the go modules: + +```shell +go mod download +``` + +And then you should be able to run bbgo with `go run` + +```shell +go run ./cmd/bbgo run +``` + +You can also use the makefile to build bbgo: + +```shell +cd frontend && yarn install +make bbgo +``` + +If you don't need the web interface, you can build the slim version of bbgo: + +```shell +make bbgo-slim +``` + ## Build inside a Alpine container Starts a docker container with the alpine image: diff --git a/doc/configuration/telegram.md b/doc/configuration/telegram.md index 5788d489c..27fda9094 100644 --- a/doc/configuration/telegram.md +++ b/doc/configuration/telegram.md @@ -22,6 +22,20 @@ you can set `TELEGRAM_AUTH_TOKEN` in the `.env.local` file, e.g., ```sh TELEGRAM_BOT_AUTH_TOKEN=itsme55667788 ``` + +The alerting strategies use Telegram bot notification without further configuration. You can check the [pricealert +yaml file](../../config/pricealert-tg.yaml) in the `config/` directory for example. + +If you want the order submitting/filling notification, add the following to your `bbgo.yaml`: + +```yaml +notifications: + routing: + trade: "$symbol" + order: "$symbol" + submitOrder: "$session" + pnL: "bbgo-pnl" +``` Run your bbgo. diff --git a/doc/development/new-exchange.md b/doc/development/new-exchange.md new file mode 100644 index 000000000..f631382cf --- /dev/null +++ b/doc/development/new-exchange.md @@ -0,0 +1,57 @@ +# Adding New Exchange + +Open an issue and paste the following checklist to that issue. + +You should send multiple small pull request to implement them. + +**Please avoid sending a pull request with huge changes** + +## Checklist + +Exchange Interface (minimum) + +- [ ] QueryMarkets +- [ ] QueryKLines +- [ ] QueryTickers +- [ ] QueryOrders +- [ ] QueryTrades +- [ ] SubmitOrders + +Convert functions: + +- [ ] MarketData convert functions + - [ ] toGlobalMarket + - [ ] toGlobalTicker + - [ ] toGlobalKLine +- [ ] UserData convert functions + - [ ] toGlobalOrder + - [ ] toGlobalTrade + - [ ] toGlobalAccount + - [ ] toGlobalBalance + +Stream + +- [ ] UserDataStream + - [ ] Trade message parser + - [ ] Order message parser + - [ ] Account message parser + - [ ] Balance message parser +- [ ] MarketDataStream + - [ ] OrderBook message parser (or depth) + - [ ] KLine message parser (required for backtesting) + - [ ] Public trade message parser (optional) + - [ ] Ticker message parser (optional) +- [ ] ping/pong handling. +- [ ] heart-beat hanlding or keep-alive handling. +- [ ] handling reconnect + +Database + +- [ ] Add a new kline table for the exchange (this is required for back-testing) + - [ ] Add MySQL migration SQL + - [ ] Add SQLite migration SQL + +Exchange Factory + +- [ ] Add the exchange constructor to the exchange instance factory function. +- [ ] Add extended fields to the ExchangeSession struct. (optional) diff --git a/doc/release/v1.20.0.md b/doc/release/v1.20.0.md index 3fd5ef0f6..7609a61a5 100644 --- a/doc/release/v1.20.0.md +++ b/doc/release/v1.20.0.md @@ -1,7 +1,7 @@ ## Fixes -- +- ftx: fixed FTX OnKLineClosed event -## Features +## Migrations -- +- add `is_futures` fields to orders and trades table diff --git a/doc/strategy/pricealert.md b/doc/strategy/pricealert.md new file mode 100644 index 000000000..ec40dc8a0 --- /dev/null +++ b/doc/strategy/pricealert.md @@ -0,0 +1,25 @@ +### Price Alert Strategy + +This strategy will send notifications to specified channels when the price change of the specified trading pairs is +larger than the threshold. + + +### Prerequisite +Setup Telegram/Slack notification before using Price Alert Strategy. See [Setting up Telegram Bot Notification +](../configuration/telegram.md) and [Setting up Slack Notification](../configuration/slack.md). + + +#### Parameters + +- `symbol` + - The trading pair symbol, e.g., `BTCUSDT`, `ETHUSDT` +- `interval` + - The K-line interval, e.g., `5m`, `1h` +- `minChange` + - Alert threshold, e.g., `100`, `500`. This is a fixed value of price change. Any price change in a single K-line + larger than this value will trigger the alert. + + +#### Examples + +See [pricealert.yaml](../../config/pricealert.yaml) and [pricealert-tg.yaml](../../config/pricealert-tg.yaml) \ No newline at end of file diff --git a/migrations/mysql/20211205162043_add_is_futures_column.sql b/migrations/mysql/20211205162043_add_is_futures_column.sql new file mode 100644 index 000000000..b2929edd2 --- /dev/null +++ b/migrations/mysql/20211205162043_add_is_futures_column.sql @@ -0,0 +1,18 @@ +-- +up +-- +begin +ALTER TABLE `trades` ADD COLUMN `is_futures` BOOLEAN NOT NULL DEFAULT FALSE; +-- +end + +-- +begin +ALTER TABLE `orders` ADD COLUMN `is_futures` BOOLEAN NOT NULL DEFAULT FALSE; +-- +end + +-- +down + +-- +begin +ALTER TABLE `trades` DROP COLUMN `is_futures`; +-- +end + +-- +begin +ALTER TABLE `orders` DROP COLUMN `is_futures`; +-- +end diff --git a/migrations/sqlite3/20211205162302_add_is_futures_column.sql b/migrations/sqlite3/20211205162302_add_is_futures_column.sql new file mode 100644 index 000000000..ca020183e --- /dev/null +++ b/migrations/sqlite3/20211205162302_add_is_futures_column.sql @@ -0,0 +1,18 @@ +-- +up +-- +begin +ALTER TABLE `trades` ADD COLUMN `is_futures` BOOLEAN NOT NULL DEFAULT FALSE; +-- +end + +-- +begin +ALTER TABLE `orders` ADD COLUMN `is_futures` BOOLEAN NOT NULL DEFAULT FALSE; +-- +end + +-- +down + +-- +begin +ALTER TABLE `trades` RENAME COLUMN `is_futures` TO `is_futures_deleted`; +-- +end + +-- +begin +ALTER TABLE `orders` RENAME COLUMN `is_futures` TO `is_futures_deleted`; +-- +end diff --git a/pkg/backtest/exchange.go b/pkg/backtest/exchange.go index bdcc8c242..177b51a78 100644 --- a/pkg/backtest/exchange.go +++ b/pkg/backtest/exchange.go @@ -33,6 +33,8 @@ import ( "sync" "time" + "github.com/c9s/bbgo/pkg/exchange/ftx" + "github.com/c9s/bbgo/pkg/exchange/okex" "github.com/pkg/errors" "github.com/c9s/bbgo/pkg/bbgo" @@ -68,29 +70,29 @@ type Exchange struct { doneC chan struct{} } -func NewExchange(sourceName types.ExchangeName, srv *service.BacktestService, config *bbgo.Backtest) *Exchange { +func NewExchange(sourceName types.ExchangeName, srv *service.BacktestService, config *bbgo.Backtest) (*Exchange, error) { ex, err := newPublicExchange(sourceName) if err != nil { - panic(err) + return nil, err } if config == nil { - panic(errors.New("backtest config can not be nil")) + return nil, errors.New("backtest config can not be nil") } markets, err := bbgo.LoadExchangeMarketsWithCache(context.Background(), ex) if err != nil { - panic(err) + return nil, err } startTime, err := config.ParseStartTime() if err != nil { - panic(err) + return nil, err } endTime, err := config.ParseEndTime() if err != nil { - panic(err) + return nil, err } account := &types.Account{ @@ -117,7 +119,7 @@ func NewExchange(sourceName types.ExchangeName, srv *service.BacktestService, co } e.resetMatchingBooks() - return e + return e, nil } func (e *Exchange) addTrade(trade types.Trade) { @@ -313,7 +315,11 @@ func newPublicExchange(sourceExchange types.ExchangeName) (types.Exchange, error return binance.New("", ""), nil case types.ExchangeMax: return max.New("", ""), nil + case types.ExchangeFTX: + return ftx.NewExchange("", "", ""), nil + case types.ExchangeOKEx: + return okex.New("", "", ""), nil } - return nil, fmt.Errorf("exchange %s is not supported", sourceExchange) + return nil, fmt.Errorf("public data from exchange %s is not supported", sourceExchange) } diff --git a/pkg/backtest/matching.go b/pkg/backtest/matching.go index 997f27454..fd716ffcf 100644 --- a/pkg/backtest/matching.go +++ b/pkg/backtest/matching.go @@ -179,11 +179,11 @@ func (m *SimplePriceMatching) executeTrade(trade types.Trade) { if trade.IsBuyer { err = m.Account.UseLockedBalance(m.Market.QuoteCurrency, fixedpoint.NewFromFloat(trade.Price*trade.Quantity)) - _ = m.Account.AddBalance(m.Market.BaseCurrency, fixedpoint.NewFromFloat(trade.Quantity)) + m.Account.AddBalance(m.Market.BaseCurrency, fixedpoint.NewFromFloat(trade.Quantity)) } else { err = m.Account.UseLockedBalance(m.Market.BaseCurrency, fixedpoint.NewFromFloat(trade.Quantity)) - _ = m.Account.AddBalance(m.Market.QuoteCurrency, fixedpoint.NewFromFloat(trade.Quantity*trade.Price)) + m.Account.AddBalance(m.Market.QuoteCurrency, fixedpoint.NewFromFloat(trade.Quantity*trade.Price)) } if err != nil { diff --git a/pkg/cmd/backtest.go b/pkg/cmd/backtest.go index e2cfbb41a..41eeb6dc2 100644 --- a/pkg/cmd/backtest.go +++ b/pkg/cmd/backtest.go @@ -180,7 +180,7 @@ var BacktestCmd = &cobra.Command{ // if we don't have klines before the start time endpoint, the back-test will fail. // because the last price will be missing. - if firstKLine != nil && syncFromTime.Before(firstKLine.EndTime) { + if firstKLine != nil && syncFromTime.Before(firstKLine.StartTime) { return fmt.Errorf("the sync-from-time you gave %s is earlier than the previous sync-start-time %s. "+ "re-syncing data from the earlier date before your first sync is not support,"+ "please clean up the kline table and restart a new sync", @@ -261,7 +261,11 @@ var BacktestCmd = &cobra.Command{ } } - backtestExchange := backtest.NewExchange(exchangeName, backtestService, userConfig.Backtest) + backtestExchange, err := backtest.NewExchange(exchangeName, backtestService, userConfig.Backtest) + if err != nil { + return errors.Wrap(err, "failed to create backtest exchange") + } + environ.SetStartTime(startTime) environ.AddExchange(exchangeName.String(), backtestExchange) @@ -338,11 +342,15 @@ var BacktestCmd = &cobra.Command{ if jsonOutputEnabled { result := struct { Symbol string `json:"symbol,omitempty"` + LastPrice float64 `json:"lastPrice,omitempty"` + StartPrice float64 `json:"startPrice,omitempty"` PnLReport *pnl.AverageCostPnlReport `json:"pnlReport,omitempty"` InitialBalances types.BalanceMap `json:"initialBalances,omitempty"` FinalBalances types.BalanceMap `json:"finalBalances,omitempty"` }{ Symbol: symbol, + LastPrice: lastPrice, + StartPrice: startPrice, PnLReport: report, InitialBalances: initBalances, FinalBalances: finalBalances, diff --git a/pkg/exchange/binance/convert.go b/pkg/exchange/binance/convert.go index 7573fdcc4..0385ac338 100644 --- a/pkg/exchange/binance/convert.go +++ b/pkg/exchange/binance/convert.go @@ -7,6 +7,7 @@ import ( "time" "github.com/adshao/go-binance/v2" + "github.com/adshao/go-binance/v2/futures" "github.com/pkg/errors" "github.com/c9s/bbgo/pkg/fixedpoint" @@ -14,6 +15,42 @@ import ( "github.com/c9s/bbgo/pkg/util" ) +func toGlobalMarket(symbol binance.Symbol) types.Market { + market := types.Market{ + Symbol: symbol.Symbol, + LocalSymbol: symbol.Symbol, + PricePrecision: symbol.QuotePrecision, + VolumePrecision: symbol.BaseAssetPrecision, + QuoteCurrency: symbol.QuoteAsset, + BaseCurrency: symbol.BaseAsset, + } + + if f := symbol.MinNotionalFilter(); f != nil { + market.MinNotional = util.MustParseFloat(f.MinNotional) + market.MinAmount = util.MustParseFloat(f.MinNotional) + } + + // The LOT_SIZE filter defines the quantity (aka "lots" in auction terms) rules for a symbol. + // There are 3 parts: + // minQty defines the minimum quantity/icebergQty allowed. + // maxQty defines the maximum quantity/icebergQty allowed. + // stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by. + if f := symbol.LotSizeFilter(); f != nil { + market.MinQuantity = util.MustParseFloat(f.MinQuantity) + market.MaxQuantity = util.MustParseFloat(f.MaxQuantity) + market.StepSize = util.MustParseFloat(f.StepSize) + } + + if f := symbol.PriceFilter(); f != nil { + market.MaxPrice = util.MustParseFloat(f.MaxPrice) + market.MinPrice = util.MustParseFloat(f.MinPrice) + market.TickSize = util.MustParseFloat(f.TickSize) + } + + return market +} + + func toGlobalIsolatedUserAsset(userAsset binance.IsolatedUserAsset) types.IsolatedUserAsset { return types.IsolatedUserAsset{ Asset: userAsset.Asset, @@ -90,8 +127,8 @@ func toGlobalMarginAccount(account *binance.MarginAccount) *types.MarginAccount } } -func toGlobalTicker(stats *binance.PriceChangeStats) types.Ticker { - return types.Ticker{ +func toGlobalTicker(stats *binance.PriceChangeStats) (*types.Ticker, error) { + return &types.Ticker{ Volume: util.MustParseFloat(stats.Volume), Last: util.MustParseFloat(stats.LastPrice), Open: util.MustParseFloat(stats.OpenPrice), @@ -100,9 +137,10 @@ func toGlobalTicker(stats *binance.PriceChangeStats) types.Ticker { Buy: util.MustParseFloat(stats.BidPrice), Sell: util.MustParseFloat(stats.AskPrice), Time: time.Unix(0, stats.CloseTime*int64(time.Millisecond)), - } + },nil } + func toLocalOrderType(orderType types.OrderType) (binance.OrderType, error) { switch orderType { @@ -303,3 +341,27 @@ func convertSubscription(s types.Subscription) string { return fmt.Sprintf("%s@%s", strings.ToLower(s.Symbol), s.Channel) } + +func convertPremiumIndex(index *futures.PremiumIndex) (*types.PremiumIndex, error) { + markPrice, err := fixedpoint.NewFromString(index.MarkPrice) + if err != nil { + return nil, err + } + + lastFundingRate, err := fixedpoint.NewFromString(index.LastFundingRate) + if err != nil { + return nil, err + } + + nextFundingTime := time.Unix(0, index.NextFundingTime*int64(time.Millisecond)) + t := time.Unix(0, index.Time*int64(time.Millisecond)) + + return &types.PremiumIndex{ + Symbol: index.Symbol, + MarkPrice: markPrice, + NextFundingTime: nextFundingTime, + LastFundingRate: lastFundingRate, + Time: t, + }, nil +} + diff --git a/pkg/exchange/binance/exchange.go b/pkg/exchange/binance/exchange.go index 6e51c0665..e136dd12b 100644 --- a/pkg/exchange/binance/exchange.go +++ b/pkg/exchange/binance/exchange.go @@ -3,14 +3,14 @@ package binance import ( "context" "fmt" - "github.com/adshao/go-binance/v2/futures" - "golang.org/x/time/rate" "net/http" "os" "strconv" "strings" "time" + "golang.org/x/time/rate" + "github.com/adshao/go-binance/v2" "github.com/google/uuid" "github.com/pkg/errors" @@ -36,6 +36,7 @@ func init() { _ = types.Exchange(&Exchange{}) _ = types.MarginExchange(&Exchange{}) + // FIXME: this is not effected since dotenv is loaded in the rootCmd, not in the init function if ok, _ := strconv.ParseBool(os.Getenv("DEBUG_BINANCE_STREAM")); ok { log.Level = logrus.DebugLevel } @@ -74,8 +75,7 @@ func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticke return nil, err } - ticker := toGlobalTicker(stats[0]) - return &ticker, nil + return toGlobalTicker(stats[0]) } func (e *Exchange) QueryTickers(ctx context.Context, symbol ...string) (map[string]types.Ticker, error) { @@ -136,38 +136,7 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) { markets := types.MarketMap{} for _, symbol := range exchangeInfo.Symbols { - market := types.Market{ - Symbol: symbol.Symbol, - LocalSymbol: symbol.Symbol, - PricePrecision: symbol.QuotePrecision, - VolumePrecision: symbol.BaseAssetPrecision, - QuoteCurrency: symbol.QuoteAsset, - BaseCurrency: symbol.BaseAsset, - } - - if f := symbol.MinNotionalFilter(); f != nil { - market.MinNotional = util.MustParseFloat(f.MinNotional) - market.MinAmount = util.MustParseFloat(f.MinNotional) - } - - // The LOT_SIZE filter defines the quantity (aka "lots" in auction terms) rules for a symbol. - // There are 3 parts: - // minQty defines the minimum quantity/icebergQty allowed. - // maxQty defines the maximum quantity/icebergQty allowed. - // stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by. - if f := symbol.LotSizeFilter(); f != nil { - market.MinQuantity = util.MustParseFloat(f.MinQuantity) - market.MaxQuantity = util.MustParseFloat(f.MaxQuantity) - market.StepSize = util.MustParseFloat(f.StepSize) - } - - if f := symbol.PriceFilter(); f != nil { - market.MaxPrice = util.MustParseFloat(f.MaxPrice) - market.MinPrice = util.MustParseFloat(f.MinPrice) - market.TickSize = util.MustParseFloat(f.TickSize) - } - - markets[symbol.Symbol] = market + markets[symbol.Symbol] = toGlobalMarket(symbol) } return markets, nil @@ -211,15 +180,6 @@ func (e *Exchange) QueryIsolatedMarginAccount(ctx context.Context, symbols ...st return toGlobalIsolatedMarginAccount(account), nil } -func (e *Exchange) getLaunchDate() (time.Time, error) { - // binance launch date 12:00 July 14th, 2017 - loc, err := time.LoadLocation("Asia/Shanghai") - if err != nil { - return time.Time{}, err - } - - return time.Date(2017, time.July, 14, 0, 0, 0, 0, loc), nil -} func (e *Exchange) Withdrawal(ctx context.Context, asset string, amount fixedpoint.Value, address string, options *types.WithdrawalOptions) error { req := e.Client.NewCreateWithdrawService() @@ -250,7 +210,7 @@ func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since var emptyTime = time.Time{} if startTime == emptyTime { - startTime, err = e.getLaunchDate() + startTime, err = getLaunchDate() if err != nil { return nil, err } @@ -338,7 +298,7 @@ func (e *Exchange) QueryDepositHistory(ctx context.Context, asset string, since, var emptyTime = time.Time{} if startTime == emptyTime { - startTime, err = e.getLaunchDate() + startTime, err = getLaunchDate() if err != nil { return nil, err } @@ -925,44 +885,7 @@ func (e *Exchange) BatchQueryKLines(ctx context.Context, symbol string, interval return allKLines, nil } -type FundingRate struct { - FundingRate fixedpoint.Value - FundingTime time.Time - Time time.Time -} - -type PremiumIndex struct { - Symbol string `json:"symbol"` - MarkPrice fixedpoint.Value `json:"markPrice"` - LastFundingRate fixedpoint.Value `json:"lastFundingRate"` - NextFundingTime time.Time `json:"nextFundingTime"` - Time time.Time `json:"time"` -} - -func convertPremiumIndex(index *futures.PremiumIndex) (*PremiumIndex, error) { - markPrice, err := fixedpoint.NewFromString(index.MarkPrice) - if err != nil { - return nil, err - } - - lastFundingRate, err := fixedpoint.NewFromString(index.LastFundingRate) - if err != nil { - return nil, err - } - - nextFundingTime := time.Unix(0, index.NextFundingTime*int64(time.Millisecond)) - t := time.Unix(0, index.Time*int64(time.Millisecond)) - - return &PremiumIndex{ - Symbol: index.Symbol, - MarkPrice: markPrice, - NextFundingTime: nextFundingTime, - LastFundingRate: lastFundingRate, - Time: t, - }, nil -} - -func (e *Exchange) QueryPremiumIndex(ctx context.Context, symbol string) (*PremiumIndex, error) { +func (e *Exchange) QueryPremiumIndex(ctx context.Context, symbol string) (*types.PremiumIndex, error) { futuresClient := binance.NewFuturesClient(e.key, e.secret) // when symbol is set, only one index will be returned. @@ -974,7 +897,7 @@ func (e *Exchange) QueryPremiumIndex(ctx context.Context, symbol string) (*Premi return convertPremiumIndex(indexes[0]) } -func (e *Exchange) QueryFundingRateHistory(ctx context.Context, symbol string) (*FundingRate, error) { +func (e *Exchange) QueryFundingRateHistory(ctx context.Context, symbol string) (*types.FundingRate, error) { futuresClient := binance.NewFuturesClient(e.key, e.secret) rates, err := futuresClient.NewFundingRateService(). Symbol(symbol). @@ -994,9 +917,19 @@ func (e *Exchange) QueryFundingRateHistory(ctx context.Context, symbol string) ( return nil, err } - return &FundingRate{ + return &types.FundingRate{ FundingRate: fundingRate, FundingTime: time.Unix(0, rate.FundingTime*int64(time.Millisecond)), Time: time.Unix(0, rate.Time*int64(time.Millisecond)), }, nil } + +func getLaunchDate() (time.Time, error) { + // binance launch date 12:00 July 14th, 2017 + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + return time.Time{}, err + } + + return time.Date(2017, time.July, 14, 0, 0, 0, 0, loc), nil +} \ No newline at end of file diff --git a/pkg/exchange/ftx/exchange.go b/pkg/exchange/ftx/exchange.go index a067dd357..0bd443d08 100644 --- a/pkg/exchange/ftx/exchange.go +++ b/pkg/exchange/ftx/exchange.go @@ -3,6 +3,7 @@ package ftx import ( "context" "fmt" + "golang.org/x/time/rate" "net/http" "net/url" "sort" @@ -23,6 +24,9 @@ const ( var logger = logrus.WithField("exchange", "ftx") +// POST https://ftx.com/api/orders 429, Success: false, err: Do not send more than 2 orders on this market per 200ms +var requestLimit = rate.NewLimiter(rate.Every(220*time.Millisecond), 2) + //go:generate go run generate_symbol_map.go type Exchange struct { @@ -338,6 +342,9 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder if so.TimeInForce != "GTC" && so.TimeInForce != "" { return createdOrders, fmt.Errorf("unsupported TimeInForce %s. only support GTC", so.TimeInForce) } + if err := requestLimit.Wait(ctx); err != nil { + logrus.WithError(err).Error("rate limit error") + } or, err := e.newRest().PlaceOrder(ctx, PlaceOrderPayload{ Market: toLocalSymbol(TrimUpperString(so.Symbol)), Side: TrimLowerString(string(so.Side)), @@ -437,6 +444,9 @@ func sortByCreatedASC(orders []order) { func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error { for _, o := range orders { rest := e.newRest() + if err := requestLimit.Wait(ctx); err != nil { + logrus.WithError(err).Error("rate limit error") + } if len(o.ClientOrderID) > 0 { if _, err := rest.CancelOrderByClientID(ctx, o.ClientOrderID); err != nil { return err diff --git a/pkg/exchange/ftx/stream.go b/pkg/exchange/ftx/stream.go index 20836a534..3e3abcc5d 100644 --- a/pkg/exchange/ftx/stream.go +++ b/pkg/exchange/ftx/stream.go @@ -141,7 +141,9 @@ func (s *Stream) pollKLines(ctx context.Context) { if len(klines) > 0 { // handle mutiple klines, get the latest one - s.EmitKLineClosed(klines[len(klines)-1]) + kline := klines[len(klines)-1] + s.EmitKLine(kline) + s.EmitKLineClosed(kline) } } diff --git a/pkg/exchange/ftx/symbols.go b/pkg/exchange/ftx/symbols.go index 3c604a83c..37c37e64d 100644 --- a/pkg/exchange/ftx/symbols.go +++ b/pkg/exchange/ftx/symbols.go @@ -1,58 +1,62 @@ // Code generated by go generate; DO NOT EDIT. package ftx var symbolMap = map[string]string{ - "1INCH-0625": "1INCH-0625", + "1INCH-1231": "1INCH-1231", "1INCH-PERP": "1INCH-PERP", "1INCHUSD": "1INCH/USD", - "AAPL-0625": "AAPL-0625", + "AAPL-1231": "AAPL-1231", "AAPLUSD": "AAPL/USD", - "AAVE-0625": "AAVE-0625", + "AAVE-1231": "AAVE-1231", "AAVE-PERP": "AAVE-PERP", "AAVEUSD": "AAVE/USD", "AAVEUSDT": "AAVE/USDT", - "ABNB-0625": "ABNB-0625", + "ABNB-1231": "ABNB-1231", "ABNBUSD": "ABNB/USD", - "ACB-0625": "ACB-0625", + "ACB-1231": "ACB-1231", "ACBUSD": "ACB/USD", - "ADA-0625": "ADA-0625", + "ADA-1231": "ADA-1231", "ADA-PERP": "ADA-PERP", "ADABEARUSD": "ADABEAR/USD", "ADABULLUSD": "ADABULL/USD", "ADAHALFUSD": "ADAHALF/USD", "ADAHEDGEUSD": "ADAHEDGE/USD", + "AGLD-PERP": "AGLD-PERP", + "AGLDUSD": "AGLD/USD", "AKROUSD": "AKRO/USD", "AKROUSDT": "AKRO/USDT", "ALCX-PERP": "ALCX-PERP", "ALCXUSD": "ALCX/USD", - "ALGO-0625": "ALGO-0625", + "ALEPHUSD": "ALEPH/USD", + "ALGO-1231": "ALGO-1231", "ALGO-PERP": "ALGO-PERP", "ALGOBEARUSD": "ALGOBEAR/USD", "ALGOBULLUSD": "ALGOBULL/USD", "ALGOHALFUSD": "ALGOHALF/USD", "ALGOHEDGEUSD": "ALGOHEDGE/USD", + "ALICE-PERP": "ALICE-PERP", + "ALICEUSD": "ALICE/USD", "ALPHA-PERP": "ALPHA-PERP", "ALPHAUSD": "ALPHA/USD", - "ALT-0625": "ALT-0625", + "ALT-1231": "ALT-1231", "ALT-PERP": "ALT-PERP", "ALTBEARUSD": "ALTBEAR/USD", "ALTBULLUSD": "ALTBULL/USD", "ALTHALFUSD": "ALTHALF/USD", "ALTHEDGEUSD": "ALTHEDGE/USD", - "AMC-0625": "AMC-0625", + "AMC-1231": "AMC-1231", "AMCUSD": "AMC/USD", - "AMD-0625": "AMD-0625", + "AMD-1231": "AMD-1231", "AMDUSD": "AMD/USD", "AMPL-PERP": "AMPL-PERP", "AMPLUSD": "AMPL/USD", "AMPLUSDT": "AMPL/USDT", - "AMZN-0625": "AMZN-0625", + "AMZN-1231": "AMZN-1231", "AMZNUSD": "AMZN/USD", - "APHA-0625": "APHA-0625", + "APHA-1231": "APHA-1231", "APHAUSD": "APHA/USD", "AR-PERP": "AR-PERP", - "ARKK-0625": "ARKK-0625", + "ARKK-1231": "ARKK-1231", "ARKKUSD": "ARKK/USD", - "ASD-0625": "ASD-0625", "ASD-PERP": "ASD-PERP", "ASDBEARUSD": "ASDBEAR/USD", "ASDBEARUSDT": "ASDBEAR/USDT", @@ -61,7 +65,9 @@ var symbolMap = map[string]string{ "ASDHALFUSD": "ASDHALF/USD", "ASDHEDGEUSD": "ASDHEDGE/USD", "ASDUSD": "ASD/USD", - "ATOM-0625": "ATOM-0625", + "ATLAS-PERP": "ATLAS-PERP", + "ATLASUSD": "ATLAS/USD", + "ATOM-1231": "ATOM-1231", "ATOM-PERP": "ATOM-PERP", "ATOMBEARUSD": "ATOMBEAR/USD", "ATOMBULLUSD": "ATOMBULL/USD", @@ -70,16 +76,16 @@ var symbolMap = map[string]string{ "AUDIO-PERP": "AUDIO-PERP", "AUDIOUSD": "AUDIO/USD", "AUDIOUSDT": "AUDIO/USDT", - "AUDUSD": "AUD/USD", - "AVAX-0625": "AVAX-0625", + "AURYUSD": "AURY/USD", + "AVAX-1231": "AVAX-1231", "AVAX-PERP": "AVAX-PERP", "AXS-PERP": "AXS-PERP", "AXSUSD": "AXS/USD", - "BABA-0625": "BABA-0625", + "BABA-1231": "BABA-1231", "BABAUSD": "BABA/USD", "BADGER-PERP": "BADGER-PERP", "BADGERUSD": "BADGER/USD", - "BAL-0625": "BAL-0625", + "BAL-1231": "BAL-1231", "BAL-PERP": "BAL-PERP", "BALBEARUSD": "BALBEAR/USD", "BALBEARUSDT": "BALBEAR/USDT", @@ -93,11 +99,12 @@ var symbolMap = map[string]string{ "BANDUSD": "BAND/USD", "BAO-PERP": "BAO-PERP", "BAOUSD": "BAO/USD", + "BARUSD": "BAR/USD", "BAT-PERP": "BAT-PERP", "BATUSD": "BAT/USD", - "BB-0625": "BB-0625", + "BB-1231": "BB-1231", "BBUSD": "BB/USD", - "BCH-0625": "BCH-0625", + "BCH-1231": "BCH-1231", "BCH-PERP": "BCH-PERP", "BCHBEARUSD": "BCHBEAR/USD", "BCHBEARUSDT": "BCHBEAR/USDT", @@ -111,11 +118,17 @@ var symbolMap = map[string]string{ "BEARSHITUSD": "BEARSHIT/USD", "BEARUSD": "BEAR/USD", "BEARUSDT": "BEAR/USDT", - "BILI-0625": "BILI-0625", + "BICOUSD": "BICO/USD", + "BILI-1231": "BILI-1231", "BILIUSD": "BILI/USD", - "BITW-0625": "BITW-0625", + "BIT-PERP": "BIT-PERP", + "BITO-1231": "BITO-1231", + "BITOUSD": "BITO/USD", + "BITUSD": "BIT/USD", + "BITW-1231": "BITW-1231", "BITWUSD": "BITW/USD", - "BNB-0625": "BNB-0625", + "BLTUSD": "BLT/USD", + "BNB-1231": "BNB-1231", "BNB-PERP": "BNB-PERP", "BNBBEARUSD": "BNBBEAR/USD", "BNBBEARUSDT": "BNBBEAR/USDT", @@ -128,14 +141,15 @@ var symbolMap = map[string]string{ "BNBUSDT": "BNB/USDT", "BNT-PERP": "BNT-PERP", "BNTUSD": "BNT/USD", - "BNTX-0625": "BNTX-0625", + "BNTX-1231": "BNTX-1231", "BNTXUSD": "BNTX/USD", + "BOBA-PERP": "BOBA-PERP", + "BOBAUSD": "BOBA/USD", "BOLSONARO2022": "BOLSONARO2022", - "BRZ-0625": "BRZ-0625", "BRZ-PERP": "BRZ-PERP", "BRZUSD": "BRZ/USD", "BRZUSDT": "BRZ/USDT", - "BSV-0625": "BSV-0625", + "BSV-1231": "BSV-1231", "BSV-PERP": "BSV-PERP", "BSVBEARUSD": "BSVBEAR/USD", "BSVBEARUSDT": "BSVBEAR/USDT", @@ -143,20 +157,18 @@ var symbolMap = map[string]string{ "BSVBULLUSDT": "BSVBULL/USDT", "BSVHALFUSD": "BSVHALF/USD", "BSVHEDGEUSD": "BSVHEDGE/USD", - "BTC-0625": "BTC-0625", - "BTC-0924": "BTC-0924", + "BTC-0325": "BTC-0325", "BTC-1231": "BTC-1231", - "BTC-MOVE-0526": "BTC-MOVE-0526", - "BTC-MOVE-0527": "BTC-MOVE-0527", - "BTC-MOVE-2021Q2": "BTC-MOVE-2021Q2", - "BTC-MOVE-2021Q3": "BTC-MOVE-2021Q3", + "BTC-MOVE-1209": "BTC-MOVE-1209", + "BTC-MOVE-1210": "BTC-MOVE-1210", "BTC-MOVE-2021Q4": "BTC-MOVE-2021Q4", - "BTC-MOVE-WK-0528": "BTC-MOVE-WK-0528", - "BTC-MOVE-WK-0604": "BTC-MOVE-WK-0604", - "BTC-MOVE-WK-0611": "BTC-MOVE-WK-0611", - "BTC-MOVE-WK-0618": "BTC-MOVE-WK-0618", + "BTC-MOVE-2022Q1": "BTC-MOVE-2022Q1", + "BTC-MOVE-2022Q2": "BTC-MOVE-2022Q2", + "BTC-MOVE-WK-1210": "BTC-MOVE-WK-1210", + "BTC-MOVE-WK-1217": "BTC-MOVE-WK-1217", + "BTC-MOVE-WK-1224": "BTC-MOVE-WK-1224", + "BTC-MOVE-WK-1231": "BTC-MOVE-WK-1231", "BTC-PERP": "BTC-PERP", - "BTCAUD": "BTC/AUD", "BTCBRZ": "BTC/BRZ", "BTCEUR": "BTC/EUR", "BTCTRYB": "BTC/TRYB", @@ -169,20 +181,30 @@ var symbolMap = map[string]string{ "BVOLBTC": "BVOL/BTC", "BVOLUSD": "BVOL/USD", "BVOLUSDT": "BVOL/USDT", - "BYND-0625": "BYND-0625", + "BYND-1231": "BYND-1231", "BYNDUSD": "BYND/USD", + "C98-PERP": "C98-PERP", + "C98USD": "C98/USD", "CADUSD": "CAD/USD", "CAKE-PERP": "CAKE-PERP", - "CEL-0625": "CEL-0625", + "CEL-1231": "CEL-1231", + "CEL-PERP": "CEL-PERP", + "CELBTC": "CEL/BTC", + "CELO-PERP": "CELO-PERP", "CELUSD": "CEL/USD", - "CGC-0625": "CGC-0625", + "CGC-1231": "CGC-1231", "CGCUSD": "CGC/USD", - "CHZ-0625": "CHZ-0625", + "CHR-PERP": "CHR-PERP", + "CHRUSD": "CHR/USD", + "CHZ-1231": "CHZ-1231", "CHZ-PERP": "CHZ-PERP", "CHZUSD": "CHZ/USD", "CHZUSDT": "CHZ/USDT", + "CITYUSD": "CITY/USD", + "CLV-PERP": "CLV-PERP", + "CLVUSD": "CLV/USD", "COINUSD": "COIN/USD", - "COMP-0625": "COMP-0625", + "COMP-1231": "COMP-1231", "COMP-PERP": "COMP-PERP", "COMPBEARUSD": "COMPBEAR/USD", "COMPBEARUSDT": "COMPBEAR/USDT", @@ -195,12 +217,12 @@ var symbolMap = map[string]string{ "CONV-PERP": "CONV-PERP", "CONVUSD": "CONV/USD", "COPEUSD": "COPE/USD", - "CREAM-0625": "CREAM-0625", + "CQTUSD": "CQT/USD", "CREAM-PERP": "CREAM-PERP", "CREAMUSD": "CREAM/USD", "CREAMUSDT": "CREAM/USDT", "CRO-PERP": "CRO-PERP", - "CRON-0625": "CRON-0625", + "CRON-1231": "CRON-1231", "CRONUSD": "CRON/USD", "CROUSD": "CRO/USD", "CRV-PERP": "CRV-PERP", @@ -214,12 +236,14 @@ var symbolMap = map[string]string{ "CUSDTHEDGEUSD": "CUSDTHEDGE/USD", "CUSDTUSD": "CUSDT/USD", "CUSDTUSDT": "CUSDT/USDT", + "CVC-PERP": "CVC-PERP", + "CVCUSD": "CVC/USD", "DAIUSD": "DAI/USD", "DAIUSDT": "DAI/USDT", "DASH-PERP": "DASH-PERP", "DAWN-PERP": "DAWN-PERP", "DAWNUSD": "DAWN/USD", - "DEFI-0625": "DEFI-0625", + "DEFI-1231": "DEFI-1231", "DEFI-PERP": "DEFI-PERP", "DEFIBEARUSD": "DEFIBEAR/USD", "DEFIBEARUSDT": "DEFIBEAR/USDT", @@ -229,12 +253,14 @@ var symbolMap = map[string]string{ "DEFIHEDGEUSD": "DEFIHEDGE/USD", "DENT-PERP": "DENT-PERP", "DENTUSD": "DENT/USD", - "DMG-PERP": "DMG-PERP", + "DFLUSD": "DFL/USD", + "DKNG-1231": "DKNG-1231", + "DKNGUSD": "DKNG/USD", "DMGUSD": "DMG/USD", "DMGUSDT": "DMG/USDT", "DODO-PERP": "DODO-PERP", "DODOUSD": "DODO/USD", - "DOGE-0625": "DOGE-0625", + "DOGE-1231": "DOGE-1231", "DOGE-PERP": "DOGE-PERP", "DOGEBEAR2021USD": "DOGEBEAR2021/USD", "DOGEBTC": "DOGE/BTC", @@ -243,19 +269,26 @@ var symbolMap = map[string]string{ "DOGEHEDGEUSD": "DOGEHEDGE/USD", "DOGEUSD": "DOGE/USD", "DOGEUSDT": "DOGE/USDT", - "DOT-0625": "DOT-0625", + "DOT-1231": "DOT-1231", "DOT-PERP": "DOT-PERP", - "DRGN-0625": "DRGN-0625", + "DRGN-1231": "DRGN-1231", "DRGN-PERP": "DRGN-PERP", "DRGNBEARUSD": "DRGNBEAR/USD", "DRGNBULLUSD": "DRGNBULL/USD", "DRGNHALFUSD": "DRGNHALF/USD", "DRGNHEDGEUSD": "DRGNHEDGE/USD", + "DYDX-PERP": "DYDX-PERP", + "DYDXUSD": "DYDX/USD", + "EDEN-1231": "EDEN-1231", + "EDEN-PERP": "EDEN-PERP", + "EDENUSD": "EDEN/USD", "EGLD-PERP": "EGLD-PERP", "EMBUSD": "EMB/USD", "ENJ-PERP": "ENJ-PERP", "ENJUSD": "ENJ/USD", - "EOS-0625": "EOS-0625", + "ENS-PERP": "ENS-PERP", + "ENSUSD": "ENS/USD", + "EOS-1231": "EOS-1231", "EOS-PERP": "EOS-PERP", "EOSBEARUSD": "EOSBEAR/USD", "EOSBEARUSDT": "EOSBEAR/USDT", @@ -268,18 +301,16 @@ var symbolMap = map[string]string{ "ETCBULLUSD": "ETCBULL/USD", "ETCHALFUSD": "ETCHALF/USD", "ETCHEDGEUSD": "ETCHEDGE/USD", - "ETH-0625": "ETH-0625", - "ETH-0924": "ETH-0924", + "ETH-0325": "ETH-0325", "ETH-1231": "ETH-1231", "ETH-PERP": "ETH-PERP", - "ETHAUD": "ETH/AUD", "ETHBEARUSD": "ETHBEAR/USD", "ETHBEARUSDT": "ETHBEAR/USDT", "ETHBRZ": "ETH/BRZ", "ETHBTC": "ETH/BTC", "ETHBULLUSD": "ETHBULL/USD", "ETHBULLUSDT": "ETHBULL/USDT", - "ETHE-0625": "ETHE-0625", + "ETHE-1231": "ETHE-1231", "ETHEUR": "ETH/EUR", "ETHEUSD": "ETHE/USD", "ETHHALFUSD": "ETHHALF/USD", @@ -287,18 +318,18 @@ var symbolMap = map[string]string{ "ETHUSD": "ETH/USD", "ETHUSDT": "ETH/USDT", "EURUSD": "EUR/USD", - "EXCH-0625": "EXCH-0625", + "EXCH-1231": "EXCH-1231", "EXCH-PERP": "EXCH-PERP", "EXCHBEARUSD": "EXCHBEAR/USD", "EXCHBULLUSD": "EXCHBULL/USD", "EXCHHALFUSD": "EXCHHALF/USD", "EXCHHEDGEUSD": "EXCHHEDGE/USD", - "FB-0625": "FB-0625", + "FB-1231": "FB-1231", "FBUSD": "FB/USD", "FIDA-PERP": "FIDA-PERP", "FIDAUSD": "FIDA/USD", "FIDAUSDT": "FIDA/USDT", - "FIL-0625": "FIL-0625", + "FIL-1231": "FIL-1231", "FIL-PERP": "FIL-PERP", "FLM-PERP": "FLM-PERP", "FLOW-PERP": "FLOW-PERP", @@ -310,21 +341,26 @@ var symbolMap = map[string]string{ "FTTBTC": "FTT/BTC", "FTTUSD": "FTT/USD", "FTTUSDT": "FTT/USDT", + "GALA-PERP": "GALA-PERP", + "GALAUSD": "GALA/USD", + "GALUSD": "GAL/USD", "GBPUSD": "GBP/USD", - "GBTC-0625": "GBTC-0625", + "GBTC-1231": "GBTC-1231", "GBTCUSD": "GBTC/USD", - "GDX-0625": "GDX-0625", - "GDXJ-0625": "GDXJ-0625", + "GDX-1231": "GDX-1231", + "GDXJ-1231": "GDXJ-1231", "GDXJUSD": "GDXJ/USD", "GDXUSD": "GDX/USD", - "GLD-0625": "GLD-0625", + "GENEUSD": "GENE/USD", + "GLD-1231": "GLD-1231", "GLDUSD": "GLD/USD", "GLXYUSD": "GLXY/USD", - "GME-0625": "GME-0625", + "GME-1231": "GME-1231", "GMEUSD": "GME/USD", - "GOOGL-0625": "GOOGL-0625", + "GODSUSD": "GODS/USD", + "GOOGL-1231": "GOOGL-1231", "GOOGLUSD": "GOOGL/USD", - "GRT-0625": "GRT-0625", + "GRT-1231": "GRT-1231", "GRT-PERP": "GRT-PERP", "GRTBEARUSD": "GRTBEAR/USD", "GRTBULLUSD": "GRTBULL/USD", @@ -337,6 +373,7 @@ var symbolMap = map[string]string{ "HEDGEUSD": "HEDGE/USD", "HGETUSD": "HGET/USD", "HGETUSDT": "HGET/USDT", + "HMTUSD": "HMT/USD", "HNT-PERP": "HNT-PERP", "HNTUSD": "HNT/USD", "HNTUSDT": "HNT/USDT", @@ -358,7 +395,11 @@ var symbolMap = map[string]string{ "IBVOLUSD": "IBVOL/USD", "IBVOLUSDT": "IBVOL/USDT", "ICP-PERP": "ICP-PERP", + "ICX-PERP": "ICX-PERP", + "IMXUSD": "IMX/USD", + "INTERUSD": "INTER/USD", "IOTA-PERP": "IOTA-PERP", + "JETUSD": "JET/USD", "JSTUSD": "JST/USD", "KAVA-PERP": "KAVA-PERP", "KIN-PERP": "KIN-PERP", @@ -372,8 +413,9 @@ var symbolMap = map[string]string{ "KNCHEDGEUSD": "KNCHEDGE/USD", "KNCUSD": "KNC/USD", "KNCUSDT": "KNC/USDT", + "KSHIB-PERP": "KSHIB-PERP", + "KSHIBUSD": "KSHIB/USD", "KSM-PERP": "KSM-PERP", - "LB-0812": "LB-0812", "LEO-PERP": "LEO-PERP", "LEOBEARUSD": "LEOBEAR/USD", "LEOBULLUSD": "LEOBULL/USD", @@ -382,7 +424,7 @@ var symbolMap = map[string]string{ "LEOUSD": "LEO/USD", "LINA-PERP": "LINA-PERP", "LINAUSD": "LINA/USD", - "LINK-0625": "LINK-0625", + "LINK-1231": "LINK-1231", "LINK-PERP": "LINK-PERP", "LINKBEARUSD": "LINKBEAR/USD", "LINKBEARUSDT": "LINKBEAR/USDT", @@ -395,7 +437,7 @@ var symbolMap = map[string]string{ "LINKUSDT": "LINK/USDT", "LRC-PERP": "LRC-PERP", "LRCUSD": "LRC/USD", - "LTC-0625": "LTC-0625", + "LTC-1231": "LTC-1231", "LTC-PERP": "LTC-PERP", "LTCBEARUSD": "LTCBEAR/USD", "LTCBEARUSDT": "LTCBEAR/USDT", @@ -409,6 +451,8 @@ var symbolMap = map[string]string{ "LUAUSD": "LUA/USD", "LUAUSDT": "LUA/USDT", "LUNA-PERP": "LUNA-PERP", + "MANA-PERP": "MANA-PERP", + "MANAUSD": "MANA/USD", "MAPS-PERP": "MAPS-PERP", "MAPSUSD": "MAPS/USD", "MAPSUSDT": "MAPS/USDT", @@ -416,15 +460,18 @@ var symbolMap = map[string]string{ "MATHUSDT": "MATH/USDT", "MATIC-PERP": "MATIC-PERP", "MATICBEAR2021USD": "MATICBEAR2021/USD", + "MATICBTC": "MATIC/BTC", "MATICBULLUSD": "MATICBULL/USD", "MATICHALFUSD": "MATICHALF/USD", "MATICHEDGEUSD": "MATICHEDGE/USD", "MATICUSD": "MATIC/USD", + "MCB-PERP": "MCB-PERP", + "MCBUSD": "MCB/USD", "MEDIA-PERP": "MEDIA-PERP", "MEDIAUSD": "MEDIA/USD", "MER-PERP": "MER-PERP", "MERUSD": "MER/USD", - "MID-0625": "MID-0625", + "MID-1231": "MID-1231", "MID-PERP": "MID-PERP", "MIDBEARUSD": "MIDBEAR/USD", "MIDBULLUSD": "MIDBULL/USD", @@ -435,45 +482,49 @@ var symbolMap = map[string]string{ "MKRBULLUSD": "MKRBULL/USD", "MKRUSD": "MKR/USD", "MKRUSDT": "MKR/USDT", + "MNGO-PERP": "MNGO-PERP", + "MNGOUSD": "MNGO/USD", "MOBUSD": "MOB/USD", "MOBUSDT": "MOB/USDT", - "MRNA-0625": "MRNA-0625", + "MRNA-1231": "MRNA-1231", "MRNAUSD": "MRNA/USD", - "MSTR-0625": "MSTR-0625", + "MSOLUSD": "MSOL/USD", + "MSTR-1231": "MSTR-1231", "MSTRUSD": "MSTR/USD", "MTA-PERP": "MTA-PERP", "MTAUSD": "MTA/USD", "MTAUSDT": "MTA/USDT", "MTL-PERP": "MTL-PERP", "MTLUSD": "MTL/USD", + "MVDA10-PERP": "MVDA10-PERP", + "MVDA25-PERP": "MVDA25-PERP", "NEAR-PERP": "NEAR-PERP", "NEO-PERP": "NEO-PERP", - "NFLX-0625": "NFLX-0625", + "NFLX-1231": "NFLX-1231", "NFLXUSD": "NFLX/USD", - "NIO-0625": "NIO-0625", + "NIO-1231": "NIO-1231", "NIOUSD": "NIO/USD", - "NOK-0625": "NOK-0625", + "NOK-1231": "NOK-1231", "NOKUSD": "NOK/USD", - "NVDA-0625": "NVDA-0625", + "NVDA-1231": "NVDA-1231", "NVDAUSD": "NVDA/USD", - "OKB-0625": "OKB-0625", + "OKB-1231": "OKB-1231", "OKB-PERP": "OKB-PERP", "OKBBEARUSD": "OKBBEAR/USD", "OKBBULLUSD": "OKBBULL/USD", "OKBHALFUSD": "OKBHALF/USD", "OKBHEDGEUSD": "OKBHEDGE/USD", "OKBUSD": "OKB/USD", - "OLY2021": "OLY2021", - "OMG-0625": "OMG-0625", + "OMG-1231": "OMG-1231", "OMG-PERP": "OMG-PERP", "OMGUSD": "OMG/USD", + "ONE-PERP": "ONE-PERP", "ONT-PERP": "ONT-PERP", "ORBS-PERP": "ORBS-PERP", "ORBSUSD": "ORBS/USD", "OXY-PERP": "OXY-PERP", "OXYUSD": "OXY/USD", "OXYUSDT": "OXY/USDT", - "PAXG-0625": "PAXG-0625", "PAXG-PERP": "PAXG-PERP", "PAXGBEARUSD": "PAXGBEAR/USD", "PAXGBULLUSD": "PAXGBULL/USD", @@ -481,13 +532,16 @@ var symbolMap = map[string]string{ "PAXGHEDGEUSD": "PAXGHEDGE/USD", "PAXGUSD": "PAXG/USD", "PAXGUSDT": "PAXG/USDT", - "PENN-0625": "PENN-0625", + "PENN-1231": "PENN-1231", "PENNUSD": "PENN/USD", "PERP-PERP": "PERP-PERP", "PERPUSD": "PERP/USD", - "PFE-0625": "PFE-0625", + "PFE-1231": "PFE-1231", "PFEUSD": "PFE/USD", - "PRIV-0625": "PRIV-0625", + "POLIS-PERP": "POLIS-PERP", + "POLISUSD": "POLIS/USD", + "PORTUSD": "PORT/USD", + "PRIV-1231": "PRIV-1231", "PRIV-PERP": "PRIV-PERP", "PRIVBEARUSD": "PRIVBEAR/USD", "PRIVBULLUSD": "PRIVBULL/USD", @@ -495,20 +549,24 @@ var symbolMap = map[string]string{ "PRIVHEDGEUSD": "PRIVHEDGE/USD", "PROM-PERP": "PROM-PERP", "PROMUSD": "PROM/USD", + "PSGUSD": "PSG/USD", + "PTUUSD": "PTU/USD", "PUNDIX-PERP": "PUNDIX-PERP", "PUNDIXUSD": "PUNDIX/USD", - "PYPL-0625": "PYPL-0625", + "PYPL-1231": "PYPL-1231", "PYPLUSD": "PYPL/USD", "QTUM-PERP": "QTUM-PERP", "RAMP-PERP": "RAMP-PERP", "RAMPUSD": "RAMP/USD", "RAY-PERP": "RAY-PERP", "RAYUSD": "RAY/USD", - "REEF-0625": "REEF-0625", + "REEF-1231": "REEF-1231", "REEF-PERP": "REEF-PERP", "REEFUSD": "REEF/USD", "REN-PERP": "REN-PERP", "RENUSD": "REN/USD", + "RNDR-PERP": "RNDR-PERP", + "RNDRUSD": "RNDR/USD", "ROOK-PERP": "ROOK-PERP", "ROOKUSD": "ROOK/USD", "ROOKUSDT": "ROOK/USDT", @@ -522,61 +580,73 @@ var symbolMap = map[string]string{ "SC-PERP": "SC-PERP", "SECO-PERP": "SECO-PERP", "SECOUSD": "SECO/USD", - "SGDUSD": "SGD/USD", "SHIB-PERP": "SHIB-PERP", "SHIBUSD": "SHIB/USD", - "SHIT-0625": "SHIT-0625", + "SHIT-1231": "SHIT-1231", "SHIT-PERP": "SHIT-PERP", "SKL-PERP": "SKL-PERP", "SKLUSD": "SKL/USD", - "SLV-0625": "SLV-0625", + "SLNDUSD": "SLND/USD", + "SLP-PERP": "SLP-PERP", + "SLPUSD": "SLP/USD", + "SLRSUSD": "SLRS/USD", + "SLV-1231": "SLV-1231", "SLVUSD": "SLV/USD", "SNX-PERP": "SNX-PERP", "SNXUSD": "SNX/USD", - "SOL-0625": "SOL-0625", + "SNYUSD": "SNY/USD", + "SOL-1231": "SOL-1231", "SOL-PERP": "SOL-PERP", "SOLBTC": "SOL/BTC", "SOLUSD": "SOL/USD", "SOLUSDT": "SOL/USDT", - "SPY-0625": "SPY-0625", + "SPELL-PERP": "SPELL-PERP", + "SPELLUSD": "SPELL/USD", + "SPY-1231": "SPY-1231", "SPYUSD": "SPY/USD", - "SQ-0625": "SQ-0625", + "SQ-1231": "SQ-1231", "SQUSD": "SQ/USD", "SRM-PERP": "SRM-PERP", "SRMUSD": "SRM/USD", "SRMUSDT": "SRM/USDT", "SRN-PERP": "SRN-PERP", + "STARSUSD": "STARS/USD", "STEP-PERP": "STEP-PERP", "STEPUSD": "STEP/USD", + "STETHUSD": "STETH/USD", "STMX-PERP": "STMX-PERP", "STMXUSD": "STMX/USD", "STORJ-PERP": "STORJ-PERP", "STORJUSD": "STORJ/USD", + "STSOLUSD": "STSOL/USD", "STX-PERP": "STX-PERP", "SUNUSD": "SUN/USD", - "SUSHI-0625": "SUSHI-0625", + "SUSHI-1231": "SUSHI-1231", "SUSHI-PERP": "SUSHI-PERP", "SUSHIBEARUSD": "SUSHIBEAR/USD", "SUSHIBTC": "SUSHI/BTC", "SUSHIBULLUSD": "SUSHIBULL/USD", "SUSHIUSD": "SUSHI/USD", "SUSHIUSDT": "SUSHI/USDT", - "SXP-0625": "SXP-0625", + "SXP-1231": "SXP-1231", "SXP-PERP": "SXP-PERP", "SXPBEARUSD": "SXPBEAR/USD", + "SXPBTC": "SXP/BTC", "SXPBULLUSD": "SXPBULL/USD", "SXPHALFUSD": "SXPHALF/USD", "SXPHALFUSDT": "SXPHALF/USDT", "SXPHEDGEUSD": "SXPHEDGE/USD", "SXPUSD": "SXP/USD", "SXPUSDT": "SXP/USDT", - "THETA-0625": "THETA-0625", + "THETA-1231": "THETA-1231", "THETA-PERP": "THETA-PERP", "THETABEARUSD": "THETABEAR/USD", "THETABULLUSD": "THETABULL/USD", "THETAHALFUSD": "THETAHALF/USD", "THETAHEDGEUSD": "THETAHEDGE/USD", - "TLRY-0625": "TLRY-0625", + "TLM-PERP": "TLM-PERP", + "TLMUSD": "TLM/USD", + "TLRY-1231": "TLRY-1231", "TLRYUSD": "TLRY/USD", "TOMO-PERP": "TOMO-PERP", "TOMOBEAR2021USD": "TOMOBEAR2021/USD", @@ -585,55 +655,58 @@ var symbolMap = map[string]string{ "TOMOHEDGEUSD": "TOMOHEDGE/USD", "TOMOUSD": "TOMO/USD", "TOMOUSDT": "TOMO/USDT", - "TRU-0625": "TRU-0625", + "TONCOIN-PERP": "TONCOIN-PERP", + "TONCOINUSD": "TONCOIN/USD", "TRU-PERP": "TRU-PERP", "TRUMP2024": "TRUMP2024", "TRUUSD": "TRU/USD", "TRUUSDT": "TRU/USDT", - "TRX-0625": "TRX-0625", + "TRX-1231": "TRX-1231", "TRX-PERP": "TRX-PERP", "TRXBEARUSD": "TRXBEAR/USD", + "TRXBTC": "TRX/BTC", "TRXBULLUSD": "TRXBULL/USD", "TRXHALFUSD": "TRXHALF/USD", "TRXHEDGEUSD": "TRXHEDGE/USD", "TRXUSD": "TRX/USD", "TRXUSDT": "TRX/USDT", - "TRYB-0625": "TRYB-0625", "TRYB-PERP": "TRYB-PERP", "TRYBBEARUSD": "TRYBBEAR/USD", "TRYBBULLUSD": "TRYBBULL/USD", "TRYBHALFUSD": "TRYBHALF/USD", "TRYBHEDGEUSD": "TRYBHEDGE/USD", "TRYBUSD": "TRYB/USD", - "TSLA-0625": "TSLA-0625", + "TSLA-1231": "TSLA-1231", "TSLABTC": "TSLA/BTC", "TSLADOGE": "TSLA/DOGE", "TSLAUSD": "TSLA/USD", - "TSM-0625": "TSM-0625", + "TSM-1231": "TSM-1231", "TSMUSD": "TSM/USD", - "TWTR-0625": "TWTR-0625", + "TULIP-PERP": "TULIP-PERP", + "TULIPUSD": "TULIP/USD", + "TWTR-1231": "TWTR-1231", "TWTRUSD": "TWTR/USD", - "UBER-0625": "UBER-0625", + "UBER-1231": "UBER-1231", "UBERUSD": "UBER/USD", "UBXTUSD": "UBXT/USD", "UBXTUSDT": "UBXT/USDT", - "UNI-0625": "UNI-0625", + "UNI-1231": "UNI-1231", "UNI-PERP": "UNI-PERP", "UNIBTC": "UNI/BTC", - "UNISWAP-0625": "UNISWAP-0625", + "UNISWAP-1231": "UNISWAP-1231", "UNISWAP-PERP": "UNISWAP-PERP", "UNISWAPBEARUSD": "UNISWAPBEAR/USD", "UNISWAPBULLUSD": "UNISWAPBULL/USD", "UNIUSD": "UNI/USD", "UNIUSDT": "UNI/USDT", - "USDT-0625": "USDT-0625", + "USDT-1231": "USDT-1231", "USDT-PERP": "USDT-PERP", "USDTBEARUSD": "USDTBEAR/USD", "USDTBULLUSD": "USDTBULL/USD", "USDTHALFUSD": "USDTHALF/USD", "USDTHEDGEUSD": "USDTHEDGE/USD", "USDTUSD": "USDT/USD", - "USO-0625": "USO-0625", + "USO-1231": "USO-1231", "USOUSD": "USO/USD", "VET-PERP": "VET-PERP", "VETBEARUSD": "VETBEAR/USD", @@ -641,14 +714,17 @@ var symbolMap = map[string]string{ "VETBULLUSD": "VETBULL/USD", "VETBULLUSDT": "VETBULL/USDT", "VETHEDGEUSD": "VETHEDGE/USD", - "WAVES-0625": "WAVES-0625", + "VGXUSD": "VGX/USD", + "WAVES-1231": "WAVES-1231", "WAVES-PERP": "WAVES-PERP", "WAVESUSD": "WAVES/USD", + "WBTCBTC": "WBTC/BTC", "WBTCUSD": "WBTC/USD", + "WNDRUSD": "WNDR/USD", "WRXUSD": "WRX/USD", "WRXUSDT": "WRX/USDT", - "WSB-0625": "WSB-0625", - "XAUT-0625": "XAUT-0625", + "WSB-1231": "WSB-1231", + "XAUT-1231": "XAUT-1231", "XAUT-PERP": "XAUT-PERP", "XAUTBEARUSD": "XAUTBEAR/USD", "XAUTBULLUSD": "XAUTBULL/USD", @@ -661,7 +737,7 @@ var symbolMap = map[string]string{ "XLMBEARUSD": "XLMBEAR/USD", "XLMBULLUSD": "XLMBULL/USD", "XMR-PERP": "XMR-PERP", - "XRP-0625": "XRP-0625", + "XRP-1231": "XRP-1231", "XRP-PERP": "XRP-PERP", "XRPBEARUSD": "XRPBEAR/USD", "XRPBEARUSDT": "XRPBEAR/USDT", @@ -672,7 +748,7 @@ var symbolMap = map[string]string{ "XRPHEDGEUSD": "XRPHEDGE/USD", "XRPUSD": "XRP/USD", "XRPUSDT": "XRP/USDT", - "XTZ-0625": "XTZ-0625", + "XTZ-1231": "XTZ-1231", "XTZ-PERP": "XTZ-PERP", "XTZBEARUSD": "XTZBEAR/USD", "XTZBEARUSDT": "XTZBEAR/USDT", @@ -680,7 +756,7 @@ var symbolMap = map[string]string{ "XTZBULLUSDT": "XTZBULL/USDT", "XTZHALFUSD": "XTZHALF/USD", "XTZHEDGEUSD": "XTZHEDGE/USD", - "YFI-0625": "YFI-0625", + "YFI-1231": "YFI-1231", "YFI-PERP": "YFI-PERP", "YFIBTC": "YFI/BTC", "YFII-PERP": "YFII-PERP", @@ -691,7 +767,7 @@ var symbolMap = map[string]string{ "ZECBEARUSD": "ZECBEAR/USD", "ZECBULLUSD": "ZECBULL/USD", "ZIL-PERP": "ZIL-PERP", - "ZM-0625": "ZM-0625", + "ZM-1231": "ZM-1231", "ZMUSD": "ZM/USD", "ZRX-PERP": "ZRX-PERP", "ZRXUSD": "ZRX/USD", diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index cfa49056a..63626dfd1 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -28,7 +28,10 @@ type Exchange struct { func New(key, secret, passphrase string) *Exchange { client := okexapi.NewClient() - client.Auth(key, secret, passphrase) + + if len(key) > 0 && len(secret) > 0 { + client.Auth(key, secret, passphrase) + } return &Exchange{ key: key, diff --git a/pkg/migrations/mysql/20211205162043_add_is_futures_column.go b/pkg/migrations/mysql/20211205162043_add_is_futures_column.go new file mode 100644 index 000000000..1ee5ac34b --- /dev/null +++ b/pkg/migrations/mysql/20211205162043_add_is_futures_column.go @@ -0,0 +1,44 @@ +package mysql + +import ( + "context" + + "github.com/c9s/rockhopper" +) + +func init() { + AddMigration(upAddIsFuturesColumn, downAddIsFuturesColumn) + +} + +func upAddIsFuturesColumn(ctx context.Context, tx rockhopper.SQLExecutor) (err error) { + // This code is executed when the migration is applied. + + _, err = tx.ExecContext(ctx, "ALTER TABLE `trades` ADD COLUMN `is_futures` BOOLEAN NOT NULL DEFAULT FALSE;") + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "ALTER TABLE `orders` ADD COLUMN `is_futures` BOOLEAN NOT NULL DEFAULT FALSE;") + if err != nil { + return err + } + + return err +} + +func downAddIsFuturesColumn(ctx context.Context, tx rockhopper.SQLExecutor) (err error) { + // This code is executed when the migration is rolled back. + + _, err = tx.ExecContext(ctx, "ALTER TABLE `trades` DROP COLUMN `is_futures`;") + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "ALTER TABLE `orders` DROP COLUMN `is_futures`;") + if err != nil { + return err + } + + return err +} diff --git a/pkg/migrations/sqlite3/20211205162302_add_is_futures_column.go b/pkg/migrations/sqlite3/20211205162302_add_is_futures_column.go new file mode 100644 index 000000000..7970df098 --- /dev/null +++ b/pkg/migrations/sqlite3/20211205162302_add_is_futures_column.go @@ -0,0 +1,44 @@ +package sqlite3 + +import ( + "context" + + "github.com/c9s/rockhopper" +) + +func init() { + AddMigration(upAddIsFuturesColumn, downAddIsFuturesColumn) + +} + +func upAddIsFuturesColumn(ctx context.Context, tx rockhopper.SQLExecutor) (err error) { + // This code is executed when the migration is applied. + + _, err = tx.ExecContext(ctx, "ALTER TABLE `trades` ADD COLUMN `is_futures` BOOLEAN NOT NULL DEFAULT FALSE;") + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "ALTER TABLE `orders` ADD COLUMN `is_futures` BOOLEAN NOT NULL DEFAULT FALSE;") + if err != nil { + return err + } + + return err +} + +func downAddIsFuturesColumn(ctx context.Context, tx rockhopper.SQLExecutor) (err error) { + // This code is executed when the migration is rolled back. + + _, err = tx.ExecContext(ctx, "ALTER TABLE `trades` RENAME COLUMN `is_futures` TO `is_futures_deleted`;") + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "ALTER TABLE `orders` RENAME COLUMN `is_futures` TO `is_futures_deleted`;") + if err != nil { + return err + } + + return err +} diff --git a/pkg/service/order.go b/pkg/service/order.go index 04753d304..85070ad32 100644 --- a/pkg/service/order.go +++ b/pkg/service/order.go @@ -20,7 +20,9 @@ type OrderService struct { func (s *OrderService) Sync(ctx context.Context, exchange types.Exchange, symbol string, startTime time.Time) error { isMargin := false + isFutures := false isIsolated := false + if marginExchange, ok := exchange.(types.MarginExchange); ok { marginSettings := marginExchange.GetMarginSettings() isMargin = marginSettings.IsMargin @@ -30,7 +32,17 @@ func (s *OrderService) Sync(ctx context.Context, exchange types.Exchange, symbol } } - records, err := s.QueryLast(exchange.Name(), symbol, isMargin, isIsolated, 50) + if futuresExchange, ok := exchange.(types.FuturesExchange); ok { + futuresSettings := futuresExchange.GetFuturesSettings() + isFutures = futuresSettings.IsFutures + isIsolated = futuresSettings.IsIsolatedFutures + if futuresSettings.IsIsolatedFutures { + symbol = futuresSettings.IsolatedFuturesSymbol + } + } + + + records, err := s.QueryLast(exchange.Name(), symbol, isMargin, isFutures, isIsolated, 50) if err != nil { return err } @@ -78,14 +90,15 @@ func (s *OrderService) Sync(ctx context.Context, exchange types.Exchange, symbol // QueryLast queries the last order from the database -func (s *OrderService) QueryLast(ex types.ExchangeName, symbol string, isMargin, isIsolated bool, limit int) ([]types.Order, error) { - log.Infof("querying last order exchange = %s AND symbol = %s AND is_margin = %v AND is_isolated = %v", ex, symbol, isMargin, isIsolated) +func (s *OrderService) QueryLast(ex types.ExchangeName, symbol string, isMargin, isFutures, isIsolated bool, limit int) ([]types.Order, error) { + log.Infof("querying last order exchange = %s AND symbol = %s AND is_margin = %v AND is_futures = %v AND is_isolated = %v", ex, symbol, isMargin, isFutures, isIsolated) - sql := `SELECT * FROM orders WHERE exchange = :exchange AND symbol = :symbol AND is_margin = :is_margin AND is_isolated = :is_isolated ORDER BY gid DESC LIMIT :limit` + sql := `SELECT * FROM orders WHERE exchange = :exchange AND symbol = :symbol AND is_margin = :is_margin AND is_futures = :is_futures AND is_isolated = :is_isolated ORDER BY gid DESC LIMIT :limit` rows, err := s.DB.NamedQuery(sql, map[string]interface{}{ "exchange": ex, "symbol": symbol, "is_margin": isMargin, + "is_futures": isFutures, "is_isolated": isIsolated, "limit": limit, }) @@ -195,15 +208,15 @@ func (s *OrderService) scanRows(rows *sqlx.Rows) (orders []types.Order, err erro func (s *OrderService) Insert(order types.Order) (err error) { if s.DB.DriverName() == "mysql" { _, err = s.DB.NamedExec(` - INSERT INTO orders (exchange, order_id, client_order_id, order_type, status, symbol, price, stop_price, quantity, executed_quantity, side, is_working, time_in_force, created_at, updated_at, is_margin, is_isolated) - VALUES (:exchange, :order_id, :client_order_id, :order_type, :status, :symbol, :price, :stop_price, :quantity, :executed_quantity, :side, :is_working, :time_in_force, :created_at, :updated_at, :is_margin, :is_isolated) + INSERT INTO orders (exchange, order_id, client_order_id, order_type, status, symbol, price, stop_price, quantity, executed_quantity, side, is_working, time_in_force, created_at, updated_at, is_margin, is_futures, is_isolated) + VALUES (:exchange, :order_id, :client_order_id, :order_type, :status, :symbol, :price, :stop_price, :quantity, :executed_quantity, :side, :is_working, :time_in_force, :created_at, :updated_at, :is_margin, :is_futures, :is_isolated) ON DUPLICATE KEY UPDATE status=:status, executed_quantity=:executed_quantity, is_working=:is_working, updated_at=:updated_at`, order) return err } _, err = s.DB.NamedExec(` - INSERT INTO orders (exchange, order_id, client_order_id, order_type, status, symbol, price, stop_price, quantity, executed_quantity, side, is_working, time_in_force, created_at, updated_at, is_margin, is_isolated) - VALUES (:exchange, :order_id, :client_order_id, :order_type, :status, :symbol, :price, :stop_price, :quantity, :executed_quantity, :side, :is_working, :time_in_force, :created_at, :updated_at, :is_margin, :is_isolated) + INSERT INTO orders (exchange, order_id, client_order_id, order_type, status, symbol, price, stop_price, quantity, executed_quantity, side, is_working, time_in_force, created_at, updated_at, is_margin, is_futures, is_isolated) + VALUES (:exchange, :order_id, :client_order_id, :order_type, :status, :symbol, :price, :stop_price, :quantity, :executed_quantity, :side, :is_working, :time_in_force, :created_at, :updated_at, :is_margin, :is_futures, :is_isolated) `, order) return err diff --git a/pkg/service/trade.go b/pkg/service/trade.go index f089b8e15..45ad9049c 100644 --- a/pkg/service/trade.go +++ b/pkg/service/trade.go @@ -52,7 +52,9 @@ func NewTradeService(db *sqlx.DB) *TradeService { func (s *TradeService) Sync(ctx context.Context, exchange types.Exchange, symbol string) error { isMargin := false + isFutures := false isIsolated := false + if marginExchange, ok := exchange.(types.MarginExchange); ok { marginSettings := marginExchange.GetMarginSettings() isMargin = marginSettings.IsMargin @@ -62,8 +64,18 @@ func (s *TradeService) Sync(ctx context.Context, exchange types.Exchange, symbol } } + if futuresExchange, ok := exchange.(types.FuturesExchange); ok { + futuresSettings := futuresExchange.GetFuturesSettings() + isFutures = futuresSettings.IsFutures + isIsolated = futuresSettings.IsIsolatedFutures + if futuresSettings.IsIsolatedFutures { + symbol = futuresSettings.IsolatedFuturesSymbol + } + } + + // records descending ordered - records, err := s.QueryLast(exchange.Name(), symbol, isMargin, isIsolated, 50) + records, err := s.QueryLast(exchange.Name(), symbol, isMargin, isFutures, isIsolated, 50) if err != nil { return err } @@ -265,14 +277,15 @@ func generateMysqlTradingVolumeQuerySQL(options TradingVolumeQueryOptions) strin } // QueryLast queries the last trade from the database -func (s *TradeService) QueryLast(ex types.ExchangeName, symbol string, isMargin, isIsolated bool, limit int) ([]types.Trade, error) { - log.Debugf("querying last trade exchange = %s AND symbol = %s AND is_margin = %v AND is_isolated = %v", ex, symbol, isMargin, isIsolated) +func (s *TradeService) QueryLast(ex types.ExchangeName, symbol string, isMargin, isFutures, isIsolated bool, limit int) ([]types.Trade, error) { + log.Debugf("querying last trade exchange = %s AND symbol = %s AND is_margin = %v AND is_futures = %v AND is_isolated = %v", ex, symbol, isMargin, isFutures, isIsolated) - sql := "SELECT * FROM trades WHERE exchange = :exchange AND symbol = :symbol AND is_margin = :is_margin AND is_isolated = :is_isolated ORDER BY gid DESC LIMIT :limit" + sql := "SELECT * FROM trades WHERE exchange = :exchange AND symbol = :symbol AND is_margin = :is_margin AND is_futures = :is_futures AND is_isolated = :is_isolated ORDER BY gid DESC LIMIT :limit" rows, err := s.DB.NamedQuery(sql, map[string]interface{}{ "symbol": symbol, "exchange": ex, "is_margin": isMargin, + "is_futures": isFutures, "is_isolated": isIsolated, "limit": limit, }) @@ -439,8 +452,8 @@ func (s *TradeService) scanRows(rows *sqlx.Rows) (trades []types.Trade, err erro func (s *TradeService) Insert(trade types.Trade) error { _, err := s.DB.NamedExec(` - INSERT INTO trades (id, exchange, order_id, symbol, price, quantity, quote_quantity, side, is_buyer, is_maker, fee, fee_currency, traded_at, is_margin, is_isolated) - VALUES (:id, :exchange, :order_id, :symbol, :price, :quantity, :quote_quantity, :side, :is_buyer, :is_maker, :fee, :fee_currency, :traded_at, :is_margin, :is_isolated)`, + INSERT INTO trades (id, exchange, order_id, symbol, price, quantity, quote_quantity, side, is_buyer, is_maker, fee, fee_currency, traded_at, is_margin, is_futures, is_isolated) + VALUES (:id, :exchange, :order_id, :symbol, :price, :quantity, :quote_quantity, :side, :is_buyer, :is_maker, :fee, :fee_currency, :traded_at, :is_margin, :is_futures, :is_isolated)`, trade) return err } diff --git a/pkg/strategy/techsignal/strategy.go b/pkg/strategy/techsignal/strategy.go index 6f6d4d2fe..53c23d56e 100644 --- a/pkg/strategy/techsignal/strategy.go +++ b/pkg/strategy/techsignal/strategy.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" - "github.com/c9s/bbgo/pkg/exchange/binance" - "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/sirupsen/logrus" "math" "strings" "time" + "github.com/c9s/bbgo/pkg/exchange/binance" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/sirupsen/logrus" + "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/types" ) @@ -87,7 +88,7 @@ func (s *Strategy) Validate() error { } func (s *Strategy) listenToFundingRate(ctx context.Context, exchange *binance.Exchange) { - var previousIndex, fundingRate24HoursLowIndex *binance.PremiumIndex + var previousIndex, fundingRate24HoursLowIndex *types.PremiumIndex fundingRateTicker := time.NewTicker(1 * time.Hour) defer fundingRateTicker.Stop() diff --git a/pkg/types/account.go b/pkg/types/account.go index 74c48b493..c703a209b 100644 --- a/pkg/types/account.go +++ b/pkg/types/account.go @@ -225,7 +225,7 @@ func (a *Account) Balance(currency string) (balance Balance, ok bool) { return balance, ok } -func (a *Account) AddBalance(currency string, fund fixedpoint.Value) error { +func (a *Account) AddBalance(currency string, fund fixedpoint.Value) { a.Lock() defer a.Unlock() @@ -233,7 +233,7 @@ func (a *Account) AddBalance(currency string, fund fixedpoint.Value) error { if ok { balance.Available += fund a.balances[currency] = balance - return nil + return } a.balances[currency] = Balance{ @@ -241,7 +241,6 @@ func (a *Account) AddBalance(currency string, fund fixedpoint.Value) error { Available: fund, Locked: 0, } - return nil } func (a *Account) UseLockedBalance(currency string, fund fixedpoint.Value) error { diff --git a/pkg/types/account_test.go b/pkg/types/account_test.go index b46cc18c1..eecbf4275 100644 --- a/pkg/types/account_test.go +++ b/pkg/types/account_test.go @@ -10,9 +10,9 @@ import ( func TestAccountLockAndUnlock(t *testing.T) { a := NewAccount() - err := a.AddBalance("USDT", 1000) - assert.NoError(t, err) + a.AddBalance("USDT", 1000) + var err error balance, ok := a.Balance("USDT") assert.True(t, ok) assert.Equal(t, balance.Available, fixedpoint.Value(1000)) @@ -36,9 +36,9 @@ func TestAccountLockAndUnlock(t *testing.T) { func TestAccountLockAndUse(t *testing.T) { a := NewAccount() - err := a.AddBalance("USDT", 1000) - assert.NoError(t, err) + a.AddBalance("USDT", 1000) + var err error balance, ok := a.Balance("USDT") assert.True(t, ok) assert.Equal(t, balance.Available, fixedpoint.Value(1000)) diff --git a/pkg/types/fundingrate.go b/pkg/types/fundingrate.go new file mode 100644 index 000000000..3b99bc739 --- /dev/null +++ b/pkg/types/fundingrate.go @@ -0,0 +1,13 @@ +package types + +import ( + "time" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +type FundingRate struct { + FundingRate fixedpoint.Value + FundingTime time.Time + Time time.Time +} diff --git a/pkg/types/order.go b/pkg/types/order.go index 06a5aa662..32321ccb5 100644 --- a/pkg/types/order.go +++ b/pkg/types/order.go @@ -114,6 +114,10 @@ type SubmitOrder struct { GroupID uint32 `json:"groupID,omitempty"` MarginSideEffect MarginOrderSideEffectType `json:"marginSideEffect,omitempty"` // AUTO_REPAY = repay, MARGIN_BUY = borrow, defaults to NO_SIDE_EFFECT + + // futures order fields + ReduceOnly bool `json:"reduceOnly" db:"reduce_only"` + ClosePosition bool `json:"closePosition" db:"close_position"` } func (o *SubmitOrder) String() string { diff --git a/pkg/types/premiumindex.go b/pkg/types/premiumindex.go new file mode 100644 index 000000000..c9ffcd0aa --- /dev/null +++ b/pkg/types/premiumindex.go @@ -0,0 +1,15 @@ +package types + +import ( + "time" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +type PremiumIndex struct { + Symbol string `json:"symbol"` + MarkPrice fixedpoint.Value `json:"markPrice"` + LastFundingRate fixedpoint.Value `json:"lastFundingRate"` + NextFundingTime time.Time `json:"nextFundingTime"` + Time time.Time `json:"time"` +} diff --git a/pkg/types/trade.go b/pkg/types/trade.go index 1eca4b961..f29741078 100644 --- a/pkg/types/trade.go +++ b/pkg/types/trade.go @@ -66,6 +66,7 @@ type Trade struct { FeeCurrency string `json:"feeCurrency" db:"fee_currency"` IsMargin bool `json:"isMargin" db:"is_margin"` + IsFutures bool `json:"isFutures" db:"is_futures"` IsIsolated bool `json:"isIsolated" db:"is_isolated"` StrategyID sql.NullString `json:"strategyID" db:"strategy"` diff --git a/pkg/version/version.go b/pkg/version/version.go index 50694613a..01bd8bbca 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -2,7 +2,7 @@ package version -const Version = "v1.19.4-5b11ef81" +const Version = "v1.20.0-c1e69194" -const VersionGitRef = "5b11ef81" +const VersionGitRef = "c1e69194" diff --git a/utils/generate-version-file.sh b/utils/generate-version-file.sh index 7d5a65f57..8278d78e7 100755 --- a/utils/generate-version-file.sh +++ b/utils/generate-version-file.sh @@ -7,6 +7,10 @@ if [[ -z $VERSION ]] ; then VERSION=$(git describe --tags) fi +if [[ -n $VERSION_SUFFIX ]] ; then + VERSION=${VERSION}${VERSION_SUFFIX} +fi + cat <