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