nautilus_system/python/
controller.rs1use std::{
17 cell::RefCell,
18 rc::{Rc, Weak},
19};
20
21use nautilus_common::{
22 actor::data_actor::{DataActorConfig, ImportableActorConfig},
23 python::actor::PyDataActor,
24};
25use nautilus_core::python::to_pyruntime_err;
26use nautilus_model::identifiers::{ActorId, StrategyId};
27use nautilus_trading::ImportableStrategyConfig;
28use pyo3::prelude::*;
29use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
30
31use crate::{controller::Controller, trader::Trader};
32
33#[gen_stub_pyclass(module = "nautilus_trader.trading")]
38#[pyclass(
39 module = "nautilus_trader.trading",
40 name = "Controller",
41 extends = PyDataActor,
42 subclass,
43 unsendable
44)]
45#[derive(Debug, Default)]
46pub struct PyController {
47 trader: Option<Weak<RefCell<Trader>>>,
48}
49
50impl PyController {
51 pub(crate) fn bind_trader(&mut self, trader: &Rc<RefCell<Trader>>) {
52 self.trader = Some(Rc::downgrade(trader));
53 }
54}
55
56fn controller_for(slf: &PyRef<'_, PyController>) -> PyResult<Controller> {
59 let trader = slf
60 .trader
61 .as_ref()
62 .ok_or_else(|| to_pyruntime_err("Controller is not registered with a trader"))?
63 .upgrade()
64 .ok_or_else(|| to_pyruntime_err("Controller trader is no longer available"))?;
65
66 Ok(Controller::new(
67 trader,
68 Some(DataActorConfig {
69 actor_id: Some(slf.as_super().actor_id()),
70 ..Default::default()
71 }),
72 ))
73}
74
75#[gen_stub_pymethods]
76#[pymethods]
77#[allow(
78 clippy::needless_pass_by_value,
79 reason = "PyO3 and the stub generator only recognize an owned `PyRef<'_, Self>` as the receiver"
80)]
81impl PyController {
82 #[new]
83 #[gen_stub(override_return_type(type_repr = "typing.Self", imports = ("typing",)))]
84 #[pyo3(signature = (config=None))]
85 fn py_new(config: Option<Py<PyAny>>) -> PyClassInitializer<Self> {
86 PyClassInitializer::from(PyDataActor::from_py_config(config)).add_subclass(Self::default())
87 }
88
89 #[pyo3(name = "create_actor_from_config", signature = (actor_config, start=true))]
90 fn py_create_actor_from_config(
91 slf: PyRef<'_, Self>,
92 actor_config: ImportableActorConfig,
93 start: bool,
94 ) -> PyResult<ActorId> {
95 controller_for(&slf)?
96 .create_actor_from_config(&actor_config, start)
97 .map_err(to_pyruntime_err)
98 }
99
100 #[pyo3(name = "create_strategy_from_config", signature = (strategy_config, start=true))]
101 fn py_create_strategy_from_config(
102 slf: PyRef<'_, Self>,
103 strategy_config: ImportableStrategyConfig,
104 start: bool,
105 ) -> PyResult<StrategyId> {
106 controller_for(&slf)?
107 .create_strategy_from_config(&strategy_config, start)
108 .map_err(to_pyruntime_err)
109 }
110
111 #[pyo3(name = "start_actor")]
112 fn py_start_actor(slf: PyRef<'_, Self>, actor_id: ActorId) -> PyResult<()> {
113 controller_for(&slf)?
114 .start_actor(&actor_id)
115 .map_err(to_pyruntime_err)
116 }
117
118 #[pyo3(name = "start_actor_from_id")]
119 fn py_start_actor_from_id(slf: PyRef<'_, Self>, actor_id: ActorId) -> PyResult<()> {
120 Self::py_start_actor(slf, actor_id)
121 }
122
123 #[pyo3(name = "stop_actor")]
124 fn py_stop_actor(slf: PyRef<'_, Self>, actor_id: ActorId) -> PyResult<()> {
125 controller_for(&slf)?
126 .stop_actor(&actor_id)
127 .map_err(to_pyruntime_err)
128 }
129
130 #[pyo3(name = "stop_actor_from_id")]
131 fn py_stop_actor_from_id(slf: PyRef<'_, Self>, actor_id: ActorId) -> PyResult<()> {
132 Self::py_stop_actor(slf, actor_id)
133 }
134
135 #[pyo3(name = "remove_actor")]
136 fn py_remove_actor(slf: PyRef<'_, Self>, actor_id: ActorId) -> PyResult<()> {
137 controller_for(&slf)?
138 .remove_actor(&actor_id)
139 .map_err(to_pyruntime_err)
140 }
141
142 #[pyo3(name = "remove_actor_from_id")]
143 fn py_remove_actor_from_id(slf: PyRef<'_, Self>, actor_id: ActorId) -> PyResult<()> {
144 Self::py_remove_actor(slf, actor_id)
145 }
146
147 #[pyo3(name = "start_strategy")]
148 fn py_start_strategy(slf: PyRef<'_, Self>, strategy_id: StrategyId) -> PyResult<()> {
149 controller_for(&slf)?
150 .start_strategy(&strategy_id)
151 .map_err(to_pyruntime_err)
152 }
153
154 #[pyo3(name = "start_strategy_from_id")]
155 fn py_start_strategy_from_id(slf: PyRef<'_, Self>, strategy_id: StrategyId) -> PyResult<()> {
156 Self::py_start_strategy(slf, strategy_id)
157 }
158
159 #[pyo3(name = "stop_strategy")]
160 fn py_stop_strategy(slf: PyRef<'_, Self>, strategy_id: StrategyId) -> PyResult<()> {
161 controller_for(&slf)?
162 .stop_strategy(&strategy_id)
163 .map_err(to_pyruntime_err)
164 }
165
166 #[pyo3(name = "stop_strategy_from_id")]
167 fn py_stop_strategy_from_id(slf: PyRef<'_, Self>, strategy_id: StrategyId) -> PyResult<()> {
168 Self::py_stop_strategy(slf, strategy_id)
169 }
170
171 #[pyo3(name = "market_exit_strategy")]
172 fn py_market_exit_strategy(slf: PyRef<'_, Self>, strategy_id: StrategyId) -> PyResult<()> {
173 controller_for(&slf)?
174 .exit_market(&strategy_id)
175 .map_err(to_pyruntime_err)
176 }
177
178 #[pyo3(name = "market_exit_strategy_from_id")]
179 fn py_market_exit_strategy_from_id(
180 slf: PyRef<'_, Self>,
181 strategy_id: StrategyId,
182 ) -> PyResult<()> {
183 Self::py_market_exit_strategy(slf, strategy_id)
184 }
185
186 #[pyo3(name = "remove_strategy")]
187 fn py_remove_strategy(slf: PyRef<'_, Self>, strategy_id: StrategyId) -> PyResult<()> {
188 controller_for(&slf)?
189 .remove_strategy(&strategy_id)
190 .map_err(to_pyruntime_err)
191 }
192
193 #[pyo3(name = "remove_strategy_from_id")]
194 fn py_remove_strategy_from_id(slf: PyRef<'_, Self>, strategy_id: StrategyId) -> PyResult<()> {
195 Self::py_remove_strategy(slf, strategy_id)
196 }
197}
198
199pub(crate) fn bind_controller_trader(
201 python_controller: &Py<PyAny>,
202 trader: &Rc<RefCell<Trader>>,
203) -> anyhow::Result<()> {
204 Python::attach(|py| -> anyhow::Result<()> {
205 let mut controller = python_controller
206 .bind(py)
207 .extract::<PyRefMut<PyController>>()
208 .map_err(|e| {
209 anyhow::anyhow!(
210 "Controller must inherit from `nautilus_trader.trading.Controller`: {e}"
211 )
212 })?;
213
214 controller.bind_trader(trader);
215
216 Ok(())
217 })
218}