@@ -54,7 +54,8 @@ use nautilus_model::{
54
54
instruments:: { InstrumentAny , EXPIRING_INSTRUMENT_TYPES } ,
55
55
orderbook:: OrderBook ,
56
56
orders:: {
57
- OrderAny , PassiveOrderAny , StopOrderAny , TrailingStopLimitOrder , TrailingStopMarketOrder ,
57
+ LimitOrderAny , OrderAny , PassiveOrderAny , StopOrderAny , TrailingStopLimitOrder ,
58
+ TrailingStopMarketOrder ,
58
59
} ,
59
60
position:: Position ,
60
61
types:: {
@@ -618,8 +619,8 @@ impl OrderMatchingEngine {
618
619
)
619
620
. into ( ) ,
620
621
) ;
622
+ return ;
621
623
}
622
- return ;
623
624
}
624
625
625
626
// Check for valid order trigger price precision
@@ -737,8 +738,53 @@ impl OrderMatchingEngine {
737
738
self . fill_market_order ( order) ;
738
739
}
739
740
740
- fn process_limit_order ( & mut self , order : & OrderAny ) {
741
- todo ! ( "process_limit_order" )
741
+ fn process_limit_order ( & mut self , order : & mut OrderAny ) {
742
+ if order. is_post_only ( )
743
+ && self
744
+ . core
745
+ . is_limit_matched ( & LimitOrderAny :: from ( order. to_owned ( ) ) )
746
+ {
747
+ self . generate_order_rejected (
748
+ order,
749
+ format ! (
750
+ "POST_ONLY {} {} order limit px of {} would have been a TAKER: bid={}, ask={}" ,
751
+ order. order_type( ) ,
752
+ order. order_side( ) ,
753
+ order. price( ) . unwrap( ) ,
754
+ self . core
755
+ . bid
756
+ . map( |p| p. to_string( ) )
757
+ . unwrap_or_else( || "None" . to_string( ) ) ,
758
+ self . core
759
+ . ask
760
+ . map( |p| p. to_string( ) )
761
+ . unwrap_or_else( || "None" . to_string( ) )
762
+ )
763
+ . into ( ) ,
764
+ ) ;
765
+ return ;
766
+ }
767
+
768
+ // Order is valid and accepted
769
+ self . accept_order ( order) ;
770
+
771
+ // Check for immediate fill
772
+ if self
773
+ . core
774
+ . is_limit_matched ( & LimitOrderAny :: from ( order. to_owned ( ) ) )
775
+ {
776
+ // Filling as liquidity taker
777
+ if order. liquidity_side ( ) . is_some ( )
778
+ && order. liquidity_side ( ) . unwrap ( ) == LiquiditySide :: NoLiquiditySide
779
+ {
780
+ order. set_liquidity_side ( LiquiditySide :: Taker )
781
+ }
782
+ self . fill_limit_order ( order) ;
783
+ } else if order. time_in_force ( ) == TimeInForce :: Fok
784
+ || order. time_in_force ( ) == TimeInForce :: Ioc
785
+ {
786
+ self . cancel_order ( order, None ) ;
787
+ }
742
788
}
743
789
744
790
fn process_market_to_limit_order ( & mut self , order : & OrderAny ) {
@@ -822,9 +868,18 @@ impl OrderMatchingEngine {
822
868
}
823
869
824
870
// Move market back to targets
825
- self . core . bid = self . target_bid ;
826
- self . core . ask = self . target_ask ;
827
- self . core . last = self . target_last ;
871
+ if let Some ( target_bid) = self . target_bid {
872
+ self . core . bid = Some ( target_bid) ;
873
+ self . target_bid = None ;
874
+ }
875
+ if let Some ( target_ask) = self . target_ask {
876
+ self . core . ask = Some ( target_ask) ;
877
+ self . target_ask = None ;
878
+ }
879
+ if let Some ( target_last) = self . target_last {
880
+ self . core . last = Some ( target_last) ;
881
+ self . target_last = None ;
882
+ }
828
883
}
829
884
830
885
// Reset any targets after iteration
@@ -833,8 +888,107 @@ impl OrderMatchingEngine {
833
888
self . target_last = None ;
834
889
}
835
890
836
- fn determine_limit_price_and_volume ( & self , order : & OrderAny ) {
837
- todo ! ( "determine_limit_price_and_volume" )
891
+ fn determine_limit_price_and_volume ( & mut self , order : & OrderAny ) -> Vec < ( Price , Quantity ) > {
892
+ match order. price ( ) {
893
+ Some ( order_price) => {
894
+ // construct book order with price as passive with limit order price
895
+ let book_order =
896
+ BookOrder :: new ( order. order_side ( ) , order_price, order. quantity ( ) , 1 ) ;
897
+
898
+ let mut fills = self . book . simulate_fills ( & book_order) ;
899
+
900
+ // return immediately if no fills
901
+ if fills. is_empty ( ) {
902
+ return fills;
903
+ }
904
+
905
+ // check if trigger price exists
906
+ if let Some ( triggered_price) = order. trigger_price ( ) {
907
+ // Filling as TAKER from trigger
908
+ if order
909
+ . liquidity_side ( )
910
+ . is_some_and ( |liquidity_side| liquidity_side == LiquiditySide :: Taker )
911
+ {
912
+ if order. order_side ( ) == OrderSide :: Sell && order_price > triggered_price {
913
+ // manually change the fills index 0
914
+ let first_fill = fills. first ( ) . unwrap ( ) ;
915
+ let triggered_qty = first_fill. 1 ;
916
+ fills[ 0 ] = ( triggered_price, triggered_qty) ;
917
+ self . target_bid = self . core . bid ;
918
+ self . target_ask = self . core . ask ;
919
+ self . target_last = self . core . last ;
920
+ self . core . set_ask_raw ( order_price) ;
921
+ self . core . set_last_raw ( order_price) ;
922
+ } else if order. order_side ( ) == OrderSide :: Buy
923
+ && order_price < triggered_price
924
+ {
925
+ // manually change the fills index 0
926
+ let first_fill = fills. first ( ) . unwrap ( ) ;
927
+ let triggered_qty = first_fill. 1 ;
928
+ fills[ 0 ] = ( triggered_price, triggered_qty) ;
929
+ self . target_bid = self . core . bid ;
930
+ self . target_ask = self . core . ask ;
931
+ self . target_last = self . core . last ;
932
+ self . core . set_bid_raw ( order_price) ;
933
+ self . core . set_last_raw ( order_price) ;
934
+ }
935
+ }
936
+ }
937
+
938
+ // Filling as MAKER from trigger
939
+ if order
940
+ . liquidity_side ( )
941
+ . is_some_and ( |liquidity_side| liquidity_side == LiquiditySide :: Maker )
942
+ {
943
+ if order. order_side ( ) == OrderSide :: Buy {
944
+ let target_price = if order
945
+ . trigger_price ( )
946
+ . is_some_and ( |trigger_price| order_price > trigger_price)
947
+ {
948
+ order. trigger_price ( ) . unwrap ( )
949
+ } else {
950
+ order_price
951
+ } ;
952
+ for fill in & fills {
953
+ let last_px = fill. 0 ;
954
+ if last_px < order_price {
955
+ // Marketable SELL would have filled at limit
956
+ self . target_bid = self . core . bid ;
957
+ self . target_ask = self . core . ask ;
958
+ self . target_last = self . core . last ;
959
+ self . core . set_ask_raw ( target_price) ;
960
+ self . core . set_last_raw ( target_price) ;
961
+ }
962
+ }
963
+ } else if order. order_side ( ) == OrderSide :: Sell {
964
+ let target_price = if order
965
+ . trigger_price ( )
966
+ . is_some_and ( |trigger_price| order_price < trigger_price)
967
+ {
968
+ order. trigger_price ( ) . unwrap ( )
969
+ } else {
970
+ order_price
971
+ } ;
972
+ for fill in & fills {
973
+ let last_px = fill. 0 ;
974
+ if last_px > order_price {
975
+ // Marketable BUY would have filled at limit
976
+ self . target_bid = self . core . bid ;
977
+ self . target_ask = self . core . ask ;
978
+ self . target_last = self . core . last ;
979
+ self . core . set_bid_raw ( target_price) ;
980
+ self . core . set_last_raw ( target_price) ;
981
+ }
982
+ }
983
+ } else {
984
+ panic ! ( "Invalid order side {}" , order. order_side( ) ) ;
985
+ }
986
+ }
987
+
988
+ fills
989
+ }
990
+ None => panic ! ( "Limit order must have a price" ) ,
991
+ }
838
992
}
839
993
840
994
fn determine_market_price_and_volume ( & self , order : & OrderAny ) -> Vec < ( Price , Quantity ) > {
@@ -892,7 +1046,69 @@ impl OrderMatchingEngine {
892
1046
}
893
1047
894
1048
fn fill_limit_order ( & mut self , order : & OrderAny ) {
895
- todo ! ( "fill_limit_order" )
1049
+ match order. price ( ) {
1050
+ Some ( order_price) => {
1051
+ let cached_filled_qty = self . cached_filled_qty . get ( & order. client_order_id ( ) ) ;
1052
+ if cached_filled_qty. is_some ( ) && * cached_filled_qty. unwrap ( ) >= order. quantity ( ) {
1053
+ log:: debug!(
1054
+ "Ignoring fill as already filled pending pending application of events: {}, {}, {}, {}" ,
1055
+ cached_filled_qty. unwrap( ) ,
1056
+ order. quantity( ) ,
1057
+ order. filled_qty( ) ,
1058
+ order. leaves_qty( ) ,
1059
+ ) ;
1060
+ return ;
1061
+ }
1062
+
1063
+ if order
1064
+ . liquidity_side ( )
1065
+ . is_some_and ( |liquidity_side| liquidity_side == LiquiditySide :: Maker )
1066
+ {
1067
+ if order. order_side ( ) == OrderSide :: Buy
1068
+ && self . core . bid . is_some_and ( |bid| bid == order_price)
1069
+ && !self . fill_model . is_limit_filled ( )
1070
+ {
1071
+ // no filled
1072
+ return ;
1073
+ }
1074
+ if order. order_side ( ) == OrderSide :: Sell
1075
+ && self . core . ask . is_some_and ( |ask| ask == order_price)
1076
+ && !self . fill_model . is_limit_filled ( )
1077
+ {
1078
+ // no filled
1079
+ return ;
1080
+ }
1081
+ }
1082
+
1083
+ let venue_position_id = self . ids_generator . get_position_id ( order, None ) ;
1084
+ let position = if let Some ( venue_position_id) = venue_position_id {
1085
+ let cache = self . cache . as_ref ( ) . borrow ( ) ;
1086
+ cache. position ( & venue_position_id) . cloned ( )
1087
+ } else {
1088
+ None
1089
+ } ;
1090
+
1091
+ if self . config . use_reduce_only && order. is_reduce_only ( ) && position. is_none ( ) {
1092
+ log:: warn!(
1093
+ "Canceling REDUCE_ONLY {} as would increase position" ,
1094
+ order. order_type( )
1095
+ ) ;
1096
+ self . cancel_order ( order, None ) ;
1097
+ return ;
1098
+ }
1099
+
1100
+ let fills = self . determine_limit_price_and_volume ( order) ;
1101
+
1102
+ self . apply_fills (
1103
+ order,
1104
+ fills,
1105
+ order. liquidity_side ( ) . unwrap ( ) ,
1106
+ venue_position_id,
1107
+ position,
1108
+ ) ;
1109
+ }
1110
+ None => panic ! ( "Limit order must have a price" ) ,
1111
+ }
896
1112
}
897
1113
898
1114
fn apply_fills (
0 commit comments