package main import ( "errors" "git.beejay.kim/Gshopper/sentio" "git.beejay.kim/Gshopper/sentio/indicator" "math" "time" ) const ( ATR_MULTIPLIER = 1.5 ATR_PERIOD = 5 ATR_HHV = 4 ATR_STOPLOSS_THRESHOLD = 1 EXTRA_POSITION_THRESHOLD = 1.002 ) var Strategy = qqq{} type qqq struct{} func (strategy qqq) Name() string { return "Alpaca: QQQ [TQQQ : SQQQ]" } func (strategy qqq) Model() string { return "qqq15" } func (strategy qqq) MarketId() string { return "alpaca" } func (strategy qqq) Interval() uint8 { return 5 } func (strategy qqq) Cooldown(periods uint8) time.Duration { return time.Minute * time.Duration(strategy.Interval()*periods) } func (strategy qqq) PositionSymbols() map[sentio.Side]string { return map[sentio.Side]string{ sentio.BASE: "QQQ", sentio.LONG: "TQQQ", sentio.SHORT: "SQQQ", } } func (strategy qqq) PositionProbabilities() map[sentio.Side]float64 { return map[sentio.Side]float64{ sentio.BASE: -1, sentio.LONG: 1.0008, sentio.SHORT: .9990, } } func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability) error { var ( now = market.Clock().Now() symbols = strategy.Symbols() stoplosses map[string]float64 quotes map[string]sentio.Quote orders []sentio.Order portfolio sentio.Portfolio err error ) // skip too early orders if now.Hour() == 9 && now.Minute() < 45 { return nil } // close all orders before market close if now.Hour() == 15 && now.Minute() > 50 { return strategy.CloseAllOrders(market) } // 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 { return err } // update stoplosses or close running orders for i := range orders { if strategy.PositionSymbols()[sentio.LONG] == orders[i].GetSymbol() && probability.Value < 1.0008 { if _, err = market.CloseOrder(orders[i].GetId()); err != nil { return err } continue } if strategy.PositionSymbols()[sentio.SHORT] == orders[i].GetSymbol() && probability.Value > .9998 { if _, err = market.CloseOrder(orders[i].GetId()); err != nil { return err } continue } if f, ok := stoplosses[orders[i].GetId()]; ok && f > 0 { if err = market.UpdateOrder(orders[i].GetId(), f); err != nil { return err } continue } } if probability.TS.Add(time.Minute * time.Duration(int64(strategy.Interval()/2))).Before(now.Round(time.Minute)) { return nil } // Prevent BUYs on closing market if now.Hour() == 15 && now.Minute() > 46 { return nil } // Prevent Market.CreateOrder while probability is not clear if probability.Value == 1 || probability.Value < 0 { return nil } if portfolio, err = market.Portfolio(); err != nil { return err } if quotes, err = market.Quotes(symbols...); err != nil { return err } var t = sentio.SHORT if probability.Value > 1 { 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 }