API Reference
Complete documentation for the PaperBrokerClient Python API - a high-level wrapper for FIX protocol trading.
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.
Start FIX session and REST authentication. Non-blocking.
Stop FIX session and close all connections gracefully.
Block until FIX session is established or timeout expires.
True if logged on, False if timeout
Check if FIX session is currently active.
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.
Submit a new order to the exchange.
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 an existing order by client order ID.
fix:order:canceled event for confirmation
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.
Query available cash balance.
{"remainCash": float, "totalCash": float, ...}
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 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 transaction history within a date range.
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
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.
Subscribe to an event. Handler called synchronously from emitter thread.
Unsubscribe from an event.
Session Events
| 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
| 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 |
Direct GET for current quote snapshot. Use for one-time price lookups.
Subscribe to real-time updates via Redis pub/sub.
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
Subscribe to real-time quote updates for an instrument.
Start the Kafka consumer. Must be called after subscriptions.
Stop the Kafka consumer and close connections.
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 |