1use std::any::Any;
17
18use nautilus_common::actor::data_actor::ImportableActorConfig;
19use nautilus_core::{UUID4, UnixNanos};
20use nautilus_model::identifiers::{ActorId, StrategyId};
21use nautilus_trading::ImportableStrategyConfig;
22use serde::{Deserialize, Serialize};
23
24#[derive(Clone, Debug, Serialize, Deserialize)]
25#[serde(tag = "type")]
26pub struct CreateActor {
27 pub actor_config: ImportableActorConfig,
28 pub start: bool,
29 pub command_id: UUID4,
30 pub ts_init: UnixNanos,
31}
32
33impl CreateActor {
34 #[must_use]
36 pub const fn new(
37 actor_config: ImportableActorConfig,
38 start: bool,
39 command_id: UUID4,
40 ts_init: UnixNanos,
41 ) -> Self {
42 Self {
43 actor_config,
44 start,
45 command_id,
46 ts_init,
47 }
48 }
49
50 #[must_use]
51 pub fn as_any(&self) -> &dyn Any {
52 self
53 }
54}
55
56#[derive(Clone, Debug, Serialize, Deserialize)]
57#[serde(tag = "type")]
58pub struct CreateStrategy {
59 pub strategy_config: ImportableStrategyConfig,
60 pub start: bool,
61 pub command_id: UUID4,
62 pub ts_init: UnixNanos,
63}
64
65impl CreateStrategy {
66 #[must_use]
68 pub const fn new(
69 strategy_config: ImportableStrategyConfig,
70 start: bool,
71 command_id: UUID4,
72 ts_init: UnixNanos,
73 ) -> Self {
74 Self {
75 strategy_config,
76 start,
77 command_id,
78 ts_init,
79 }
80 }
81
82 #[must_use]
83 pub fn as_any(&self) -> &dyn Any {
84 self
85 }
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
89#[serde(tag = "type")]
90pub struct StartActor {
91 pub actor_id: ActorId,
92 pub command_id: UUID4,
93 pub ts_init: UnixNanos,
94}
95
96impl StartActor {
97 #[must_use]
99 pub const fn new(actor_id: ActorId, command_id: UUID4, ts_init: UnixNanos) -> Self {
100 Self {
101 actor_id,
102 command_id,
103 ts_init,
104 }
105 }
106
107 #[must_use]
108 pub fn as_any(&self) -> &dyn Any {
109 self
110 }
111}
112
113#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
114#[serde(tag = "type")]
115pub struct StartStrategy {
116 pub strategy_id: StrategyId,
117 pub command_id: UUID4,
118 pub ts_init: UnixNanos,
119}
120
121impl StartStrategy {
122 #[must_use]
124 pub const fn new(strategy_id: StrategyId, command_id: UUID4, ts_init: UnixNanos) -> Self {
125 Self {
126 strategy_id,
127 command_id,
128 ts_init,
129 }
130 }
131
132 #[must_use]
133 pub fn as_any(&self) -> &dyn Any {
134 self
135 }
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
139#[serde(tag = "type")]
140pub struct StopActor {
141 pub actor_id: ActorId,
142 pub command_id: UUID4,
143 pub ts_init: UnixNanos,
144}
145
146impl StopActor {
147 #[must_use]
149 pub const fn new(actor_id: ActorId, command_id: UUID4, ts_init: UnixNanos) -> Self {
150 Self {
151 actor_id,
152 command_id,
153 ts_init,
154 }
155 }
156
157 #[must_use]
158 pub fn as_any(&self) -> &dyn Any {
159 self
160 }
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
164#[serde(tag = "type")]
165pub struct StopStrategy {
166 pub strategy_id: StrategyId,
167 pub command_id: UUID4,
168 pub ts_init: UnixNanos,
169}
170
171impl StopStrategy {
172 #[must_use]
174 pub const fn new(strategy_id: StrategyId, command_id: UUID4, ts_init: UnixNanos) -> Self {
175 Self {
176 strategy_id,
177 command_id,
178 ts_init,
179 }
180 }
181
182 #[must_use]
183 pub fn as_any(&self) -> &dyn Any {
184 self
185 }
186}
187
188#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
189#[serde(tag = "type")]
190pub struct RemoveActor {
191 pub actor_id: ActorId,
192 pub command_id: UUID4,
193 pub ts_init: UnixNanos,
194}
195
196impl RemoveActor {
197 #[must_use]
199 pub const fn new(actor_id: ActorId, command_id: UUID4, ts_init: UnixNanos) -> Self {
200 Self {
201 actor_id,
202 command_id,
203 ts_init,
204 }
205 }
206
207 #[must_use]
208 pub fn as_any(&self) -> &dyn Any {
209 self
210 }
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
214#[serde(tag = "type")]
215pub struct RemoveStrategy {
216 pub strategy_id: StrategyId,
217 pub command_id: UUID4,
218 pub ts_init: UnixNanos,
219}
220
221impl RemoveStrategy {
222 #[must_use]
224 pub const fn new(strategy_id: StrategyId, command_id: UUID4, ts_init: UnixNanos) -> Self {
225 Self {
226 strategy_id,
227 command_id,
228 ts_init,
229 }
230 }
231
232 #[must_use]
233 pub fn as_any(&self) -> &dyn Any {
234 self
235 }
236}
237
238#[derive(Debug, Clone)]
240pub enum ControllerCommand {
241 CreateActor(CreateActor),
242 StartActor(StartActor),
243 StopActor(StopActor),
244 RemoveActor(RemoveActor),
245 CreateStrategy(CreateStrategy),
246 StartStrategy(StartStrategy),
247 StopStrategy(StopStrategy),
248 ExitMarket(StrategyId),
249 RemoveStrategy(RemoveStrategy),
250}
251
252impl From<CreateActor> for ControllerCommand {
253 fn from(command: CreateActor) -> Self {
254 Self::CreateActor(command)
255 }
256}
257
258impl From<CreateStrategy> for ControllerCommand {
259 fn from(command: CreateStrategy) -> Self {
260 Self::CreateStrategy(command)
261 }
262}
263
264impl From<StartActor> for ControllerCommand {
265 fn from(command: StartActor) -> Self {
266 Self::StartActor(command)
267 }
268}
269
270impl From<StartStrategy> for ControllerCommand {
271 fn from(command: StartStrategy) -> Self {
272 Self::StartStrategy(command)
273 }
274}
275
276impl From<StopActor> for ControllerCommand {
277 fn from(command: StopActor) -> Self {
278 Self::StopActor(command)
279 }
280}
281
282impl From<StopStrategy> for ControllerCommand {
283 fn from(command: StopStrategy) -> Self {
284 Self::StopStrategy(command)
285 }
286}
287
288impl From<RemoveActor> for ControllerCommand {
289 fn from(command: RemoveActor) -> Self {
290 Self::RemoveActor(command)
291 }
292}
293
294impl From<RemoveStrategy> for ControllerCommand {
295 fn from(command: RemoveStrategy) -> Self {
296 Self::RemoveStrategy(command)
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use std::collections::HashMap;
303
304 use rstest::rstest;
305
306 use super::*;
307
308 #[rstest]
309 fn test_create_actor_command_fields() {
310 let actor_config = ImportableActorConfig {
311 actor_path: "tests.actors:Actor".to_string(),
312 config_path: "tests.actors:ActorConfig".to_string(),
313 config: HashMap::new(),
314 };
315 let command_id = UUID4::new();
316 let command = CreateActor::new(actor_config.clone(), false, command_id, UnixNanos::new(1));
317
318 assert_eq!(command.actor_config.actor_path, actor_config.actor_path);
319 assert_eq!(command.actor_config.config_path, actor_config.config_path);
320 assert!(!command.start);
321 assert_eq!(command.command_id, command_id);
322 assert_eq!(command.ts_init, UnixNanos::new(1));
323 assert!(matches!(
324 ControllerCommand::from(command),
325 ControllerCommand::CreateActor(_)
326 ));
327 }
328
329 #[rstest]
330 fn test_create_actor_command_serde_round_trip() {
331 let actor_config = ImportableActorConfig {
332 actor_path: "tests.actors:Actor".to_string(),
333 config_path: "tests.actors:ActorConfig".to_string(),
334 config: HashMap::from([(
335 "threshold".to_string(),
336 serde_json::Value::String("10".to_string()),
337 )]),
338 };
339 let command_id = UUID4::new();
340 let command = CreateActor::new(actor_config, true, command_id, UnixNanos::new(9));
341
342 let value = serde_json::to_value(&command).unwrap();
343 assert_eq!(value["type"], "CreateActor");
344 let round_trip: CreateActor = serde_json::from_value(value).unwrap();
345
346 assert_eq!(round_trip.actor_config.actor_path, "tests.actors:Actor");
347 assert_eq!(
348 round_trip.actor_config.config_path,
349 "tests.actors:ActorConfig"
350 );
351 assert_eq!(
352 round_trip.actor_config.config["threshold"],
353 serde_json::Value::String("10".to_string())
354 );
355 assert!(round_trip.start);
356 assert_eq!(round_trip.command_id, command_id);
357 assert_eq!(round_trip.ts_init, UnixNanos::new(9));
358 }
359
360 #[rstest]
361 fn test_create_strategy_command_fields() {
362 let strategy_config = ImportableStrategyConfig {
363 strategy_path: "tests.strategies:Strategy".to_string(),
364 config_path: "tests.strategies:StrategyConfig".to_string(),
365 config: HashMap::new(),
366 };
367 let command_id = UUID4::new();
368 let command =
369 CreateStrategy::new(strategy_config.clone(), true, command_id, UnixNanos::new(2));
370
371 assert_eq!(
372 command.strategy_config.strategy_path,
373 strategy_config.strategy_path
374 );
375 assert_eq!(
376 command.strategy_config.config_path,
377 strategy_config.config_path
378 );
379 assert!(command.start);
380 assert_eq!(command.command_id, command_id);
381 assert_eq!(command.ts_init, UnixNanos::new(2));
382 assert!(matches!(
383 ControllerCommand::from(command),
384 ControllerCommand::CreateStrategy(_)
385 ));
386 }
387
388 #[rstest]
389 fn test_create_strategy_command_serde_round_trip() {
390 let strategy_config = ImportableStrategyConfig {
391 strategy_path: "tests.strategies:Strategy".to_string(),
392 config_path: "tests.strategies:StrategyConfig".to_string(),
393 config: HashMap::from([(
394 "threshold".to_string(),
395 serde_json::Value::String("20".to_string()),
396 )]),
397 };
398 let command_id = UUID4::new();
399 let command = CreateStrategy::new(strategy_config, false, command_id, UnixNanos::new(11));
400
401 let value = serde_json::to_value(&command).unwrap();
402 assert_eq!(value["type"], "CreateStrategy");
403 let round_trip: CreateStrategy = serde_json::from_value(value).unwrap();
404
405 assert_eq!(
406 round_trip.strategy_config.strategy_path,
407 "tests.strategies:Strategy"
408 );
409 assert_eq!(
410 round_trip.strategy_config.config_path,
411 "tests.strategies:StrategyConfig"
412 );
413 assert_eq!(
414 round_trip.strategy_config.config["threshold"],
415 serde_json::Value::String("20".to_string())
416 );
417 assert!(!round_trip.start);
418 assert_eq!(round_trip.command_id, command_id);
419 assert_eq!(round_trip.ts_init, UnixNanos::new(11));
420 }
421
422 #[rstest]
423 fn test_start_actor_command_fields() {
424 let command_id = UUID4::new();
425 let command = StartActor::new(ActorId::from("Actor-001"), command_id, UnixNanos::new(3));
426
427 assert_eq!(command.actor_id, ActorId::from("Actor-001"));
428 assert_eq!(command.command_id, command_id);
429 assert_eq!(command.ts_init, UnixNanos::new(3));
430 assert!(matches!(
431 ControllerCommand::from(command),
432 ControllerCommand::StartActor(_)
433 ));
434 }
435
436 #[rstest]
437 fn test_start_actor_command_serde_round_trip() {
438 let actor_id = ActorId::from("Actor-001");
439 let command_id = UUID4::new();
440 let command = StartActor::new(actor_id, command_id, UnixNanos::new(10));
441
442 let value = serde_json::to_value(command).unwrap();
443 assert_eq!(value["type"], "StartActor");
444 let round_trip: StartActor = serde_json::from_value(value).unwrap();
445
446 assert_eq!(round_trip.actor_id, actor_id);
447 assert_eq!(round_trip.command_id, command_id);
448 assert_eq!(round_trip.ts_init, UnixNanos::new(10));
449 }
450
451 #[rstest]
452 fn test_stop_actor_command_fields() {
453 let command_id = UUID4::new();
454 let command = StopActor::new(ActorId::from("Actor-001"), command_id, UnixNanos::new(4));
455
456 assert_eq!(command.actor_id, ActorId::from("Actor-001"));
457 assert_eq!(command.command_id, command_id);
458 assert_eq!(command.ts_init, UnixNanos::new(4));
459 assert!(matches!(
460 ControllerCommand::from(command),
461 ControllerCommand::StopActor(_)
462 ));
463 }
464
465 #[rstest]
466 fn test_remove_actor_command_fields() {
467 let command_id = UUID4::new();
468 let command = RemoveActor::new(ActorId::from("Actor-001"), command_id, UnixNanos::new(5));
469
470 assert_eq!(command.actor_id, ActorId::from("Actor-001"));
471 assert_eq!(command.command_id, command_id);
472 assert_eq!(command.ts_init, UnixNanos::new(5));
473 assert!(matches!(
474 ControllerCommand::from(command),
475 ControllerCommand::RemoveActor(_)
476 ));
477 }
478
479 #[rstest]
480 fn test_start_strategy_command_fields() {
481 let command_id = UUID4::new();
482 let command = StartStrategy::new(
483 StrategyId::from("Strategy-001"),
484 command_id,
485 UnixNanos::new(6),
486 );
487
488 assert_eq!(command.strategy_id, StrategyId::from("Strategy-001"));
489 assert_eq!(command.command_id, command_id);
490 assert_eq!(command.ts_init, UnixNanos::new(6));
491 assert!(matches!(
492 ControllerCommand::from(command),
493 ControllerCommand::StartStrategy(_)
494 ));
495 }
496
497 #[rstest]
498 fn test_start_strategy_command_serde_round_trip() {
499 let strategy_id = StrategyId::from("Strategy-001");
500 let command_id = UUID4::new();
501 let command = StartStrategy::new(strategy_id, command_id, UnixNanos::new(12));
502
503 let value = serde_json::to_value(command).unwrap();
504 assert_eq!(value["type"], "StartStrategy");
505 let round_trip: StartStrategy = serde_json::from_value(value).unwrap();
506
507 assert_eq!(round_trip.strategy_id, strategy_id);
508 assert_eq!(round_trip.command_id, command_id);
509 assert_eq!(round_trip.ts_init, UnixNanos::new(12));
510 }
511
512 #[rstest]
513 fn test_stop_strategy_command_fields() {
514 let command_id = UUID4::new();
515 let command = StopStrategy::new(
516 StrategyId::from("Strategy-001"),
517 command_id,
518 UnixNanos::new(7),
519 );
520
521 assert_eq!(command.strategy_id, StrategyId::from("Strategy-001"));
522 assert_eq!(command.command_id, command_id);
523 assert_eq!(command.ts_init, UnixNanos::new(7));
524 assert!(matches!(
525 ControllerCommand::from(command),
526 ControllerCommand::StopStrategy(_)
527 ));
528 }
529
530 #[rstest]
531 fn test_remove_strategy_command_fields() {
532 let command_id = UUID4::new();
533 let command = RemoveStrategy::new(
534 StrategyId::from("Strategy-001"),
535 command_id,
536 UnixNanos::new(8),
537 );
538
539 assert_eq!(command.strategy_id, StrategyId::from("Strategy-001"));
540 assert_eq!(command.command_id, command_id);
541 assert_eq!(command.ts_init, UnixNanos::new(8));
542 assert!(matches!(
543 ControllerCommand::from(command),
544 ControllerCommand::RemoveStrategy(_)
545 ));
546 }
547}