2
0

order.go 2.8 KB

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