Skip to content

Commit

Permalink
Implement event OrderCancelRejected in Rust (#1402)
Browse files Browse the repository at this point in the history
  • Loading branch information
filipmacek authored Dec 11, 2023
1 parent 4e0d3e5 commit 1173782
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 8 deletions.
84 changes: 81 additions & 3 deletions nautilus_core/model/src/events/order/cancel_rejected.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
// limitations under the License.
// -------------------------------------------------------------------------------------------------

use std::fmt::Display;

use anyhow::Result;
use derive_builder::{self, Builder};
use nautilus_core::{time::UnixNanos, uuid::UUID4};
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use ustr::Ustr;

Expand All @@ -27,16 +31,90 @@ use crate::identifiers::{
#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, Builder)]
#[builder(default)]
#[serde(tag = "type")]
#[cfg_attr(
feature = "python",
pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
)]
pub struct OrderCancelRejected {
pub trader_id: TraderId,
pub strategy_id: StrategyId,
pub instrument_id: InstrumentId,
pub client_order_id: ClientOrderId,
pub venue_order_id: Option<VenueOrderId>,
pub account_id: Option<AccountId>,
pub reason: Ustr,
pub event_id: UUID4,
pub ts_event: UnixNanos,
pub ts_init: UnixNanos,
pub reconciliation: bool,
pub reconciliation: u8,
pub venue_order_id: Option<VenueOrderId>,
pub account_id: Option<AccountId>,
}

impl OrderCancelRejected {
#[allow(clippy::too_many_arguments)]
pub fn new(
trader_id: TraderId,
strategy_id: StrategyId,
instrument_id: InstrumentId,
client_order_id: ClientOrderId,
reason: Ustr,
event_id: UUID4,
ts_event: UnixNanos,
ts_init: UnixNanos,
reconciliation: bool,
venue_order_id: Option<VenueOrderId>,
account_id: Option<AccountId>,
) -> Result<OrderCancelRejected> {
Ok(OrderCancelRejected {
trader_id,
strategy_id,
instrument_id,
client_order_id,
reason,
event_id,
ts_event,
ts_init,
reconciliation: reconciliation as u8,
venue_order_id,
account_id,
})
}
}

impl Display for OrderCancelRejected {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"OrderCancelRejected(instrument_id={}, client_order_id={}, venue_order_id={}, account_id={}, reason={}, ts_event={})",
self.instrument_id,
self.client_order_id,
self.venue_order_id
.map(|venue_order_id| format!("{}", venue_order_id))
.unwrap_or_else(|| "None".to_string()),
self.account_id
.map(|account_id| format!("{}", account_id))
.unwrap_or_else(|| "None".to_string()),
self.reason,
self.ts_event
)
}
}

////////////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {

use rstest::rstest;

use crate::events::order::{cancel_rejected::OrderCancelRejected, stubs::*};

#[rstest]
fn test_order_cancel_rejected(order_cancel_rejected: OrderCancelRejected) {
let display = format!("{}", order_cancel_rejected);
assert_eq!(
display,
"OrderCancelRejected(instrument_id=BTCUSDT.COINBASE, client_order_id=O-20200814-102234-001-001-1, venue_order_id=001, account_id=SIM-001, reason=ORDER_DOES_NOT_EXISTS, ts_event=0)"
);
}
}
36 changes: 31 additions & 5 deletions nautilus_core/model/src/events/order/stubs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ use ustr::Ustr;
use crate::{
enums::{ContingencyType, LiquiditySide, OrderSide, OrderType, TimeInForce, TriggerType},
events::order::{
accepted::OrderAccepted, denied::OrderDenied, emulated::OrderEmulated, filled::OrderFilled,
initialized::OrderInitialized, modify_rejected::OrderModifyRejected,
pending_cancel::OrderPendingCancel, pending_update::OrderPendingUpdate,
rejected::OrderRejected, released::OrderReleased, submitted::OrderSubmitted,
triggered::OrderTriggered, updated::OrderUpdated,
accepted::OrderAccepted, cancel_rejected::OrderCancelRejected, denied::OrderDenied,
emulated::OrderEmulated, filled::OrderFilled, initialized::OrderInitialized,
modify_rejected::OrderModifyRejected, pending_cancel::OrderPendingCancel,
pending_update::OrderPendingUpdate, rejected::OrderRejected, released::OrderReleased,
submitted::OrderSubmitted, triggered::OrderTriggered, updated::OrderUpdated,
},
identifiers::{
account_id::AccountId, client_order_id::ClientOrderId, instrument_id::InstrumentId,
Expand Down Expand Up @@ -377,3 +377,29 @@ pub fn order_accepted(
)
.unwrap()
}

#[fixture]
pub fn order_cancel_rejected(
trader_id: TraderId,
strategy_id_ema_cross: StrategyId,
instrument_id_btc_usdt: InstrumentId,
client_order_id: ClientOrderId,
venue_order_id: VenueOrderId,
account_id: AccountId,
uuid4: UUID4,
) -> OrderCancelRejected {
OrderCancelRejected::new(
trader_id,
strategy_id_ema_cross,
instrument_id_btc_usdt,
client_order_id,
Ustr::from("ORDER_DOES_NOT_EXISTS"),
uuid4,
0,
0,
false,
Some(venue_order_id),
Some(account_id),
)
.unwrap()
}
143 changes: 143 additions & 0 deletions nautilus_core/model/src/python/events/order/cancel_rejected.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// -------------------------------------------------------------------------------------------------
// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved.
// https://nautechsystems.io
//
// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -------------------------------------------------------------------------------------------------

use std::str::FromStr;

use nautilus_core::{
python::{serialization::from_dict_pyo3, to_pyvalue_err},
time::UnixNanos,
uuid::UUID4,
};
use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
use rust_decimal::prelude::ToPrimitive;
use ustr::Ustr;

use crate::{
events::order::cancel_rejected::OrderCancelRejected,
identifiers::{
account_id::AccountId, client_order_id::ClientOrderId, instrument_id::InstrumentId,
strategy_id::StrategyId, trader_id::TraderId, venue_order_id::VenueOrderId,
},
};

#[pymethods]
impl OrderCancelRejected {
#[allow(clippy::too_many_arguments)]
#[new]
fn py_new(
trader_id: TraderId,
strategy_id: StrategyId,
instrument_id: InstrumentId,
client_order_id: ClientOrderId,
reason: &str,
event_id: UUID4,
ts_event: UnixNanos,
ts_init: UnixNanos,
reconciliation: bool,
venue_order_id: Option<VenueOrderId>,
account_id: Option<AccountId>,
) -> PyResult<Self> {
let reason = Ustr::from_str(reason).unwrap();
Self::new(
trader_id,
strategy_id,
instrument_id,
client_order_id,
reason,
event_id,
ts_event,
ts_init,
reconciliation,
venue_order_id,
account_id,
)
.map_err(to_pyvalue_err)
}

fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
match op {
CompareOp::Eq => self.eq(other).into_py(py),
CompareOp::Ne => self.ne(other).into_py(py),
_ => py.NotImplemented(),
}
}

fn __repr__(&self) -> String {
format!(
"{}(trader_id={}, strategy_id={}, instrument_id={}, client_order_id={}, venue_order_id={}, account_id={}, reason={}, event_id={}, ts_event={}, ts_init={})",
stringify!(OrderCancelRejected),
self.trader_id,
self.strategy_id,
self.instrument_id,
self.client_order_id,
self.venue_order_id
.map(|venue_order_id| format!("{}", venue_order_id))
.unwrap_or_else(|| "None".to_string()),
self.account_id
.map(|account_id| format!("{}", account_id))
.unwrap_or_else(|| "None".to_string()),
self.reason,
self.event_id,
self.ts_event,
self.ts_init
)
}

fn __str__(&self) -> String {
format!(
"{}(instrument_id={}, client_order_id={}, venue_order_id={}, account_id={}, reason={}, ts_event={})",
stringify!(OrderCancelRejected),
self.instrument_id,
self.client_order_id,
self.venue_order_id
.map(|venue_order_id| format!("{}", venue_order_id))
.unwrap_or_else(|| "None".to_string()),
self.account_id
.map(|account_id| format!("{}", account_id))
.unwrap_or_else(|| "None".to_string()),
self.reason,
self.ts_event,
)
}

#[staticmethod]
#[pyo3(name = "from_dict")]
fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
from_dict_pyo3(py, values)
}

#[pyo3(name = "to_dict")]
fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
let dict = PyDict::new(py);
dict.set_item("trader_id", self.trader_id.to_string())?;
dict.set_item("strategy_id", self.strategy_id.to_string())?;
dict.set_item("instrument_id", self.instrument_id.to_string())?;
dict.set_item("client_order_id", self.client_order_id.to_string())?;
dict.set_item("reason", self.reason.as_str())?;
dict.set_item("event_id", self.event_id.to_string())?;
dict.set_item("ts_event", self.ts_event.to_u64())?;
dict.set_item("ts_init", self.ts_init.to_u64())?;
dict.set_item("reconciliation", self.reconciliation)?;
match self.venue_order_id {
Some(venue_order_id) => dict.set_item("venue_order_id", venue_order_id.to_string())?,
None => dict.set_item("venue_order_id", py.None())?,
}
match self.account_id {
Some(account_id) => dict.set_item("account_id", account_id.to_string())?,
None => dict.set_item("account_id", py.None())?,
}
Ok(dict.into())
}
}
1 change: 1 addition & 0 deletions nautilus_core/model/src/python/events/order/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// -------------------------------------------------------------------------------------------------

pub mod accepted;
pub mod cancel_rejected;
pub mod denied;
pub mod emulated;
pub mod filled;
Expand Down
1 change: 1 addition & 0 deletions nautilus_core/model/src/python/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,5 +306,6 @@ pub fn model(_: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<crate::events::order::pending_cancel::OrderPendingCancel>()?;
m.add_class::<crate::events::order::modify_rejected::OrderModifyRejected>()?;
m.add_class::<crate::events::order::accepted::OrderAccepted>()?;
m.add_class::<crate::events::order::cancel_rejected::OrderCancelRejected>()?;
Ok(())
}
19 changes: 19 additions & 0 deletions nautilus_trader/core/nautilus_pyo3.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,25 @@ class OrderAccepted:
def to_dict(self) -> dict[str, str]: ...


class OrderCancelRejected:
def __init__(
self,
trader_id: TraderId,
strategy_id: StrategyId,
instrument_id: InstrumentId,
client_order_id: ClientOrderId,
reason: str,
event_id: UUID4,
ts_event: int,
ts_init: int,
reconciliation: bool,
venue_order_id: VenueOrderId | None = None,
account_id: AccountId | None = None,
)-> None: ...
@classmethod
def from_dict(cls, values: dict[str, str]) -> OrderCancelRejected: ...
def to_dict(self) -> dict[str, str]: ...

###################################################################################################
# Infrastructure
###################################################################################################
Expand Down
17 changes: 17 additions & 0 deletions nautilus_trader/test_kit/rust/events_pyo3.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from nautilus_trader.core.nautilus_pyo3 import LiquiditySide
from nautilus_trader.core.nautilus_pyo3 import Money
from nautilus_trader.core.nautilus_pyo3 import OrderAccepted
from nautilus_trader.core.nautilus_pyo3 import OrderCancelRejected
from nautilus_trader.core.nautilus_pyo3 import OrderDenied
from nautilus_trader.core.nautilus_pyo3 import OrderEmulated
from nautilus_trader.core.nautilus_pyo3 import OrderFilled
Expand Down Expand Up @@ -262,3 +263,19 @@ def order_accepted() -> OrderAccepted:
ts_event=0,
reconciliation=False,
)

@staticmethod
def order_cancel_rejected() -> OrderCancelRejected:
return OrderCancelRejected(
trader_id=TestIdProviderPyo3.trader_id(),
strategy_id=TestIdProviderPyo3.strategy_id(),
instrument_id=TestIdProviderPyo3.ethusdt_binance_id(),
client_order_id=TestIdProviderPyo3.client_order_id(),
account_id=TestIdProviderPyo3.account_id(),
venue_order_id=TestIdProviderPyo3.venue_order_id(),
reason="ORDER_DOES_NOT_EXIST",
event_id=UUID4(uuid),
ts_init=0,
ts_event=0,
reconciliation=False,
)
19 changes: 19 additions & 0 deletions tests/unit_tests/model/test_events_pyo3.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@


from nautilus_trader.core.nautilus_pyo3 import OrderAccepted
from nautilus_trader.core.nautilus_pyo3 import OrderCancelRejected
from nautilus_trader.core.nautilus_pyo3 import OrderDenied
from nautilus_trader.core.nautilus_pyo3 import OrderEmulated
from nautilus_trader.core.nautilus_pyo3 import OrderFilled
Expand Down Expand Up @@ -266,3 +267,21 @@ def test_order_accepted():
== "OrderAccepted(trader_id=TESTER-001, strategy_id=S-001, instrument_id=ETHUSDT.BINANCE, client_order_id=O-20210410-022422-001-001-1, "
+ "venue_order_id=123456, account_id=SIM-000, event_id=91762096-b188-49ea-8562-8d8a4cc22ff2, ts_event=0, ts_init=0)"
)


def test_order_cancel_rejected():
event = TestEventsProviderPyo3.order_cancel_rejected()
result_dict = OrderCancelRejected.to_dict(event)
order_cancel_rejected = OrderCancelRejected.from_dict(result_dict)
assert order_cancel_rejected == event
assert (
str(event)
== "OrderCancelRejected(instrument_id=ETHUSDT.BINANCE, client_order_id=O-20210410-022422-001-001-1, venue_order_id=123456, "
+ "account_id=SIM-000, reason=ORDER_DOES_NOT_EXIST, ts_event=0)"
)
assert (
repr(event)
== "OrderCancelRejected(trader_id=TESTER-001, strategy_id=S-001, instrument_id=ETHUSDT.BINANCE, "
+ "client_order_id=O-20210410-022422-001-001-1, venue_order_id=123456, account_id=SIM-000, "
+ "reason=ORDER_DOES_NOT_EXIST, event_id=91762096-b188-49ea-8562-8d8a4cc22ff2, ts_event=0, ts_init=0)"
)

0 comments on commit 1173782

Please sign in to comment.