Merge branch 'main' into feature/336-kline-table

This commit is contained in:
TonyQ Wang 2021-12-11 02:26:01 +08:00 committed by GitHub
commit 776f82fcd5
34 changed files with 731 additions and 258 deletions

View File

@ -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

2
.gitignore vendored
View File

@ -34,3 +34,5 @@
/pkg/server/assets.go
bbgo.sqlite3
node_modules

View File

@ -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)

View File

@ -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: <https://max.maicoin.com/signup?r=c7982718>
- For Binance: <https://www.binancezh.com/en/register?ref=VGDGLT80>
- For FTX: <https://ftx.com/#a=7710474>
- MAX: <https://max.maicoin.com/signup?r=c7982718>
- Binance: <https://www.binancezh.com/en/register?ref=VGDGLT80>
- FTX: <https://ftx.com/#a=7710474>
- OKEx: <https://www.okex.com/join/2412712?src=from:ios-share>
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

20
config/pricealert-tg.yaml Normal file
View File

@ -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

View File

@ -1,5 +1,7 @@
# Build From Source
## Install Go SDK
Go to the Go official website to download the Go SDK <https://go.dev/dl/>.
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:

View File

@ -23,6 +23,20 @@ you can set `TELEGRAM_AUTH_TOKEN` in the `.env.local` file, e.g.,
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.
Open your Telegram app, search your bot `bbgo_bot_711222333`

View File

@ -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)

View File

@ -1,7 +1,7 @@
## Fixes
-
- ftx: fixed FTX OnKLineClosed event
## Features
## Migrations
-
- add `is_futures` fields to orders and trades table

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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 {

View File

@ -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,

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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",

View File

@ -28,7 +28,10 @@ type Exchange struct {
func New(key, secret, passphrase string) *Exchange {
client := okexapi.NewClient()
if len(key) > 0 && len(secret) > 0 {
client.Auth(key, secret, passphrase)
}
return &Exchange{
key: key,

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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()

View File

@ -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 {

View File

@ -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))

13
pkg/types/fundingrate.go Normal file
View File

@ -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
}

View File

@ -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 {

15
pkg/types/premiumindex.go Normal file
View File

@ -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"`
}

View File

@ -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"`

View File

@ -2,7 +2,7 @@
package version
const Version = "v1.19.4-5b11ef81"
const Version = "v1.20.0-c1e69194"
const VersionGitRef = "5b11ef81"
const VersionGitRef = "c1e69194"

View File

@ -7,6 +7,10 @@ if [[ -z $VERSION ]] ; then
VERSION=$(git describe --tags)
fi
if [[ -n $VERSION_SUFFIX ]] ; then
VERSION=${VERSION}${VERSION_SUFFIX}
fi
cat <<END
// +build release