@@ -673,6 +673,245 @@ def create_limit_buy_order(
673673 market = market ,
674674 )
675675
676+ def order_value (
677+ self ,
678+ target_symbol ,
679+ value ,
680+ order_side ,
681+ price ,
682+ market = None ,
683+ precision = None ,
684+ metadata = None ,
685+ ) -> Order :
686+ """
687+ Place a LIMIT order for a fixed currency amount.
688+
689+ The order amount is computed as ``value / price``.
690+
691+ Args:
692+ target_symbol: The symbol of the asset to trade.
693+ value: Currency amount to spend (BUY) or to sell (SELL).
694+ order_side: ``OrderSide.BUY`` or ``OrderSide.SELL``.
695+ price: Limit price.
696+ market: Optional market identifier.
697+ precision: Optional decimal precision to round the
698+ computed amount down.
699+ metadata: Optional order metadata.
700+
701+ Returns:
702+ Order: The created order.
703+ """
704+ if value is None or value <= 0 :
705+ raise OperationalException (
706+ "`value` must be a positive number."
707+ )
708+ if price is None or price <= 0 :
709+ raise OperationalException (
710+ "`price` must be a positive number."
711+ )
712+ amount = value / price
713+ if precision is not None :
714+ amount = RoundingService .round_down (amount , precision )
715+ return self .create_limit_order (
716+ target_symbol = target_symbol ,
717+ price = price ,
718+ order_side = order_side ,
719+ amount = amount ,
720+ market = market ,
721+ metadata = metadata ,
722+ )
723+
724+ def order_percent (
725+ self ,
726+ target_symbol ,
727+ percent ,
728+ order_side ,
729+ price ,
730+ market = None ,
731+ precision = None ,
732+ metadata = None ,
733+ ) -> Order :
734+ """
735+ Place a LIMIT order for ``percent`` of the portfolio's net size.
736+
737+ Args:
738+ target_symbol: The symbol of the asset to trade.
739+ percent: Percentage (0-100) of portfolio net size to allocate.
740+ order_side: ``OrderSide.BUY`` or ``OrderSide.SELL``.
741+ price: Limit price.
742+ market: Optional market identifier.
743+ precision: Optional decimal precision to round the
744+ computed amount down.
745+ metadata: Optional order metadata.
746+
747+ Returns:
748+ Order: The created order.
749+ """
750+ if percent is None or percent <= 0 :
751+ raise OperationalException (
752+ "`percent` must be a positive number."
753+ )
754+ portfolio = self .portfolio_service .find ({"market" : market })
755+ net_size = portfolio .get_net_size ()
756+ value = net_size * (percent / 100.0 )
757+ return self .order_value (
758+ target_symbol = target_symbol ,
759+ value = value ,
760+ order_side = order_side ,
761+ price = price ,
762+ market = market ,
763+ precision = precision ,
764+ metadata = metadata ,
765+ )
766+
767+ def order_target (
768+ self ,
769+ target_symbol ,
770+ target_amount ,
771+ price ,
772+ market = None ,
773+ precision = None ,
774+ metadata = None ,
775+ ) -> Order :
776+ """
777+ Adjust the position in ``target_symbol`` to hold exactly
778+ ``target_amount`` units.
779+
780+ The difference between the current position size and
781+ ``target_amount`` is submitted as a BUY (positive diff) or
782+ SELL (negative diff) LIMIT order. If the position already
783+ matches the target, no order is placed.
784+
785+ Args:
786+ target_symbol: The symbol of the asset to adjust.
787+ target_amount: Desired position size in units.
788+ price: Limit price.
789+ market: Optional market identifier.
790+ precision: Optional decimal precision to round the diff down.
791+ metadata: Optional order metadata.
792+
793+ Returns:
794+ Order: The created order, or ``None`` if no adjustment
795+ was required.
796+ """
797+ if target_amount is None or target_amount < 0 :
798+ raise OperationalException (
799+ "`target_amount` must be a non-negative number."
800+ )
801+ if price is None or price <= 0 :
802+ raise OperationalException (
803+ "`price` must be a positive number."
804+ )
805+ portfolio = self .portfolio_service .find ({"market" : market })
806+ try :
807+ position = self .position_service .find (
808+ {"symbol" : target_symbol , "portfolio" : portfolio .id }
809+ )
810+ current_amount = position .get_amount () \
811+ if position is not None else 0
812+ except OperationalException :
813+ current_amount = 0
814+ diff = target_amount - current_amount
815+ if precision is not None :
816+ sign = 1 if diff >= 0 else - 1
817+ diff = sign * RoundingService .round_down (abs (diff ), precision )
818+ if diff == 0 :
819+ return None
820+ order_side = OrderSide .BUY if diff > 0 else OrderSide .SELL
821+ return self .create_limit_order (
822+ target_symbol = target_symbol ,
823+ price = price ,
824+ order_side = order_side ,
825+ amount = abs (diff ),
826+ market = market ,
827+ metadata = metadata ,
828+ )
829+
830+ def order_target_value (
831+ self ,
832+ target_symbol ,
833+ target_value ,
834+ price ,
835+ market = None ,
836+ precision = None ,
837+ metadata = None ,
838+ ) -> Order :
839+ """
840+ Adjust the position in ``target_symbol`` so its market value at
841+ ``price`` equals ``target_value``.
842+
843+ Args:
844+ target_symbol: The symbol of the asset to adjust.
845+ target_value: Desired position market value in trading currency.
846+ price: Limit price.
847+ market: Optional market identifier.
848+ precision: Optional decimal precision to round the diff down.
849+ metadata: Optional order metadata.
850+
851+ Returns:
852+ Order: The created order, or ``None`` if no adjustment
853+ was required.
854+ """
855+ if target_value is None or target_value < 0 :
856+ raise OperationalException (
857+ "`target_value` must be a non-negative number."
858+ )
859+ if price is None or price <= 0 :
860+ raise OperationalException (
861+ "`price` must be a positive number."
862+ )
863+ target_amount = target_value / price
864+ return self .order_target (
865+ target_symbol = target_symbol ,
866+ target_amount = target_amount ,
867+ price = price ,
868+ market = market ,
869+ precision = precision ,
870+ metadata = metadata ,
871+ )
872+
873+ def order_target_percent (
874+ self ,
875+ target_symbol ,
876+ target_percent ,
877+ price ,
878+ market = None ,
879+ precision = None ,
880+ metadata = None ,
881+ ) -> Order :
882+ """
883+ Adjust the position in ``target_symbol`` so its market value
884+ equals ``target_percent`` of the portfolio's net size.
885+
886+ Args:
887+ target_symbol: The symbol of the asset to adjust.
888+ target_percent: Desired position size as a percentage (0-100)
889+ of portfolio net size.
890+ price: Limit price.
891+ market: Optional market identifier.
892+ precision: Optional decimal precision to round the diff down.
893+ metadata: Optional order metadata.
894+
895+ Returns:
896+ Order: The created order, or ``None`` if no adjustment
897+ was required.
898+ """
899+ if target_percent is None or target_percent < 0 :
900+ raise OperationalException (
901+ "`target_percent` must be a non-negative number."
902+ )
903+ portfolio = self .portfolio_service .find ({"market" : market })
904+ net_size = portfolio .get_net_size ()
905+ target_value = net_size * (target_percent / 100.0 )
906+ return self .order_target_value (
907+ target_symbol = target_symbol ,
908+ target_value = target_value ,
909+ price = price ,
910+ market = market ,
911+ precision = precision ,
912+ metadata = metadata ,
913+ )
914+
676915 def get_portfolio (self , market = None ) -> Portfolio :
677916 """
678917 Function to get the portfolio of the algorithm. This function
0 commit comments