Skip to content

Commit

Permalink
CurrencyPair in rust with pyo3
Browse files Browse the repository at this point in the history
  • Loading branch information
filipmacek committed Oct 29, 2023
1 parent c7350ea commit a77c491
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 19 deletions.
87 changes: 71 additions & 16 deletions nautilus_core/model/src/instruments/currency_pair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use std::hash::{Hash, Hasher};

use pyo3::prelude::*;
use anyhow::Result;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};

Expand All @@ -43,19 +44,18 @@ pub struct CurrencyPair {
pub size_precision: u8,
pub price_increment: Price,
pub size_increment: Quantity,
pub margin_init: Decimal,
pub margin_maint: Decimal,
pub maker_fee: Decimal,
pub taker_fee: Decimal,
pub lot_size: Option<Quantity>,
pub max_quantity: Option<Quantity>,
pub min_quantity: Option<Quantity>,
pub max_price: Option<Price>,
pub min_price: Option<Price>,
pub margin_init: Decimal,
pub margin_maint: Decimal,
pub maker_fee: Decimal,
pub taker_fee: Decimal,
}

impl CurrencyPair {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
id: InstrumentId,
Expand All @@ -66,17 +66,17 @@ impl CurrencyPair {
size_precision: u8,
price_increment: Price,
size_increment: Quantity,
margin_init: Decimal,
margin_maint: Decimal,
maker_fee: Decimal,
taker_fee: Decimal,
lot_size: Option<Quantity>,
max_quantity: Option<Quantity>,
min_quantity: Option<Quantity>,
max_price: Option<Price>,
min_price: Option<Price>,
margin_init: Decimal,
margin_maint: Decimal,
maker_fee: Decimal,
taker_fee: Decimal,
) -> Self {
Self {
) -> Result<Self> {
Ok(Self {
id,
raw_symbol,
quote_currency,
Expand All @@ -85,16 +85,16 @@ impl CurrencyPair {
size_precision,
price_increment,
size_increment,
margin_init,
margin_maint,
maker_fee,
taker_fee,
lot_size,
max_quantity,
min_quantity,
max_price,
min_price,
margin_init,
margin_maint,
maker_fee,
taker_fee,
}
})
}
}

Expand Down Expand Up @@ -202,3 +202,58 @@ impl Instrument for CurrencyPair {
self.taker_fee
}
}


////////////////////////////////////////////////////////////////////////////////
// Stubs
////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
pub mod stubs{
use std::str::FromStr;
use rstest::fixture;
use rust_decimal::Decimal;
use crate::identifiers::instrument_id::InstrumentId;
use crate::identifiers::symbol::Symbol;
use crate::instruments::currency_pair::CurrencyPair;
use crate::types::currency::Currency;
use crate::types::price::Price;
use crate::types::quantity::Quantity;

#[fixture]
pub fn currency_pair_btcusdt()-> CurrencyPair{
CurrencyPair::new(
InstrumentId::from("BTCUSDT.BINANCE"),
Symbol::from("BTCUSDT"),
Currency::from("BTC"),
Currency::from("USDT"),
2, 6,
Price::from("0.01"),
Quantity::from("0.000001"),
Decimal::from_str("0.0").unwrap(),
Decimal::from_str("0.0").unwrap(),
Decimal::from_str("0.001").unwrap(),
Decimal::from_str("0.001").unwrap(),
None,
Some(Quantity::from("9000")),
Some(Quantity::from("0.000001")),
Some(Price::from("1000000")),
Some(Price::from("0.01")),
).unwrap()
}
}

////////////////////////////////////////////////////////////////////////////////
// Tests
///////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests{
use rstest::rstest;
use crate::instruments::currency_pair::CurrencyPair;
use crate::instruments::currency_pair::stubs::currency_pair_btcusdt;

#[rstest]
fn test_equality(currency_pair_btcusdt: CurrencyPair) {
let cloned = currency_pair_btcusdt.clone();
assert_eq!(currency_pair_btcusdt, cloned)
}
}
136 changes: 136 additions & 0 deletions nautilus_core/model/src/python/instruments/currency_pair.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// -------------------------------------------------------------------------------------------------
// 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::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use pyo3::basic::CompareOp;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use rust_decimal::Decimal;
use rust_decimal::prelude::ToPrimitive;
use nautilus_core::python::serialization::from_dict_pyo3;
use nautilus_core::python::to_pyvalue_err;
use crate::identifiers::instrument_id::InstrumentId;
use crate::identifiers::symbol::Symbol;
use crate::instruments::currency_pair::CurrencyPair;
use crate::types::currency::Currency;
use crate::types::price::Price;
use crate::types::quantity::Quantity;

#[pymethods]
impl CurrencyPair{
#[allow(clippy::too_many_arguments)]
#[new]
fn py_new(
id: InstrumentId,
raw_symbol: Symbol,
base_currency: Currency,
quote_currency: Currency,
price_precision: u8,
size_precision: u8,
price_increment: Price,
size_increment: Quantity,
margin_init: Decimal,
margin_maint: Decimal,
maker_fee: Decimal,
taker_fee: Decimal,
lot_size: Option<Quantity>,
max_quantity: Option<Quantity>,
min_quantity: Option<Quantity>,
max_price: Option<Price>,
min_price: Option<Price>,
)->PyResult<Self>{
Self::new(
id,
raw_symbol,
base_currency,
quote_currency,
price_precision,
size_precision,
price_increment,
size_increment,
margin_init,
margin_maint,
maker_fee,
taker_fee,
lot_size,
max_quantity,
min_quantity,
max_price,
min_price
).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),
_ => panic!("Not implemented"),
}
}

fn __hash__(&self) -> isize {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish() as isize
}

#[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("type", stringify!(CurrencyPair))?;
dict.set_item("id", self.id.to_string())?;
dict.set_item("raw_symbol", self.raw_symbol.to_string())?;
dict.set_item("base_currency", self.base_currency.code.to_string())?;
dict.set_item("quote_currency", self.quote_currency.code.to_string())?;
dict.set_item("price_precision", self.price_precision)?;
dict.set_item("size_precision", self.size_precision)?;
dict.set_item("price_increment", self.price_increment.to_string())?;
dict.set_item("size_increment", self.size_increment.to_string())?;
dict.set_item("margin_init", self.margin_init.to_f64())?;
dict.set_item("margin_maint", self.margin_maint.to_f64())?;
dict.set_item("maker_fee", self.margin_init.to_f64())?;
dict.set_item("taker_fee", self.margin_init.to_f64())?;
match self.lot_size {
Some(value) => dict.set_item("lot_size", value.to_string())?,
None => dict.set_item("lot_size", py.None())?,
}
match self.max_quantity {
Some(value) => dict.set_item("max_quantity", value.to_string())?,
None => dict.set_item("max_quantity", py.None())?,
}
match self.min_quantity {
Some(value) => dict.set_item("min_quantity", value.to_string())?,
None => dict.set_item("min_quantity", py.None())?,
}
match self.max_price {
Some(value) => dict.set_item("max_price", value.to_string())?,
None => dict.set_item("max_price", py.None())?,
}
match self.min_price {
Some(value) => dict.set_item("min_price", value.to_string())?,
None => dict.set_item("min_price", py.None())?,
}
Ok(dict.into())
}
}
1 change: 1 addition & 0 deletions nautilus_core/model/src/python/instruments/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@

pub mod crypto_future;
pub mod crypto_perpetual;
pub mod currency_pair;
2 changes: 1 addition & 1 deletion nautilus_trader/core/nautilus_pyo3.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ class Quantity:

class CryptoFuture: ...
class CryptoPerpetual: ...
class CurrenyPair: ...
class CurrencyPair: ...
class Equity: ...
class FuturesContract: ...
class OptionsContract: ...
Expand Down
23 changes: 23 additions & 0 deletions nautilus_trader/test_kit/rust/instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from nautilus_trader.core.nautilus_pyo3 import CryptoFuture
from nautilus_trader.core.nautilus_pyo3 import CryptoPerpetual
from nautilus_trader.core.nautilus_pyo3 import CurrencyPair
from nautilus_trader.core.nautilus_pyo3 import InstrumentId
from nautilus_trader.core.nautilus_pyo3 import Money
from nautilus_trader.core.nautilus_pyo3 import Price
Expand Down Expand Up @@ -83,3 +84,25 @@ def btcusdt_future_binance(expiry: pd.Timestamp | None = None) -> CryptoFuture:
Price.from_str("1000000.0"),
Price.from_str("0.01"),
)

@staticmethod
def btcusdt_binance() -> CurrencyPair:
return CurrencyPair( # type: ignore
InstrumentId.from_str("BTCUSDT.BINANCE"),
Symbol("BTCUSDT"),
TestTypesProviderPyo3.currency_btc(),
TestTypesProviderPyo3.currency_usdt(),
2,
6,
Price.from_str("0.01"),
Quantity.from_str("0.000001"),
0.0,
0.0,
0.001,
0.001,
None,
Quantity.from_str("9000"),
Quantity.from_str("0.00001"),
Price.from_str("1000000"),
Price.from_str("0.01")
)
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
crypto_future_btcusdt = TestInstrumentProviderPyo3.btcusdt_future_binance()


class TestCryptoFuture:
class TestCryptoFuturePyo3:
def test_equality(self):
item_1 = TestInstrumentProviderPyo3.btcusdt_future_binance()
item_2 = TestInstrumentProviderPyo3.btcusdt_future_binance()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
crypto_perpetual_ethusdt_perp = TestInstrumentProviderPyo3.ethusdt_perp_binance()


class TestCryptoPerpetual:
class TestCryptoPerpetualPyo3:
def test_equality(self):
item_1 = TestInstrumentProviderPyo3.ethusdt_perp_binance()
item_2 = TestInstrumentProviderPyo3.ethusdt_perp_binance()
Expand Down
55 changes: 55 additions & 0 deletions tests/unit_tests/model/instruments/test_currency_pair_pyo3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# -------------------------------------------------------------------------------------------------
# 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.
# -------------------------------------------------------------------------------------------------

from nautilus_trader.test_kit.rust.instruments import TestInstrumentProviderPyo3
from nautilus_trader.core.nautilus_pyo3 import CurrencyPair

btcusdt_binance = TestInstrumentProviderPyo3.btcusdt_binance()

class TestCurrencyPairPyo3:

def test_equality(self):
item_1 = TestInstrumentProviderPyo3.btcusdt_binance()
item_2 = TestInstrumentProviderPyo3.btcusdt_binance()
assert item_1 == item_2

def test_hash(self):
assert hash(btcusdt_binance) == hash(btcusdt_binance)


def test_to_dict(self):
dict = btcusdt_binance.to_dict()
assert CurrencyPair.from_dict(dict) == btcusdt_binance
assert dict == {
"type": "CurrencyPair",
"id": "BTCUSDT.BINANCE",
"raw_symbol": "BTCUSDT",
"base_currency": "BTC",
"quote_currency": "USDT",
"price_precision": 2,
"size_precision": 6,
"price_increment": "0.01",
"size_increment": "0.000001",
"margin_maint": 0.0,
"margin_init": 0.0,
"maker_fee": 0.0,
"taker_fee": 0.0,
"lot_size": None,
"max_quantity": "9000",
"min_quantity": "0.00001",
"min_price": "0.01",
"max_price": "1000000",
}

0 comments on commit a77c491

Please sign in to comment.