Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Add Overrides for remaining orderConstraints via config, closes #141, c…
Browse files Browse the repository at this point in the history
…loses #152 (#153)

renamed `MIN_CENTRALIZED_BASE_VOLUME` to `CENTRALIZED_MIN_BASE_VOLUME_OVERRIDE` in trader config, see sample config files for latest examples
  • Loading branch information
nikhilsaraf authored Apr 10, 2019
1 parent 50b5053 commit 6d989c8
Show file tree
Hide file tree
Showing 15 changed files with 402 additions and 116 deletions.
2 changes: 2 additions & 0 deletions api/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ type FillTrackable interface {
type Constrainable interface {
// return nil if the constraint does not exist for the exchange
GetOrderConstraints(pair *model.TradingPair) *model.OrderConstraints

OverrideOrderConstraints(pair *model.TradingPair, override *model.OrderConstraintsOverride)
}

// OrderbookFetcher extracts out the method that should go into ExchangeShim for now
Expand Down
47 changes: 40 additions & 7 deletions cmd/trade.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,19 @@ func validateBotConfig(l logger.Logger, botConfig trader.BotConfig) {
logger.Fatal(l, fmt.Errorf("The `FEE` object needs to exist in the trader config file when trading on SDEX"))
}

if !botConfig.IsTradingSdex() && botConfig.MinCentralizedBaseVolume == 0.0 {
logger.Fatal(l, fmt.Errorf("need to specify non-zero MIN_CENTRALIZED_BASE_VOLUME config param in trader config file when not trading on SDEX"))
if !botConfig.IsTradingSdex() && botConfig.CentralizedMinBaseVolumeOverride != nil && *botConfig.CentralizedMinBaseVolumeOverride <= 0.0 {
logger.Fatal(l, fmt.Errorf("need to specify positive CENTRALIZED_MIN_BASE_VOLUME_OVERRIDE config param in trader config file when not trading on SDEX"))
}
if !botConfig.IsTradingSdex() && botConfig.CentralizedMinQuoteVolumeOverride != nil && *botConfig.CentralizedMinQuoteVolumeOverride <= 0.0 {
logger.Fatal(l, fmt.Errorf("need to specify positive CENTRALIZED_MIN_QUOTE_VOLUME_OVERRIDE config param in trader config file when not trading on SDEX"))
}
validatePrecisionConfig(l, botConfig.IsTradingSdex(), botConfig.CentralizedVolumePrecisionOverride, "CENTRALIZED_VOLUME_PRECISION_OVERRIDE")
validatePrecisionConfig(l, botConfig.IsTradingSdex(), botConfig.CentralizedPricePrecisionOverride, "CENTRALIZED_PRICE_PRECISION_OVERRIDE")
}

func validatePrecisionConfig(l logger.Logger, isTradingSdex bool, precisionField *int8, name string) {
if !isTradingSdex && precisionField != nil && *precisionField < 0 {
logger.Fatal(l, fmt.Errorf("need to specify non-negative %s config param in trader config file when not trading on SDEX", name))
}
}

Expand Down Expand Up @@ -202,6 +213,33 @@ func makeExchangeShimSdex(
}

exchangeShim = plugins.MakeBatchedExchange(exchangeAPI, *options.simMode, botConfig.AssetBase(), botConfig.AssetQuote(), botConfig.TradingAccount())

// update precision overrides
exchangeShim.OverrideOrderConstraints(tradingPair, model.MakeOrderConstraintsOverride(
botConfig.CentralizedPricePrecisionOverride,
botConfig.CentralizedVolumePrecisionOverride,
nil,
nil,
))
if botConfig.CentralizedMinBaseVolumeOverride != nil {
// use updated precision overrides to convert the minCentralizedBaseVolume to a model.Number
exchangeShim.OverrideOrderConstraints(tradingPair, model.MakeOrderConstraintsOverride(
nil,
nil,
model.NumberFromFloat(*botConfig.CentralizedMinBaseVolumeOverride, exchangeShim.GetOrderConstraints(tradingPair).VolumePrecision),
nil,
))
}
if botConfig.CentralizedMinQuoteVolumeOverride != nil {
// use updated precision overrides to convert the minCentralizedQuoteVolume to a model.Number
minQuoteVolume := model.NumberFromFloat(*botConfig.CentralizedMinQuoteVolumeOverride, exchangeShim.GetOrderConstraints(tradingPair).VolumePrecision)
exchangeShim.OverrideOrderConstraints(tradingPair, model.MakeOrderConstraintsOverride(
nil,
nil,
nil,
&minQuoteVolume,
))
}
}

sdexAssetMap := map[model.Asset]horizon.Asset{
Expand Down Expand Up @@ -294,17 +332,12 @@ func makeBot(
if e != nil {
l.Infof("Unable to set up monitoring for alert type '%s' with the given API key\n", botConfig.AlertType)
}
minCentralizedBaseVolume := &botConfig.MinCentralizedBaseVolume
if botConfig.IsTradingSdex() {
minCentralizedBaseVolume = nil
}
bot := trader.MakeBot(
client,
ieif,
botConfig.AssetBase(),
botConfig.AssetQuote(),
tradingPair,
minCentralizedBaseVolume,
botConfig.TradingAccount(),
sdex,
exchangeShim,
Expand Down
10 changes: 8 additions & 2 deletions examples/configs/trader/sample_mirror.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,16 @@ VOLUME_DIVIDE_BY=500.0
# in this example the spread is 0.5%
PER_LEVEL_SPREAD=0.005

# minimum volume of base units needed to place an order on the backing exchange
# minimum values for Kraken: https://support.kraken.com/hc/en-us/articles/205893708-What-is-the-minimum-order-size-volume-
# minimum order value for Binance: https://support.binance.com/hc/en-us/articles/115000594711-Trading-Rule
MIN_BASE_VOLUME=30.0
# (optional) number of decimal units to be used for price, which is specified in units of the quote asset, needed to place an order on the backing exchange
#PRICE_PRECISION_OVERRIDE=6
# (optional) number of decimal units to be used for volume, which is specified in units of the base asset, needed to place an order on the backing exchange
#VOLUME_PRECISION_OVERRIDE=1
# (optional) minimum volume of base units needed to place an order on the backing exchange
#MIN_BASE_VOLUME_OVERRIDE=30.0
# (optional) minimum volume of quote units needed to place an order on the backing exchange
#MIN_QUOTE_VOLUME_OVERRIDE=30.0

# set to true if you want the bot to offset your trades onto the backing exchange to realize the per_level_spread against each trade
# requires you to specify the EXCHANGE_API_KEYS below
Expand Down
11 changes: 9 additions & 2 deletions examples/configs/trader/sample_trader.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,17 @@ MAX_OP_FEE_STROOPS=5000
# Google authentication.
#ACCEPTABLE_GOOGLE_EMAILS=""

# minimum volume of base units needed to place an order on the non-sdex (centralized) exchange
# minimum values for Kraken: https://support.kraken.com/hc/en-us/articles/205893708-What-is-the-minimum-order-size-volume-
# minimum order value for Binance: https://support.binance.com/hc/en-us/articles/115000594711-Trading-Rule
#MIN_CENTRALIZED_BASE_VOLUME=30.0
# (optional) number of decimal units to be used for price, which is specified in units of the quote asset, to place an order on the non-sdex (centralized) exchange
#CENTRALIZED_PRICE_PRECISION_OVERRIDE=6
# (optional) number of decimal units to be used for volume, which is specified in units of the base asset, to place an order on the non-sdex (centralized) exchange
#CENTRALIZED_VOLUME_PRECISION_OVERRIDE=1
# (optional) minimum volume of base units needed to place an order on the non-sdex (centralized) exchange
#CENTRALIZED_MIN_BASE_VOLUME_OVERRIDE=30.0
# (optional) minimum volume of quote units needed to place an order on the non-sdex (centralized) exchange
#CENTRALIZED_MIN_QUOTE_VOLUME_OVERRIDE=10.0

# uncomment lines below to use kraken. Can use "sdex" or leave out to trade on the Stellar Decentralized Exchange.
# can alternatively use any of the ccxt-exchanges marked as "Trading" (run `kelp exchanges` for full list)
#TRADING_EXCHANGE="kraken"
Expand Down
95 changes: 95 additions & 0 deletions model/orderbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,28 @@ func MakeOrderConstraintsWithCost(pricePrecision int8, volumePrecision int8, min
}
}

// MakeOrderConstraintsWithOverride is a factory method for OrderConstraints, oc is not a pointer because we want a copy since we modify it
func MakeOrderConstraintsWithOverride(oc OrderConstraints, override *OrderConstraintsOverride) *OrderConstraints {
if override.PricePrecision != nil {
oc.PricePrecision = *override.PricePrecision
}
if override.VolumePrecision != nil {
oc.VolumePrecision = *override.VolumePrecision
}
if override.MinBaseVolume != nil {
oc.MinBaseVolume = *override.MinBaseVolume
}
if override.MinQuoteVolume != nil {
oc.MinQuoteVolume = *override.MinQuoteVolume
}
return &oc
}

// MakeOrderConstraintsFromOverride is a factory method to convert an OrderConstraintsOverride to an OrderConstraints
func MakeOrderConstraintsFromOverride(override *OrderConstraintsOverride) *OrderConstraints {
return MakeOrderConstraintsWithOverride(OrderConstraints{}, override)
}

// OrderConstraints describes constraints when placing orders on an excahnge
func (o *OrderConstraints) String() string {
minQuoteVolumeStr := nilString
Expand All @@ -315,3 +337,76 @@ func (o *OrderConstraints) String() string {
return fmt.Sprintf("OrderConstraints[PricePrecision: %d, VolumePrecision: %d, MinBaseVolume: %s, MinQuoteVolume: %s]",
o.PricePrecision, o.VolumePrecision, o.MinBaseVolume.AsString(), minQuoteVolumeStr)
}

// OrderConstraintsOverride describes an override for an OrderConstraint
type OrderConstraintsOverride struct {
PricePrecision *int8
VolumePrecision *int8
MinBaseVolume *Number
MinQuoteVolume **Number
}

// MakeOrderConstraintsOverride is a factory method
func MakeOrderConstraintsOverride(
pricePrecision *int8,
volumePrecision *int8,
minBaseVolume *Number,
minQuoteVolume **Number,
) *OrderConstraintsOverride {
return &OrderConstraintsOverride{
PricePrecision: pricePrecision,
VolumePrecision: volumePrecision,
MinBaseVolume: minBaseVolume,
MinQuoteVolume: minQuoteVolume,
}
}

// MakeOrderConstraintsOverrideFromConstraints is a factory method for OrderConstraintsOverride
func MakeOrderConstraintsOverrideFromConstraints(oc *OrderConstraints) *OrderConstraintsOverride {
return &OrderConstraintsOverride{
PricePrecision: &oc.PricePrecision,
VolumePrecision: &oc.VolumePrecision,
MinBaseVolume: &oc.MinBaseVolume,
MinQuoteVolume: &oc.MinQuoteVolume,
}
}

// IsComplete returns true if the override contains all values
func (override *OrderConstraintsOverride) IsComplete() bool {
if override.PricePrecision == nil {
return false
}

if override.VolumePrecision == nil {
return false
}

if override.MinBaseVolume == nil {
return false
}

if override.MinQuoteVolume == nil {
return false
}

return true
}

// Augment only updates values if updates are non-nil
func (override *OrderConstraintsOverride) Augment(updates *OrderConstraintsOverride) {
if updates.PricePrecision != nil {
override.PricePrecision = updates.PricePrecision
}

if updates.VolumePrecision != nil {
override.VolumePrecision = updates.VolumePrecision
}

if updates.MinBaseVolume != nil {
override.MinBaseVolume = updates.MinBaseVolume
}

if updates.MinQuoteVolume != nil {
override.MinQuoteVolume = updates.MinQuoteVolume
}
}
11 changes: 5 additions & 6 deletions plugins/batchedExchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ func (b BatchedExchange) GetOrderConstraints(pair *model.TradingPair) *model.Ord
return b.inner.GetOrderConstraints(pair)
}

// OverrideOrderConstraints impl, can partially override values for specific pairs
func (b BatchedExchange) OverrideOrderConstraints(pair *model.TradingPair, override *model.OrderConstraintsOverride) {
b.inner.OverrideOrderConstraints(pair, override)
}

// GetOrderBook impl
func (b BatchedExchange) GetOrderBook(pair *model.TradingPair, maxCount int32) (*model.OrderBook, error) {
return b.inner.GetOrderBook(pair, maxCount)
Expand Down Expand Up @@ -193,12 +198,6 @@ func (b BatchedExchange) SubmitOps(ops []build.TransactionMutator, asyncCallback
return nil
}

pair := &model.TradingPair{
Base: model.FromHorizonAsset(b.baseAsset),
Quote: model.FromHorizonAsset(b.quoteAsset),
}
log.Printf("order constraints for trading pair %s: %s", pair, b.inner.GetOrderConstraints(pair))

results := []submitResult{}
numProcessed := 0
for _, c := range b.commands {
Expand Down
39 changes: 19 additions & 20 deletions plugins/ccxtExchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ var _ api.Exchange = ccxtExchange{}

// ccxtExchange is the implementation for the CCXT REST library that supports many exchanges (https://github.com/franz-see/ccxt-rest, https://github.com/ccxt/ccxt/)
type ccxtExchange struct {
assetConverter *model.AssetConverter
delimiter string
orderConstraints map[model.TradingPair]model.OrderConstraints
api *sdk.Ccxt
simMode bool
assetConverter *model.AssetConverter
delimiter string
ocOverridesHandler *OrderConstraintsOverridesHandler
api *sdk.Ccxt
simMode bool
}

// makeCcxtExchange is a factory method to make an exchange using the CCXT interface
Expand All @@ -47,16 +47,17 @@ func makeCcxtExchange(
return nil, fmt.Errorf("error making a ccxt exchange: %s", e)
}

if orderConstraintOverrides == nil {
orderConstraintOverrides = map[model.TradingPair]model.OrderConstraints{}
ocOverridesHandler := MakeEmptyOrderConstraintsOverridesHandler()
if orderConstraintOverrides != nil {
ocOverridesHandler = MakeOrderConstraintsOverridesHandler(orderConstraintOverrides)
}

return ccxtExchange{
assetConverter: model.CcxtAssetConverter,
delimiter: "/",
orderConstraints: orderConstraintOverrides,
api: c,
simMode: simMode,
assetConverter: model.CcxtAssetConverter,
delimiter: "/",
ocOverridesHandler: ocOverridesHandler,
api: c,
simMode: simMode,
}, nil
}

Expand Down Expand Up @@ -99,10 +100,6 @@ func (c ccxtExchange) GetAssetConverter() *model.AssetConverter {

// GetOrderConstraints impl
func (c ccxtExchange) GetOrderConstraints(pair *model.TradingPair) *model.OrderConstraints {
if oc, ok := c.orderConstraints[*pair]; ok {
return &oc
}

pairString, e := pair.ToString(c.assetConverter, c.delimiter)
if e != nil {
// this should never really panic because we would have converted this trading pair to a string previously
Expand All @@ -114,12 +111,14 @@ func (c ccxtExchange) GetOrderConstraints(pair *model.TradingPair) *model.OrderC
if ccxtMarket == nil {
panic(fmt.Errorf("CCXT does not have precision and limit data for the passed in market: %s", pairString))
}
oc := *model.MakeOrderConstraintsWithCost(ccxtMarket.Precision.Price, ccxtMarket.Precision.Amount, ccxtMarket.Limits.Amount.Min, ccxtMarket.Limits.Cost.Min)
oc := model.MakeOrderConstraintsWithCost(ccxtMarket.Precision.Price, ccxtMarket.Precision.Amount, ccxtMarket.Limits.Amount.Min, ccxtMarket.Limits.Cost.Min)

// cache it before returning
c.orderConstraints[*pair] = oc
return c.ocOverridesHandler.Apply(pair, oc)
}

return &oc
// OverrideOrderConstraints impl, can partially override values for specific pairs
func (c ccxtExchange) OverrideOrderConstraints(pair *model.TradingPair, override *model.OrderConstraintsOverride) {
c.ocOverridesHandler.Upsert(pair, override)
}

// GetAccountBalances impl
Expand Down
30 changes: 21 additions & 9 deletions plugins/krakenExchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type krakenExchange struct {
apis []*krakenapi.KrakenApi
apiNextIndex uint8
delimiter string
ocOverridesHandler *OrderConstraintsOverridesHandler
withdrawKeys asset2Address2Key
isSimulated bool // will simulate add and cancel orders if this is true
}
Expand Down Expand Up @@ -65,11 +66,12 @@ func makeKrakenExchange(apiKeys []api.ExchangeAPIKey, isSimulated bool) (api.Exc
return &krakenExchange{
assetConverter: model.KrakenAssetConverter,
assetConverterOpenOrders: model.KrakenAssetConverterOpenOrders,
apis: krakenAPIs,
apiNextIndex: 0,
delimiter: "",
withdrawKeys: asset2Address2Key{},
isSimulated: isSimulated,
apis: krakenAPIs,
apiNextIndex: 0,
delimiter: "",
ocOverridesHandler: MakeEmptyOrderConstraintsOverridesHandler(),
withdrawKeys: asset2Address2Key{},
isSimulated: isSimulated,
}, nil
}

Expand Down Expand Up @@ -194,11 +196,21 @@ func getFieldValue(object krakenapi.BalanceResponse, fieldName string) float64 {

// GetOrderConstraints impl
func (k *krakenExchange) GetOrderConstraints(pair *model.TradingPair) *model.OrderConstraints {
constraints, ok := krakenPrecisionMatrix[*pair]
if !ok {
panic(fmt.Sprintf("krakenExchange could not find orderConstraints for trading pair %v. Try using the \"ccxt-kraken\" integration instead.", pair))
oc, ok := krakenPrecisionMatrix[*pair]
if ok {
return k.ocOverridesHandler.Apply(pair, &oc)
}

if k.ocOverridesHandler.IsCompletelyOverriden(pair) {
override := k.ocOverridesHandler.Get(pair)
return model.MakeOrderConstraintsFromOverride(override)
}
return &constraints
panic(fmt.Sprintf("krakenExchange could not find orderConstraints for trading pair %v. Try using the \"ccxt-kraken\" integration instead.", pair))
}

// OverrideOrderConstraints impl, can partially override values for specific pairs
func (k *krakenExchange) OverrideOrderConstraints(pair *model.TradingPair, override *model.OrderConstraintsOverride) {
k.ocOverridesHandler.Upsert(pair, override)
}

// GetAssetConverter impl.
Expand Down
11 changes: 6 additions & 5 deletions plugins/krakenExchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import (
var testKrakenExchange api.Exchange = &krakenExchange{
assetConverter: model.KrakenAssetConverter,
assetConverterOpenOrders: model.KrakenAssetConverterOpenOrders,
apis: []*krakenapi.KrakenApi{krakenapi.New("", "")},
apiNextIndex: 0,
delimiter: "",
withdrawKeys: asset2Address2Key{},
isSimulated: true,
apis: []*krakenapi.KrakenApi{krakenapi.New("", "")},
apiNextIndex: 0,
delimiter: "",
ocOverridesHandler: MakeEmptyOrderConstraintsOverridesHandler(),
withdrawKeys: asset2Address2Key{},
isSimulated: true,
}

func TestGetTickerPrice(t *testing.T) {
Expand Down
Loading

0 comments on commit 6d989c8

Please sign in to comment.