use std::{any::Any, cell::RefCell, rc::Rc};
use nautilus_common::{
cache::Cache,
clients::{DataClient, ExecutionClient},
clock::Clock,
};
use nautilus_live::ExecutionClientCore;
use nautilus_model::{
enums::{AccountType, OmsType},
identifiers::ClientId,
};
use nautilus_system::{
ExecutionClientFactory,
factories::{ClientConfig, DataClientFactory},
};
use crate::{
config::{BlockchainDataClientConfig, BlockchainExecutionClientConfig},
constants::BLOCKCHAIN_VENUE,
data::client::BlockchainDataClient,
execution::client::BlockchainExecutionClient,
};
impl ClientConfig for BlockchainDataClientConfig {
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.core.nautilus_pyo3.blockchain",
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.blockchain")
)]
pub struct BlockchainDataClientFactory;
impl BlockchainDataClientFactory {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl Default for BlockchainDataClientFactory {
fn default() -> Self {
Self::new()
}
}
impl DataClientFactory for BlockchainDataClientFactory {
fn create(
&self,
_name: &str,
config: &dyn ClientConfig,
_cache: Rc<RefCell<Cache>>,
_clock: Rc<RefCell<dyn Clock>>,
) -> anyhow::Result<Box<dyn DataClient>> {
let blockchain_config = config
.as_any()
.downcast_ref::<BlockchainDataClientConfig>()
.ok_or_else(|| {
anyhow::anyhow!(
"Invalid config type for BlockchainDataClientFactory. Expected `BlockchainDataClientConfig`, was {config:?}"
)
})?;
let client = BlockchainDataClient::new(blockchain_config.clone());
Ok(Box::new(client))
}
fn name(&self) -> &'static str {
"BLOCKCHAIN"
}
fn config_type(&self) -> &'static str {
"BlockchainDataClientConfig"
}
}
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.core.nautilus_pyo3.blockchain",
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.blockchain")
)]
pub struct BlockchainExecutionClientFactory;
impl BlockchainExecutionClientFactory {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl Default for BlockchainExecutionClientFactory {
fn default() -> Self {
Self::new()
}
}
impl ExecutionClientFactory for BlockchainExecutionClientFactory {
fn create(
&self,
name: &str,
config: &dyn ClientConfig,
cache: Rc<RefCell<Cache>>,
) -> anyhow::Result<Box<dyn ExecutionClient>> {
let blockchain_execution_config = config
.as_any()
.downcast_ref::<BlockchainExecutionClientConfig>()
.ok_or_else(|| {
anyhow::anyhow!(
"Invalid config type for BlockchainDataClientFactory. Expected `BlockchainDataClientConfig`, was {config:?}"
)
})?;
let core_execution_client = ExecutionClientCore::new(
blockchain_execution_config.trader_id,
ClientId::from(name),
*BLOCKCHAIN_VENUE,
OmsType::Netting,
blockchain_execution_config.client_id,
AccountType::Wallet,
None,
cache,
);
let client = BlockchainExecutionClient::new(
core_execution_client,
blockchain_execution_config.clone(),
)?;
Ok(Box::new(client))
}
fn name(&self) -> &'static str {
"BLOCKCHAIN"
}
fn config_type(&self) -> &'static str {
"BlockchainExecutionClientConfig"
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use nautilus_model::defi::chain::{Blockchain, chains};
use nautilus_system::factories::DataClientFactory;
use rstest::rstest;
use crate::{config::BlockchainDataClientConfig, factories::BlockchainDataClientFactory};
#[rstest]
fn test_blockchain_data_client_config_creation() {
let chain = Arc::new(chains::ETHEREUM.clone());
let config = BlockchainDataClientConfig::new(
chain,
vec![],
"https://eth-mainnet.example.com".to_string(),
None,
None,
None,
false,
None,
None,
None,
);
assert_eq!(config.chain.name, Blockchain::Ethereum);
assert_eq!(config.http_rpc_url, "https://eth-mainnet.example.com");
}
#[rstest]
fn test_factory_creation() {
let factory = BlockchainDataClientFactory::new();
assert_eq!(factory.name(), "BLOCKCHAIN");
assert_eq!(factory.config_type(), "BlockchainDataClientConfig");
}
}