/ LiveMode / src / orders / create.jl
create.jl
  1  using .Executors: AnyLimitOrder
  2  using .PaperMode: create_sim_limit_order
  3  using .PaperMode.SimMode: construct_order_func
  4  using .Executors.Instruments: AbstractAsset
  5  using .OrderTypes: ordertype, MarketOrderType, GTCOrderType, ForcedOrderType
  6  using .Lang: filterkws
  7  
  8  function isactive(s::Strategy, ai::AssetInstance, resp::Py, eid::EIDType; fetched=false)
  9      isopen, status = _ccxtisopen(resp, eid, Val(:status))
 10      hasfill = resp_order_filled(resp, eid) > 0.0
 11      oid = resp_order_id(resp, eid, String)
 12      hasid = !isempty(oid)
 13  
 14      @debug "create order: isopen" _module = LogCreateOrder isopen hasfill oid hasid
 15      if !isopen && !hasfill && !hasid
 16          @warn "create order: refusing" ai oid isopen hasfill hasid
 17          return false, resp
 18      else
 19          status = resp_order_status(resp, eid)
 20          if (!_ccxtisstatus(resp, eid) && !fetched)
 21              if isprocessed_order(s, ai, oid)
 22                  fetched_resp = fetch_orders(s, ai; ids=(oid,))
 23                  if hasels(fetched_resp)
 24                      this_resp = first(fetched_resp)
 25                      return if resp_order_id(this_resp, eid) != oid
 26                          @error "create order: wrong id" oid this_resp
 27                          false, this_resp
 28                      else
 29                          isactive(s, ai, this_resp, eid; fetched=true)
 30                      end
 31                  else
 32                      @debug "create order: order not found on exchange (canceled?)" _module = LogCreateOrder ai oid hasfill hasid fetched_resp
 33                      return false, resp
 34                  end
 35              else
 36                  @warn "create order: unknown status" ai oid hasfill hasid resp status
 37                  return hasid, resp
 38              end
 39          elseif _ccxtisstatus(status, "canceled", "rejected", "expired") || fetched
 40              @warn "create order: $status" ai oid hasfill hasid
 41              return false, resp
 42          end
 43      end
 44      return true, resp
 45  end
 46  
 47  @doc """ Creates a live order.
 48  
 49  $(TYPEDSIGNATURES)
 50  
 51  This function is designed to create a live order on a given strategy and asset instance.
 52  It verifies the response from the exchange and constructs the order with the provided parameters.
 53  If the order fails to construct and is marked as synced, it attempts to synchronize the strategy and universe cash, and then retries order creation.
 54  Finally, if the order is marked as active, the function sets it as the active order.
 55  """
 56  function _create_live_order(
 57      s::LiveStrategy,
 58      ai::AssetInstance,
 59      resp;
 60      t,
 61      price,
 62      amount,
 63      synced=true,
 64      activate=true,
 65      skipcommit=false,
 66      kwargs...,
 67  )
 68      if isnothing(resp)
 69          @warn "create order: empty response ($(raw(ai)))"
 70          return nothing
 71      end
 72  
 73      eid = side = type = loss = profit = date = id = nothing
 74      try
 75          eid = exchangeid(ai)
 76          status = resp_order_status(resp, eid)
 77          side = @something _orderside(resp, eid) orderside(t)
 78          @debug "create order: parsing" _module = LogCreateOrder status filled =
 79              resp_order_filled(resp, eid) > 0.0 id = resp_order_id(resp, eid) side
 80          isopen_flag, resp = isactive(s, ai, resp, eid)
 81          if !isopen_flag
 82              return nothing
 83          end
 84          this_order_type(ot) = begin
 85              pos = @something posside(t) posside(ai) Long()
 86              Order{ot{side},<:AbstractAsset,<:ExchangeID,typeof(pos)}
 87          end
 88          type = let ot = ordertype_fromccxt(resp, eid)
 89              if isnothing(ot)
 90                  if t isa Type{<:Order}
 91                      t
 92                  else
 93                      @something ordertype_fromtif(resp, eid) (
 94                          if _ccxtisstatus(resp, "closed", eid)
 95                              MarketOrderType
 96                          else
 97                              GTCOrderType
 98                          end |> this_order_type
 99                      )
100                  end
101              else
102                  this_order_type(ot)
103              end
104          end
105          amount = resp_order_amount(resp, eid, amount, Val(:amount); ai)
106          price = resp_order_price(resp, eid, price, Val(:price); ai)
107          loss = resp_order_loss_price(resp, eid)
108          profit = resp_order_profit_price(resp, eid)
109          date = let this_date = @something pytodate(resp, eid) now()
110              # ensure order pricetime doesn't clash
111              while haskey(s, ai, (; price, time=this_date), side)
112                  this_date += Millisecond(1)
113              end
114              this_date
115          end
116          id = @something _orderid(resp, eid) begin
117              @warn "create order: missing id (default to pricetime hash)" ai = raw(ai) s = nameof(
118                  s
119              )
120              string(hash((price, date)))
121          end
122      catch
123          @error "create order: parsing failed" resp
124          @debug_backtrace LogCreateOrder
125          return nothing
126      end
127      o = let f = construct_order_func(type)
128          function create(; skipcommit)
129              @debug "create order: local" _module = LogCreateOrder ai id amount date type price leverage(
130                  ai
131              ) loss profit @caller(20)
132              f(
133                  s,
134                  type,
135                  ai;
136                  id,
137                  amount,
138                  date,
139                  type,
140                  price,
141                  loss,
142                  profit,
143                  skipcommit,
144                  kwargs...,
145              )
146          end
147          o = create(; skipcommit)
148          if isnothing(o) && synced
149              o = findorder(s, ai; resp, side)
150              if isnothing(o)
151                  @warn "create order: can't construct (back-tracking)" id = resp_order_id(
152                      resp, eid
153                  ) resp_order_status(resp, eid) ai = raw(ai) cash(ai) s = nameof(s) t
154              end
155          end
156          if isnothing(o)
157              @debug "create order: retrying (no commits)" _module = LogCreateOrder ai = raw(ai) side = posside(t)
158              o = @inlock ai create(skipcommit=true)
159          end
160          o
161      end
162      if isnothing(o)
163          @error "create order: failed to sync" id ai = raw(ai) cash(ai) amount s = nameof(s) type
164          @debug "create order: failed sync response" _module = LogCreateOrder resp
165          return nothing
166      elseif activate
167          @debug "create order: activating order" _module = LogCreateOrder id resp_order_status(resp, eid) resp_order_filled(resp, eid) resp_order_remaining(resp, eid) resp_order_type(resp, eid)
168          state = set_active_order!(s, ai, o; ap=resp_order_average(resp, eid))
169          # Perform a trade if the order has been filled instantly
170          function not_filled()
171              !isequal(ai, resp_order_filled(resp, eid), filled_amount(o), Val(:amount))
172          end
173          if not_filled()
174              @debug "create order: scheduling emulation" _module = LogCreateOrder resp_order_filled(resp, eid) filled_amount(o) not_filled()
175              func() =
176                  if not_filled()
177                      t = @inlock ai emulate_trade!(s, o, ai; state.average_price, resp)
178                  end
179              sendrequest!(ai, resp_order_timestamp(resp, eid), func)
180          end
181      end
182      event!(
183          ai,
184          AssetEvent,
185          :order_created,
186          s;
187          order=o,
188          req_type=t,
189          req_price=price,
190          req_amount=amount,
191      )
192      @debug "create order: done" _module = LogCreateOrder committed(o) o.amount ordertype(o)
193      return o
194  end
195  
196  @doc """ Sends and constructs a live order.
197  
198  $(TYPEDSIGNATURES)
199  
200  This function sends a live order using the provided parameters and constructs it based on the response received.
201  
202  """
203  function _create_live_order(
204      s::LiveStrategy,
205      ai::AssetInstance,
206      args...;
207      t,
208      amount,
209      price=lastprice(s, ai, t),
210      exc_kwargs=(),
211      skipchecks=false,
212      kwargs...,
213  )
214      @debug "create order: sending request" _module = LogCreateOrder ai t price amount f = @caller
215      resp = try
216          live_send_order(
217              s,
218              ai,
219              t,
220              args...;
221              skipchecks,
222              amount,
223              price,
224              withoutkws(:date; kwargs=exc_kwargs)...,
225          )
226      catch
227          @debug_backtrace LogCreateOrder
228          @error "create order: send failed" ai t amount price
229          return nothing
230      end
231      if resp isa Exception
232          @error "create order: send failed" ai t amount price exception = resp
233      else
234          @debug "create order: after request" _module = LogCreateOrder ai t price amount f = @caller() resp
235          _create_live_order(s, ai, resp; amount, price, t, kwargs...)
236      end
237  end
238  
239  function create_live_order(s, ai, args...; waitfor=Second(15), kwargs...)
240      ans = Ref{Union{Order,Nothing,Exception}}(nothing)
241      func() = (ans[] = (@inlock ai _create_live_order(s, ai, args...; kwargs...)))
242      sendrequest!(ai, now(), func, waitfor)
243      ans[]
244  end