Metadata-Version: 2.4
Name: renko_rs
Version: 0.2.1
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
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)](https://pypi.org/project/renko_rs/)
[![Python](https://img.shields.io/pypi/pyversions/renko_rs.svg)](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
```

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.

---

## Contents

- [Why Rust?](#why-rust)
- [Quick Start](#quick-start)
- [Usage](#usage)
- [Renko Modes](#renko-modes)
- [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 |

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

### 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, 9)
┌─────────────────────┬─────────┬─────────┬─────────┬─────────┬────────┬───────────┬─────────────┬──────────────────┐
│ datetime            ┆ open    ┆ high    ┆ low     ┆ close   ┆ volume ┆ direction ┆ is_reversal ┆ tick_index_close │
│ ---                 ┆ ---     ┆ ---     ┆ ---     ┆ ---     ┆ ---    ┆ ---       ┆ ---         ┆ ---              │
│ datetime[ns]        ┆ f64     ┆ f64     ┆ f64     ┆ f64     ┆ i64    ┆ i32       ┆ bool        ┆ i64              │
╞═════════════════════╪═════════╪═════════╪═════════╪═════════╪════════╪═══════════╪═════════════╪══════════════════╡
│ 2023-06-23 01:21:58 ┆ 0.8595  ┆ 0.8598  ┆ 0.8595  ┆ 0.8598  ┆ 3458   ┆ 1         ┆ false       ┆ 3458             │
│ 2023-06-23 01:33:24 ┆ 0.8598  ┆ 0.8601  ┆ 0.8598  ┆ 0.8601  ┆ 571    ┆ 1         ┆ false       ┆ 4029             │
│ 2023-06-23 03:18:30 ┆ 0.8601  ┆ 0.8604  ┆ 0.8601  ┆ 0.8604  ┆ 4993   ┆ 1         ┆ false       ┆ 9022             │
│ 2023-06-23 04:40:26 ┆ 0.8604  ┆ 0.8607  ┆ 0.8604  ┆ 0.8607  ┆ 3358   ┆ 1         ┆ false       ┆ 12380            │
│ 2023-06-23 05:15:54 ┆ 0.8604  ┆ 0.8604  ┆ 0.8601  ┆ 0.8601  ┆ 1669   ┆ -1        ┆ true        ┆ 14049            │
└─────────────────────┴─────────┴─────────┴─────────┴─────────┴────────┴───────────┴─────────────┴──────────────────┘
```

### Input Data Requirements

Your DataFrame must contain:
- **`close`** (required): Price values (float)
- **`datetime`** (required): Timestamps (datetime)

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({
    "datetime": [...],
    "close": [...]
})
```

### 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")
```

---

## 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 |

### Mode Comparison

```python
import matplotlib.pyplot as plt

modes = ["normal", "wicks", "nongap", "reverse-wicks"]
fig, axes = plt.subplots(2, 2, figsize=(15, 10))

for ax, mode in zip(axes.flat, modes):
    df = r.renko_df(mode, utils_columns=False)
    # Plot using your preferred charting library
    ax.set_title(f"Mode: {mode}")
    
plt.tight_layout()
plt.show()
```

### Utility Columns

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

- **`direction`**: 1 for up brick, -1 for down brick
- **`is_reversal`**: True if this brick reverses the previous trend
- **`tick_index_open`**: Index of first tick in this brick
- **`tick_index_close`**: Index of last tick in this brick

```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
```

---

## 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

**Example:**
```python
r = Renko(df_ticks, brick_size=0.0003)
```

### `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

**Example:**
```python
df = r.renko_df("wicks", utils_columns=True)
```

---

## 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/
```

The test suite compares the Rust implementation against the original Python version to ensure identical output.

### Test Coverage

- All 7 Renko modes
- Edge cases (small datasets, large brick sizes, tiny brick sizes)
- Polars and pandas input compatibility
- Numerical precision (tolerance: 1e-9)

---

## 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.1.0
git push origin v0.1.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 × 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
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 ≥ 2× `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** | Built-in (`mplfinance`) | None (use your preferred library) |
| **Real-time** | `RenkoWS` class | Not implemented (yet) |
| **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()
```

---

## 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

### 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

- [ ] Implement `RenkoWS` for real-time WebSocket streaming
- [ ] Add plotting utilities (matplotlib, plotly)
- [ ] Support for additional Renko variations (ATR-based, percentage-based)
- [ ] GPU acceleration via CUDA/OpenCL (experimental)

---

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

