use chrono::Duration;
use rust_decimal::Decimal;
use std::sync::Arc;
use tempfile::NamedTempFile;
use tesser_core::{ExecutionHint, Signal, SignalKind};
use tesser_execution::{
algorithm::TwapAlgorithm, AlgoStatus, ExecutionAlgorithm, ExecutionEngine, FixedOrderSizer,
NoopRiskChecker, OrderOrchestrator, RiskContext, SqliteAlgoStateRepository,
};
use tesser_paper::PaperExecutionClient;
#[tokio::test]
async fn test_twap_algorithm_basic() {
let signal = Signal::new("BTCUSDT", SignalKind::EnterLong, 0.8);
let mut twap = TwapAlgorithm::new(
signal.clone(),
Decimal::ONE, Duration::seconds(2), 2, )
.unwrap();
assert_eq!(twap.status(), AlgoStatus::Working);
let initial_orders = twap.start().unwrap();
assert!(initial_orders.is_empty());
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
let orders = twap.on_timer().unwrap();
assert_eq!(orders.len(), 1);
assert_eq!(orders[0].order_request.quantity, Decimal::new(5, 1));
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
let orders = twap.on_timer().unwrap();
assert_eq!(orders.len(), 1);
assert_eq!(orders[0].order_request.quantity, Decimal::ONE);
let orders = twap.on_timer().unwrap();
assert_eq!(orders.len(), 0);
}
#[tokio::test]
async fn test_orchestrator_integration() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.path();
let client = Arc::new(PaperExecutionClient::default());
let sizer = Box::new(FixedOrderSizer {
quantity: Decimal::ONE,
});
let risk_checker = Arc::new(NoopRiskChecker);
let execution_engine = Arc::new(ExecutionEngine::new(client, sizer, risk_checker));
let algo_state_repo = Arc::new(SqliteAlgoStateRepository::new(temp_path).unwrap());
let orchestrator = OrderOrchestrator::new(execution_engine, algo_state_repo)
.await
.unwrap();
let signal =
Signal::new("BTCUSDT", SignalKind::EnterLong, 0.8).with_hint(ExecutionHint::Twap {
duration: Duration::minutes(2),
});
let ctx = RiskContext {
signed_position_qty: Decimal::ZERO,
portfolio_equity: Decimal::from(10_000),
last_price: Decimal::from(50_000),
liquidate_only: false,
};
orchestrator.on_signal(&signal, &ctx).await.unwrap();
assert_eq!(orchestrator.active_algorithms_count(), 1);
orchestrator.on_timer_tick().await.unwrap();
assert_eq!(orchestrator.active_algorithms_count(), 1);
}
#[test]
fn test_twap_state_persistence() {
let signal = Signal::new("BTCUSDT", SignalKind::EnterLong, 0.8);
let twap = TwapAlgorithm::new(signal, Decimal::ONE, Duration::minutes(5), 5).unwrap();
let state = twap.state();
let restored_twap = TwapAlgorithm::from_state(state).unwrap();
assert_eq!(restored_twap.status(), AlgoStatus::Working);
}
#[tokio::test]
async fn orchestrator_restores_from_sqlite() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.path().to_path_buf();
let repo = Arc::new(SqliteAlgoStateRepository::new(&temp_path).unwrap());
let client = Arc::new(PaperExecutionClient::default());
let sizer = Box::new(FixedOrderSizer {
quantity: Decimal::ONE,
});
let risk_checker = Arc::new(NoopRiskChecker);
let engine = Arc::new(ExecutionEngine::new(client, sizer, risk_checker));
let orchestrator = OrderOrchestrator::new(engine.clone(), repo.clone())
.await
.unwrap();
let signal =
Signal::new("BTCUSDT", SignalKind::EnterLong, 0.5).with_hint(ExecutionHint::Twap {
duration: Duration::minutes(1),
});
let ctx = RiskContext {
signed_position_qty: Decimal::ZERO,
portfolio_equity: Decimal::from(10_000),
last_price: Decimal::from(25_000),
liquidate_only: false,
};
orchestrator.on_signal(&signal, &ctx).await.unwrap();
assert_eq!(orchestrator.active_algorithms_count(), 1);
drop(orchestrator);
let restored = OrderOrchestrator::new(engine, repo).await.unwrap();
assert_eq!(restored.active_algorithms_count(), 1);
}