use melin_app::app_factory::AppFactory;
use melin_trading::trading_event::TradingEvent;
use melin_types::types::{AccountId, CurrencyId, InstrumentSpec, Symbol};
use crate::exchange_app::ServerApp;
#[derive(Debug, Clone, Copy)]
pub struct FactoryConfig {
pub accounts: u32,
pub instruments: u32,
pub max_orders_per_account: u32,
pub max_orders_per_second: u32,
pub max_orders_burst: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct Factory {
config: FactoryConfig,
}
impl Factory {
pub fn new(config: FactoryConfig) -> Self {
Self { config }
}
}
impl AppFactory for Factory {
type App = ServerApp;
fn empty(&self) -> ServerApp {
ServerApp(melin_exchange_core::exchange::Exchange::with_capacity())
}
fn prefault(&self, app: &mut ServerApp) {
app.0.prefault_seed(
self.config.accounts as usize,
self.config.instruments as usize,
);
}
fn apply_operator_policy(&self, app: &mut ServerApp) {
let restored_buckets = app.order_bucket_count();
let limiter_disabled =
self.config.max_orders_per_second == 0 || self.config.max_orders_burst == 0;
if limiter_disabled && restored_buckets > 0 {
tracing::warn!(
restored_buckets,
max_orders_per_second = self.config.max_orders_per_second,
max_orders_burst = self.config.max_orders_burst,
"config mismatch: snapshot carries rate-limit buckets but local limiter \
is disabled — primary and replica must run with matching values"
);
}
app.set_max_open_orders_per_account(self.config.max_orders_per_account);
app.set_max_orders_per_second(
self.config.max_orders_per_second,
self.config.max_orders_burst,
);
tracing::info!(
max_orders_per_account = self.config.max_orders_per_account,
max_orders_per_second = self.config.max_orders_per_second,
max_orders_burst = self.config.max_orders_burst,
"applied per-account order limits (SEC-03 cap, SEC-04 rate)"
);
}
fn seed_events(&self) -> Vec<TradingEvent> {
let mut events =
Vec::with_capacity(self.config.instruments as usize + self.config.accounts as usize);
for i in 0..self.config.instruments {
events.push(TradingEvent::AddInstrument {
spec: InstrumentSpec {
symbol: Symbol(i),
base: CurrencyId(i * 2),
quote: CurrencyId(i * 2 + 1),
},
});
}
for acct in 1..=self.config.accounts {
events.push(TradingEvent::ProvisionAccount {
account: AccountId(acct),
amount: u64::MAX / 4,
});
}
events
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cfg(accounts: u32, instruments: u32) -> FactoryConfig {
FactoryConfig {
accounts,
instruments,
max_orders_per_account: 100,
max_orders_per_second: 1_000,
max_orders_burst: 100,
}
}
#[test]
fn seed_events_count_matches_config() {
let factory = Factory::new(cfg(5, 3));
let events = factory.seed_events();
assert_eq!(events.len(), 8);
}
#[test]
fn seed_events_order_is_instruments_then_accounts() {
let factory = Factory::new(cfg(2, 2));
let events = factory.seed_events();
assert!(matches!(events[0], TradingEvent::AddInstrument { .. }));
assert!(matches!(events[1], TradingEvent::AddInstrument { .. }));
assert!(matches!(events[2], TradingEvent::ProvisionAccount { .. }));
assert!(matches!(events[3], TradingEvent::ProvisionAccount { .. }));
}
#[test]
fn seed_events_empty_when_no_accounts_or_instruments() {
let factory = Factory::new(cfg(0, 0));
assert!(factory.seed_events().is_empty());
}
#[test]
fn empty_does_not_apply_policy() {
let factory = Factory::new(cfg(2, 2));
let app = factory.empty();
assert_ne!(app.max_open_orders_per_account(), 100);
}
#[test]
fn apply_operator_policy_overrides_default() {
let factory = Factory::new(cfg(2, 2));
let mut app = factory.empty();
factory.apply_operator_policy(&mut app);
assert_eq!(app.max_open_orders_per_account(), 100);
}
}