Metadata-Version: 2.4
Name: insidertracker
Version: 0.1.2
Summary: Python wrapper for OpenInsider.com
Project-URL: Homepage, https://github.com/William-Kruta/InsiderTracker
Requires-Python: >=3.12
Requires-Dist: duckdb>=1.5.1
Requires-Dist: lxml>=6.0.2
Requires-Dist: polars>=1.39.3
Requires-Dist: pyarrow>=23.0.1
Requires-Dist: requests>=2.33.1
Description-Content-Type: text/markdown

# insidertracker

A Python wrapper for [OpenInsider.com](http://openinsider.com) that scrapes SEC Form 4 insider trading data and caches it locally in DuckDB.

## Installation

```bash
pip install insidertracker
```

## Quick Start

```python
from insidertracker import InsiderTracker

tracker = InsiderTracker()

# Insider trades for one or more tickers
df = tracker.ticker.get_insider_trades("AAPL")
df = tracker.ticker.get_insider_trades(["AAPL", "TSLA", "NVDA"])

# Latest cluster buys (multiple insiders buying the same stock)
df = tracker.cluster_buys.get_cluster_buys()

# CEO/CFO purchases over $25k (high-signal)
df = tracker.ceo_cfo_purchases_25k.get_ceo_cfo_purchases_25k()
```

## Endpoints

| Attribute                    | Method                             | Source Page                          |
| ---------------------------- | ---------------------------------- | ------------------------------------ |
| `ticker`                     | `get_insider_trades(ticker)`       | Screener filtered by ticker          |
| `ticker`                     | `get_insider_sales(ticker)`        | Same, filtered to sales only         |
| `cluster_buys`               | `get_cluster_buys()`               | `/latest-cluster-buys`               |
| `insider_purchases`          | `get_insider_purchases()`          | `/insider-purchases`                 |
| `insider_sales`              | `get_insider_sales()`              | `/insider-sales`                     |
| `insider_purchases_25k`      | `get_insider_purchases_25k()`      | `/latest-insider-purchases-25k`      |
| `ceo_cfo_purchases_25k`      | `get_ceo_cfo_purchases_25k()`      | `/latest-ceo-cfo-purchases-25k`      |
| `ceo_cfo_sales_100k`         | `get_ceo_cfo_sales_100k()`         | `/latest-ceo-cfo-sales-100k`         |
| `top_officer_purchases_week` | `get_top_officer_purchases_week()` | `/top-officer-purchases-of-the-week` |
| `screener`                   | `get(**kwargs)`                    | `/screener` with full filter support |

All methods return a [Polars](https://pola.rs) DataFrame.

## Common Parameters

All endpoint methods accept these optional parameters:

| Parameter         | Type        | Description                                          |
| ----------------- | ----------- | ---------------------------------------------------- |
| `stale_threshold` | `timedelta` | How old cached data can be before re-fetching        |
| `force_update`    | `bool`      | Bypass cache and always re-fetch                     |
| `min_date`        | `datetime`  | Only return rows with `filing_date` after this date  |
| `max_date`        | `datetime`  | Only return rows with `filing_date` before this date |

```python
import datetime as dt

df = tracker.cluster_buys.get_cluster_buys(
    stale_threshold=dt.timedelta(days=1),
    force_update=False,
    min_date=dt.datetime(2025, 1, 1, tzinfo=dt.timezone.utc),
    max_date=dt.datetime(2025, 12, 31, tzinfo=dt.timezone.utc),
)
```

## Screener

The `screener` module exposes the full OpenInsider screener with 30+ filter parameters:

```python
df = tracker.screener.get(
    ticker="AAPL",
    transaction_value_min_usd_thousands=100,
    filing_date_within_days=30,
    page_size=200,
)
```

## Data Schema

### Ticker / Latest Purchase & Sale endpoints

| Column             | Type          | Description                                   |
| ------------------ | ------------- | --------------------------------------------- |
| `filing_date`      | `TIMESTAMPTZ` | SEC filing timestamp                          |
| `trade_date`       | `DATE`        | Date the trade occurred                       |
| `ticker`           | `VARCHAR`     | Stock symbol                                  |
| `company_name`     | `VARCHAR`     | Company name                                  |
| `insider_name`     | `VARCHAR`     | Name of the insider                           |
| `title`            | `VARCHAR`     | Insider's role (CEO, CFO, Dir, etc.)          |
| `trade_type`       | `VARCHAR`     | e.g. `S - Sale`, `P - Purchase`               |
| `price`            | `DOUBLE`      | Trade price per share                         |
| `quantity`         | `DOUBLE`      | Number of shares traded                       |
| `owned`            | `DOUBLE`      | Shares owned after trade                      |
| `ownership_change` | `DOUBLE`      | Fractional change in ownership (e.g. `-0.20`) |
| `value`            | `DOUBLE`      | Total value of trade in USD                   |

### Cluster Buys

Same as above, with `insider_name` and `title` replaced by:

| Column         | Type      | Description                   |
| -------------- | --------- | ----------------------------- |
| `industry`     | `VARCHAR` | Industry classification       |
| `num_insiders` | `VARCHAR` | Number of insiders who bought |

## Database

Data is cached in a local DuckDB database. The path is resolved in this order:

1. `INSIDERTRACKER_DB` environment variable
2. `database` key in the config file
3. Default: `~/.config/insidertracker/insidertracker.db` (Linux), `~/Library/Application Support/insidertracker/insidertracker.db` (macOS), `%APPDATA%\insidertracker\insidertracker.db` (Windows)

To use a custom path:

```bash
export INSIDERTRACKER_DB=/path/to/my.db
```

Or create a config file at the platform default config path:

```json
{
  "database": "/path/to/my.db"
}
```

## Individual Classes

You can import and use each module independently without going through `InsiderTracker`:

```python
from insidertracker import CeoCfoPurchases25k

ceo = CeoCfoPurchases25k()
df = ceo.get_ceo_cfo_purchases_25k(force_update=True)
```
