# Signalab

Tick-level backtesting for quantitative FX and CFD research

[![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue)](#)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](#)
[![PyPI](https://img.shields.io/badge/pypi-signalab-orange)](https://pypi.org/project/signalab/)

Signalab is a research-focused backtesting library that simulates trading at raw tick
resolution — every bid/ask update — rather than bar-close approximations. It models
the execution mechanics that matter for FX and CFD strategies: bid/ask spread, per-lot
commissions, and daily swap/carry costs, with margin tracking and automatic liquidation.

---

## Key features

- **Tick-level simulation** — the loop runs on every raw bid/ask update. Indicator
  signals computed on candles are injected back into the tick stream at exact
  candle-close timestamps — no lookahead, no bar approximation.

- **Simple and easy to extend** — subclass `Strategy`, implement `perform()`.
  Need a custom indicator? Subclass `Indicator` and implement `compute()`.
  That's it!

- **Vectorized computations** —
  indicators and analytics are implemented as vectorized operations on the full
  data arrays. The engine coordinates the data flow, but all computations are
  done in batches for maximum performance.

- **Parallel parameter grid** — test dozens of strategy/indicator parameter
  combinations in one command. Run them in parallel and get a ranked report of
  all runs with their metrics and configs.

---

## Install

```bash
pip install signalab
```

---

## Quick showcase

```python
from signalab import (
    MarketData, BacktestEngine, Account,
    RSI, Strategy, Action
)

class MyStrategy(Strategy):
    def __init__(self, name="My Strategy", label="my_strat", stop_loss=50):
        super().__init__(name=name, label=label)
        self.stop_loss = stop_loss

    def perform(self, market):
        if self.account.floating_pl() < -self.stop_loss:
            self.account.close_all_positions()

        if self.account.open_position_count > 0:
            return

        buy_signals = self.buy_signal_count()
        sell_signals = self.sell_signal_count()
        if buy_signals >= sell_signals:
            self.account.open(Action.BUY, size=0.01)
        else:
            self.account.open(Action.SELL, size=0.01)
        

data   = MarketData.load_from_ticks("EURUSD_ticks.csv")
report = BacktestEngine().run(
    data       = data,
    indicators = [RSI(period=14)],
    strategies = [MyStrategy()],
)

r = report.get_account_report()
print(r.metrics())
# {
#   'total_return': 0.12,
#   'sharpe_ratio': 1.43,
#   'max_drawdown': -0.07,
#   'win_rate': 0.54,
#   ...
# }
```

More examples and tutorials coming soon!

---

<sub>🚀 Project starter provided by [Cookie Pyrate](https://github.com/gvieralopez/cookie-pyrate)</sub>
