@@ -333,6 +333,225 @@ def create_limit_order(
333333 order_data , execute = execute , validate = validate , sync = sync
334334 )
335335
336+ def create_market_order (
337+ self ,
338+ target_symbol ,
339+ order_side ,
340+ amount = None ,
341+ amount_trading_symbol = None ,
342+ percentage = None ,
343+ percentage_of_portfolio = None ,
344+ percentage_of_position = None ,
345+ precision = None ,
346+ market = None ,
347+ execute = True ,
348+ validate = True ,
349+ sync = True ,
350+ metadata = None
351+ ) -> Order :
352+ """
353+ Function to create a market order. Market orders execute at
354+ the best available price. In backtesting, this means the
355+ open price of the next candle (+ slippage).
356+
357+ An estimated price (current latest price) is used for amount
358+ calculation and cash reservation. The actual fill price is
359+ determined at fill time and the portfolio is reconciled.
360+
361+ Args:
362+ target_symbol: The symbol of the asset to trade
363+ order_side: The side of the order (BUY or SELL)
364+ amount (optional): The amount of the asset to trade
365+ amount_trading_symbol (optional): The amount of the
366+ trading symbol to trade
367+ percentage (optional): The percentage of the portfolio
368+ to allocate to the order
369+ percentage_of_portfolio (optional): The percentage
370+ of the portfolio to allocate to the order
371+ percentage_of_position (optional): The percentage
372+ of the position to allocate to the
373+ order. (Only supported for SELL orders)
374+ precision (optional): The precision of the amount
375+ market (optional): The market to trade the asset
376+ execute (optional): Default True. If set to True,
377+ the order will be executed
378+ validate (optional): Default True. If set to
379+ True, the order will be validated
380+ sync (optional): Default True. If set to True,
381+ the created order will be synced with the
382+ portfolio of the algorithm
383+ metadata (optional): Additional metadata for the order
384+
385+ Returns:
386+ Order: Instance of the order created
387+ """
388+ portfolio = self .portfolio_service .find ({"market" : market })
389+ full_symbol = (f"{ target_symbol } /{ portfolio .trading_symbol } " )
390+ estimated_price = self .get_latest_price (full_symbol , market = market )
391+
392+ if estimated_price is None :
393+ raise OperationalException (
394+ f"Cannot create market order for { target_symbol } : "
395+ f"no price data available to estimate order size."
396+ )
397+
398+ if percentage_of_portfolio is not None :
399+ if not OrderSide .BUY .equals (order_side ):
400+ raise OperationalException (
401+ "Percentage of portfolio is only supported for BUY orders."
402+ )
403+
404+ net_size = portfolio .get_net_size ()
405+ size = net_size * (percentage_of_portfolio / 100 )
406+ amount = size / estimated_price
407+
408+ elif percentage_of_position is not None :
409+
410+ if not OrderSide .SELL .equals (order_side ):
411+ raise OperationalException (
412+ "Percentage of position is only supported for SELL orders."
413+ )
414+
415+ position = self .position_service .find (
416+ {
417+ "symbol" : target_symbol ,
418+ "portfolio" : portfolio .id
419+ }
420+ )
421+ amount = position .get_amount () * (percentage_of_position / 100 )
422+
423+ elif percentage is not None :
424+ net_size = portfolio .get_net_size ()
425+ size = net_size * (percentage / 100 )
426+ amount = size / estimated_price
427+
428+ if precision is not None :
429+ amount = RoundingService .round_down (amount , precision )
430+
431+ if amount_trading_symbol is not None :
432+ amount = amount_trading_symbol / estimated_price
433+
434+ if amount is None :
435+ raise OperationalException (
436+ "The amount parameter is required to create a market order. "
437+ "Either the amount, amount_trading_symbol, percentage, "
438+ "percentage_of_portfolio or percentage_of_position "
439+ "parameter must be specified."
440+ )
441+
442+ logger .info (
443+ f"Creating market order: { target_symbol } "
444+ f"{ order_side } { amount } @ estimated { estimated_price } "
445+ )
446+
447+ order_metadata = metadata if metadata is not None else {}
448+ order_metadata ["estimated_price" ] = estimated_price
449+
450+ order_data = {
451+ "target_symbol" : target_symbol ,
452+ "price" : estimated_price ,
453+ "amount" : amount ,
454+ "order_type" : OrderType .MARKET .value ,
455+ "order_side" : OrderSide .from_value (order_side ).value ,
456+ "portfolio_id" : portfolio .id ,
457+ "status" : OrderStatus .CREATED .value ,
458+ "trading_symbol" : portfolio .trading_symbol ,
459+ "metadata" : order_metadata ,
460+ }
461+
462+ if BACKTESTING_FLAG in self .configuration_service .config \
463+ and self .configuration_service .config [BACKTESTING_FLAG ]:
464+ order_data ["created_at" ] = \
465+ self .configuration_service .config [INDEX_DATETIME ]
466+
467+ return self .order_service .create (
468+ order_data , execute = execute , validate = validate , sync = sync
469+ )
470+
471+ def create_market_buy_order (
472+ self ,
473+ target_symbol ,
474+ amount = None ,
475+ percentage_of_portfolio = None ,
476+ market = None ,
477+ portfolio_id = None ,
478+ metadata = None
479+ ) -> Order :
480+ """
481+ Function to create a market buy order.
482+
483+ Args:
484+ target_symbol (str): The symbol of the asset to buy
485+ amount (float, optional): The amount of the asset to buy
486+ percentage_of_portfolio (float, optional): The percentage of the
487+ portfolio to buy.
488+ market (str, optional): the portfolio corresponding to the market
489+ to buy the asset
490+ portfolio_id (str, optional): The ID of the portfolio to buy
491+ the asset from.
492+ metadata (dict, optional): Additional metadata for the order
493+
494+ Returns:
495+ Order: The order created
496+ """
497+
498+ if amount is None and percentage_of_portfolio is None :
499+ raise OperationalException (
500+ "Either amount or percentage_of_portfolio must be specified "
501+ "to create a market buy order."
502+ )
503+
504+ return self .create_market_order (
505+ target_symbol = target_symbol ,
506+ order_side = OrderSide .BUY ,
507+ amount = amount ,
508+ percentage_of_portfolio = percentage_of_portfolio ,
509+ market = market ,
510+ metadata = metadata
511+ )
512+
513+ def create_market_sell_order (
514+ self ,
515+ target_symbol ,
516+ amount = None ,
517+ percentage_of_position = None ,
518+ market = None ,
519+ portfolio_id = None ,
520+ metadata = None
521+ ) -> Order :
522+ """
523+ Function to create a market sell order.
524+
525+ Args:
526+ target_symbol (str): The symbol of the asset to sell
527+ amount (float, optional): The amount of the asset to sell
528+ percentage_of_position (float, optional): The percentage of the
529+ position to sell.
530+ market (str, optional): the portfolio corresponding to the market
531+ to sell the asset
532+ portfolio_id (str, optional): The ID of the portfolio to sell
533+ the asset from.
534+ metadata (dict, optional): Additional metadata for the order
535+
536+ Returns:
537+ Order: The order created
538+ """
539+
540+ if amount is None and percentage_of_position is None :
541+ raise OperationalException (
542+ "Either amount or percentage_of_position must be specified "
543+ "to create a market sell order."
544+ )
545+
546+ return self .create_market_order (
547+ target_symbol = target_symbol ,
548+ order_side = OrderSide .SELL ,
549+ amount = amount ,
550+ percentage_of_position = percentage_of_position ,
551+ market = market ,
552+ metadata = metadata
553+ )
554+
336555 def create_limit_sell_order (
337556 self ,
338557 target_symbol ,
0 commit comments