Browse Source

Refactoring

Alexey Kim 2 days ago
parent
commit
2b6a1df35f

+ 46 - 8
indicator/art_trailing_stop_loss.go

@@ -1,19 +1,57 @@
 package indicator
 
 import (
+	"errors"
+	"git.beejay.kim/Gshopper/sentio"
 	"git.beejay.kim/Gshopper/sentio/talib"
+	"time"
 )
 
-func AtrTrailingStopLoss(inHigh []float64, inLow []float64, inClose []float64, inTimePeriod int, multiplier float64, hhv int) []float64 {
-	atr := talib.Atr(inHigh, inLow, inClose, inTimePeriod)
-	sl := make([]float64, len(atr))
-	for i := range atr {
-		sl[i] = inClose[i] - multiplier*atr[i]
+func AtrTrailingStopLoss(m sentio.Market,
+	period uint,
+	length uint,
+	multiplier float64,
+	hhv int,
+	symbols ...string,
+) (map[string]float64, error) {
+	var (
+		stoplosses map[string]float64
+		bars       map[string][]sentio.Bar
+		err        error
+	)
+
+	if bars, err = m.HistoricalBars(symbols, time.Minute*time.Duration(period), nil); err != nil {
+		return nil, err
 	}
 
-	if hhv > 1 {
-		sl = talib.Max(sl, hhv)
+	stoplosses = make(map[string]float64)
+	for s := range bars {
+		if bars == nil || len(bars[s]) < int(length) {
+			return nil, errors.New("AtrStopLoss: could not calculate stoploss for too short timeseries")
+		}
+
+		h := make([]float64, len(bars[s]))
+		l := make([]float64, len(bars[s]))
+		c := make([]float64, len(bars[s]))
+
+		for i := range bars[s] {
+			h[i] = bars[s][i].High
+			l[i] = bars[s][i].Low
+			c[i] = bars[s][i].Close
+		}
+
+		atr := talib.Atr(h, l, c, int(length))
+		trailing := make([]float64, len(atr))
+		for i := range atr {
+			trailing[i] = c[i] - multiplier*atr[i]
+		}
+
+		if hhv > 1 {
+			trailing = talib.Max(trailing, hhv)
+		}
+
+		stoplosses[s] = trailing[len(trailing)-1]
 	}
 
-	return sl
+	return stoplosses, nil
 }

+ 0 - 45
market.go

@@ -2,8 +2,6 @@ package sentio
 
 import (
 	"errors"
-	"fmt"
-	"strings"
 	"time"
 )
 
@@ -33,46 +31,3 @@ type Market interface {
 	Quotes(symbols ...string) (map[string]Quote, error)
 	HistoricalBars(symbols []string, interval time.Duration, from *time.Time) (map[string][]Bar, error)
 }
-
-type OrderListCriteria struct {
-	Status  string
-	Limit   uint
-	Symbols []string
-	After   *time.Time
-	Until   *time.Time
-	Nested  bool
-	Side    *string
-}
-
-func (criteria OrderListCriteria) Values() map[string]string {
-	var values = make(map[string]string)
-
-	if criteria.Limit < 1 {
-		criteria.Limit = 500
-	}
-
-	if criteria.Symbols != nil && len(criteria.Symbols) > 0 {
-		values["symbols"] = strings.Join(criteria.Symbols, ",")
-	}
-
-	if criteria.After == nil {
-		t := time.Now().Add(-time.Hour * 24).Round(time.Hour * 24)
-		criteria.After = &t
-	}
-
-	if criteria.Until != nil && !criteria.Until.IsZero() {
-		values["until"] = criteria.Until.In(time.UTC).Format(time.RFC3339)
-	}
-
-	if criteria.Side != nil {
-		values["side"] = *criteria.Side
-	}
-
-	values["status"] = "all"
-	values["nested"] = "true"
-	values["limit"] = fmt.Sprintf("%d", criteria.Limit)
-	values["after"] = criteria.After.In(time.UTC).Format(time.RFC3339)
-	values["direction"] = "asc"
-
-	return values
-}

+ 49 - 0
order_list_criteria.go

@@ -0,0 +1,49 @@
+package sentio
+
+import (
+	"fmt"
+	"strings"
+	"time"
+)
+
+type OrderListCriteria struct {
+	Status  string
+	Limit   uint
+	Symbols []string
+	After   *time.Time
+	Until   *time.Time
+	Side    *string
+}
+
+func (criteria OrderListCriteria) Values() map[string]string {
+	var values = make(map[string]string)
+
+	if criteria.Limit < 1 {
+		criteria.Limit = 500
+	}
+
+	if criteria.Symbols != nil && len(criteria.Symbols) > 0 {
+		values["symbols"] = strings.Join(criteria.Symbols, ",")
+	}
+
+	if criteria.After == nil {
+		t := time.Now().Add(-time.Hour * 24).Round(time.Hour * 24)
+		criteria.After = &t
+	}
+
+	if criteria.Until != nil && !criteria.Until.IsZero() {
+		values["until"] = criteria.Until.In(time.UTC).Format(time.RFC3339)
+	}
+
+	if criteria.Side != nil {
+		values["side"] = *criteria.Side
+	}
+
+	values["status"] = "all"
+	values["nested"] = "true"
+	values["limit"] = fmt.Sprintf("%d", criteria.Limit)
+	values["after"] = criteria.After.In(time.UTC).Format(time.RFC3339)
+	values["direction"] = "asc"
+
+	return values
+}

+ 6 - 149
strategy/alpaca/qqq15/strategy.go

@@ -1,10 +1,9 @@
 package main
 
 import (
-	"errors"
 	"git.beejay.kim/Gshopper/sentio"
 	"git.beejay.kim/Gshopper/sentio/indicator"
-	"math"
+	"git.beejay.kim/Gshopper/sentio/util"
 	"time"
 )
 
@@ -12,9 +11,6 @@ const (
 	ATR_MULTIPLIER = 1.5
 	ATR_PERIOD     = 5
 	ATR_HHV        = 4
-
-	ATR_STOPLOSS_THRESHOLD   = 1
-	EXTRA_POSITION_THRESHOLD = 1.002
 )
 
 var Strategy = qqq{}
@@ -60,11 +56,10 @@ func (strategy qqq) PositionProbabilities() map[sentio.Side]float64 {
 func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability) error {
 	var (
 		now        = market.Clock().Now()
-		symbols    = strategy.Symbols()
+		symbols    = util.Symbols(strategy)
 		stoplosses map[string]float64
 		quotes     map[string]sentio.Quote
 		orders     []sentio.Order
-		portfolio  sentio.Portfolio
 		err        error
 	)
 
@@ -75,19 +70,18 @@ func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability)
 
 	// close all orders before market close
 	if now.Hour() == 15 && now.Minute() > 50 {
-		return strategy.CloseAllOrders(market)
+		return util.CloseAllOrders(market, strategy)
 	}
 
 	// retrieve running orders
 	if orders, err = market.Orders(sentio.OrderListCriteria{
 		Status:  "open",
 		Symbols: symbols,
-		Nested:  true,
 	}); err != nil {
 		return err
 	}
 
-	if stoplosses, err = strategy.AtrStopLoss(market, symbols...); err != nil {
+	if stoplosses, err = indicator.AtrTrailingStopLoss(market, 1, ATR_PERIOD, ATR_MULTIPLIER, ATR_HHV, symbols...); err != nil {
 		return err
 	}
 
@@ -111,7 +105,7 @@ func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability)
 			continue
 		}
 
-		if f, ok := stoplosses[orders[i].GetId()]; ok && f > 0 {
+		if f, ok := stoplosses[orders[i].GetSymbol()]; ok && f > 0 {
 			if err = market.UpdateOrder(orders[i].GetId(), f); err != nil {
 				return err
 			}
@@ -134,10 +128,6 @@ func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability)
 		return nil
 	}
 
-	if portfolio, err = market.Portfolio(); err != nil {
-		return err
-	}
-
 	if quotes, err = market.Quotes(symbols...); err != nil {
 		return err
 	}
@@ -147,138 +137,5 @@ func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability)
 		t = sentio.LONG
 	}
 
-	return strategy.CreateOrder(market, t, probability, portfolio, quotes, stoplosses)
-}
-
-func (strategy qqq) Symbols() []string {
-	var symbols []string
-
-	for side, s := range strategy.PositionSymbols() {
-		if sentio.BASE == side {
-			continue
-		}
-
-		symbols = append(symbols, s)
-	}
-
-	return symbols
-}
-
-func (strategy qqq) CloseAllOrders(market sentio.Market) error {
-	var (
-		symbols = strategy.Symbols()
-		orders  []sentio.Order
-		err     error
-	)
-
-	if orders, err = market.Orders(sentio.OrderListCriteria{
-		Status:  "open",
-		Symbols: symbols,
-		Nested:  true,
-	}); err != nil {
-		return err
-	}
-
-	for i := range orders {
-		if _, err = market.CloseOrder(orders[i].GetId()); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func (strategy qqq) AtrStopLoss(market sentio.Market, symbols ...string) (map[string]float64, error) {
-	var (
-		stoplosses map[string]float64
-		bars       map[string][]sentio.Bar
-		err        error
-	)
-
-	if bars, err = market.HistoricalBars(symbols, time.Minute, nil); err != nil {
-		return nil, err
-	}
-
-	stoplosses = make(map[string]float64)
-	for s := range bars {
-		if bars == nil || len(bars[s]) < ATR_PERIOD {
-			return nil, errors.New("AtrStopLoss: could not calculate stoploss for too short timeseries")
-		}
-
-		h := make([]float64, len(bars[s]))
-		l := make([]float64, len(bars[s]))
-		c := make([]float64, len(bars[s]))
-
-		for i := range bars[s] {
-			h[i] = bars[s][i].High
-			l[i] = bars[s][i].Low
-			c[i] = bars[s][i].Close
-		}
-
-		trailing := indicator.AtrTrailingStopLoss(h, l, c, ATR_PERIOD, ATR_MULTIPLIER, ATR_HHV)
-		stoplosses[s] = trailing[len(trailing)-1]
-	}
-
-	return stoplosses, nil
-}
-
-func (strategy qqq) CreateOrder(m sentio.Market, t sentio.Side, proba sentio.Probability, p sentio.Portfolio, q map[string]sentio.Quote, sl map[string]float64) error {
-	var (
-		symbol    = strategy.PositionSymbols()[t]
-		position  sentio.Position
-		account   sentio.MarketAccount
-		has       = false
-		threshold float64
-		size      uint
-		ok        bool
-		err       error
-	)
-
-	// ensure portfolio
-	position, has = p.Get(symbol)
-
-	// define threshold
-	if threshold, ok = strategy.PositionProbabilities()[t]; !ok {
-		threshold = -1
-	}
-
-	if threshold == -1 || symbol == "" {
-		return nil
-	}
-
-	// Prevent Market.CreateOrder when BidPrice less than ATR_STOPLOSS_THRESHOLD
-	if q[symbol].BidPrice/sl[symbol] < ATR_STOPLOSS_THRESHOLD {
-		return nil
-	}
-
-	if account, err = m.Account(); err != nil {
-		return err
-	}
-
-	if account.GetCash() < q[symbol].BidPrice {
-		return sentio.ErrTooSmallOrder
-	}
-
-	if !has && proba.Value > threshold {
-
-		// create a new order
-		if size = uint(math.Floor(account.GetCash() * .7 / q[symbol].BidPrice)); size < 1 {
-			return sentio.ErrTooSmallOrder
-		}
-
-		_, err = m.CreateOrder(symbol, size, sl[symbol])
-		return err
-
-	} else if has && position.GetAvgPrice()/position.GetCurrentPrice() > EXTRA_POSITION_THRESHOLD {
-
-		// create an extra position
-		if size = uint(math.Floor(account.GetCash() * .5 / q[symbol].BidPrice)); size < 1 {
-			return sentio.ErrTooSmallOrder
-		}
-
-		_, err = m.CreateOrder(symbol, size, sl[symbol])
-		return err
-	}
-
-	return nil
+	return util.CreateOrder(market, strategy, t, probability, quotes, stoplosses)
 }

+ 6 - 149
strategy/alpaca/qqq15_nodelay/strategy.go

@@ -1,10 +1,9 @@
 package main
 
 import (
-	"errors"
 	"git.beejay.kim/Gshopper/sentio"
 	"git.beejay.kim/Gshopper/sentio/indicator"
-	"math"
+	"git.beejay.kim/Gshopper/sentio/util"
 	"time"
 )
 
@@ -12,9 +11,6 @@ const (
 	ATR_MULTIPLIER = 1.5
 	ATR_PERIOD     = 5
 	ATR_HHV        = 4
-
-	ATR_STOPLOSS_THRESHOLD   = 1
-	EXTRA_POSITION_THRESHOLD = 1.002
 )
 
 var Strategy = qqq{}
@@ -60,11 +56,10 @@ func (strategy qqq) PositionProbabilities() map[sentio.Side]float64 {
 func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability) error {
 	var (
 		now        = market.Clock().Now()
-		symbols    = strategy.Symbols()
+		symbols    = util.Symbols(strategy)
 		stoplosses map[string]float64
 		quotes     map[string]sentio.Quote
 		orders     []sentio.Order
-		portfolio  sentio.Portfolio
 		err        error
 	)
 
@@ -75,19 +70,18 @@ func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability)
 
 	// close all orders before market close
 	if now.Hour() == 15 && now.Minute() > 50 {
-		return strategy.CloseAllOrders(market)
+		return util.CloseAllOrders(market, strategy)
 	}
 
 	// retrieve running orders
 	if orders, err = market.Orders(sentio.OrderListCriteria{
 		Status:  "open",
 		Symbols: symbols,
-		Nested:  true,
 	}); err != nil {
 		return err
 	}
 
-	if stoplosses, err = strategy.AtrStopLoss(market, symbols...); err != nil {
+	if stoplosses, err = indicator.AtrTrailingStopLoss(market, 1, ATR_PERIOD, ATR_MULTIPLIER, ATR_HHV, symbols...); err != nil {
 		return err
 	}
 
@@ -111,7 +105,7 @@ func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability)
 			continue
 		}
 
-		if f, ok := stoplosses[orders[i].GetId()]; ok && f > 0 {
+		if f, ok := stoplosses[orders[i].GetSymbol()]; ok && f > 0 {
 			if err = market.UpdateOrder(orders[i].GetId(), f); err != nil {
 				return err
 			}
@@ -134,10 +128,6 @@ func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability)
 		return nil
 	}
 
-	if portfolio, err = market.Portfolio(); err != nil {
-		return err
-	}
-
 	if quotes, err = market.Quotes(symbols...); err != nil {
 		return err
 	}
@@ -147,138 +137,5 @@ func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability)
 		t = sentio.LONG
 	}
 
-	return strategy.CreateOrder(market, t, probability, portfolio, quotes, stoplosses)
-}
-
-func (strategy qqq) Symbols() []string {
-	var symbols []string
-
-	for side, s := range strategy.PositionSymbols() {
-		if sentio.BASE == side {
-			continue
-		}
-
-		symbols = append(symbols, s)
-	}
-
-	return symbols
-}
-
-func (strategy qqq) CloseAllOrders(market sentio.Market) error {
-	var (
-		symbols = strategy.Symbols()
-		orders  []sentio.Order
-		err     error
-	)
-
-	if orders, err = market.Orders(sentio.OrderListCriteria{
-		Status:  "open",
-		Symbols: symbols,
-		Nested:  true,
-	}); err != nil {
-		return err
-	}
-
-	for i := range orders {
-		if _, err = market.CloseOrder(orders[i].GetId()); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func (strategy qqq) AtrStopLoss(market sentio.Market, symbols ...string) (map[string]float64, error) {
-	var (
-		stoplosses map[string]float64
-		bars       map[string][]sentio.Bar
-		err        error
-	)
-
-	if bars, err = market.HistoricalBars(symbols, time.Minute, nil); err != nil {
-		return nil, err
-	}
-
-	stoplosses = make(map[string]float64)
-	for s := range bars {
-		if bars == nil || len(bars[s]) < ATR_PERIOD {
-			return nil, errors.New("AtrStopLoss: could not calculate stoploss for too short timeseries")
-		}
-
-		h := make([]float64, len(bars[s]))
-		l := make([]float64, len(bars[s]))
-		c := make([]float64, len(bars[s]))
-
-		for i := range bars[s] {
-			h[i] = bars[s][i].High
-			l[i] = bars[s][i].Low
-			c[i] = bars[s][i].Close
-		}
-
-		trailing := indicator.AtrTrailingStopLoss(h, l, c, ATR_PERIOD, ATR_MULTIPLIER, ATR_HHV)
-		stoplosses[s] = trailing[len(trailing)-1]
-	}
-
-	return stoplosses, nil
-}
-
-func (strategy qqq) CreateOrder(m sentio.Market, t sentio.Side, proba sentio.Probability, p sentio.Portfolio, q map[string]sentio.Quote, sl map[string]float64) error {
-	var (
-		symbol    = strategy.PositionSymbols()[t]
-		position  sentio.Position
-		account   sentio.MarketAccount
-		has       = false
-		threshold float64
-		size      uint
-		ok        bool
-		err       error
-	)
-
-	// ensure portfolio
-	position, has = p.Get(symbol)
-
-	// define threshold
-	if threshold, ok = strategy.PositionProbabilities()[t]; !ok {
-		threshold = -1
-	}
-
-	if threshold == -1 || symbol == "" {
-		return nil
-	}
-
-	// Prevent Market.CreateOrder when BidPrice less than ATR_STOPLOSS_THRESHOLD
-	if q[symbol].BidPrice/sl[symbol] < ATR_STOPLOSS_THRESHOLD {
-		return nil
-	}
-
-	if account, err = m.Account(); err != nil {
-		return err
-	}
-
-	if account.GetCash() < q[symbol].BidPrice {
-		return sentio.ErrTooSmallOrder
-	}
-
-	if !has && proba.Value > threshold {
-
-		// create a new order
-		if size = uint(math.Floor(account.GetCash() * .7 / q[symbol].BidPrice)); size < 1 {
-			return sentio.ErrTooSmallOrder
-		}
-
-		_, err = m.CreateOrder(symbol, size, sl[symbol])
-		return err
-
-	} else if has && position.GetAvgPrice()/position.GetCurrentPrice() > EXTRA_POSITION_THRESHOLD {
-
-		// create an extra position
-		if size = uint(math.Floor(account.GetCash() * .5 / q[symbol].BidPrice)); size < 1 {
-			return sentio.ErrTooSmallOrder
-		}
-
-		_, err = m.CreateOrder(symbol, size, sl[symbol])
-		return err
-	}
-
-	return nil
+	return util.CreateOrder(market, strategy, t, probability, quotes, stoplosses)
 }

+ 113 - 0
util/order.go

@@ -0,0 +1,113 @@
+package util
+
+import (
+	"errors"
+	"git.beejay.kim/Gshopper/sentio"
+	"math"
+)
+
+func CreateOrder(
+	m sentio.Market,
+	s sentio.Strategy,
+	t sentio.Side,
+	proba sentio.Probability,
+	q map[string]sentio.Quote,
+	sl map[string]float64,
+) error {
+	var (
+		symbol    string
+		orders    []sentio.Order
+		account   sentio.MarketAccount
+		has       = false
+		avgPrice  float64
+		threshold float64
+		size      uint
+		ok        bool
+		err       error
+	)
+
+	if symbol, ok = s.PositionSymbols()[t]; !ok {
+		return errors.New("`CreateOrder`: unknown Side for the current strategy")
+	}
+
+	if orders, err = m.Orders(sentio.OrderListCriteria{
+		Status:  "open",
+		Symbols: []string{symbol},
+	}); err != nil {
+		return err
+	}
+
+	has = len(orders) > 0
+	for i := range orders {
+		avgPrice += orders[i].GetEntryPrice()
+	}
+
+	if has {
+		avgPrice = avgPrice / float64(len(orders))
+	}
+
+	// define threshold
+	if threshold, ok = s.PositionProbabilities()[t]; !ok {
+		return nil
+	}
+
+	// Prevent cases when order will be closed ASAP they were opened.
+	// Also, Alpaca requires at least 0.01 gap against base_price
+	if sl[symbol] >= q[symbol].BidPrice-0.011 {
+		return sentio.ErrTooSmallOrder
+	}
+
+	if account, err = m.Account(); err != nil {
+		return err
+	}
+
+	if account.GetCash() < q[symbol].BidPrice {
+		return sentio.ErrTooSmallOrder
+	}
+
+	if !has && ((sentio.LONG == t && proba.Value > threshold) || (sentio.SHORT == t && proba.Value < threshold)) {
+
+		// create a new order
+		if size = uint(math.Floor(account.GetCash() * .7 / q[symbol].BidPrice)); size < 1 {
+			return sentio.ErrTooSmallOrder
+		}
+
+		_, err = m.CreateOrder(symbol, size, sl[symbol])
+		return err
+
+	} else if has && avgPrice/q[symbol].BidPrice > 1.002 {
+
+		// create an extra position
+		if size = uint(math.Floor(account.GetCash() * .5 / q[symbol].BidPrice)); size < 1 {
+			return sentio.ErrTooSmallOrder
+		}
+
+		_, err = m.CreateOrder(symbol, size, sl[symbol])
+		return err
+	}
+
+	return nil
+}
+
+func CloseAllOrders(m sentio.Market, s sentio.Strategy) error {
+	var (
+		symbols = Symbols(s)
+		orders  []sentio.Order
+		err     error
+	)
+
+	if orders, err = m.Orders(sentio.OrderListCriteria{
+		Status:  "open",
+		Symbols: symbols,
+	}); err != nil {
+		return err
+	}
+
+	for i := range orders {
+		if _, err = m.CloseOrder(orders[i].GetId()); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 21 - 0
util/strategy.go

@@ -0,0 +1,21 @@
+package util
+
+import "git.beejay.kim/Gshopper/sentio"
+
+func Symbols(strategy sentio.Strategy) []string {
+	var symbols []string
+
+	if strategy == nil {
+		return nil
+	}
+
+	for side, s := range strategy.PositionSymbols() {
+		if sentio.BASE == side {
+			continue
+		}
+
+		symbols = append(symbols, s)
+	}
+
+	return symbols
+}