use polyfill2::{
book::{OrderBook, OrderBookManager},
errors::{PolyfillError, Result},
fill::{FillEngine, FillProcessor},
stream::{StreamManager, WebSocketStream},
types::*,
utils::{address, math, rate_limit, retry, time, url},
ClientConfig,
ClobClient,
OrderArgs,
OrderType,
PolyfillClient,
Side,
};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::time::Duration;
use tokio::time::sleep;
use tracing::{error, info};
#[allow(dead_code)]
pub struct PolyfillDemo {
client: ClobClient,
advanced_client: PolyfillClient,
book_manager: OrderBookManager,
fill_engine: FillEngine,
fill_processor: FillProcessor,
stream_manager: StreamManager,
rate_limiter: rate_limit::TokenBucket,
stats: DemoStats,
}
#[derive(Debug, Clone)]
pub struct DemoStats {
pub api_calls: u64,
pub orders_processed: u64,
pub fills_processed: u64,
pub stream_messages: u64,
pub errors: u64,
pub total_volume: Decimal,
}
impl Default for DemoStats {
fn default() -> Self {
Self {
api_calls: 0,
orders_processed: 0,
fills_processed: 0,
stream_messages: 0,
errors: 0,
total_volume: dec!(0),
}
}
}
impl PolyfillDemo {
pub fn new() -> Result<Self> {
let client = ClobClient::new("https://clob.polymarket.com");
let _config = ClientConfig {
base_url: "https://clob.polymarket.com".to_string(),
chain_id: 137, private_key: None, api_credentials: None, max_slippage: Some(dec!(0.01)), fee_rate: Some(dec!(0.02)), timeout: Some(Duration::from_secs(30)),
max_connections: Some(100),
};
let advanced_client = PolyfillClient::new("https://clob.polymarket.com");
let book_manager = OrderBookManager::new(100);
let fill_engine = FillEngine::new(
dec!(1.0), dec!(2.0), 5, );
let fill_processor = FillProcessor::new(1000);
let stream_manager = StreamManager::new();
let rate_limiter = rate_limit::TokenBucket::new(100, 100);
Ok(Self {
client,
advanced_client,
book_manager,
fill_engine,
fill_processor,
stream_manager,
rate_limiter,
stats: DemoStats::default(),
})
}
pub async fn demo_basic_api_operations(&mut self) -> Result<()> {
info!("=== Demo 1: Basic API Operations ===");
let is_ok = self.client.get_ok().await;
info!("API connectivity: {}", is_ok);
self.stats.api_calls += 1;
match self.client.get_server_time().await {
Ok(timestamp) => {
info!("Server time: {}", timestamp);
self.stats.api_calls += 1;
},
Err(e) => {
error!("Failed to get server time: {}", e);
self.stats.errors += 1;
},
}
match self.client.get_sampling_markets(None).await {
Ok(markets) => {
info!("Found {} markets", markets.data.len());
for market in &markets.data[..std::cmp::min(3, markets.data.len())] {
info!(" Market: {} - {}", market.question, market.market_slug);
}
self.stats.api_calls += 1;
},
Err(e) => {
error!("Failed to get markets: {}", e);
self.stats.errors += 1;
},
}
Ok(())
}
pub async fn demo_order_book_operations(&mut self) -> Result<()> {
info!("=== Demo 2: Order Book Operations ===");
let token_id = "12345";
match self.client.get_order_book(token_id).await {
Ok(order_book) => {
info!(
"Order book for token {}: {} bids, {} asks",
token_id,
order_book.bids.len(),
order_book.asks.len()
);
let mut local_book = OrderBook::new(token_id.to_string(), 50);
for (i, bid) in order_book.bids.iter().enumerate() {
local_book.apply_delta(OrderDelta {
token_id: token_id.to_string(),
timestamp: chrono::Utc::now(),
side: Side::BUY,
price: bid.price,
size: bid.size,
sequence: i as u64,
})?;
}
for (i, ask) in order_book.asks.iter().enumerate() {
local_book.apply_delta(OrderDelta {
token_id: token_id.to_string(),
timestamp: chrono::Utc::now(),
side: Side::SELL,
price: ask.price,
size: ask.size,
sequence: (order_book.bids.len() + i) as u64,
})?;
}
let analytics = local_book.analytics();
info!("Book analytics:");
info!(
" Bid levels: {}, Ask levels: {}",
analytics.bid_count, analytics.ask_count
);
info!(
" Total bid size: {}, Total ask size: {}",
analytics.total_bid_size, analytics.total_ask_size
);
if let Some(spread) = analytics.spread {
info!(
" Spread: {} ({:.2}%)",
spread,
analytics.spread_pct.unwrap_or(dec!(0))
);
}
if let Some(mid) = analytics.mid_price {
info!(" Mid price: {}", mid);
}
if let Some(impact) = local_book.calculate_market_impact(Side::BUY, dec!(100.0)) {
info!("Market impact for 100 size buy:");
info!(" Average price: {}", impact.average_price);
info!(" Impact: {:.2}%", impact.impact_pct);
info!(" Total cost: {}", impact.total_cost);
}
self.stats.api_calls += 1;
},
Err(e) => {
error!("Failed to get order book: {}", e);
self.stats.errors += 1;
},
}
Ok(())
}
pub async fn demo_market_data_operations(&mut self) -> Result<()> {
info!("=== Demo 3: Market Data Operations ===");
let token_id = "12345";
match self.client.get_midpoint(token_id).await {
Ok(midpoint) => {
info!("Midpoint for {}: {}", token_id, midpoint.mid);
self.stats.api_calls += 1;
},
Err(e) => {
error!("Failed to get midpoint: {}", e);
self.stats.errors += 1;
},
}
match self.client.get_spread(token_id).await {
Ok(spread) => {
info!("Spread for {}: {}", token_id, spread.spread);
self.stats.api_calls += 1;
},
Err(e) => {
error!("Failed to get spread: {}", e);
self.stats.errors += 1;
},
}
for side in [Side::BUY, Side::SELL] {
match self.client.get_price(token_id, side).await {
Ok(price) => {
info!("{} price for {}: {}", side.as_str(), token_id, price.price);
self.stats.api_calls += 1;
},
Err(e) => {
error!("Failed to get {} price: {}", side.as_str(), e);
self.stats.errors += 1;
},
}
}
match self.client.get_tick_size(token_id).await {
Ok(tick_size) => {
info!("Tick size for {}: {}", token_id, tick_size);
self.stats.api_calls += 1;
},
Err(e) => {
error!("Failed to get tick size: {}", e);
self.stats.errors += 1;
},
}
match self.client.get_neg_risk(token_id).await {
Ok(neg_risk) => {
info!("Neg risk for {}: {}", token_id, neg_risk);
self.stats.api_calls += 1;
},
Err(e) => {
error!("Failed to get neg risk: {}", e);
self.stats.errors += 1;
},
}
Ok(())
}
pub async fn demo_order_operations(&mut self) -> Result<()> {
info!("=== Demo 4: Order Creation and Management ===");
let order_args = OrderArgs::new("12345", dec!(0.75), dec!(100.0), Side::BUY);
info!("Created order args: {:?}", order_args);
let market_order = MarketOrderRequest {
token_id: "12345".to_string(),
side: Side::BUY,
amount: dec!(100.0),
slippage_tolerance: Some(dec!(1.0)), client_id: Some("demo_market_order".to_string()),
};
info!("Created market order request: {:?}", market_order);
let limit_order = OrderRequest {
token_id: "12345".to_string(),
side: Side::BUY,
price: dec!(0.75),
size: dec!(100.0),
order_type: OrderType::GTC,
expiration: None,
client_id: Some("demo_limit_order".to_string()),
};
info!("Created limit order request: {:?}", limit_order);
self.stats.orders_processed += 2;
Ok(())
}
pub async fn demo_fill_execution(&mut self) -> Result<()> {
info!("=== Demo 5: Fill Execution ===");
let mut book = OrderBook::new("12345".to_string(), 50);
for i in 1..=5 {
book.apply_delta(OrderDelta {
token_id: "12345".to_string(),
timestamp: chrono::Utc::now(),
side: Side::BUY,
price: dec!(0.70) + Decimal::from(i) * dec!(0.01),
size: dec!(100.0),
sequence: i,
})?;
}
for i in 1..=5 {
book.apply_delta(OrderDelta {
token_id: "12345".to_string(),
timestamp: chrono::Utc::now(),
side: Side::SELL,
price: dec!(0.80) + Decimal::from(i) * dec!(0.01),
size: dec!(100.0),
sequence: i + 10,
})?;
}
info!("Created order book with liquidity");
let market_order = MarketOrderRequest {
token_id: "12345".to_string(),
side: Side::BUY,
amount: dec!(50.0),
slippage_tolerance: Some(dec!(2.0)),
client_id: Some("demo_market_buy".to_string()),
};
let fill_result = self
.fill_engine
.execute_market_order(&market_order, &book)?;
info!("Market order execution result:");
info!(" Status: {:?}", fill_result.status);
info!(" Total size: {}", fill_result.total_size);
info!(" Average price: {}", fill_result.average_price);
info!(" Total cost: {}", fill_result.total_cost);
info!(" Fees: {}", fill_result.fees);
info!(" Number of fills: {}", fill_result.fills.len());
for fill in &fill_result.fills {
self.fill_processor.process_fill(fill.clone())?;
self.stats.fills_processed += 1;
self.stats.total_volume += fill.size;
}
let limit_order = OrderRequest {
token_id: "12345".to_string(),
side: Side::SELL,
price: dec!(0.85),
size: dec!(25.0),
order_type: OrderType::GTC,
expiration: None,
client_id: Some("demo_limit_sell".to_string()),
};
let limit_result = self.fill_engine.execute_limit_order(&limit_order, &book)?;
info!("Limit order execution result:");
info!(" Status: {:?}", limit_result.status);
info!(" Total size: {}", limit_result.total_size);
info!(" Average price: {}", limit_result.average_price);
self.stats.orders_processed += 2;
Ok(())
}
pub async fn demo_utility_functions(&mut self) -> Result<()> {
info!("=== Demo 6: Utility Functions ===");
info!("Time utilities:");
info!(" Current timestamp (secs): {}", time::now_secs());
info!(" Current timestamp (millis): {}", time::now_millis());
info!(" Current timestamp (micros): {}", time::now_micros());
info!("Math utilities:");
let price = dec!(0.7534);
let tick_size = dec!(0.01);
let rounded_price = math::round_to_tick(price, tick_size);
info!(
" Price: {}, Tick size: {}, Rounded: {}",
price, tick_size, rounded_price
);
let notional = math::notional(price, dec!(100.0));
info!(" Notional value: {}", notional);
let spread_pct = math::spread_pct(dec!(0.75), dec!(0.76));
info!(" Spread percentage: {:?}", spread_pct);
let mid_price = math::mid_price(dec!(0.75), dec!(0.76));
info!(" Mid price: {:?}", mid_price);
info!("Address utilities:");
let address = "0x1234567890123456789012345678901234567890";
match address::parse_address(address) {
Ok(addr) => info!(" Parsed address: {:?}", addr),
Err(e) => error!(" Failed to parse address: {}", e),
}
let token_id = "12345";
match address::validate_token_id(token_id) {
Ok(_) => info!(" Valid token ID: {}", token_id),
Err(e) => error!(" Invalid token ID: {}", e),
}
info!("URL utilities:");
let endpoint = url::build_endpoint("https://api.example.com", "/v1/orders")?;
info!(" Built endpoint: {}", endpoint);
info!("Rate limiting:");
for i in 0..5 {
let allowed = self.rate_limiter.try_consume();
info!(
" Request {}: {}",
i + 1,
if allowed { "ALLOWED" } else { "RATE LIMITED" }
);
}
Ok(())
}
pub async fn demo_error_handling(&mut self) -> Result<()> {
info!("=== Demo 7: Error Handling and Retry Logic ===");
let retry_config = retry::RetryConfig {
max_attempts: 3,
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_secs(1),
backoff_factor: 2.0,
jitter: true,
};
let operation = || async {
if rand::random::<bool>() {
Ok("Success!")
} else {
Err(PolyfillError::network(
"Simulated network error",
std::io::Error::other("Simulated error"),
))
}
};
match retry::with_retry(&retry_config, operation).await {
Ok(result) => {
info!("Retry operation succeeded: {}", result);
},
Err(e) => {
error!("Retry operation failed after all attempts: {}", e);
self.stats.errors += 1;
},
}
info!("Error types demonstration:");
let api_error = PolyfillError::api(400, "Bad Request");
info!(" API Error: {:?}", api_error);
let network_error = PolyfillError::network(
"Connection timeout",
std::io::Error::new(std::io::ErrorKind::TimedOut, "Connection timeout"),
);
info!(" Network Error: {:?}", network_error);
let parse_error = PolyfillError::parse("Invalid JSON", None);
info!(" Parse Error: {:?}", parse_error);
let config_error = PolyfillError::config("Invalid configuration");
info!(" Config Error: {:?}", config_error);
Ok(())
}
pub async fn demo_streaming_capabilities(&mut self) -> Result<()> {
info!("=== Demo 8: Streaming Capabilities ===");
let _stream = WebSocketStream::new("wss://ws-subscriptions-clob.polymarket.com/ws/market");
info!("Created WebSocket stream");
let subscription = WssSubscription {
channel_type: "user".to_string(),
operation: Some("subscribe".to_string()),
markets: vec!["market1".to_string(), "market2".to_string()],
asset_ids: vec!["12345".to_string(), "67890".to_string()],
initial_dump: Some(true),
custom_feature_enabled: None,
auth: Some(WssAuth {
api_key: "test-api-key".to_string(),
secret: "test-secret".to_string(),
passphrase: "test-passphrase".to_string(),
}),
};
info!("Created subscription: {:?}", subscription);
let messages = vec![
StreamMessage::Book(BookUpdate {
asset_id: "12345".to_string(),
market: "market1".to_string(),
timestamp: time::now_millis(),
bids: vec![OrderSummary {
price: dec!(0.75),
size: dec!(100.0),
}],
asks: vec![OrderSummary {
price: dec!(0.76),
size: dec!(50.0),
}],
hash: None,
}),
StreamMessage::Trade(TradeMessage {
id: "fill1".to_string(),
market: "market1".to_string(),
asset_id: "12345".to_string(),
side: Side::BUY,
size: dec!(50.0),
price: dec!(0.75),
status: TradeMessageStatus::Matched,
msg_type: None,
last_update: None,
matchtime: None,
timestamp: None,
outcome: None,
owner: None,
trade_owner: None,
taker_order_id: None,
maker_orders: vec![],
fee_rate_bps: None,
transaction_hash: None,
trader_side: None,
bucket_index: None,
}),
];
for message in messages {
info!("Received stream message: {:?}", message);
self.stats.stream_messages += 1;
match &message {
StreamMessage::Book(book) => {
info!(" Processing book update for asset: {}", book.asset_id);
for level in &book.bids {
let _ = self.book_manager.apply_delta(OrderDelta {
token_id: book.asset_id.clone(),
timestamp: chrono::Utc::now(),
side: Side::BUY,
price: level.price,
size: level.size,
sequence: book.timestamp,
});
}
for level in &book.asks {
let _ = self.book_manager.apply_delta(OrderDelta {
token_id: book.asset_id.clone(),
timestamp: chrono::Utc::now(),
side: Side::SELL,
price: level.price,
size: level.size,
sequence: book.timestamp,
});
}
},
StreamMessage::Trade(trade) => {
info!(
" Processing trade: {} {} @ {}",
trade.side.as_str(),
trade.size,
trade.price
);
},
_ => {
info!(" Unhandled message type");
},
}
}
Ok(())
}
pub async fn demo_performance_analytics(&mut self) -> Result<()> {
info!("=== Demo 9: Performance and Analytics ===");
let fill_stats = self.fill_engine.get_stats();
info!("Fill engine statistics:");
info!(" Total orders: {}", fill_stats.total_orders);
info!(" Total fills: {}", fill_stats.total_fills);
info!(" Total volume: {}", fill_stats.total_volume);
info!(" Total fees: {}", fill_stats.total_fees);
let processor_stats = self.fill_processor.get_stats();
info!("Fill processor statistics:");
info!(" Pending orders: {}", processor_stats.pending_orders);
info!(" Pending fills: {}", processor_stats.pending_fills);
info!(" Pending volume: {}", processor_stats.pending_volume);
info!(" Processed fills: {}", processor_stats.processed_fills);
info!(" Processed volume: {}", processor_stats.processed_volume);
info!("Demo statistics:");
info!(" API calls: {}", self.stats.api_calls);
info!(" Orders processed: {}", self.stats.orders_processed);
info!(" Fills processed: {}", self.stats.fills_processed);
info!(" Stream messages: {}", self.stats.stream_messages);
info!(" Errors: {}", self.stats.errors);
info!(" Total volume: {}", self.stats.total_volume);
let total_operations =
self.stats.api_calls + self.stats.orders_processed + self.stats.stream_messages;
let error_rate = if total_operations > 0 {
(self.stats.errors as f64 / total_operations as f64) * 100.0
} else {
0.0
};
info!(" Error rate: {:.2}%", error_rate);
Ok(())
}
pub async fn run_all_demos(&mut self) -> Result<()> {
info!("Starting polyfill-rs demo...");
self.demo_basic_api_operations().await?;
sleep(Duration::from_millis(500)).await;
self.demo_order_book_operations().await?;
sleep(Duration::from_millis(500)).await;
self.demo_market_data_operations().await?;
sleep(Duration::from_millis(500)).await;
self.demo_order_operations().await?;
sleep(Duration::from_millis(500)).await;
self.demo_fill_execution().await?;
sleep(Duration::from_millis(500)).await;
self.demo_utility_functions().await?;
sleep(Duration::from_millis(500)).await;
self.demo_error_handling().await?;
sleep(Duration::from_millis(500)).await;
self.demo_streaming_capabilities().await?;
sleep(Duration::from_millis(500)).await;
self.demo_performance_analytics().await?;
info!("Demo completed successfully!");
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
info!("Polyfill-rs Demo");
info!("==============================");
let mut demo = PolyfillDemo::new()?;
if let Err(e) = demo.run_all_demos().await {
error!("Demo failed: {}", e);
std::process::exit(1);
}
info!("Demo completed successfully!");
Ok(())
}