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