order.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package util
  2. import (
  3. "errors"
  4. "fmt"
  5. "git.beejay.kim/Gshopper/sentio"
  6. "math"
  7. )
  8. const TradingRange = .02
  9. func SideOf(intent sentio.OrderIntent) sentio.Side {
  10. switch intent {
  11. case sentio.BuyToOpen:
  12. return sentio.LONG
  13. case sentio.SellToClose:
  14. return sentio.LONG
  15. case sentio.BuyToClose:
  16. return sentio.SHORT
  17. case sentio.SellToOpen:
  18. return sentio.SHORT
  19. }
  20. panic(fmt.Errorf("unexpected `OrderIntent`: %v", intent))
  21. }
  22. func NewOrder(
  23. m sentio.Market,
  24. s sentio.Strategy,
  25. action sentio.Action,
  26. quotes map[string]sentio.Quote,
  27. rm sentio.RiskManager,
  28. ) (opts sentio.OrderCreateOptions, err error) {
  29. var (
  30. side sentio.Side
  31. base string
  32. bid float64
  33. ok bool
  34. )
  35. if side = sentio.ActionToSide(action); sentio.BASE == side {
  36. err = errors.New("`CreateOrder`: do nothing while `sentio.BASE`")
  37. return
  38. }
  39. if base, ok = s.PositionSymbols()[sentio.BASE]; !ok {
  40. err = errors.New("`CreateOrder`: unknown `sentio.BASE`")
  41. return
  42. }
  43. if opts.Symbol, ok = s.PositionSymbols()[side]; !ok {
  44. err = errors.New("`CreateOrder`: unknown Side for the current strategy")
  45. return
  46. }
  47. canLeverage := (side == sentio.LONG && m.EnableLongLeverage()) || (side == sentio.SHORT && m.EnableShortLeverage())
  48. if !canLeverage {
  49. opts.Symbol = base
  50. }
  51. bid = sentio.ToFixed(quotes[opts.Symbol].BidPrice, 2)
  52. opts.Action = sentio.Action_BUY
  53. opts.TakeProfit = ToPtr(sentio.ToFixed(rm.TakeProfit(opts.Symbol, bid), 2))
  54. opts.StopLoss = ToPtr(sentio.ToFixed(rm.StopLoss(opts.Symbol, bid), 2))
  55. // Invert OrderOptions if we will do SHORT for BASE symbol
  56. if sentio.SHORT == side && !canLeverage {
  57. tmp := opts.StopLoss
  58. opts.StopLoss = opts.TakeProfit
  59. opts.TakeProfit = tmp
  60. opts.Action = sentio.Action_SELL
  61. }
  62. // Prevent orders those have too small expected profit
  63. if (opts.Action == sentio.Action_BUY && *opts.TakeProfit < bid+TradingRange) ||
  64. (opts.Action == sentio.Action_SELL && *opts.TakeProfit > bid+TradingRange) {
  65. err = sentio.ErrLowTakeProfit
  66. return
  67. }
  68. // Prevent cases when order will be closed ASAP they were opened.
  69. // Also, Alpaca requires at least 0.01 gap against base_price
  70. if opts.Action == sentio.Action_BUY && *opts.StopLoss > bid-TradingRange {
  71. err = sentio.ErrHighStoploss
  72. return
  73. }
  74. var (
  75. account sentio.MarketAccount
  76. cash float64
  77. ratio float64
  78. )
  79. if account, err = m.Account(); err != nil {
  80. return
  81. }
  82. cash = account.GetCash(false)
  83. if budget := m.MaxBudget(); budget > 0 && cash > budget {
  84. cash = budget
  85. }
  86. if cash <= bid {
  87. err = sentio.ErrTooSmallOrder
  88. return
  89. }
  90. if ratio = rm.GetOrderSize(opts.Symbol, bid); ratio <= 0 {
  91. err = sentio.ErrRiskManagementPrevent
  92. return
  93. }
  94. if opts.Size = uint(math.Floor(cash * ratio / bid)); opts.Size < 1 {
  95. err = sentio.ErrTooSmallOrder
  96. }
  97. return opts, nil
  98. }
  99. func CloseAllOrders(m sentio.Market, s sentio.Strategy) error {
  100. var (
  101. symbols = Symbols(s)
  102. orders []sentio.Order
  103. err error
  104. )
  105. if orders, err = m.Orders(sentio.OrderListCriteria{
  106. Status: ToPtr("open"),
  107. Symbols: symbols,
  108. }); err != nil {
  109. return err
  110. }
  111. for i := range orders {
  112. if _, err = m.CloseOrder(orders[i], nil); err != nil {
  113. return err
  114. }
  115. }
  116. return nil
  117. }