An open API service indexing awesome lists of open source software.

https://github.com/AlexanderMerkel/binance-fix-connector-python

High-performance async Python library for Binance FIX API trading with full async/await support
https://github.com/AlexanderMerkel/binance-fix-connector-python

Last synced: 29 days ago
JSON representation

High-performance async Python library for Binance FIX API trading with full async/await support

Awesome Lists containing this project

README

          

# Async Binance SPOT FIX Connector for Python

This is an unofficial `asyncio`-native Python connector for Binance Financial Information eXchange (FIX) [SPOT messages](https://github.com/binance/binance-spot-api-docs/blob/master/fix-api.md#message-components).
It is aimed at testnet workflows, session experiments, and local latency research across Order Entry, Market Data, and Drop Copy.

## Key Features

- **Async-First API**: `asyncio` session management for running multiple FIX workflows in one event loop
- **Quick Testing Workflow**: Examples for order entry, market data, drop copy, instrument queries, and limit checks
- **Feed/Session Comparison**: Shared API surface for comparing Order Entry, Market Data, and Drop Copy behavior
- **Latency Research Support**: Local sync-vs-async benchmark checks for research and regression testing
- **Typed Core**: Type-annotated core library with `basedpyright` checks for `src/`
- **SPOT FIX Workflows**: Helpers for Binance public SPOT FIX workflow areas: Order Entry, Market Data, and Drop Copy

## Prerequisites

Before using or testing the library, ensure that the necessary dependencies are installed. You can do this by running the following command:

```
pip install binance-fix-connector-async
```

**Notes:**

- Python 3.13 or newer is required.
- Binance FIX API supports Ed25519 keys. Please refer to this [tutorial](https://www.binance.com/en/support/faq/how-to-generate-an-ed25519-key-pair-to-send-api-requests-on-binance-6b9a63f1e3384cf48a2eedb82767a69a) for setting up an Ed25519 key pair on the mainnet, and this one for the [testnet](https://testnet.binance.vision/).
- Ensure that your API key has the appropriate Fix API permissions for the Testnet environment before you begin testing.
- Real testnet scenarios are marker-driven with `requires_testnet` and are not part of default CI runs.

## Example

All the FIX messages can be created with the `BinanceFixConnector` class. The following example demonstrates how to create a simple order using the async FIX API:

```python
import asyncio
import time
from binance_fix_connector_async.fix_connector import create_order_entry_session
from binance_fix_connector_async.utils import get_private_key

# Credentials
API_KEY = "..."
PATH_TO_PRIVATE_KEY_PEM_FILE = "/path/to/ed25519_private_key.pem"

# FIX URL
FIX_OE_URL = "tcp+tls://fix-oe.testnet.binance.vision:9000"

# Response types
ORD_STATUS = {
"0": "NEW",
"1": "PARTIALLY_FILLED",
"2": "FILLED",
"4": "CANCELED",
"6": "PENDING_CANCEL",
"8": "REJECTED",
"A": "PENDING_NEW",
"C": "EXPIRED",
}
ORD_TYPES = {"1": "MARKET", "2": "LIMIT", "3": "STOP", "4": "STOP_LIMIT"}
SIDES = {"1": "BUY", "2": "SELL"}
TIME_IN_FORCE = {
"1": "GOOD_TILL_CANCEL",
"3": "IMMEDIATE_OR_CANCEL",
"4": "FILL_OR_KILL",
}
ORD_REJECT_REASON = {"99": "OTHER"}

# Parameter
INSTRUMENT = "BNBUSDT"

async def main():
client_oe = await create_order_entry_session(
api_key=API_KEY,
private_key=get_private_key(PATH_TO_PRIVATE_KEY_PEM_FILE),
endpoint=FIX_OE_URL,
)
await client_oe.retrieve_messages_until(message_type="A")

example = "This example shows how to place a single order. Order type LIMIT.\nCheck https://github.com/binance/binance-spot-api-docs/blob/master/fix-api.md#newordersingled for additional types."
client_oe.logger.info(example)

# PLACING SIMPLE ORDER
msg = await client_oe.create_fix_message_with_basic_header("D")
msg.append_pair(38, 1) # ORD QTY
msg.append_pair(40, 2) # ORD TYPE
msg.append_pair(11, str(time.time_ns())) # CL ORD ID
msg.append_pair(44, "CURRENT_FILTER_VALID_PRICE") # PRICE; see examples/trade/new_order.py
msg.append_pair(54, 2) # SIDE
msg.append_pair(55, INSTRUMENT) # SYMBOL
msg.append_pair(59, 1) # TIME IN FORCE
await client_oe.send_message(msg)

responses = await client_oe.retrieve_messages_until(message_type="8")
resp = next(
(x for x in responses if x.message_type.decode("utf-8") == "8"),
None,
)
client_oe.logger.info("Parsing response Execution Report (8) for an order LIMIT type.")

cl_ord_id = None if not resp.get(11) else resp.get(11).decode("utf-8")
order_qty = None if not resp.get(38) else resp.get(38).decode("utf-8")
ord_type = None if not resp.get(40) else resp.get(40).decode("utf-8")
side = None if not resp.get(54) else resp.get(54).decode("utf-8")
symbol = None if not resp.get(55) else resp.get(55).decode("utf-8")
price = None if not resp.get(44) else resp.get(44).decode("utf-8")
time_in_force = None if not resp.get(59) else resp.get(59).decode("utf-8")
cum_qty = None if not resp.get(14) else resp.get(14).decode("utf-8")
last_qty = None if not resp.get(32) else resp.get(32).decode("utf-8")
ord_status = None if not resp.get(39) else resp.get(39).decode("utf-8")
ord_rej_reason = None if not resp.get(103) else resp.get(103).decode("utf-8")
error_code = None if not resp.get(25016) else resp.get(25016).decode("utf-8")
text = None if not resp.get(58) else resp.get(58).decode("utf-8")

client_oe.logger.info(f"Client order ID: {cl_ord_id}")
client_oe.logger.info(f"Symbol: {symbol}")
client_oe.logger.info(
f"Order -> Type: {ORD_TYPES.get(ord_type, ord_type)} | Side: {SIDES.get(side, side)} | TimeInForce: {TIME_IN_FORCE.get(time_in_force,time_in_force)}",
)
client_oe.logger.info(
f"Price: {price} | Quantity: {order_qty} | cum qty: {cum_qty} | last qty: {last_qty}"
)
client_oe.logger.info(
f"Status: {ORD_STATUS.get(ord_status,ord_status)} | Msg: {ORD_REJECT_REASON.get(ord_rej_reason,ord_rej_reason)}",
)
client_oe.logger.info(f"Error code: {error_code} | Reason: {text}")

# LOGOUT
client_oe.logger.info("LOGOUT (5)")
await client_oe.logout()
await client_oe.retrieve_messages_until(message_type="5")
client_oe.logger.info(
"Closing the connection with server as we already sent the logout message"
)
await client_oe.disconnect()

# Run the async main function
if __name__ == "__main__":
asyncio.run(main())
```

Please look at [`examples`](./examples) folder to test the examples.
To try the examples, use [`config.json.example`](./config.json.example) in the repository root as a template, or provide credentials via environment variables.
Examples use testnet credentials by default:

```bash
export BINANCE_TESTNET_FIX_KEY="..."
export BINANCE_TESTNET_FIX_PRIVATE_KEY_PATH="/path/to/ed25519_private_key.pem"
```

Set `BINANCE_FIX_ENV=mainnet` or `USE_TESTNET=false` only when you explicitly want mainnet credentials.

## API Reference

### Factory Functions

The library provides three factory functions for different types of FIX sessions:

#### `create_order_entry_session()`

```python
create_order_entry_session(
api_key: str,
private_key: ed25519.Ed25519PrivateKey,
endpoint: str = "tcp+tls://fix-oe.binance.com:9000",
sender_comp_id: str = "TRADE",
target_comp_id: str = "SPOT",
fix_version: str = "FIX.4.4",
heart_bt_int: int = 30,
message_handling: int = 2,
response_mode: int = 1,
recv_window: int | None = None,
) -> BinanceFixConnector
```

Creates a session for order placement and management.

#### `create_market_data_session()`

```python
create_market_data_session(
api_key: str,
private_key: ed25519.Ed25519PrivateKey,
endpoint: str = "tcp+tls://fix-md.binance.com:9000",
sender_comp_id: str = "WATCH",
target_comp_id: str = "SPOT",
fix_version: str = "FIX.4.4",
heart_bt_int: int = 30,
message_handling: int = 2,
recv_window: int | None = None,
) -> BinanceFixConnector
```

Creates a session for market data streaming.

#### `create_drop_copy_session()`

```python
create_drop_copy_session(
api_key: str,
private_key: ed25519.Ed25519PrivateKey,
endpoint: str = "tcp+tls://fix-dc.binance.com:9000",
sender_comp_id: str = "TECH",
target_comp_id: str = "SPOT",
fix_version: str = "FIX.4.4",
heart_bt_int: int = 30,
message_handling: int = 2,
response_mode: int = 1,
recv_window: int | None = None,
check_permissions: bool = False,
hmac_secret: str | None = None,
permission_base_url: str = "https://api.binance.com",
) -> BinanceFixConnector
```

Creates a session for trade reporting and compliance.
The optional permission check uses Binance's mainnet HMAC Wallet API restriction endpoint. Spot Testnet does not support `/sapi`; testnet Ed25519 FIX-key compatibility is validated by the FIX logon itself.

### Core Methods

#### Connection Management

- `connect()` - Establish connection to Binance FIX server
- `disconnect()` - Close connection and cleanup resources
- `logon(recv_window=None)` - Perform FIX logon with authentication
- `logout(text=None, recv_window=None)` - Send logout message

#### Message Operations

- `create_fix_message_with_basic_header(msg_type, recv_window=None)` - Create properly formatted FIX message
- `send_message(message, raw=False)` - Send FIX message to server
- `retrieve_messages_until(message_type, message_cl_ord_id=None, timeout_seconds=3)` - Retrieve messages until a specific message type, and optionally `ClOrdID`, is received
- `get_all_new_messages_received()` - Get all received messages not returned by previous calls to this method

#### Utility Functions

- `get_api_key(config_path)` - Load API credentials from config file
- `get_private_key(key_path)` - Load Ed25519 private key from PEM file

## Async Testing and Feed Research

This project is an unofficial async research connector, not a replacement for Binance's [official sync library](https://github.com/binance/binance-fix-connector-python). It targets public Binance SPOT FIX workflow areas with an async execution model, making it easier to run multiple sessions, compare feed behavior, and prototype Python FIX workflows.

### Async Connector Advantages

| Feature | Async Implementation | Benefit |
|---------|---------------------|---------|
| **Concurrent Sessions** | Native `asyncio` support | Run market data, order entry, and drop copy experiments together |
| **Feed Research** | Shared async connector surface | Compare session behavior without changing execution model |
| **Python Prototyping** | Built for Python 3.13+ | Fits notebooks, scripts, FastAPI, and asyncio research tooling |
| **Latency Research** | Local operation-latency benchmark | Compare sync vs async overhead on the target host |
| **Regression Checks** | Repeated benchmark rows | Track performance drift while changing parser/session code |

### Original Library Advantages

| Feature | Sync Implementation | Benefit |
|---------|-------------------|---------|
| **Single Session Performance** | Thread-based execution | Can be slightly faster in single-session throughput; validate locally |
| **Simplicity** | Traditional blocking I/O | Easier to understand for sync-only developers |
| **Ecosystem Maturity** | Longer production usage | More battle-tested in diverse environments |
| **Thread Integration** | Native threading | Better integration with thread-based applications |

### Performance Comparison

The benchmark suite supports local research and regression testing. It is reproducible by command, but the exact microbenchmark values are host- and run-dependent. It uses one warmup run, seven measured repeats, and writes median/min-max rows to `benchmark/analysis_results.md`:

| Metric | Reproducible report row |
|--------|-------------------------|
| **Message Creation Speed** | Async vs sync message creation throughput |
| **Memory Usage** | Async vs sync peak memory usage |
| **Mean Operation Latency** | Async vs sync mean operation latency |

Run `benchmark/comprehensive_analysis.py` on the target host before using benchmark numbers for comparisons. Treat the generated values as local measurements, not universal latency claims.

### 🔄 Migration Path

**Migration Scope:**

- Async code must add `await` around session creation, send/receive, and cleanup calls.
- Message construction and supported FIX fields are intended to stay close to the original connector where the public APIs overlap.
- Both libraries can be installed side by side under different import names.

**Migration Steps:**

```python
# 1. Install async version
pip install binance-fix-connector-async

# 2. Update imports
from binance_fix_connector_async.fix_connector import create_order_entry_session

# 3. Add async/await keywords
async def main():
client = await create_order_entry_session(api_key, private_key)
await client.send_message(msg)
await client.logout()

# 4. Run with asyncio
asyncio.run(main())
```

### 📈 Validation Results

The benchmark suite supports:

- **Local performance research**: repeated median/min-max checks for message creation speed, peak memory usage, and mean operation latency
- **Consistency**: message construction and state-transition parity checks
- **Compatibility**: supported public factory and connector methods

**Run validation yourself:**

```bash
cd benchmark/
python comprehensive_analysis.py
```

## Documentation

### Benchmark and Research Notes

- **[Benchmark Results](benchmark/README.md)** - Reproducible local benchmark and compatibility analysis
- **Run Analysis**: `python benchmark/comprehensive_analysis.py`

### Official Binance Documentation

- **[Binance FIX API Documentation](https://developers.binance.com/docs/binance-spot-api-docs/fix-api)** - Official FIX API reference

## Summary

Both libraries provide Binance SPOT FIX access with different execution models. Choose based on your workflow:

- **Async**: Useful for concurrent testnet scripts, feed/session comparison, and async research workflows
- **Official sync connector**: Better default choice for simple, single-session, thread-based applications

Validate the real testnet examples and E2E runner with your own FIX key before using the async connector in production.

## License

MIT