123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- 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
- }
|