use log::LevelFilter;
use nautilus_common::{enums::Environment, logging::logger::LoggerConfig};
use nautilus_hyperliquid::{
HyperliquidDataClientConfig, HyperliquidDataClientFactory, HyperliquidExecutionClientConfig,
HyperliquidExecutionClientFactory,
common::{consts::HYPERLIQUID_CLIENT_ID, enums::HyperliquidEnvironment},
};
use nautilus_live::node::LiveNode;
use nautilus_model::{
identifiers::{AccountId, InstrumentId, StrategyId, TraderId},
types::Quantity,
};
use nautilus_testkit::testers::{ExecTester, ExecTesterConfig};
use nautilus_trading::strategy::StrategyConfig;
const DRY_RUN: bool = false;
const HYPERLIQUID_ENVIRONMENT: HyperliquidEnvironment = HyperliquidEnvironment::Mainnet;
const TRADER_ID: &str = "TESTER-001";
const ACCOUNT_ID: &str = "HYPERLIQUID-001";
const NODE_NAME: &str = "HYPERLIQUID-OUTCOME-EXEC-TESTER-001";
const STRATEGY_ID: &str = "OUTCOME_EXEC_TESTER-001";
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenvy::dotenv().ok();
let nt_environment = Environment::Live;
let hl_environment = HYPERLIQUID_ENVIRONMENT;
let trader_id = TraderId::from(TRADER_ID);
let account_id = AccountId::from(ACCOUNT_ID);
let node_name = NODE_NAME.to_string();
let client_id = *HYPERLIQUID_CLIENT_ID;
let instrument_id = std::env::var("HYPERLIQUID_OUTCOME_INSTRUMENT_ID")
.map(InstrumentId::from)
.map_err(|_| {
anyhow::anyhow!(
"HYPERLIQUID_OUTCOME_INSTRUMENT_ID must be set to an active HIP-4 side token"
)
})?;
let order_qty_raw = std::env::var("HYPERLIQUID_OUTCOME_ORDER_QTY")
.map_err(|_| anyhow::anyhow!("HYPERLIQUID_OUTCOME_ORDER_QTY must be set"))?;
let order_qty = order_qty_raw.parse::<Quantity>().map_err(|e| {
anyhow::anyhow!("Invalid HYPERLIQUID_OUTCOME_ORDER_QTY '{order_qty_raw}': {e}")
})?;
let data_config = HyperliquidDataClientConfig {
environment: hl_environment,
..Default::default()
};
let exec_config = HyperliquidExecutionClientConfig {
account_id,
environment: hl_environment,
..Default::default()
};
let data_factory = HyperliquidDataClientFactory::new();
let exec_factory = HyperliquidExecutionClientFactory::new();
let log_config = LoggerConfig {
stdout_level: LevelFilter::Info,
..Default::default()
};
let mut node = LiveNode::builder(trader_id, nt_environment)?
.with_name(node_name)
.with_logging(log_config)
.add_data_client(None, Box::new(data_factory), Box::new(data_config))?
.add_exec_client(None, Box::new(exec_factory), Box::new(exec_config))?
.with_reconciliation(true)
.with_delay_post_stop_secs(10)
.build()?;
let tester_config = ExecTesterConfig::builder()
.base(StrategyConfig {
strategy_id: Some(StrategyId::from(STRATEGY_ID)),
external_order_claims: Some(vec![instrument_id]),
use_hyphens_in_client_order_ids: true,
..Default::default()
})
.instrument_id(instrument_id)
.client_id(client_id)
.order_qty(order_qty)
.dry_run(DRY_RUN)
.tob_offset_ticks(5)
.enable_limit_buys(true)
.enable_limit_sells(false)
.enable_stop_buys(false)
.enable_stop_sells(false)
.enable_brackets(false)
.use_post_only(true)
.reduce_only_on_stop(false)
.cancel_orders_on_stop(true)
.close_positions_on_stop(false)
.clamp_to_instrument_price_range(true)
.log_data(false)
.build()?;
let tester = ExecTester::new(tester_config);
node.add_strategy(tester)?;
node.run().await?;
Ok(())
}