Metadata-Version: 2.4
Name: renko_rs
Version: 0.3.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
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 :: 3.14
Classifier: Programming Language :: Rust
Classifier: Topic :: Office/Business :: Financial
Requires-Dist: polars>=1.0
Requires-Dist: plotly>=5.0 ; extra == 'plot'
Provides-Extra: plot
License-File: LICENSE
Summary: High-performance Renko chart generator written in Rust
Keywords: renko,finance,trading,charts,polars
Author-email: BARUN SINGH <barun@nglogix.com>
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/baruns/renko_rs
Project-URL: Issues, https://github.com/baruns/renko_rs/issues
Project-URL: Repository, https://github.com/baruns/renko_rs

# renko_rs

### High-Performance Rust Implementation of Renko Charts

Transform tick data into OHLCV Renko DataFrames at native speed!

[![PyPI version](https://img.shields.io/pypi/v/renko_rs.svg?v=0.3.0)](https://pypi.org/project/renko_rs/)
[![Python](https://img.shields.io/pypi/pyversions/renko_rs.svg?v=0.3.0)](https://pypi.org/project/renko_rs/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

This is a **Rust implementation** of the [renkodf](https://github.com/srlcarlg/renkodf) library, providing identical Renko chart calculations with **10-100x performance improvement** through native code execution.

---

## Installation

```bash
pip install renko_rs
```

For plotting support (optional):
```bash
pip install renko_rs[plot]
```

Pre-built wheels are available for:
- **Linux**: x86_64, ARM64
- **Windows**: x86_64
- **macOS**: Intel (x86_64), Apple Silicon (ARM64)

Supports Python 3.10, 3.11, 3.12, 3.13, and 3.14.

- **renko_rs** requires [polars](https://pypi.org/project/polars/)
- Plotting requires [plotly](https://pypi.org/project/plotly/) (optional, install with `renko_rs[plot]`)

---

## Contents

- [Why Rust?](#why-rust)
- [Quick Start](#quick-start)
- [Usage](#usage)
  - [Renko Class](#renko-class)
  - [RenkoWS Class (Real-time)](#renkows-class-real-time)
- [Renko Modes](#renko-modes)
- [Plotting](#plotting)
- [API Reference](#api-reference)
- [Performance](#performance)
- [Building from Source](#building-from-source)
- [Testing](#testing)
- [Publishing to PyPI](#publishing-to-pypi)
- [Algorithm Details](#algorithm-details)
- [Credits](#credits)

---

## Why Rust?

The original [renkodf](https://github.com/srlcarlg/renkodf) is an excellent Python implementation using NumPy for performance. However, when processing millions of ticks (common in high-frequency trading or large historical datasets), even optimized Python code has limitations.

**renko_rs** rewrites the core algorithm in Rust while maintaining the same Python API:

| Aspect | renkodf (Python) | renko_rs (Rust) |
|--------|------------------|-----------------|
| **Language** | Python + NumPy | Rust + Polars |
| **Speed** | Baseline | 10-100x faster |
| **Memory** | Higher (Python overhead) | Lower (zero-copy where possible) |
| **API** | Python | Python (via PyO3) |
| **Output** | pandas DataFrame | Polars DataFrame |
| **Plotting** | mplfinance | plotly (optional) |
| **Real-time** | `RenkoWS` class | `RenkoWS` class |

The algorithm is **identical** - same calculations, same edge cases, same output. Only the execution engine changes.

---

## Quick Start

```python
from renko_rs import Renko
import polars as pl

# Load your tick data (must have 'close' and 'datetime' columns)
df = pl.read_parquet("ticks.parquet")

# Create Renko instance
r = Renko(df, brick_size=0.0003)

# Generate Renko chart with desired mode
renko_df = r.renko_df("wicks")

print(renko_df)
```

---

## Usage

There are two classes available:

- **`Renko(df, brick_size)`** — To create Renko OHLCV dataframe with existing tick data.
- **`RenkoWS(ws_timestamp, ws_price, brick_size, external_df, ts_unit)`** — To create real-time Renko charts, usually over a **WebSocket** connection.

### Renko Class

#### Basic Example

```python
from renko_rs import Renko
import polars as pl

# Load tick data
df_ticks = pl.read_parquet("EURGBP_ticks.parquet")

# Create Renko chart
r = Renko(df_ticks, brick_size=0.0003)

# Get Renko OHLCV data (default mode is 'wicks')
df = r.renko_df("wicks", utils_columns=True)

print(df.head())
```

Output:
```
shape: (5, 10)
┌─────────────────────┬─────────┬─────────┬─────────┬─────────┬────────┬───────────┬─────────────┬───────────────────┬──────────────────┐
│ datetime            ┆ open    ┆ high    ┆ low     ┆ close   ┆ volume ┆ direction ┆ is_reversal ┆ tick_index_open   ┆ tick_index_close │
│ ---                 ┆ ---     ┆ ---     ┆ ---     ┆ ---     ┆ ---    ┆ ---       ┆ ---         ┆ ---               ┆ ---              │
│ datetime[ns]        ┆ f64     ┆ f64     ┆ f64     ┆ f64     ┆ i64    ┆ i64       ┆ i64         ┆ i64               ┆ i64              │
╞═════════════════════╪═════════╪═════════╪═════════╪═════════╪════════╪═══════════╪═════════════╪═══════════════════╪══════════════════╡
│ 2023-06-23 01:21:58 ┆ 0.8595  ┆ 0.8598  ┆ 0.8595  ┆ 0.8598  ┆ 3458   ┆ 1         ┆ 0           ┆ 1                 ┆ 3458             │
│ 2023-06-23 01:33:24 ┆ 0.8598  ┆ 0.8601  ┆ 0.8598  ┆ 0.8601  ┆ 571    ┆ 1         ┆ 0           ┆ 3458              ┆ 4029             │
│ 2023-06-23 03:18:30 ┆ 0.8601  ┆ 0.8604  ┆ 0.8601  ┆ 0.8604  ┆ 4993   ┆ 1         ┆ 0           ┆ 4029              ┆ 9022             │
│ 2023-06-23 04:40:26 ┆ 0.8604  ┆ 0.8607  ┆ 0.8604  ┆ 0.8607  ┆ 3358   ┆ 1         ┆ 0           ┆ 9022              ┆ 12380            │
│ 2023-06-23 05:15:54 ┆ 0.8604  ┆ 0.8604  ┆ 0.8601  ┆ 0.8601  ┆ 1669   ┆ -1        ┆ 1           ┆ 12380             ┆ 14049            │
└─────────────────────┴─────────┴─────────┴─────────┴─────────┴────────┴───────────┴─────────────┴───────────────────┴──────────────────┘
```

#### Datetime and Timestamp Columns

The output always includes a `datetime` column as `Datetime(ns)`. If your source DataFrame contains a `timestamp` column, it will be passed through to the output automatically.

```python
# Source with timestamp
df = pl.DataFrame({
    "datetime": [...],
    "close": [...],
    "timestamp": [1577836800000, ...],  # optional
})

r = Renko(df, brick_size=2.0)
result = r.renko_df("wicks")
# result columns: datetime, timestamp, open, high, low, close, volume, ...
```

#### Input Data Requirements

Your DataFrame must contain:
- **`close`** (required): Price values (float)
- **`datetime`** (required): Timestamps (datetime). If not present in a pandas DataFrame, the index will be used.
- **`timestamp`** (optional): Integer timestamps that will be passed through to the output.

The DataFrame can be either **Polars** or **pandas** format. If pandas, it will be automatically converted.

```python
# Polars DataFrame (recommended)
df = pl.DataFrame({
    "datetime": [...],
    "close": [...]
})

# pandas DataFrame (also supported)
import pandas as pd
df = pd.DataFrame({
    "close": [...]
}, index=dates)  # index used as datetime if column not present
```

#### Multiple Modes from Same Instance

You can generate different Renko representations from the same calculation:

```python
r = Renko(df_ticks, brick_size=0.0003)

# Standard Renko with wicks
df_wicks = r.renko_df("wicks")

# Renko without gaps
df_nongap = r.renko_df("nongap")

# Standard Renko (no wicks)
df_normal = r.renko_df("normal")
```

#### Prepare Data for Real-time (to_rws)

Use `to_rws()` to export the Renko data in a format suitable for initializing `RenkoWS`:

```python
r = Renko(df_ticks, brick_size=0.0003)
rws_df = r.to_rws()

# Optionally slice to keep only recent data
rws_df = r.to_rws(use_iloc=-100)  # last 100 bricks
rws_df = r.to_rws(use_iloc=50)    # first 50 bricks
```

---

### RenkoWS Class (Real-time)

Create real-time Renko charts, usually over a **WebSocket** connection.

Only the following combinations are possible:
- `RenkoWS(ws_timestamp, ws_price, brick_size, ts_unit)` — Start from scratch
- `RenkoWS(external_df, ts_unit)` — Continue from data produced by `Renko.to_rws()`

#### From Scratch

```python
from renko_rs import RenkoWS

brick_size = 0.0003
rws = RenkoWS(
    ws_timestamp=1577836800000000,  # initial timestamp (microseconds)
    ws_price=0.85945,               # initial price
    brick_size=brick_size,
    ts_unit="us"                    # timestamp unit: D, s, ms, us, ns
)

# At every price change
rws.add_prices(ws_timestamp=1577836801000000, ws_price=0.85975)
rws.add_prices(ws_timestamp=1577836802000000, ws_price=0.86010)
rws.add_prices(ws_timestamp=1577836803000000, ws_price=0.86040)

# Get completed bricks
df = rws.renko_df("wicks")

# Get bricks + forming (incomplete) brick
df_animated = rws.renko_animate("wicks")
```

#### From External DataFrame (Renko.to_rws)

```python
from renko_rs import Renko, RenkoWS

# First, create historical Renko data
r = Renko(df_ticks, brick_size=0.0003)
rws_df = r.to_rws(use_iloc=-250)  # keep last 250 bricks

# Initialize RenkoWS from historical data
rws = RenkoWS(external_df=rws_df, ts_unit="ns")

# Now feed real-time prices
rws.add_prices(ws_timestamp=new_ts, ws_price=new_price)

# Get updated chart
df = rws.renko_df("wicks")
```

#### renko_animate

`renko_animate()` returns completed bricks plus a **forming brick** that represents the current real-time price action:

```python
# Optionally manage memory by trimming old bricks
df = rws.renko_animate(
    mode="wicks",
    max_len=500,   # when df reaches this length, trim
    keep=250       # keep last N bricks after trim
)
```

---

## Renko Modes

Seven modes are available, each providing a different OHLC representation:

| Mode | Description | Use Case |
|------|-------------|----------|
| **`normal`** | Standard Renko (no wicks) | Clean brick visualization |
| **`wicks`** | Standard Renko with wicks (default) | Shows price extremes within brick period |
| **`nongap`** | Wicks mode but open = wick value | Continuous price representation |
| **`reverse-wicks`** | Wicks only on reversals | Emphasizes trend changes |
| **`reverse-nongap`** | Nongap only on reversals | Smooth reversals |
| **`fake-r-wicks`** | Fake reverse wicks (open = prev close) | Backtesting compatibility |
| **`fake-r-nongap`** | Fake reverse nongap (open = prev close) | Backtesting compatibility |

### Utility Columns

When `utils_columns=True` (default), additional columns are included:

- **`direction`**: 1 for up brick, -1 for down brick
- **`is_reversal`**: 1 if this brick reverses the previous trend, 0 otherwise
- **`tick_index_open`**: Index of first tick in this brick (Renko class only)
- **`tick_index_close`**: Index of last tick in this brick (Renko class only)

```python
# With utility columns (default)
df = r.renko_df("wicks", utils_columns=True)
# Columns: datetime, open, high, low, close, volume, direction, is_reversal, tick_index_open, tick_index_close

# Without utility columns
df = r.renko_df("wicks", utils_columns=False)
# Columns: datetime, open, high, low, close, volume
```

---

## Plotting

renko_rs uses [plotly](https://plotly.com/python/) for interactive charting. Install with:

```bash
pip install renko_rs[plot]
```

### Renko Plot

```python
r = Renko(df_ticks, brick_size=0.0003)

# Simple plot with default mode
r.plot("wicks")

# Plot without volume
r.plot("wicks", volume=False)

# Plot with external dataframe and additional traces
import plotly.graph_objects as go
sma_trace = go.Scatter(x=df["datetime"], y=sma_values, name="SMA(20)")
r.plot("wicks", df=df, add_traces=[sma_trace])
```

### RenkoWS Plot

```python
rws = RenkoWS(ws_timestamp=ts, ws_price=price, brick_size=0.0003, ts_unit="us")
rws.add_prices(new_ts, new_price)

# Plot with forming brick
rws.plot("wicks")
```

---

## API Reference

### `Renko(df, brick_size)`

Create a Renko chart from tick data.

**Parameters:**
- `df` (Polars or pandas DataFrame): Tick data with `close` and `datetime` columns
- `brick_size` (float): Size of each Renko brick (must be > 0)

**Returns:** Renko instance

### `Renko.renko_df(mode="wicks", utils_columns=True)`

Generate Renko OHLCV DataFrame.

**Parameters:**
- `mode` (str): One of `["normal", "wicks", "nongap", "reverse-wicks", "reverse-nongap", "fake-r-wicks", "fake-r-nongap"]`
- `utils_columns` (bool): Include direction, is_reversal, tick indices

**Returns:** Polars DataFrame with Renko OHLCV data (includes `datetime` column, and `timestamp` if available in source)

### `Renko.to_rws(use_iloc=None)`

Export Renko data for use with `RenkoWS`.

**Parameters:**
- `use_iloc` (int, optional): If positive, keep first N rows. If negative, keep last N rows.

**Returns:** Polars DataFrame with `timestamp`, `brick_size`, and all Renko mode columns.

### `Renko.plot(mode="wicks", volume=True, df=None, add_traces=None)`

Plot Renko chart using plotly.

**Parameters:**
- `mode` (str): Renko mode
- `volume` (bool): Show volume subplot
- `df` (Polars DataFrame, optional): External dataframe to plot
- `add_traces` (list, optional): Additional plotly traces

**Returns:** plotly Figure

### `RenkoWS(ws_timestamp, ws_price, brick_size, ts_unit)`

Create real-time Renko charts from scratch.

**Parameters:**
- `ws_timestamp` (int): Initial timestamp
- `ws_price` (float): Initial price
- `brick_size` (float): Brick size (must be > 0)
- `ts_unit` (str): Timestamp unit for datetime conversion (`D`, `s`, `ms`, `us`, `ns`)

### `RenkoWS(external_df, ts_unit)`

Create real-time Renko charts from existing data (produced by `Renko.to_rws()`).

**Parameters:**
- `external_df` (Polars DataFrame): Data from `Renko.to_rws()`
- `ts_unit` (str): Timestamp unit

### `RenkoWS.add_prices(ws_timestamp, ws_price, gap_tolerance=200)`

Feed a new price tick. Must be called at every price change.

**Parameters:**
- `ws_timestamp` (int): Current timestamp
- `ws_price` (float): Current price
- `gap_tolerance` (int): Maximum number of artificial bricks at a gap (default 200)

### `RenkoWS.renko_df(mode="wicks", utils_columns=True)`

Returns completed Renko bricks (no forming brick).

### `RenkoWS.renko_animate(mode="wicks", max_len=500, keep=250)`

Returns completed bricks plus a forming brick representing current price action.

**Parameters:**
- `mode` (str): Renko mode
- `max_len` (int): Trim trigger threshold (0 to disable)
- `keep` (int): Number of bricks to keep after trimming

### `RenkoWS.plot(mode="wicks", volume=True, add_traces=None)`

Plot real-time Renko chart using plotly.

---

## Performance

Benchmark results comparing **renkodf** (Python/NumPy) vs **renko_rs** (Rust):

| Dataset Size | renkodf | renko_rs | Speedup |
|--------------|---------|----------|---------|
| 100K ticks | 0.122s | 0.007s | **18x** |
| 1M ticks | 1.085s | 0.023s | **46x** |
| 10M ticks | ~11s | ~0.23s | **~48x** |

### Benchmark Script

```python
import time
import polars as pl
from renkodf import Renko as PyRenko
from renko_rs import Renko as RsRenko

# Generate test data
df = pl.DataFrame({
    "datetime": pl.date_range(start=datetime(2020, 1, 1), end=datetime(2020, 12, 31), interval="1m"),
    "close": np.random.randn(525600).cumsum() + 100
})

# Benchmark Python version
start = time.time()
r_py = PyRenko(df.to_pandas(), brick_size=2.0)
df_py = r_py.renko_df("wicks")
py_time = time.time() - start

# Benchmark Rust version
start = time.time()
r_rs = RsRenko(df, brick_size=2.0)
df_rs = r_rs.renko_df("wicks")
rs_time = time.time() - start

print(f"Python: {py_time:.3f}s")
print(f"Rust:   {rs_time:.3f}s")
print(f"Speedup: {py_time/rs_time:.1f}x")
```

### Why is it faster?

1. **Native compilation**: Rust compiles to machine code, eliminating Python interpreter overhead
2. **Zero-copy operations**: Polars DataFrames share memory with Arrow buffers
3. **Optimized inner loop**: The core algorithm runs in a tight Rust loop without Python function calls
4. **Batch brick generation**: When multiple bricks form in the same direction, they're generated in bulk
5. **SIMD-friendly**: Rust's memory layout enables auto-vectorization by the compiler

---

## Building from Source

### Prerequisites

- Rust 1.70+ ([install](https://rustup.rs/))
- Python 3.10+
- maturin (`pip install maturin`)

### Development Build

```bash
# Clone the repository
git clone https://github.com/baruns/renko_rs.git
cd renko_rs

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install dependencies
pip install maturin polars numpy pandas

# Build and install in development mode
maturin develop --release
```

### Production Build

```bash
# Build wheel
maturin build --release

# Install wheel
pip install target/wheels/renko_rs-*.whl
```

---

## Testing

Run the test suite to verify correctness:

```bash
# Run all tests
python tests/test_renko.py

# Run with pytest (if installed)
pytest tests/
```

### Test Coverage

- All 7 Renko modes (both `Renko` and `RenkoWS`)
- Datetime and timestamp column handling
- `to_rws()` export and `RenkoWS` from external data
- `renko_animate()` with forming brick
- Edge cases (small datasets, large brick sizes, tiny brick sizes)
- Polars and pandas input compatibility
- Input validation and error handling

---

## Publishing to PyPI

Wheels are automatically built and published via GitHub Actions when a new tag is pushed.

### Trigger a Release

```bash
# Update version in Cargo.toml and pyproject.toml
git tag v0.3.0
git push origin v0.3.0
```

### Build Matrix

The CI/CD pipeline builds wheels for:

| Platform | Architecture | Python Versions |
|----------|--------------|-----------------|
| Linux | x86_64 | 3.10, 3.11, 3.12, 3.13, 3.14 |
| Linux | ARM64 | 3.10, 3.11, 3.12, 3.13, 3.14 |
| Windows | x86_64 | 3.10, 3.11, 3.12, 3.13, 3.14 |
| Windows | ARM64 | 3.10, 3.11, 3.12, 3.13, 3.14 |
| macOS | Intel (x86_64) | 3.10, 3.11, 3.12, 3.13, 3.14 |
| macOS | Apple Silicon (ARM64) | 3.10, 3.11, 3.12, 3.13, 3.14 |

Total: **30 wheels** per release (6 platforms x 5 Python versions)

### Manual Publishing

```bash
# Build all wheels locally
maturin build --release --out dist

# Upload to PyPI
pip install twine
twine upload dist/*
```

---

## Algorithm Details

### Complexity

- **Time**: O(N) where N = number of ticks
- **Space**: O(B) where B = number of bricks (typically B << N)

### How Renko Works

1. **Initialize**: Set initial price to first tick's close price, floored to nearest brick_size
2. **Iterate**: For each tick, calculate price movement from last brick
3. **Brick Formation**:
   - If price moves >= `brick_size` in same direction: add brick(s)
   - If price moves >= 2x `brick_size` in opposite direction: reversal brick + continuation bricks
4. **OHLC Calculation**: Depends on the selected mode (wicks, nongap, etc.)

### Optimizations in Rust Implementation

1. **Pre-allocated vectors**: Avoids repeated memory allocations
2. **Contiguous memory access**: Operates on slices (`&[f64]`) for cache efficiency
3. **Batch generation**: When N bricks form in same direction, generates all at once
4. **Minimal branching**: Optimized conditional logic for CPU pipeline efficiency
5. **Zero-copy DataFrame construction**: Builds Polars DataFrame directly from vectors

---

## Differences from renkodf

| Feature | renkodf | renko_rs |
|---------|---------|----------|
| **Language** | Python | Rust |
| **Input** | pandas DataFrame | Polars or pandas DataFrame |
| **Output** | pandas DataFrame | Polars DataFrame |
| **Plotting** | mplfinance | plotly (optional) |
| **Real-time** | `RenkoWS` class | `RenkoWS` class |
| **Performance** | Good (NumPy) | Excellent (native code) |

### Migration Guide

If you're currently using `renkodf`, switching to `renko_rs` is straightforward:

```python
# Before (renkodf)
from renkodf import Renko
r = Renko(df_pandas, brick_size=0.0003)
df = r.renko_df("wicks")  # Returns pandas DataFrame

# After (renko_rs)
from renko_rs import Renko
r = Renko(df_pandas, brick_size=0.0003)  # Same API
df = r.renko_df("wicks")  # Returns Polars DataFrame

# Convert to pandas if needed
df_pandas = df.to_pandas()
```

For real-time usage:

```python
# Before (renkodf)
from renkodf import RenkoWS
rws = RenkoWS(ws_timestamp=ts, ws_price=price, brick_size=0.0003, ts_unit="us")
rws.add_prices(new_ts, new_price)
df = rws.renko_animate("wicks")

# After (renko_rs)
from renko_rs import RenkoWS
rws = RenkoWS(ws_timestamp=ts, ws_price=price, brick_size=0.0003, ts_unit="us")
rws.add_prices(new_ts, new_price)
df = rws.renko_animate("wicks")  # Same API, returns Polars DataFrame
```

---

## Credits

This project is a Rust reimplementation of **[renkodf](https://github.com/srlcarlg/renkodf)** by srlcarlg.

The original renkodf library pioneered the Python-based Renko chart calculation with NumPy optimization. This Rust version preserves the exact algorithm while leveraging:

- **[Polars](https://pola.rs/)**: Lightning-fast DataFrame library
- **[PyO3](https://pyo3.rs/)**: Rust-Python bindings
- **[maturin](https://github.com/PyO3/maturin)**: Build system for Rust Python extensions
- **[Plotly](https://plotly.com/python/)**: Interactive charting (replaces mplfinance)

### References

- Original algorithm inspired by [Sergey Malchevskiy's pyrenko](https://github.com/quantroom-pro/pyrenko)
- Renko chart methodology based on traditional Japanese charting techniques

### Disclaimer

This project is not affiliated with, endorsed by, or connected to the original renkodf project or its maintainers. All trademarks are the property of their respective owners.

---

## License

MIT License - see [LICENSE](LICENSE) file for details.

---

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

### Development Setup

```bash
git clone https://github.com/baruns/renko_rs.git
cd renko_rs
pip install maturin polars numpy pandas
maturin develop
```

### Running Tests

```bash
python tests/test_renko.py
```

---

## Support

- **Issues**: [GitHub Issues](https://github.com/baruns/renko_rs/issues)
- **Discussions**: [GitHub Discussions](https://github.com/baruns/renko_rs/discussions)

---

## Roadmap

- [x] Implement `RenkoWS` for real-time WebSocket streaming
- [x] Add plotting utilities (plotly)
- [x] Return `datetime` and `timestamp` columns in output
- [ ] Support for additional Renko variations (ATR-based, percentage-based)
- [ ] GPU acceleration via CUDA/OpenCL (experimental)

---

**Made with Rust for high-performance trading systems**

