Backtester: Live trading upgrades (#1023)

* Modifications for a smoother live run

* Fixes data appending

* Successfully allows multi-currency live trading. Adds multiple currencies to live DCA strategy

* Attempting to get cash and carry working

* Poor attempts at sorting out data and appending it properly with USD in mind

* =designs new live data handler

* Updates cash and carry strat to work

* adds test coverage. begins closeallpositions function

* Updates cash and carry to work live

* New kline.Event type. Cancels orders on close. Rn types

* =Fixes USD funding issue

* =fixes tests

* fixes tests AGAIN

* adds coverage to close all orders

* crummy tests, should override

* more tests

* more tests

* more coverage

* removes scourge of currency.Pair maps. More tests

* missed currency stuff

* Fixes USD data issue & collateral issue. Needs to close ALL orders

* Now triggers updates on the very first data entry

* All my problems are solved now????

* fixes tests, extends coverage

* there is some really funky candle stuff going on

* my brain is melting

* better shutdown management, fixes freezing bug

* fixes data duplication issues, adds retries to requests

* reduces logging, adds verbose options

* expands coverage over all new functionality

* fixes fun bug from curr == curr to curr.Equal(curr)

* fixes setup issues and tests

* starts adding external wallet amounts for funding

* more setup for assets

* setup live fund calcs and placing orders

* successfully performs automated cash and carry

* merge fixes

* funding properly set at all times

* fixes some bugs, need to address currencystatistics still

* adds 'appeneded' trait, attempts to fix some stats

* fixes stat bugs, adds cool new fetchfees feature

* fixes terrible processing bugs

* tightens realorder stats, sadly loses some live stats

* this actually sets everything correctly for bothcd ..cd ..cd ..cd ..cd ..!

* fix tests

* coverage

* beautiful new test coverage

* docs

* adds new fee getter delayer

* commits from the correct directory

* Lint

* adds verbose to fund manager

* Fix bug in t2b2 strat. Update dca live config. Docs

* go mod tidy

* update buf

* buf + test improvement

* Post merge fixes

* fixes surprise offset bug

* fix sizing restrictions for cash and carry

* fix server lints

* merge fixes

* test fixesss

* lintle fixles

* slowloris

* rn run to task, bug fixes, close all on close

* rpc lint and fixes

* bugfix: order manager not processing orders properly

* somewhat addresses nits

* absolutely broken end of day commit

* absolutely massive knockon effects from nits

* massive knockon effects continue

* fixes things

* address remaining nits

* jk now fixes things

* addresses the easier nits

* more nit fixers

* more niterinos addressederinos

* refactors holdings and does some nits

* so buf

* addresses some nits, fixes holdings bugs

* cleanup

* attempts to fix alert chans to prevent many chans waiting?

* terrible code, will revert

* to be reviewed in detail tomorrow

* Fixes up channel system

* smashes those nits

* fixes extra candles, fixes collateral bug, tests

* fixes data races, introduces reflection

* more checks n tests

* Fixes cash and carry issues. Fixes more cool bugs

* fixes ~typer~ typo

* replace spot strats from ftx to binance

* fixes all the tests I just destroyed

* removes example path, rm verbose

* 1) what 2) removes FTX references from the Backtester

* renamed, non-working strategies

* Removes FTX references almost as fast as sbf removes funds

* regen docs, add contrib names,sort contrib names

* fixes merge renamings

* Addresses nits. Fixes setting API credentials. Fixes Binance limit retrieval

* Fixes live order bugs with real orders and without

* Apply suggestions from code review

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update backtester/engine/live.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update backtester/engine/live.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update backtester/config/strategyconfigbuilder/main.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* updates docs

* even better docs

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
Scott
2023-01-05 13:03:17 +11:00
committed by GitHub
parent d92ffe6e9e
commit 017cdf1384
195 changed files with 13783 additions and 8048 deletions

View File

@@ -764,7 +764,11 @@ func (f *FTX) Order(
req := make(map[string]interface{})
req["market"] = marketName
req["side"] = side
req["price"] = price
if orderType == "market" {
req["price"] = nil
} else {
req["price"] = price
}
req["type"] = orderType
req["size"] = size
if reduceOnly {
@@ -922,7 +926,7 @@ func (f *FTX) DeleteTriggerOrder(ctx context.Context, orderID string) (string, e
// GetFills gets order fills data and ensures that all
// fills are retrieved from the supplied timeframe
func (f *FTX) GetFills(ctx context.Context, market currency.Pair, item asset.Item, startTime, endTime time.Time) ([]FillsData, error) {
func (f *FTX) GetFills(ctx context.Context, market currency.Pair, item asset.Item, startTime, endTime time.Time, orderID string) ([]FillsData, error) {
var resp []FillsData
var nextEnd = endTime
limit := 200
@@ -946,6 +950,9 @@ func (f *FTX) GetFills(ctx context.Context, market currency.Pair, item asset.Ite
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(nextEnd.Unix(), 10))
}
if orderID != "" {
params.Set("orderId", orderID)
}
endpoint := common.EncodeURLValues(getFills, params)
err := f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, endpoint, nil, &data)
if err != nil {

View File

@@ -678,6 +678,7 @@ func TestSubmitOrder(t *testing.T) {
Amount: 1,
AssetType: asset.Spot,
ClientOrderID: "order12345679$$$$$",
RetrieveFees: true,
}
_, err = f.SubmitOrder(context.Background(), orderSubmission)
if err != nil {
@@ -766,25 +767,30 @@ func TestGetFills(t *testing.T) {
t.Skip()
}
_, err := f.GetFills(context.Background(),
currency.Pair{}, asset.Futures, time.Now().Add(time.Hour*24*365), time.Now())
currency.Pair{}, asset.Futures, time.Now().Add(time.Hour*24*365), time.Now(), "")
if !errors.Is(err, errStartTimeCannotBeAfterEndTime) {
t.Errorf("received '%v' expected '%v'", err, errStartTimeCannotBeAfterEndTime)
}
_, err = f.GetFills(context.Background(),
currency.Pair{}, asset.Futures, time.Time{}, time.Time{})
currency.Pair{}, asset.Futures, time.Time{}, time.Time{}, "")
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = f.GetFills(context.Background(),
currency.Pair{}, asset.Futures, time.Now().Add(-time.Hour*24*365), time.Now())
currency.Pair{}, asset.Futures, time.Now().Add(-time.Hour*24*365), time.Now(), "")
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = f.GetFills(context.Background(),
spotPair, asset.Spot, time.Now().Add(-time.Hour*24*365), time.Now())
spotPair, asset.Spot, time.Now().Add(-time.Hour*24*365), time.Now(), "")
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = f.GetFills(context.Background(),
currency.EMPTYPAIR, asset.Futures, time.Time{}, time.Time{}, "177453606715")
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
@@ -2819,3 +2825,36 @@ func TestGetReferralRebateRate(t *testing.T) {
t.Fatal(err)
}
}
func TestGetCollateralCurrencyForContract(t *testing.T) {
t.Parallel()
c, a, err := f.GetCollateralCurrencyForContract(asset.Futures, currency.NewPair(currency.XRP, currency.BABYDOGE))
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if a != asset.Futures {
t.Fatalf("received: '%v' but expected: '%v'", a, asset.Futures)
}
if !c.Equal(currency.USD) {
t.Fatalf("received: '%v' but expected: '%v'", c, currency.USD)
}
}
func TestGetCurrencyForRealisedPNL(t *testing.T) {
t.Parallel()
c, a, err := f.GetCurrencyForRealisedPNL(asset.Futures, currency.NewPair(currency.XRP, currency.BABYDOGE))
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if a != asset.Spot {
t.Fatalf("received: '%v' but expected: '%v'", a, asset.Spot)
}
if !c.Equal(currency.USD) {
t.Fatalf("received: '%v' but expected: '%v'", c, currency.USD)
}
_, _, err = f.GetCurrencyForRealisedPNL(asset.Spot, currency.NewPair(currency.SHIB, currency.DOGE))
if !errors.Is(err, order.ErrNotFuturesAsset) {
t.Fatalf("received: '%v' but expected: '%v'", err, order.ErrNotFuturesAsset)
}
}

View File

@@ -337,7 +337,7 @@ type OrderData struct {
ClientID string `json:"clientId"`
CreatedAt time.Time `json:"createdAt"`
FilledSize float64 `json:"filledSize"`
Future currency.Pair `json:"future"`
Future string `json:"future"`
ID int64 `json:"id"`
IOC bool `json:"ioc"`
Market currency.Pair `json:"market"`

View File

@@ -168,6 +168,14 @@ func (f *FTX) SetDefaults() {
f.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
f.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
f.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
err = f.LoadCollateralWeightings(context.TODO())
if err != nil {
log.Errorf(log.ExchangeSys,
"%s failed to store collateral weightings. Err: %s",
f.Name,
err)
}
}
// Setup takes in the supplied exchange configuration details and sets params
@@ -206,15 +214,6 @@ func (f *FTX) Setup(exch *config.Exchange) error {
return err
}
if err = f.CurrencyPairs.IsAssetEnabled(asset.Futures); err == nil {
err = f.LoadCollateralWeightings(context.TODO())
if err != nil {
log.Errorf(log.ExchangeSys,
"%s failed to store collateral weightings. Err: %s",
f.Name,
err)
}
}
return f.Websocket.SetupNewConnection(stream.ConnectionSetup{
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
@@ -490,7 +489,7 @@ func (f *FTX) UpdateAccountInfo(ctx context.Context, a asset.Item) (account.Hold
hold := balances[x].Total - balances[x].AvailableWithoutBorrow
acc.Currencies = append(acc.Currencies,
account.Balance{
CurrencyName: balances[x].Coin,
Currency: balances[x].Coin,
Total: balances[x].Total,
Hold: hold,
AvailableWithoutBorrow: balances[x].AvailableWithoutBorrow,
@@ -648,10 +647,10 @@ func (f *FTX) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitRe
return nil, err
}
if s.Side == order.Ask {
if s.Side.IsShort() {
s.Side = order.Sell
}
if s.Side == order.Bid {
if s.Side.IsLong() {
s.Side = order.Buy
}
@@ -674,7 +673,43 @@ func (f *FTX) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitRe
return nil, err
}
return s.DeriveSubmitResponse(strconv.FormatInt(tempResp.ID, 10))
resp, err := s.DeriveSubmitResponse(strconv.FormatInt(tempResp.ID, 10))
if err != nil {
return nil, err
}
if !s.RetrieveFees {
return resp, nil
}
time.Sleep(s.RetrieveFeeDelay)
fills, err := f.GetFills(ctx, s.Pair, s.AssetType, time.Time{}, time.Time{}, strconv.FormatInt(tempResp.ID, 10))
if err != nil {
// choosing to return with no error so that a valid order is still returned to caller
log.Errorf(log.ExchangeSys, "could not retrieve fees for order %v: %v", tempResp.ID, err)
return resp, nil
}
for i := range fills {
resp.Fee += fills[i].Fee
var side order.Side
side, err = order.StringToOrderSide(fills[i].Side)
if err != nil {
return nil, err
}
if resp.FeeAsset.IsEmpty() {
resp.FeeAsset = fills[i].FeeCurrency
}
resp.Trades = append(resp.Trades, order.TradeHistory{
Price: fills[i].Price,
Amount: fills[i].Size,
Fee: fills[i].Fee,
Exchange: f.Name,
TID: strconv.FormatInt(fills[i].TradeID, 10),
Side: side,
Timestamp: fills[i].Time,
IsMaker: fills[i].Liquidity == "maker",
FeeAsset: fills[i].FeeCurrency.String(),
})
}
return resp, nil
}
// ModifyOrder will allow of changing orderbook placement and limit to
@@ -1270,7 +1305,7 @@ func (f *FTX) GetHistoricCandlesExtended(ctx context.Context, p currency.Pair, a
if len(summary) > 0 {
log.Warnf(log.ExchangeSys, "%v - %v", f.Name, summary)
}
ret.RemoveDuplicates()
ret.RemoveDuplicateCandlesByTime()
ret.RemoveOutsideRange(start, end)
ret.SortCandlesByTimestamp(false)
return ret, nil
@@ -1696,7 +1731,7 @@ func (f *FTX) GetFuturesPositions(ctx context.Context, request *order.PositionsR
allPositions:
for {
var fills []FillsData
fills, err = f.GetFills(ctx, request.Pairs[x], request.Asset, request.StartDate, endTime)
fills, err = f.GetFills(ctx, request.Pairs[x], request.Asset, request.StartDate, endTime, "")
if err != nil {
return nil, err
}
@@ -1760,12 +1795,15 @@ func (f *FTX) GetFuturesPositions(ctx context.Context, request *order.PositionsR
}
// GetCollateralCurrencyForContract returns the collateral currency for an asset and contract pair
func (f *FTX) GetCollateralCurrencyForContract(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
func (f *FTX) GetCollateralCurrencyForContract(a asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
return currency.USD, asset.Futures, nil
}
// GetCurrencyForRealisedPNL returns where to put realised PNL
func (f *FTX) GetCurrencyForRealisedPNL(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
func (f *FTX) GetCurrencyForRealisedPNL(a asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
if !a.IsFutures() {
return currency.EMPTYCODE, asset.Empty, fmt.Errorf("%v %w", a, order.ErrNotFuturesAsset)
}
return currency.USD, asset.Spot, nil
}