strategy.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. package main
  2. import (
  3. "errors"
  4. "git.beejay.kim/Gshopper/sentio"
  5. "git.beejay.kim/Gshopper/sentio/indicator"
  6. "math"
  7. "time"
  8. )
  9. const (
  10. ATR_MULTIPLIER = 1.5
  11. ATR_PERIOD = 5
  12. ATR_HHV = 4
  13. ATR_STOPLOSS_THRESHOLD = 1
  14. EXTRA_POSITION_THRESHOLD = 1.002
  15. )
  16. var Strategy = qqq{}
  17. type qqq struct{}
  18. func (strategy qqq) Name() string {
  19. return "Alpaca: QQQ [TQQQ : SQQQ]"
  20. }
  21. func (strategy qqq) Model() string {
  22. return "qqq15"
  23. }
  24. func (strategy qqq) MarketId() string {
  25. return "alpaca"
  26. }
  27. func (strategy qqq) Interval() uint8 {
  28. return 5
  29. }
  30. func (strategy qqq) Cooldown(periods uint8) time.Duration {
  31. return time.Minute * time.Duration(strategy.Interval()*periods)
  32. }
  33. func (strategy qqq) PositionSymbols() map[sentio.Side]string {
  34. return map[sentio.Side]string{
  35. sentio.BASE: "QQQ",
  36. sentio.LONG: "TQQQ",
  37. sentio.SHORT: "SQQQ",
  38. }
  39. }
  40. func (strategy qqq) PositionProbabilities() map[sentio.Side]float64 {
  41. return map[sentio.Side]float64{
  42. sentio.BASE: -1,
  43. sentio.LONG: 1.0008,
  44. sentio.SHORT: .9990,
  45. }
  46. }
  47. func (strategy qqq) Handle(market sentio.Market, probability sentio.Probability) error {
  48. var (
  49. now = market.Clock().Now()
  50. symbols = strategy.Symbols()
  51. stoplosses map[string]float64
  52. quotes map[string]sentio.Quote
  53. orders []sentio.Order
  54. portfolio sentio.Portfolio
  55. err error
  56. )
  57. // skip too early orders
  58. if now.Hour() == 9 && now.Minute() < 45 {
  59. return nil
  60. }
  61. // close all orders before market close
  62. if now.Hour() == 15 && now.Minute() > 50 {
  63. return strategy.CloseAllOrders(market)
  64. }
  65. // retrieve running orders
  66. if orders, err = market.Orders(sentio.OrderListCriteria{
  67. Status: "open",
  68. Symbols: symbols,
  69. Nested: true,
  70. }); err != nil {
  71. return err
  72. }
  73. if stoplosses, err = strategy.AtrStopLoss(market, symbols...); err != nil {
  74. return err
  75. }
  76. // update stoplosses or close running orders
  77. for i := range orders {
  78. if strategy.PositionSymbols()[sentio.LONG] == orders[i].GetSymbol() &&
  79. probability.Value < 1.0008 {
  80. if _, err = market.CloseOrder(orders[i].GetId()); err != nil {
  81. return err
  82. }
  83. continue
  84. }
  85. if strategy.PositionSymbols()[sentio.SHORT] == orders[i].GetSymbol() &&
  86. probability.Value > .9998 {
  87. if _, err = market.CloseOrder(orders[i].GetId()); err != nil {
  88. return err
  89. }
  90. continue
  91. }
  92. if f, ok := stoplosses[orders[i].GetId()]; ok && f > 0 {
  93. if err = market.UpdateOrder(orders[i].GetId(), f); err != nil {
  94. return err
  95. }
  96. continue
  97. }
  98. }
  99. if probability.TS.Add(time.Minute * time.Duration(int64(strategy.Interval()/2))).Before(now.Round(time.Minute)) {
  100. return nil
  101. }
  102. // Prevent BUYs on closing market
  103. if now.Hour() == 15 && now.Minute() > 46 {
  104. return nil
  105. }
  106. // Prevent Market.CreateOrder while probability is not clear
  107. if probability.Value == 1 || probability.Value < 0 {
  108. return nil
  109. }
  110. if portfolio, err = market.Portfolio(); err != nil {
  111. return err
  112. }
  113. if quotes, err = market.Quotes(symbols...); err != nil {
  114. return err
  115. }
  116. var t = sentio.SHORT
  117. if probability.Value > 1 {
  118. t = sentio.LONG
  119. }
  120. return strategy.CreateOrder(market, t, probability, portfolio, quotes, stoplosses)
  121. }
  122. func (strategy qqq) Symbols() []string {
  123. var symbols []string
  124. for side, s := range strategy.PositionSymbols() {
  125. if sentio.BASE == side {
  126. continue
  127. }
  128. symbols = append(symbols, s)
  129. }
  130. return symbols
  131. }
  132. func (strategy qqq) CloseAllOrders(market sentio.Market) error {
  133. var (
  134. symbols = strategy.Symbols()
  135. orders []sentio.Order
  136. err error
  137. )
  138. if orders, err = market.Orders(sentio.OrderListCriteria{
  139. Status: "open",
  140. Symbols: symbols,
  141. Nested: true,
  142. }); err != nil {
  143. return err
  144. }
  145. for i := range orders {
  146. if _, err = market.CloseOrder(orders[i].GetId()); err != nil {
  147. return err
  148. }
  149. }
  150. return nil
  151. }
  152. func (strategy qqq) AtrStopLoss(market sentio.Market, symbols ...string) (map[string]float64, error) {
  153. var (
  154. stoplosses map[string]float64
  155. bars map[string][]sentio.Bar
  156. err error
  157. )
  158. if bars, err = market.HistoricalBars(symbols, time.Minute, nil); err != nil {
  159. return nil, err
  160. }
  161. stoplosses = make(map[string]float64)
  162. for s := range bars {
  163. if bars == nil || len(bars[s]) < ATR_PERIOD {
  164. return nil, errors.New("AtrStopLoss: could not calculate stoploss for too short timeseries")
  165. }
  166. h := make([]float64, len(bars[s]))
  167. l := make([]float64, len(bars[s]))
  168. c := make([]float64, len(bars[s]))
  169. for i := range bars[s] {
  170. h[i] = bars[s][i].High
  171. l[i] = bars[s][i].Low
  172. c[i] = bars[s][i].Close
  173. }
  174. trailing := indicator.AtrTrailingStopLoss(h, l, c, ATR_PERIOD, ATR_MULTIPLIER, ATR_HHV)
  175. stoplosses[s] = trailing[len(trailing)-1]
  176. }
  177. return stoplosses, nil
  178. }
  179. 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 {
  180. var (
  181. symbol = strategy.PositionSymbols()[t]
  182. position sentio.Position
  183. account sentio.MarketAccount
  184. has = false
  185. threshold float64
  186. size uint
  187. ok bool
  188. err error
  189. )
  190. // ensure portfolio
  191. position, has = p.Get(symbol)
  192. // define threshold
  193. if threshold, ok = strategy.PositionProbabilities()[t]; !ok {
  194. threshold = -1
  195. }
  196. if threshold == -1 || symbol == "" {
  197. return nil
  198. }
  199. // Prevent Market.CreateOrder when BidPrice less than ATR_STOPLOSS_THRESHOLD
  200. if q[symbol].BidPrice/sl[symbol] < ATR_STOPLOSS_THRESHOLD {
  201. return nil
  202. }
  203. if account, err = m.Account(); err != nil {
  204. return err
  205. }
  206. if account.GetCash() < q[symbol].BidPrice {
  207. return sentio.ErrTooSmallOrder
  208. }
  209. if !has && proba.Value > threshold {
  210. // create a new order
  211. if size = uint(math.Floor(account.GetCash() * .7 / q[symbol].BidPrice)); size < 1 {
  212. return sentio.ErrTooSmallOrder
  213. }
  214. _, err = m.CreateOrder(symbol, size, sl[symbol])
  215. return err
  216. } else if has && position.GetAvgPrice()/position.GetCurrentPrice() > EXTRA_POSITION_THRESHOLD {
  217. // create an extra position
  218. if size = uint(math.Floor(account.GetCash() * .5 / q[symbol].BidPrice)); size < 1 {
  219. return sentio.ErrTooSmallOrder
  220. }
  221. _, err = m.CreateOrder(symbol, size, sl[symbol])
  222. return err
  223. }
  224. return nil
  225. }