Back to Documentation

API Reference

Complete documentation for the PaperBrokerClient Python API - a high-level wrapper for FIX protocol trading.

Version 0.2.2

Configuration

Configure the client using constructor parameters. QuickFIX config files are auto-generated.

FIX Connection

Parameter Type Description Default
socket_connect_host str FIX server hostname or IP "localhost"
socket_connect_port int FIX server port 5001
sender_comp_id str Your FIX client identifier "CLIENT"
target_comp_id str Server FIX identifier "SERVER"

Authentication

Parameter Type Description Required
username str Trading account username Required
password str Trading account password Required
default_sub_account str Default sub-account ID for orders Required

REST API & Logging

Parameter Type Description Default
rest_base_url str Base URL for REST API endpoints "http://localhost:9090"
log_dir str Directory for log files "logs"
console bool Output logs to console False

Security: Always use environment variables for credentials. Never hardcode passwords. Add .env to .gitignore.

Environment Variables

# .env file
SOCKET_HOST=your-fix-server
SOCKET_PORT=5001
SENDER_COMP_ID=your-sender-id
TARGET_COMP_ID=SERVER

# Credentials (keep secure)
FIX_USERNAME=your-username
FIX_PASSWORD=your-password

# REST & Account
PAPER_REST_BASE_URL=http://localhost:9090
PAPER_ACCOUNT_ID=D1

Connection

Lifecycle methods for managing FIX session connection.

connect() → None

Start FIX session and REST authentication. Non-blocking.

disconnect() → None

Stop FIX session and close all connections gracefully.

wait_until_logged_on(timeout=10) → bool

Block until FIX session is established or timeout expires.

Returns True if logged on, False if timeout
is_logged_on() → bool

Check if FIX session is currently active.

last_logon_error() → str | None

Get the last authentication error message if login failed.

Usage

client.connect()

if client.wait_until_logged_on(timeout=10):
    print("✅ Connected!")
    # ... trading logic ...
    client.disconnect()
else:
    print(f"❌ Failed: {client.last_logon_error()}")

Order Management

Methods for placing, canceling, and tracking orders via FIX protocol.

place_order(full_symbol, side, qty, price, ord_type="LIMIT", tif="GTC") → str

Submit a new order to the exchange.

Returns cl_ord_id - Client order ID for tracking
Parameter Type Description
full_symbol str Instrument symbol (e.g., "HNXDS:VN30F2511")
side str "BUY" or "SELL"
qty int Order quantity
price float Limit price
ord_type str "LIMIT", "MARKET", etc.
tif str Time in force: "GTC" (default), "DAY", "IOC", "FOK"
cancel_order(cl_ord_id, timeout=2.0) → None

Cancel an existing order by client order ID.

Listen to fix:order:canceled event for confirmation
get_order_status(cl_ord_id) → str

Get current status string of an order (e.g., "NEW", "FILLED").

Usage

# Place order
cl_ord_id = client.place_order(
    full_symbol="HNXDS:VN30F2511",
    side="BUY",
    qty=1,
    price=1950.0,
    ord_type="LIMIT",  # default
    tif="GTC"          # default: Good Till Cancel
)
print(f"Order placed: {cl_ord_id}")

# Check status
status = client.get_order_status(cl_ord_id)
print(f"Status: {status}")  # NEW, PARTIALLY_FILLED, FILLED, etc.

# Cancel order (async - listen to event for confirmation)
def on_canceled(orig_cl_ord_id, **kw):
    print(f"Canceled: {orig_cl_ord_id}")

client.on("fix:order:canceled", on_canceled)
client.cancel_order(cl_ord_id, timeout=2.0)

Order Status Values

Status Description
NEW Order accepted by exchange
PARTIALLY_FILLED Order partially executed
FILLED Order fully executed
CANCELED Order canceled
REJECTED Order rejected by exchange
PENDING_NEW Order pending acceptance
PENDING_CANCEL Cancel request pending

Account Queries

REST API methods for querying account data, portfolio, and transactions.

get_cash_balance() → dict

Query available cash balance.

Returns {"remainCash": float, "totalCash": float, ...}
get_portfolio_by_sub(sub_account_id=None) → dict

Get portfolio positions with P&L for a sub-account.

Expected Output:

# Returns dict with success flag and items list
{
    "success": True,
    "items": [
        {
            "instrument": "HNXDS:VN30F2511",  # Symbol
            "quantity": 2,                    # Position size
            "totalCost": 3900.0,              # Total cost basis
            "currentPrice": 1960.0,           # Current market price
            "marketValue": 3920.0,            # quantity * currentPrice
            "pnl": 20.0                       # Unrealized P&L
        }
    ]
}
get_orders(start_date, end_date, sub_account_id=None) → dict

Get orders within a date range.

Expected Output:

# Returns dict with success flag and items list
{
    "success": True,
    "items": [
        {
            "orderId": "2503150001",          # Exchange order ID
            "clOrdId": "abc123...",           # Client order ID
            "symbol": "HNXDS:VN30F2511",      # Instrument
            "side": "1",                      # "1"=BUY, "2"=SELL
            "orderQty": 5,                    # Requested quantity
            "cumQty": 3,                      # Filled quantity
            "leavesQty": 2,                   # Remaining quantity
            "price": 1950.0,                  # Limit price
            "avgPx": 1948.5,                  # Average fill price
            "ordStatus": "PARTIALLY_FILLED"   # Order status
        }
    ]
}
get_transactions_by_date(start_date, end_date, sub_account_id=None) → list

Get transaction history within a date range.

get_max_placeable(symbol, price, side, sub_account_id=None) → dict

Calculate maximum order quantity based on buying power.

Expected Output:

{
    "maxQty": 25,               # Maximum placeable quantity
    "perUnitCost": 1960.0,      # Cost per unit (margin/price)
    "remainCash": 50000.0,      # Available cash/margin
    "unlimited": False          # True if no quantity limit
}

Sub-Account Switching

use_sub_account(sub_id) → ContextManager

Context manager for temporary sub-account switching. Thread-safe.

# Query account data
portfolio = client.get_portfolio_by_sub("D1")
print(f"Positions: {len(portfolio)}")
for pos in portfolio:
    print(f"  {pos['instrument']}: {pos['quantity']} @ {pos['currentPrice']}")
    print(f"    P&L: {pos['pnl']}")

# Get max order size
max_info = client.get_max_placeable("HNXDS:VN30F2511", 1950.0, "BUY")
print(f"Max BUY: {max_info['maxQty']} contracts")

# Switch accounts temporarily
with client.use_sub_account("D1"):
    client.place_order("HNXDS:VN30F2511", "SELL", 1, 1950.0)

with client.use_sub_account("D2"):
    client.place_order("HNXDS:VN30F2511", "BUY", 1, 1950.0)
# Restores to default after context exits

Events

Subscribe to real-time notifications without polling. Event-driven architecture for instant updates.

on(event, handler) → None

Subscribe to an event. Handler called synchronously from emitter thread.

off(event, handler) → None

Unsubscribe from an event.

Session Events

fix:logon fix:logout fix:logon_error fix:reject
Event Payload Description
fix:logon session_id FIX session established
fix:logout session_id, reason FIX session disconnected
fix:logon_error error, session_id FIX authentication failed
fix:reject reason, msg_type FIX message rejected by server

Order Events

fix:order:accepted fix:order:filled fix:order:canceled fix:order:rejected
Event Payload Description
fix:order:accepted cl_ord_id, status, exec_type, order_id Order accepted by exchange
fix:order:filled cl_ord_id, status, last_px, last_qty, cum_qty, avg_px Order fully or partially filled
fix:order:canceled orig_cl_ord_id, status, cum_qty Order cancellation confirmed
fix:order:rejected cl_ord_id, reason, status Order rejected by exchange

Usage

# Session events
def on_logon(session_id, **kw):
    print(f"✅ Connected: {session_id}")

def on_logout(session_id, reason=None, **kw):
    print(f"Disconnected: {reason or 'Normal logout'}")

def on_error(error, **kw):
    print(f"❌ Auth failed: {error}")

client.on("fix:logon", on_logon)
client.on("fix:logout", on_logout)
client.on("fix:logon_error", on_error)

# Order events
def on_accepted(cl_ord_id, status, **kw):
    print(f"Order {cl_ord_id[:8]}... accepted: {status}")

def on_filled(cl_ord_id, last_px, last_qty, cum_qty=None, **kw):
    print(f"Filled: {last_qty} @ {last_px}")
    if cum_qty:
        print(f"  Total: {cum_qty}")

def on_rejected(cl_ord_id, reason, **kw):
    print(f"❌ Rejected: {reason}")

client.on("fix:order:accepted", on_accepted)
client.on("fix:order:filled", on_filled)
client.on("fix:order:rejected", on_rejected)

Best Practice: Always use **kw in handlers to accept future payload additions. Handlers are called synchronously - keep them fast!

Market Data

Real-time market data via Redis (dev/testing) or Kafka (production). Both support query and subscribe modes.

RedisMarketDataClient

For development and testing. Lower latency, simpler setup.

Parameter Type Description
host str Redis server hostname/IP
port int Redis server port (default: 6379)
password str Redis password (optional)
merge_updates bool True for full snapshots, False for deltas only
await query(instrument) → QuoteSnapshot

Direct GET for current quote snapshot. Use for one-time price lookups.

await subscribe(instrument, callback) → None

Subscribe to real-time updates via Redis pub/sub.

await close() → None

Close Redis connections and cleanup resources.

from paperbroker.market_data import RedisMarketDataClient

client = RedisMarketDataClient(
    host=os.getenv("MARKET_REDIS_HOST"),
    port=int(os.getenv("MARKET_REDIS_PORT", 6379)),
    password=os.getenv("MARKET_REDIS_PASSWORD"),
    merge_updates=True  # Full snapshots
)

# Query mode - one-time lookup
quote = await client.query("HNXDS:VN30F2511")
if quote:
    print(f"Price: {quote.latest_matched_price}")

# Subscribe mode - real-time updates
def on_quote(instrument, quote):
    print(f"{instrument}: {quote.latest_matched_price}")

await client.subscribe("HNXDS:VN30F2511", on_quote)

KafkaMarketDataClient

For production. Higher throughput, message durability, replay capability.

Parameter Type Description
bootstrap_servers str Kafka bootstrap servers (e.g., "kafka-host:9092")
username str SASL username for Kafka authentication
password str SASL password for Kafka authentication
env_id str Environment ID for topic prefix (e.g., "real", "test")
merge_updates bool True for full snapshots, False for deltas

Topic Format: {env_id}.{exchange}.{symbol}
Example: real.HNXDS.VN30F2602 for instrument HNXDS:VN30F2602

await subscribe(instrument, callback) → None

Subscribe to real-time quote updates for an instrument.

await start() → None

Start the Kafka consumer. Must be called after subscriptions.

await stop() → None

Stop the Kafka consumer and close connections.

await query(instrument) → QuoteSnapshot

Get cached quote snapshot for a subscribed instrument.

Environment Variables

# .env file - Kafka connection
PAPERBROKER_KAFKA_BOOTSTRAP_SERVERS=your-kafka-host:9092
PAPERBROKER_KAFKA_USERNAME=your-username
PAPERBROKER_KAFKA_PASSWORD=your-password
PAPERBROKER_ENV_ID=real

Usage

import asyncio
import os
from dotenv import load_dotenv
from paperbroker.market_data import KafkaMarketDataClient

load_dotenv()

async def main():
    # Create Kafka client
    client = KafkaMarketDataClient(
        bootstrap_servers=os.getenv("PAPERBROKER_KAFKA_BOOTSTRAP_SERVERS"),
        username=os.getenv("PAPERBROKER_KAFKA_USERNAME"),
        password=os.getenv("PAPERBROKER_KAFKA_PASSWORD"),
        env_id=os.getenv("PAPERBROKER_ENV_ID"),
        merge_updates=True  # Full snapshots
    )
    
    # Quote callback
    def on_quote(instrument, quote):
        print(f"{instrument}: {quote.latest_matched_price}")
        print(f"  Bid: {quote.bid_price_1} x {quote.bid_quantity_1}")
        print(f"  Ask: {quote.ask_price_1} x {quote.ask_quantity_1}")
    
    # Subscribe and start
    await client.subscribe("HNXDS:VN30F2602", on_quote)
    await client.start()
    
    try:
        while True:
            await asyncio.sleep(1)
    finally:
        await client.stop()

asyncio.run(main())

QuoteSnapshot Fields

Field Type Description
latest_matched_price float Last traded price
latest_matched_quantity float Last traded quantity
bid_price_1/2/3 float Best bid prices (top 3 levels)
bid_quantity_1/2/3 float Bid quantities (top 3 levels)
ask_price_1/2/3 float Best ask prices (top 3 levels)
ask_quantity_1/2/3 float Ask quantities (top 3 levels)
ref_price float Reference price
spread float Bid-ask spread
total_matched_quantity float Total volume traded