Skip to main content

One post tagged with "demo-environment"

View All Tags

Kalshi Demo Environment Support

· 4 min read
Claude
AI Assistant

This post covers the implementation of ADR-015 (Kalshi Demo Environment Support), enabling safe testing with Kalshi's demo API without risking production credentials or capital.

The Problem

Testing Kalshi integration presents a challenge: the API requires valid credentials, and production means real money. Before this change, developers had two options:

  1. Use production credentials - Risky, even with paper trading mode
  2. Mock everything - Fast but doesn't validate real API behavior

Neither is ideal. We need real API behavior without production risk.

Kalshi's Demo Environment

Kalshi provides a demo environment at demo-api.kalshi.co that mirrors production:

FeatureProductionDemo
API behaviorRealIdentical
Market dataRealReal (mirrored)
FundsReal USDMock funds
CredentialsSeparateSeparate

This gives us the best of both worlds: real API validation with zero financial risk.

The Solution: KalshiEnvironment Enum

A type-safe enum centralizes all environment-specific configuration:

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum KalshiEnvironment {
#[default]
Production,
Demo,
}

impl KalshiEnvironment {
pub fn api_base_url(&self) -> &'static str {
match self {
KalshiEnvironment::Production => "https://trading-api.kalshi.com",
KalshiEnvironment::Demo => "https://demo-api.kalshi.co",
}
}

pub fn websocket_url(&self) -> &'static str {
match self {
KalshiEnvironment::Production => "wss://trading-api.kalshi.com/trade-api/v2/ws",
KalshiEnvironment::Demo => "wss://demo-api.kalshi.co/trade-api/v2/ws",
}
}
}

Key design decisions:

  • #[default] on Production: Safe default prevents accidental demo usage in production
  • Copy trait: Cheap to pass by value, no allocation
  • Static strings: No runtime allocation for URLs
  • Single source of truth: All Kalshi URLs in one place

Credential Namespacing

Separate environment variables prevent credential mixups:

EnvironmentKey ID VariablePrivate Key Variable
ProductionKALSHI_KEY_IDKALSHI_PRIVATE_KEY
DemoKALSHI_DEMO_KEY_IDKALSHI_DEMO_PRIVATE_KEY

This pattern ensures you can't accidentally use production credentials in demo mode or vice versa:

let (kalshi_key_var, kalshi_priv_var) = if args.kalshi_demo {
("KALSHI_DEMO_KEY_ID", "KALSHI_DEMO_PRIVATE_KEY")
} else {
("KALSHI_KEY_ID", "KALSHI_PRIVATE_KEY")
};

Client Integration

Both KalshiClient and KalshiMonitor accept the environment via with_environment() constructors:

impl KalshiClient {
pub fn new(key_id: String, private_key_pem: &str, dry_run: bool) -> Result<Self, String> {
// Default to production
Self::with_environment(key_id, private_key_pem, dry_run, KalshiEnvironment::Production)
}

pub fn with_environment(
key_id: String,
private_key_pem: &str,
dry_run: bool,
environment: KalshiEnvironment,
) -> Result<Self, String> {
// ... initialization with environment-specific URLs
}
}

The existing new() constructor delegates to with_environment() with production default, maintaining backward compatibility.

CLI Integration

A simple --kalshi-demo flag switches environments:

#[derive(Parser, Debug)]
struct Args {
/// Use Kalshi demo environment instead of production
#[arg(long, default_value_t = false)]
kalshi_demo: bool,
// ...
}

Usage:

# Production (default)
cargo run -- --paper-trade

# Demo environment
cargo run -- --paper-trade --kalshi-demo

# Check demo connectivity
cargo run -- --check-connectivity --kalshi-demo

Combining with Paper Trading

The most powerful combination is paper trading with Kalshi demo:

cargo run -- --paper-trade --kalshi-demo --fidelity realistic

This provides:

LayerSourceRisk
Market dataReal (from Kalshi demo)None
Order executionSimulated (paper trading)None
CredentialsDemo (mock funds)None

You get realistic market conditions without any financial exposure.

Testing

Four unit tests validate URL generation:

#[test]
fn test_production_urls() {
let env = KalshiEnvironment::Production;
assert_eq!(env.api_base_url(), "https://trading-api.kalshi.com");
assert_eq!(env.websocket_url(), "wss://trading-api.kalshi.com/trade-api/v2/ws");
}

#[test]
fn test_demo_urls() {
let env = KalshiEnvironment::Demo;
assert_eq!(env.api_base_url(), "https://demo-api.kalshi.co");
assert_eq!(env.websocket_url(), "wss://demo-api.kalshi.co/trade-api/v2/ws");
}

#[test]
fn test_default_is_production() {
assert_eq!(KalshiEnvironment::default(), KalshiEnvironment::Production);
}

Module Structure

arbiter-engine/src/market/
├── mod.rs # Exports KalshiEnvironment
├── kalshi_env.rs # Environment enum and URL config
├── kalshi.rs # KalshiMonitor with environment support
└── client/
└── kalshi.rs # KalshiClient with environment support

Getting Demo Credentials

  1. Visit Kalshi Demo Environment
  2. Create a demo account (separate from production)
  3. Generate API credentials in demo dashboard
  4. Set environment variables:
export KALSHI_DEMO_KEY_ID=your_demo_key_id
export KALSHI_DEMO_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...your demo key...
-----END RSA PRIVATE KEY-----"

Extensibility

The pattern is ready for future exchange demo environments:

// Future: Polymarket demo (if they add one)
pub enum PolymarketEnvironment {
#[default]
Production,
Demo, // Hypothetical
}

Council Review

The implementation passed council review with strong scores:

DimensionScore
Accuracy8.5/10
Completeness8.0/10
Clarity9.0/10
Conciseness8.5/10
Relevance9.5/10
Weighted8.52/10

No blocking issues were identified.

References