Metadata-Version: 2.1
Name: primary-api
Version: 0.1.1
Summary: Primary API Library
Author-email: Chi Tudi <chitudi54@gmail.com>
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic==2.13.4
Requires-Dist: requests==2.34.2
Requires-Dist: simplejson==4.1.1
Requires-Dist: websocket-client==1.9.0

# primary-api

Python library for connecting to the **Primary API** (Matba Rofex exchange). Fork/refactor of `pyRofex`. Provides REST and WebSocket clients for market data, order management, and account reports.

**Stack:** Python 3.8+, `requests`, `simplejson`, `websocket-client`, `pydantic`

---

## Installation

```bash
pip install primary-api
```

---

## Quick Start

```python
from primary_api import PrimaryApiClient, Environment, MarketDataEntry

client = PrimaryApiClient(
    user="username",
    password="password",
    account="account123",
    environment=Environment.REMARKET
)

# REST: get market data
md = client.get_market_data("DLR/MAY26", [MarketDataEntry.LAST])
print(md)

# WebSocket: subscribe to market data
def on_md(msg):
    print(msg)

sub = client.market_data_subscription(
    tickers=["DLR/MAY26"],
    entries=[MarketDataEntry.BIDS, MarketDataEntry.OFFERS],
    handler=on_md
)

client.wait_for_interrupt()
```

---

## Configuration

### Environment

Two environments are available via the `Environment` enum:

| Environment | Description |
|---|---|
| `Environment.REMARKET` | Remarkets (testing/simulation) — proprietary = `"PBCP"` |
| `Environment.LIVE` | Live production — proprietary = `"api"` |

### Custom URLs

Override the default API endpoints (useful for connecting to custom gateways or different data center endpoints):

```python
client.set_urls(
    rest_url="https://api.custom.com.ar",
    ws_url="wss://api.custom.com.ar",
    ssl=True
)
```

The library automatically normalizes URLs to always end with `/`.

### Environment variables (for testing)

Create a `.env` file:

```
CL_USERNAME=your_user
CL_PASSWORD=your_pass
CL_ACCOUNT=your_account
CL_ENVIRONMENT=REMARKET
CL_API_HTTPS=https://api.remarkets.primary.com.ar/
CL_API_WSS=wss://api.remarkets.primary.com.ar/
```

---

## REST API

### Market Data

| Method | Description |
|---|---|
| `get_segments()` | Get all available market segments |
| `get_all_instruments()` | Get all instruments |
| `get_detailed_instruments()` | Get detailed instrument list |
| `get_instrument_details(ticker, market)` | Get details for a specific instrument |
| `get_market_data(ticker, entries, depth, market)` | Get market data (bids, offers, last price, etc.) |

### Orders

| Method | Description |
|---|---|
| `send_order(ticker, size, order_type, side, ...)` | Send a new order |
| `cancel_order(client_order_id, proprietary)` | Cancel an existing order |
| `get_order_status(client_order_id, proprietary)` | Check order status |
| `get_all_orders_status(account)` | Get all orders for an account |

### Account

| Method | Description |
|---|---|
| `get_account_position(account)` | Get account position |
| `get_detailed_position(account)` | Get detailed position |
| `get_account_report(account)` | Get account report |
| `get_trade_history(ticker, start_date, end_date, market)` | Get trade history |

### Example: Sending an Order

```python
from primary_api import Side, OrderType, TimeInForce, Market

order = client.send_order(
    ticker="DLR/MAY26",
    size=10,
    order_type=OrderType.LIMIT,
    side=Side.BUY,
    market=Market.ROFEX,
    time_in_force=TimeInForce.DAY,
    price=850.50,
)
```

---

## WebSocket Subscriptions

### init_websocket_connection

Set up all handlers and establish the WebSocket connection in one call (pyRofex compatible):

```python
def on_market_data(msg):
    print(msg)

def on_order_report(msg):
    print(msg)

def on_error(msg):
    print(f"Error: {msg}")

def on_exception(e):
    print(f"Exception: {e}")

client.init_websocket_connection(
    market_data_handler=on_market_data,
    order_report_handler=on_order_report,
    error_handler=on_error,
    exception_handler=on_exception,
)

# Handlers are already set — subscribe without passing handler
sub = client.market_data_subscription(tickers=["DLR/MAY26"], entries=[...])
```

### Market Data Subscription

```python
def handle_market_data(message):
    print(message)

sub = client.market_data_subscription(
    tickers=["DLR/MAY26", "DLR/JUN26"],
    entries=[
        MarketDataEntry.BIDS,
        MarketDataEntry.OFFERS,
        MarketDataEntry.LAST,
    ],
    depth=5,
    handler=handle_market_data
)

print(sub.count())       # 2 tickers
print(sub.active)        # True
print(sub.to_dict())     # full details (handler is no longer None)
print(sub.to_dict()["handler"])  # the handler function

sub.unsubscribe()        # stop receiving updates (also removes the handler)
```

### Order Report Subscription

```python
def handle_order_report(message):
    print(message)

sub = client.order_report_subscription(
    account="account123",
    snapshot=True,
    handler=handle_order_report
)

sub.unsubscribe()        # also removes the handler
```

### WebSocket Lifecycle

- **First `connect()`:** raises `ApiException` if the endpoint is unreachable.
- **Auto-reconnect:** after a successful connection, if the WebSocket drops, it reconnects automatically in a background thread. All active subscriptions are re-sent on reconnect.
- **Cleanup:** call `client.close_websocket()` to stop the reconnection loop.
- **Handler cleanup:** `sub.unsubscribe()` removes both the subscription and its associated handler from the active list.

---

## Enums Reference

| Enum | Values |
|---|---|
| `Environment` | `REMARKET`, `LIVE` |
| `Market` | `ROFEX` |
| `MarketSegment` | `DDF`, `DDA`, `DUAL`, `U_DDF`, `U_DDA`, `U_DUAL`, `MERV` |
| `Side` | `BUY`, `SELL` |
| `OrderType` | `LIMIT`, `MARKET`, `MARKET_TO_LIMIT` |
| `TimeInForce` | `DAY`, `ImmediateOrCancel`, `FillOrKill`, `GoodTillDate` |
| `MarketDataEntry` | `BIDS`, `OFFERS`, `LAST`, `OPENING_PRICE`, `CLOSING_PRICE`, `SETTLEMENT_PRICE`, `HIGH_PRICE`, `LOW_PRICE`, `TRADE_VOLUME`, `OPEN_INTEREST`, `INDEX_VALUE`, `TRADE_EFFECTIVE_VOLUME`, `NOMINAL_VOLUME`, `ACP`, `TRADE_COUNT` |
| `CFICode` | `STOCK`, `BOND`, `CALL_STOCK`, `PUT_STOCK`, `FUTURE`, `PUT_FUTURE`, `CALL_FUTURE`, `CEDEAR`, `ON` |

---
