15
15
16
16
use std:: ops:: { Deref , DerefMut } ;
17
17
18
+ use anyhow;
18
19
use indexmap:: IndexMap ;
19
- use nautilus_core:: { UUID4 , UnixNanos } ;
20
+ use nautilus_core:: {
21
+ UUID4 , UnixNanos ,
22
+ correctness:: { FAILED , check_predicate_false} ,
23
+ } ;
20
24
use rust_decimal:: Decimal ;
21
25
use serde:: { Deserialize , Serialize } ;
22
26
use ustr:: Ustr ;
@@ -33,7 +37,7 @@ use crate::{
33
37
StrategyId , Symbol , TradeId , TraderId , Venue , VenueOrderId ,
34
38
} ,
35
39
orders:: OrderError ,
36
- types:: { Currency , Money , Price , Quantity } ,
40
+ types:: { Currency , Money , Price , Quantity , quantity :: check_positive_quantity } ,
37
41
} ;
38
42
39
43
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
@@ -52,7 +56,7 @@ pub struct MarketToLimitOrder {
52
56
impl MarketToLimitOrder {
53
57
/// Creates a new [`MarketToLimitOrder`] instance.
54
58
#[ allow( clippy:: too_many_arguments) ]
55
- pub fn new (
59
+ pub fn new_checked (
56
60
trader_id : TraderId ,
57
61
strategy_id : StrategyId ,
58
62
instrument_id : InstrumentId ,
@@ -75,8 +79,21 @@ impl MarketToLimitOrder {
75
79
tags : Option < Vec < Ustr > > ,
76
80
init_id : UUID4 ,
77
81
ts_init : UnixNanos ,
78
- ) -> Self {
79
- // TODO: Implement new_checked and check quantity positive, add error docs.
82
+ ) -> anyhow:: Result < Self > {
83
+ check_positive_quantity ( quantity, "quantity" ) ?;
84
+
85
+ if let Some ( disp) = display_qty {
86
+ check_positive_quantity ( disp, "display_qty" ) ?;
87
+ check_predicate_false ( disp > quantity, "`display_qty` may not exceed `quantity`" ) ?;
88
+ }
89
+
90
+ if time_in_force == TimeInForce :: Gtd {
91
+ check_predicate_false (
92
+ expire_time. unwrap_or_default ( ) == 0 ,
93
+ "Condition failed: `expire_time` is required for `GTD` order" ,
94
+ ) ?;
95
+ }
96
+
80
97
let init_order = OrderInitialized :: new (
81
98
trader_id,
82
99
strategy_id,
@@ -112,13 +129,66 @@ impl MarketToLimitOrder {
112
129
exec_spawn_id,
113
130
tags,
114
131
) ;
115
- Self {
132
+
133
+ Ok ( Self {
116
134
core : OrderCore :: new ( init_order) ,
117
135
price : None , // Price will be determined on fill
118
136
expire_time,
119
137
is_post_only : post_only,
120
138
display_qty,
121
- }
139
+ } )
140
+ }
141
+
142
+ #[ allow( clippy:: too_many_arguments) ]
143
+ pub fn new (
144
+ trader_id : TraderId ,
145
+ strategy_id : StrategyId ,
146
+ instrument_id : InstrumentId ,
147
+ client_order_id : ClientOrderId ,
148
+ order_side : OrderSide ,
149
+ quantity : Quantity ,
150
+ time_in_force : TimeInForce ,
151
+ expire_time : Option < UnixNanos > ,
152
+ post_only : bool ,
153
+ reduce_only : bool ,
154
+ quote_quantity : bool ,
155
+ display_qty : Option < Quantity > ,
156
+ contingency_type : Option < ContingencyType > ,
157
+ order_list_id : Option < OrderListId > ,
158
+ linked_order_ids : Option < Vec < ClientOrderId > > ,
159
+ parent_order_id : Option < ClientOrderId > ,
160
+ exec_algorithm_id : Option < ExecAlgorithmId > ,
161
+ exec_algorithm_params : Option < IndexMap < Ustr , Ustr > > ,
162
+ exec_spawn_id : Option < ClientOrderId > ,
163
+ tags : Option < Vec < Ustr > > ,
164
+ init_id : UUID4 ,
165
+ ts_init : UnixNanos ,
166
+ ) -> Self {
167
+ Self :: new_checked (
168
+ trader_id,
169
+ strategy_id,
170
+ instrument_id,
171
+ client_order_id,
172
+ order_side,
173
+ quantity,
174
+ time_in_force,
175
+ expire_time,
176
+ post_only,
177
+ reduce_only,
178
+ quote_quantity,
179
+ display_qty,
180
+ contingency_type,
181
+ order_list_id,
182
+ linked_order_ids,
183
+ parent_order_id,
184
+ exec_algorithm_id,
185
+ exec_algorithm_params,
186
+ exec_spawn_id,
187
+ tags,
188
+ init_id,
189
+ ts_init,
190
+ )
191
+ . expect ( FAILED )
122
192
}
123
193
}
124
194
@@ -404,7 +474,7 @@ impl Order for MarketToLimitOrder {
404
474
}
405
475
406
476
fn set_liquidity_side ( & mut self , liquidity_side : LiquiditySide ) {
407
- self . liquidity_side = Some ( liquidity_side)
477
+ self . liquidity_side = Some ( liquidity_side) ;
408
478
}
409
479
410
480
fn would_reduce_only ( & self , side : PositionSide , position_qty : Quantity ) -> bool {
@@ -444,3 +514,61 @@ impl From<OrderInitialized> for MarketToLimitOrder {
444
514
)
445
515
}
446
516
}
517
+
518
+ ////////////////////////////////////////////////////////////////////////////////
519
+ // Tests
520
+ ////////////////////////////////////////////////////////////////////////////////
521
+ #[ cfg( test) ]
522
+ mod tests {
523
+ use rstest:: rstest;
524
+
525
+ use crate :: {
526
+ enums:: { OrderSide , OrderType , TimeInForce } ,
527
+ instruments:: { CurrencyPair , stubs:: * } ,
528
+ orders:: builder:: OrderTestBuilder ,
529
+ types:: Quantity ,
530
+ } ;
531
+
532
+ #[ rstest]
533
+ fn ok ( audusd_sim : CurrencyPair ) {
534
+ let _ = OrderTestBuilder :: new ( OrderType :: MarketToLimit )
535
+ . instrument_id ( audusd_sim. id )
536
+ . side ( OrderSide :: Buy )
537
+ . quantity ( Quantity :: from ( 1 ) )
538
+ . build ( ) ;
539
+ }
540
+
541
+ #[ rstest]
542
+ #[ should_panic(
543
+ expected = "Condition failed: invalid `Quantity` for 'quantity' not positive, was 0"
544
+ ) ]
545
+ fn quantity_zero ( audusd_sim : CurrencyPair ) {
546
+ let _ = OrderTestBuilder :: new ( OrderType :: MarketToLimit )
547
+ . instrument_id ( audusd_sim. id )
548
+ . side ( OrderSide :: Buy )
549
+ . quantity ( Quantity :: from ( 0 ) ) // Invalid: zero quantity
550
+ . build ( ) ;
551
+ }
552
+
553
+ #[ rstest]
554
+ #[ should_panic( expected = "Condition failed: `expire_time` is required for `GTD` order" ) ]
555
+ fn gtd_without_expire ( audusd_sim : CurrencyPair ) {
556
+ let _ = OrderTestBuilder :: new ( OrderType :: MarketToLimit )
557
+ . instrument_id ( audusd_sim. id )
558
+ . side ( OrderSide :: Buy )
559
+ . quantity ( Quantity :: from ( 1 ) )
560
+ . time_in_force ( TimeInForce :: Gtd ) // Missing expire_time
561
+ . build ( ) ;
562
+ }
563
+
564
+ #[ rstest]
565
+ #[ should_panic( expected = "`display_qty` may not exceed `quantity`" ) ]
566
+ fn display_qty_gt_quantity ( audusd_sim : CurrencyPair ) {
567
+ let _ = OrderTestBuilder :: new ( OrderType :: MarketToLimit )
568
+ . instrument_id ( audusd_sim. id )
569
+ . side ( OrderSide :: Buy )
570
+ . quantity ( Quantity :: from ( 1 ) )
571
+ . display_qty ( Quantity :: from ( 2 ) ) // Invalid: display > quantity
572
+ . build ( ) ;
573
+ }
574
+ }
0 commit comments