nautechsystems/nautilus_trader · error · anyhow::Error

Invalid config type for AxDataClientFactory. Expected AxData

Error message

Invalid config type for AxDataClientFactory. Expected AxDataClientConfig, was {config:?}

What it means

AxDataClientFactory::create downcasts the passed &dyn ClientConfig to AxDataClientConfig; failure means the caller handed the wrong config object type. The message prints what was actually received. This only happens when wiring factories manually or through a custom FactoryRouter - the standard Nautilus TraderNode path always pairs the right config with the right factory.

Source

Thrown at crates/adapters/architect_ax/src/factories.rs:94

impl Default for AxDataClientFactory {
    fn default() -> Self {
        Self::new()
    }
}

impl DataClientFactory for AxDataClientFactory {
    fn create(
        &self,
        name: &str,
        config: &dyn ClientConfig,
        _cache: CacheView,
        _clock: Rc<RefCell<dyn Clock>>,
    ) -> anyhow::Result<Box<dyn DataClient>> {
        let ax_config = config
            .as_any()
            .downcast_ref::<AxDataClientConfig>()
            .ok_or_else(|| {
                anyhow::anyhow!(
                    "Invalid config type for AxDataClientFactory. Expected AxDataClientConfig, was {config:?}",
                )
            })?
            .clone();

        let client_id = ClientId::from(name);

        let http_client = if ax_config.has_api_credentials() {
            let credential =
                Credential::resolve(ax_config.api_key.clone(), ax_config.api_secret.clone())
                    .ok_or_else(|| anyhow::anyhow!("API credentials not configured"))?;

            AxHttpClient::with_credentials(
                credential.api_key().to_string(),
                credential.api_secret().to_string(),
                Some(ax_config.http_base_url()),
                None, // orders_base_url
                ax_config.http_timeout_secs,

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Pass an AxDataClientConfig (or its Python twin) to AxDataClientFactory::create
  2. Use TradingNode with the AX routing config instead of manual factory calls, which enforces the pairing
  3. Check the {config:?} payload in the message to see which config actually arrived

Example fix

// before
let data_client = AxDataClientFactory.create(
    "AX",
    &exec_config, // AxExecClientConfig -> downcast fails
    cache_view,
    clock,
)?;
// after
let data_client = AxDataClientFactory.create(
    "AX",
    &AxDataClientConfig::default(),
    cache_view,
    clock,
)?;
Defensive patterns

Strategy: type-guard

Type guard

// Rust
fn is_ax_data_config(cfg: &dyn ClientConfig) -> bool {
    cfg.as_any().downcast_ref::<AxDataClientConfig>().is_some()
}
# Python
isinstance(config, AxDataClientConfig)

Try / catch

match AxDataClientFactory.create(name, config, view, clock) {
    Err(e) if e.to_string().contains("Invalid config type") => {
        panic_with_expected_type(e, "AxDataClientConfig");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling AxDataClientFactory.create() with an AxExecClientConfig, a config from another adapter, or a custom ClientConfig implementation; reusing one config object for both the data and execution factories in hand-rolled Rust wiring.

Common situations: Custom tooling that registers factories generically over Vec<Box<dyn ClientConfig>>; copy-paste wiring code where the data and exec configs were swapped.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/0dcb33f1ec6505d2. Report an issue: GitHub.