1111from typing import Any
1212
1313import pandas as pd
14+ from quant_platform_kit .common .quantity import (
15+ floor_to_quantity_step ,
16+ format_quantity ,
17+ normalize_order_quantity ,
18+ )
1419
1520
1621def get_market_prices (
@@ -36,7 +41,7 @@ def check_order_submitted(report, *, translator):
3641 "order_filled" ,
3742 symbol = report .symbol ,
3843 side = report .side ,
39- qty = int (report .filled_quantity or report .quantity ),
44+ qty = format_quantity (report .filled_quantity or report .quantity ),
4045 price = f"{ float (report .average_fill_price or 0.0 ):.2f} " ,
4146 order_id = order_id ,
4247 ),
@@ -48,8 +53,8 @@ def check_order_submitted(report, *, translator):
4853 "order_partial" ,
4954 symbol = report .symbol ,
5055 side = report .side ,
51- executed = int (report .filled_quantity or 0 ),
52- qty = int (report .quantity or 0 ),
56+ executed = format_quantity (report .filled_quantity or 0 ),
57+ qty = format_quantity (report .quantity or 0 ),
5358 price = f"{ float (report .average_fill_price or 0.0 ):.2f} " ,
5459 order_id = order_id ,
5560 ),
@@ -348,6 +353,10 @@ def _build_target_diff_rows(
348353 return rows
349354
350355
356+ def _floor_order_quantity (quantity , * , quantity_step ):
357+ return normalize_order_quantity (floor_to_quantity_step (quantity , quantity_step ))
358+
359+
351360def _finalize_result (trade_logs , execution_summary , * , return_summary : bool ):
352361 if return_summary :
353362 return trade_logs , execution_summary
@@ -375,6 +384,8 @@ def execute_rebalance(
375384 rebalance_threshold_ratio ,
376385 limit_buy_premium ,
377386 sell_settle_delay_sec ,
387+ quantity_step = 1.0 ,
388+ min_order_notional = 50.0 ,
378389 execution_lock_dir = None ,
379390 return_summary = False ,
380391):
@@ -425,6 +436,8 @@ def execute_rebalance(
425436 reserved = equity * cash_reserve_ratio
426437 investable = equity - reserved
427438 threshold = equity * rebalance_threshold_ratio
439+ order_quantity_step = float (quantity_step or 1.0 )
440+ minimum_order_notional = max (0.0 , float (min_order_notional or 0.0 ))
428441 execution_summary ["cash_reserve_dollars" ] = float (reserved )
429442
430443 all_symbols = set (target_weights .keys ()) | set (positions .keys ())
@@ -544,7 +557,11 @@ def execute_rebalance(
544557 if not price :
545558 missing_price_symbols .append (symbol )
546559 continue
547- if int ((current - target ) / price ) > 0 :
560+ qty = _floor_order_quantity (
561+ (current - target ) / price ,
562+ quantity_step = order_quantity_step ,
563+ )
564+ if qty > 0 :
548565 has_sell_plan = True
549566 break
550567 quantity_zero_symbols .append (symbol )
@@ -566,11 +583,15 @@ def execute_rebalance(
566583 if buy_value <= 0 :
567584 insufficient_buying_power_symbols .append (symbol )
568585 continue
569- if buy_value < 50 :
586+ if buy_value < minimum_order_notional :
570587 min_notional_symbols .append (symbol )
571588 continue
572589 limit_price = round (price * limit_buy_premium , 2 )
573- qty = int (buy_value / limit_price ) if limit_price > 0 else 0
590+ qty = (
591+ _floor_order_quantity (buy_value / limit_price , quantity_step = order_quantity_step )
592+ if limit_price > 0
593+ else 0
594+ )
574595 if qty > 0 :
575596 has_buy_plan = True
576597 break
@@ -687,7 +708,10 @@ def execute_rebalance(
687708 execution_summary ["orders_skipped" ].append ({"symbol" : symbol , "side" : "sell" , "reason" : "missing_price" })
688709 execution_summary ["skipped_reasons" ].append (f"missing_price:{ symbol } " )
689710 continue
690- qty = int (sell_value / price )
711+ qty = _floor_order_quantity (
712+ sell_value / price ,
713+ quantity_step = order_quantity_step ,
714+ )
691715 if qty <= 0 :
692716 execution_summary ["orders_skipped" ].append ({"symbol" : symbol , "side" : "sell" , "reason" : "quantity_zero" })
693717 continue
@@ -696,7 +720,7 @@ def execute_rebalance(
696720 execution_summary ["orders_submitted" ].append (
697721 {"symbol" : symbol , "side" : "sell" , "quantity" : qty , "status" : "dry_run" }
698722 )
699- trade_logs .append (f"DRY_RUN sell { symbol } { qty } " )
723+ trade_logs .append (f"DRY_RUN sell { symbol } { format_quantity ( qty ) } " )
700724 continue
701725 report = submit_order_intent (
702726 ib ,
@@ -720,7 +744,7 @@ def execute_rebalance(
720744 else :
721745 execution_summary ["orders_skipped" ].append ({** order_payload , "reason" : status or "submit_failed" })
722746 execution_summary ["skipped_reasons" ].append (f"submit_failed:{ symbol } :{ status or 'unknown' } " )
723- trade_logs .append (translator ("market_sell" , symbol = symbol , qty = qty ) + f" { status_msg } " )
747+ trade_logs .append (translator ("market_sell" , symbol = symbol , qty = format_quantity ( qty ) ) + f" { status_msg } " )
724748 if ok :
725749 sell_executed = True
726750
@@ -741,12 +765,15 @@ def execute_rebalance(
741765 execution_summary ["orders_skipped" ].append ({"symbol" : symbol , "side" : "buy" , "reason" : "missing_price" })
742766 execution_summary ["skipped_reasons" ].append (f"missing_price:{ symbol } " )
743767 continue
744- if buy_value < 50 :
768+ if buy_value < minimum_order_notional :
745769 execution_summary ["orders_skipped" ].append ({"symbol" : symbol , "side" : "buy" , "reason" : "min_notional" })
746770 continue
747771
748772 limit_price = round (price * limit_buy_premium , 2 )
749- qty = int (buy_value / limit_price )
773+ qty = _floor_order_quantity (
774+ buy_value / limit_price ,
775+ quantity_step = order_quantity_step ,
776+ )
750777 if qty <= 0 :
751778 execution_summary ["orders_skipped" ].append ({"symbol" : symbol , "side" : "buy" , "reason" : "quantity_zero" })
752779 continue
@@ -761,7 +788,7 @@ def execute_rebalance(
761788 "status" : "dry_run" ,
762789 }
763790 )
764- trade_logs .append (f"DRY_RUN buy { symbol } { qty } @{ limit_price :.2f} " )
791+ trade_logs .append (f"DRY_RUN buy { symbol } { format_quantity ( qty ) } @{ limit_price :.2f} " )
765792 buying_power -= qty * limit_price
766793 continue
767794 report = submit_order_intent (
@@ -795,7 +822,7 @@ def execute_rebalance(
795822 execution_summary ["orders_skipped" ].append ({** order_payload , "reason" : status or "submit_failed" })
796823 execution_summary ["skipped_reasons" ].append (f"submit_failed:{ symbol } :{ status or 'unknown' } " )
797824 trade_logs .append (
798- translator ("limit_buy" , symbol = symbol , qty = qty , price = f"{ limit_price :.2f} " ) + f" { status_msg } "
825+ translator ("limit_buy" , symbol = symbol , qty = format_quantity ( qty ) , price = f"{ limit_price :.2f} " ) + f" { status_msg } "
799826 )
800827 if ok :
801828 buying_power -= qty * limit_price
0 commit comments