Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add modifier to customize drift from high price #23

Open
github-actions bot opened this issue Feb 27, 2021 · 0 comments
Open

Add modifier to customize drift from high price #23

github-actions bot opened this issue Feb 27, 2021 · 0 comments
Labels

Comments

@github-actions
Copy link

Add modifier to customize drift from high price

// TODO Add modifier to customize drift from high price

	"github.com/sirupsen/logrus"
	"github.com/urfave/cli/v2"
	"math/big"
	"reflect"
	"strconv"
	"strings"
	//"time"
)

type Kraken struct {
	Name          string
	Action        string
	ApiKey        string
	SecretKey     string
	Crypto        string
	Fiat          string
	Api           *krakenapi.KrakenApi
	Pair          string
	BalanceCrypto float64
	BalanceFiat   float64
	Ticker        krakenapi.PairTickerInfo
	Ask           string
	AskFloat      float64
	UserRef       int32
}

const MIN_BTC_AMOUNT = 0.0002

//func getTimeInfo() (time.Time, time.Time, time.Duration) {
//	// Always use the local timezone
//	loc, _ := time.LoadLocation("Local")
//
//	now := time.Now().In(loc)
//	year, month, day := now.Date()
//
//	// Start is TODAY at 00:00
//	start := time.Date(year, month, day, 0, 0, 0, 0, now.Location())
//
//	// END is now
//	end := now
//
//	return start, end, end.Sub(start)
//}

func (k *Kraken) Config(c *cli.Context) error {
	k.Name = strings.ToTitle("kraken")
	k.ApiKey = c.String("api-key")
	k.SecretKey = c.String("secret-key")
	k.Crypto = "XBT"

	return nil
}

func (k *Kraken) Init(c *cli.Context) error {
	k.Fiat = strings.ToUpper(c.String("fiat"))
	k.Pair = "X" + k.Crypto + "Z" + k.Fiat

	k.Api = krakenapi.New(k.ApiKey, k.SecretKey)

	k.Action = c.Command.FullName()

	if k.Action != "withdraw" {
		// Initialize the current Balance
		balance, err := k.Api.Balance()
		if err != nil {
			return errors.New("Failed to get Balance. Check API and SECRET Keys")
		}

		// Extract Values from Kraken Responses
		refBalance := reflect.ValueOf(balance)
		k.BalanceCrypto = reflect.Indirect(refBalance).FieldByName("X" + k.Crypto).Interface().(float64)
		k.BalanceFiat = reflect.Indirect(refBalance).FieldByName("Z" + k.Fiat).Interface().(float64)

		// Get the current ticker for the given PAIR
		ticker, err := k.Api.Ticker(k.Pair)
		if err != nil {
			return fmt.Errorf("Failed to get ticker for pair %s: %s", k.Pair, err)
		}

		k.Ticker = ticker.GetPairTickerInfo(k.Pair)
		k.Ask = ticker.GetPairTickerInfo(k.Pair).Ask[0]
		k.AskFloat, err = strconv.ParseFloat(k.Ask, 64)
		if err != nil {
			return fmt.Errorf("Failed to get Ask price for pair %s: %s", k.Pair, err)
		}
	}

	return nil
}

//func (k *Kraken) closedOrderTodayForUserRef(orders *krakenapi.ClosedOrdersResponse, c *cli.Context) (string, krakenapi.Order, error) {
//
//	for id, v := range orders.Closed {
//		if v.Status == "closed" {
//			return id, v, nil
//		}
//	}
//	return "", krakenapi.Order{}, nil
//}

func (k *Kraken) priceModifierBasedOnGapFromHighPrice(c *cli.Context) (float64, error) {

	// 15 interval will give a week worth of data
	ohlcs, err := k.Api.OHLCWithInterval(k.Pair, "15")
	if err != nil {
		return 0.0, fmt.Errorf("Failed to get OHLC Data for pair %s: %s", k.Pair, err)
	}

	// Find highest price in the range of OHLC
	var highest float64
	for _, o := range ohlcs.OHLC {
		if o.High > highest {
			highest = o.High
		}
	}

	// max modifier is 40% ( applied to the discount price ) when the gap is >= 25%
	maxDiscountModifier := 40.0
	var discountModifier float64
	// Is the highest price from the last week more than GAP PERCENTAGE over the current ask price ?
	gapPrice := highest - k.AskFloat
	if gapPrice > 0 {
		gapPercentage := gapPrice / highest * 100
		if gapPercentage > c.Float64("high-price-gap-percentage") {

			// calculate modifier
			discountModifier = gapPercentage / 25.0 * maxDiscountModifier
			if discountModifier > maxDiscountModifier {
				discountModifier = maxDiscountModifier
			}

			log.WithFields(logrus.Fields{
				"action":             k.Action,
				"pair":               k.Pair,
				"arg-gap-interval":   "7d",
				"arg-gap-percentage": c.Float64("high-price-gap-percentage"),
				"highest":            highest,
				"ask":                k.AskFloat,
				"gap":                gapPrice,
				"gap-percentage":     gapPercentage,
				"discount-modifier":  discountModifier,
			}).Debug("Price Gap calculator")
		}
	}

	return discountModifier, nil
}

func (k *Kraken) createOrderArgs(c *cli.Context, volume float64, price string, longshot bool) (map[string]string, error) {

	args := make(map[string]string)

	// Simple DCA
	if k.Action == "stack" {
		args["orderType"] = "market"
	} else if k.Action == "btd" {
		args["orderType"] = "limit"
	} else {
		return args, fmt.Errorf("Unknown Action: %s", k.Action)
	}

	validate := "false"
	if c.Bool("dry-run") {
		validate = "true"
		args["validate"] = "true"
	}

	args["userref"] = fmt.Sprintf("%d", k.UserRef)
	args["volume"] = strconv.FormatFloat(volume, 'f', 8, 64)
	args["price"] = price
	args["oflags"] = "fciq" // "buy" button will actually sell the quote currency in exchange for the base currency, pay fee in the the quote currenty ( fiat )

	// If volume < MIN_BTC_AMOUNT then error - this is the minimum kraken order volume
	if volume < MIN_BTC_AMOUNT {
		return args, fmt.Errorf("Minimum volume for BTC Order on Kraken is %f got %s. Consider increasing the amount of Fiat", MIN_BTC_AMOUNT, args["volume"])
	}

	log.WithFields(logrus.Fields{
		"action":     k.Action,
		"pair":       k.Pair,
		"type":       "buy",
		"orderType":  args["orderType"],
		"volume":     args["volume"],
		"price":      args["price"],
		"dryrun":     validate,
		"orderFlags": args["oflags"],
		"userref":    args["userref"],
	}).Debug("Order to execute")

	return args, nil
}

func (k *Kraken) BuyTheDips(c *cli.Context) (result string, e error) {
	// TODO Handle cancel only mode from Kraken
	// TODO Add modifier to customize drift from high price
	k.UserRef = 300

	log.WithFields(logrus.Fields{
		"action":  "btd",
		"userRef": k.UserRef,
	}).Info("Buying the DIPs on " + k.Name)

	log.WithFields(logrus.Fields{
		"action":        "btd",
		"crypto":        k.Crypto,
		"cryptoBalance": k.BalanceCrypto,
		"fiat":          k.Fiat,
		"fiatBalance":   k.BalanceFiat,
		"ask":           k.Ask,
		"budget":        c.Float64("budget"),
		"n-orders":      c.Int64("n-orders"),
	}).Debug("Balance before any action is taken")

	// Calculate order values from budget
	// Each _Unit_ will have the double the value of the unit before
	var totalOrderUnits int64
	var fiatValueUnit float64

	totalOrders := c.Int64("n-orders")
	for totalOrders != 0 {
		totalOrderUnits += totalOrders
		totalOrders -= 1
	}

	fiatValueUnit = c.Float64("budget") / float64(totalOrderUnits)

	//var dipOrders []map[string]string

	log.WithFields(logrus.Fields{
		"action":          "btd",
		"budget":          c.Float64("budget"),
		"total-sats":      fmt.Sprintf("%.8f", c.Float64("budget")/k.AskFloat),
		"dip-percentage":  c.Int64("dip-percentage"),
		"dip-increments":  c.Int64("dip-increments"),
		"n-orders":        c.Int64("n-orders"),
		"total-units":     totalOrderUnits,
		"fiat-value-unit": fiatValueUnit,
	}).Debug("Calculating orders")

	var dipOrders []map[string]string

	var orderNumber int64
	//Calculate DIP Discount for this order
	modifier, err := k.priceModifierBasedOnGapFromHighPrice(c)
	if err != nil {
		modifier = 0.0
	}

	for orderNumber != c.Int64("n-orders") {
		// Discount based on order number
		discount := float64(c.Int64("dip-percentage") + (orderNumber * c.Int64("dip-increments")))
		// Calculate modifier to apply to discount based on the gap from the Highest Weekly price
		discountModifier := (float64(discount) * modifier) / float64(100.0)
		dipDiscountedPrice := (k.AskFloat / float64(100)) * (float64(100.0) - discount + discountModifier)
		dipVolume := (fiatValueUnit * float64(orderNumber+1)) / dipDiscountedPrice

		log.WithFields(logrus.Fields{
			"action":       "btd",
			"order-number": orderNumber + 1,
			"ask-price":    k.Ask,
			"dip-discount": discount - discountModifier,
			"dip-price":    dipDiscountedPrice,
			"dip-volume":   dipVolume,
		}).Debug(fmt.Sprintf("Creating discounted order %d", orderNumber+1))

		// Create Order and add to list
		dipOrderArgs, _ := k.createOrderArgs(c, dipVolume, fmt.Sprintf("%.1f", dipDiscountedPrice), false)

		// If volume < MIN_BTC_AMOUNT then do not add to the list, skip to next iteration
		if dipVolume < MIN_BTC_AMOUNT {
			orderNumber += 1
			continue
		}

		dipOrders = append(dipOrders, dipOrderArgs)
		log.WithFields(logrus.Fields{
			"action":       "btd",
			"order-number": orderNumber + 1,
		}).Debug("Added Order to list")

		orderNumber += 1
	}

	if len(dipOrders) == 0 {
		return "", fmt.Errorf("No Orders were added to the list")
	}

	//Cancel any open order with our UserRef
	if !c.Bool("dry-run") {
		openordersArgs := make(map[string]string)
		//openordersArgs["trades"] = "true"
		openordersArgs["userref"] = fmt.Sprintf("%d", k.UserRef)

		resp, _ := k.Api.OpenOrders(openordersArgs)
		if len(resp.Open) > 0 {

			_, err := k.Api.CancelOrder(fmt.Sprintf("%d", k.UserRef))
			if err != nil {
				return "", fmt.Errorf("Failed to Cancel Orders for UserRef: %d - %s", k.UserRef, err)
			}

			log.WithFields(logrus.Fields{
				"action":  "btd",
				"userref": k.UserRef,
			}).Info(fmt.Sprintf("%d Open Orders Canceled", len(resp.Open)))
		}
	}

	//Place Orders
	orderNumber = 0
	for orderNumber != int64(len(dipOrders)) {
		thisOrder := dipOrders[orderNumber]
		order, err := k.Api.AddOrder(k.Pair, "buy", thisOrder["orderType"], thisOrder["volume"], thisOrder)
		if err != nil {
			log.WithFields(logrus.Fields{
				"action":       "btd",
				"userref":      thisOrder["userref"],
				"order-number": orderNumber + 1,
			}).Error(fmt.Sprintf("Error Creating orderNumber %d: %s", orderNumber+1, err))

			orderNumber += 1
			continue
		}

		var orderId string
		if c.Bool("dry-run") {
			orderId = "DRY-RUN"
		} else {
			orderId = strings.Join(order.TransactionIds, ",")
		}

		log.WithFields(logrus.Fields{
			"action":       "btd",
			"order":        order.Description.Order,
			"orderId":      orderId,
			"order-number": orderNumber + 1,
			"dryrun":       thisOrder["validate"],
			"orderType":    thisOrder["orderType"],
			"volume":       thisOrder["volume"],
			"askPrice":     k.Ask,
			"price":        thisOrder["price"],
			"orderFlags":   thisOrder["oflags"],
			"userref":      thisOrder["userref"],
		}).Info(fmt.Sprintf("Order Placed %d", orderNumber+1))

		orderNumber += 1
	}

	return "", nil
}

func (k *Kraken) Stack(c *cli.Context) (result string, e error) {

	k.UserRef = 100

	log.WithFields(logrus.Fields{
		"action":  "stack",
		"userRef": k.UserRef,
	}).Info("Stacking some sats on " + k.Name)

	log.WithFields(logrus.Fields{
		"action":        "stack",
		"crypto":        k.Crypto,
		"cryptoBalance": k.BalanceCrypto,
		"fiat":          k.Fiat,
		"fiatBalance":   k.BalanceFiat,
		"ask":           k.Ask,
	}).Debug("Balance before placing the Order")

	volume := (c.Float64("amount") / k.AskFloat)
	orderArgs, err := k.createOrderArgs(c, volume, k.Ask, false)
	if err != nil {
		return "", fmt.Errorf("Failed to create args to place order: %s", err)
	}

	// Place the Order
	order, err := k.Api.AddOrder(k.Pair, "buy", orderArgs["orderType"], orderArgs["volume"], orderArgs)
	if err != nil {
		log.WithFields(logrus.Fields{
			"action":  "btd",
			"userref": k.UserRef,
		}).Error(fmt.Sprintf("Error Creating order: %s", err))
		return "", fmt.Errorf("Failed to place order: %s", err)
	}

	var orderId string
	if c.Bool("dry-run") {
		orderId = "DRY-RUN"
	} else {
		orderId = strings.Join(order.TransactionIds, ",")

accc025e52fed7977012a2c16b07315bae3851cc

@github-actions github-actions bot added the todo label Feb 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

0 participants