Metadata-Version: 2.5
Name: profitdll-wrapper
Version: 0.4.0
Summary: Idiomatic, typed Python wrapper for ProfitDLL (Nelogica native API). Independent community project — not affiliated with Nelogica; no financial liability.
Project-URL: Homepage, https://github.com/diogojrdev/profitdll-wrapper
Project-URL: Repository, https://github.com/diogojrdev/profitdll-wrapper
Project-URL: Documentation, https://diogojrdev.github.io/profitdll-wrapper/
Project-URL: Bug Tracker, https://github.com/diogojrdev/profitdll-wrapper/issues
Project-URL: Changelog, https://github.com/diogojrdev/profitdll-wrapper/blob/main/CHANGELOG.md
Author-email: Diogo Ribeiro <diogojr.dev@gmail.com>
Maintainer-email: Diogo Ribeiro <diogojr.dev@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: b3,dll,market-data,nelogica,profit,profitdll,profitdll-wrapper,trading
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Natural Language :: English
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Typing :: Typed
Requires-Python: <4.0,>=3.10
Provides-Extra: all
Requires-Dist: duckdb>=1.1; extra == 'all'
Requires-Dist: psycopg[binary]<4,>=3.2; extra == 'all'
Requires-Dist: rich>=13.7; extra == 'all'
Provides-Extra: parquet
Requires-Dist: duckdb>=1.1; extra == 'parquet'
Provides-Extra: postgres
Requires-Dist: psycopg[binary]<4,>=3.2; extra == 'postgres'
Provides-Extra: tui
Requires-Dist: rich>=13.7; extra == 'tui'
Description-Content-Type: text/markdown

<div align="center">

# profitdll-wrapper

High-performance, idiomatic, typed, and memory-safe Python wrapper for **ProfitDLL** (Nelogica's native API).

[![PyPI](https://img.shields.io/pypi/v/profitdll-wrapper.svg?cacheSeconds=3600)](https://pypi.org/project/profitdll-wrapper)
[![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)](https://www.python.org)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-261230.svg)](https://docs.astral.sh/ruff)
[![Type Checking: mypy](https://img.shields.io/badge/mypy-strict-blue.svg)](https://mypy.readthedocs.io)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![Status](https://img.shields.io/badge/status-v0.4.0-blue.svg)](#status)

**English** | [Português (BR)](README.pt-BR.md)

</div>

---

> [!NOTE]
> **Status: v0.4.0 — P0 (Trades), P1 (Price Depth), P2 (Order Routing & Custody) and the ingestion stack validated against the vendor simulator / real DLL.**
> Full test suite with 287 unit and ABI contract tests (80%+ code coverage), running under `mypy --strict`, `ruff`, and `pytest`. *Pure Enqueue* architecture immune to C ↔ GIL reentrancy crashes.

---

> [!WARNING]
> **Independent, community-driven project — not affiliated with Nelogica.**
> `profitdll-wrapper` is developed and maintained by the community. Profit,
> ProfitDLL, and related names are products and trademarks of Nelogica, which
> does not endorse, sponsor, or support this project. The proprietary DLL is
> not distributed here.
>
> **No financial responsibility.** This software can place real orders with real
> money when connected to a real brokerage account. It is provided "as is", without
> warranty of any kind, for research and educational purposes. The authors accept
> **no liability for financial losses, missed or duplicated orders, incorrect or
> delayed data, or any trading outcome**. Validate everything on a simulator/demo
> account first — you are solely responsible for the orders your code sends.

---

## What is `profitdll-wrapper`

`profitdll-wrapper` is a modern Python wrapper for Nelogica's ProfitDLL — a native C/Pascal API (`stdcall` calling convention, featuring raw memory pointers and callback threads on a dedicated C `ConnectorThread`).

It abstracts away low-level ctypes complexity and provides:
- **Idiomatic API**: Context managers (`with`), immutable dataclasses (`Trade`, `PriceLevel`, `PriceBookSnapshot`, `DailyCandle`, `Order`, `Position`, `Account`), strict `enum` types, and comprehensive type hints;
- **Order Routing & Custody**: Limit orders (`send_buy_order`, `send_sell_order`), market orders (`send_market_buy`, `send_market_sell`), order cancellations (`cancel_order`, `cancel_all_orders`), and real-time custody position tracking (`get_position`, `Event.ORDER`, `Event.POSITION`);
- **Pure Enqueue Architecture**: C callbacks only enqueue lightweight positional payloads in microseconds without reentrant ctypes calls, preventing deadlocks and segfaults under high market volume;
- **Fault Tolerance & Safety**: User exception isolation in event handlers ensures callback failures never crash the native DLL process or interrupt data streams;
- **Zero Runtime Dependencies**: Built strictly using the Python standard library (`dependencies = []`).

Detailed architectural and API documentation is published at <https://diogojrdev.github.io/profitdll-wrapper/>:

| Document | Content |
|---|---|
| [Architecture](https://diogojrdev.github.io/profitdll-wrapper/ARCHITECTURE/) | Layer design, abstraction patterns, and thread-safety invariants |
| [API Surface](https://diogojrdev.github.io/profitdll-wrapper/API_SURFACE/) | Native ProfitDLL function mapping and ABI audit |
| [Ingest](https://diogojrdev.github.io/profitdll-wrapper/INGEST/) | Historical data ingestion: sinks, schema, and the `profitdll-ingest` CLI |

---

## Installation

Install from [PyPI](https://pypi.org/project/profitdll-wrapper) with pip:

```bash
pip install profitdll-wrapper
```

Or, in a project managed with [uv](https://docs.astral.sh/uv):

```bash
uv add profitdll-wrapper
```

> [!TIP]
> The distribution name is `profitdll-wrapper` (hyphen), but the import name is
> `profitdll_wrapper` (underscore):
> ```python
> from profitdll_wrapper import Event, ProfitClient
> ```

**Requirements:** Python 3.10+ on **Windows** (the native ProfitDLL is a Windows `stdcall` library).

### Optional extras

The core package has zero runtime dependencies. Ingest backends are opt-in:

```bash
pip install "profitdll-wrapper[postgres]"   # PostgreSQL / TimescaleDB sink (psycopg)
pip install "profitdll-wrapper[parquet]"    # Parquet sink (duckdb)
pip install "profitdll-wrapper[all]"        # everything
```

### The Native (Proprietary) DLL

Nelogica's ProfitDLL is proprietary and **is not** bundled with this package.
To connect to Nelogica servers or simulator:
1. Set the environment variable `PROFITDLL_PATH=/path/to/ProfitDLL.dll` (or `ProfitDLL64.dll`), or;
2. Place the DLL inside a `dll/` directory in your working directory.
3. Create a `.env` file in your working directory with your simulator credentials:
   ```env
   ACTIVATION_KEY=your_key
   USER=your_username
   PASSWORD=your_password
   ```

The DLL directory must also contain the vendor runtime data (broker routing files); keep it out of version control.

---

## Quickstart

### 1. Real-Time Trade Ticks (P0)

```python
from profitdll_wrapper import Event, ProfitClient, Trade

with ProfitClient(
    activation_key="KEY...",
    user="USER...",
    password="PASSWORD...",
    mode="market_data",  # "market_data" or "routing"
    # broker_id=15003,   # optional; defaults to BROKER in the .env file
) as client:
    client.subscribe("WDOFUT", exchange="F")

    @client.on(Event.TRADE)
    def on_trade(trade: Trade) -> None:
        print(
            f"{trade.asset.ticker} | Price: {trade.price:.2f} x{trade.quantity} | Aggressor: {trade.trade_type}"
        )

    client.run()  # blocks keeping event loop active (Ctrl+C to exit)
```

### 2. Price Book / Price Depth & Thread-Safe Queries (P1)

```python
from profitdll_wrapper import Event, PriceLevel, ProfitClient

with ProfitClient(
    activation_key="KEY...",
    user="USER...",
    password="PASSWORD...",
    mode="market_data",
) as client:
    client.subscribe_price_depth("PETR4", exchange="B")

    @client.on(Event.PRICE_LEVEL)
    def on_level(level: PriceLevel) -> None:
        print(
            f"[{level.update_type.name}] {level.side.name} pos={level.position} qty={level.quantity}"
        )

    # Thread-safe level query outside of callback
    # top_buy = client.get_price_group("PETR4", side=0, position=0, exchange="B")

    client.run()
```

---

## Practical Examples

Explore the [`examples/`](examples/) directory — eleven ready-to-run scripts, from market data streaming to trading bots:

| Script | Category | Description | Mode |
|---|---|---|---|
| [`01_subscribe_ticker.py`](examples/01_subscribe_ticker.py) | MVP / Quotes | Minimal real-time trade tick streaming | `market_data` |
| [`02_price_depth.py`](examples/02_price_depth.py) | Price Book | Order book depth updates and snapshots | `market_data` |
| [`03_live_smoke.py`](examples/03_live_smoke.py) | Smoke Test | Self-contained live validation with report | `market_data` / `routing` |
| [`04_send_order.py`](examples/04_send_order.py) | Routing | Limit buy/sell orders and execution tracking | `routing` |
| [`05_market_data_streamer.py`](examples/05_market_data_streamer.py) | Data Streamer | Trades, V2 book, close prices → CSV / pandas DataFrame | `market_data` |
| [`06_trading_bot_sample.py`](examples/06_trading_bot_sample.py) | Trading Bot | Full bot blueprint: state machine, order manager, Stop Loss & Take Profit | `routing` |
| [`07_watchdog_and_reconciliation.py`](examples/07_watchdog_and_reconciliation.py) | Infra / Reconciliation | DLL health watchdog, auto-reconnect, daily position reconciliation | `routing` |
| [`08_corporate_actions_and_history.py`](examples/08_corporate_actions_and_history.py) | History & Corporate Actions | Tick-by-tick history download and corporate actions | `market_data` |
| [`09_historical_to_database.py`](examples/09_historical_to_database.py) | History → Database | Historical trades to SQLite via the `ingest` subpackage | `market_data` |
| [`10_times_and_trades_tui.py`](examples/10_times_and_trades_tui.py) | TUI / Market Data | Native-style Times & Trades: summary bar, mirrored buyer/seller tape, quantity bars and pressure gauge (`rich`, `--demo` anywhere) | `market_data` |
| [`11_order_book_tui.py`](examples/11_order_book_tui.py) | TUI / Market Data | Full Level-2 DOM with native summary bar, mirrored bid/ask sides and proportional quantity bars (`rich`, `--demo` anywhere) | `market_data` |
| [`12_list_accounts.py`](examples/12_list_accounts.py) | Custody | Enumerates every trading account (and sub-account) for the login, validating the `.env` account | `routing` |

---

## Historical Data → Database

The `profitdll-ingest` command downloads tick-by-tick historical trades (and optional daily candles) via ProfitDLL and persists them to a configurable backend. SQLite and CSV are built in (zero extra dependencies); Parquet and PostgreSQL/TimescaleDB ship as optional extras.

### Quickstart (SQLite, zero deps)

```bash
pip install profitdll-wrapper
profitdll-ingest --ticker VALE3 --start 01/01/2026 --end 31/01/2026
# -> writes to ./profit_data.db
```

### PostgreSQL / TimescaleDB via Docker

The database runs in Docker; the ingestion script runs on the Windows host (where the native DLL lives). Grab `docker-compose.yml` and `.env.example` from the repository.

```bash
cp .env.example .env             # set TIMESCALE_PASSWORD
docker compose up -d timescaledb
pip install "profitdll-wrapper[postgres]"
profitdll-ingest --ticker VALE3,PETR4 --exchange B,B \
    --start 01/01/2026 --end 31/01/2026 \
    --to postgres \
    --db-url postgresql://profit:secret@localhost:5432/profit
```

### Programmatic API

```python
from profitdll_wrapper import ProfitClient
from profitdll_wrapper.ingest import create_sink, ingest_history

sink = create_sink("sqlite", db_url="profit.db")
with ProfitClient(activation_key="...", user="...", password="...", mode="market_data") as client:
    stats = ingest_history(
        client=client,
        sink=sink,
        tickers=[("VALE3", "B")],
        start_date="01/01/2026 09:00:00",
        end_date="31/01/2026 18:00:00",
    )
print(f"{stats.trades_written} trades persisted in {stats.elapsed_seconds:.1f}s")
sink.close()
```

**Multiple windows in one session** (e.g. each trading day with its own session
hours): use `ingest_windows` — it keeps a single request in flight, completes
each one via the DLL's progress callback (`Event.HISTORY_PROGRESS`, progress
reaching 100), and discards trades outside the current request's window:

```python
from profitdll_wrapper.ingest import ingest_windows

with ProfitClient(activation_key="...", user="...", password="...") as client:
    stats = ingest_windows(
        client=client,
        sink=sink,
        tickers=[
            ("PETR4", "B", "27/08/2026 10:00:00", "27/08/2026 16:55:00"),
            ("PETR4", "B", "02/09/2026 10:00:00", "02/09/2026 16:55:00"),
        ],
    )
    for req in stats.tickers:
        print(req.ticker, req.trades_written, "completed_by_progress =", req.completed_by_progress)
```

See the [ingestion guide](https://diogojrdev.github.io/profitdll-wrapper/INGEST/) for schema details, hypertables, idempotency, tuning, and the multi-window contract, and [`examples/09_historical_to_database.py`](examples/09_historical_to_database.py) for a runnable end-to-end example.

---

## Limitations

- **The native DLL supports a single lifecycle per process.** After
  `disconnect()` (which calls `DLLFinalize`), constructing a new `ProfitClient`
  in the same process raises
  `RuntimeError("ProfitDLL já foi finalizada neste processo; ...")`
  immediately — the DLL's global state survives `DLLFinalize` (the Windows
  loader ref-counts the module) and a re-initialization never completes its
  market-data connection. The vendor manual documents no re-initialization
  support. **Run one session per subprocess** when you need multiple
  sequential sessions (the same pattern the integration tests use).
- **`ingest_history` is one-window-per-run by contract**: every ticker shares
  `start_date`/`end_date` and all requests are fired up front. The historical
  trade event carries no window attribution, so stacking runs with different
  windows on one session contaminates tapes with late responses (a real
  production incident). Use [`ingest_windows`](#historical-data--database) for
  per-ticker windows.
- **History is capped at 30 days by the server**: requests whose start date is
  older than 30 days (server date) are rejected with
  `HistoryPeriodLimitError`. Split longer backfills into ≤30-day windows.

---

## Development & Testing

This project uses [uv](https://docs.astral.sh/uv) for dependency management and tooling.

```bash
git clone https://github.com/diogojrdev/profitdll-wrapper.git
cd profitdll-wrapper
uv sync                                  # creates virtualenv and installs dev dependencies
uv run pytest                            # runs full test suite (225 unit & ABI tests)
uv run ruff check .                      # runs linter
uv run ruff format --check .               # checks code formatting
uv run mypy --strict src                 # checks strict type annotations
```

### Integration Testing with Real Native DLL

Integration tests running against Nelogica's real DLL and simulator use the `@pytest.mark.integration` marker:

```bash
uv run pytest -m integration
```

---

## Feedback

If `profitdll-wrapper` helps you trade on B3, consider giving it a ⭐ and [opening an issue](https://github.com/diogojrdev/profitdll-wrapper/issues/new/choose) with feedback — early-stage issues are gold for prioritizing the roadmap.

---

## License

[MIT](LICENSE). Nelogica's native ProfitDLL is proprietary software and is not included in this repository.

This is an independent, community-maintained project with no affiliation to Nelogica, and it is provided with **no financial liability** for trading losses — see the disclaimer at the top.

## Contributing

Contributions are welcome! See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development guidelines.

