Metadata-Version: 2.4
Name: insidertracker
Version: 0.1.0
Summary: Python wrapper for OpenInsider.com
Project-URL: Homepage, https://github.com/William-Kruta/OpenInsider
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

# openinsider

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

## Quick Start

```python
from openinsider import OpenInsider

insider = OpenInsider()

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

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

# CEO/CFO purchases over $25k (high-signal)
df = insider.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 = insider.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 = insider.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. `OPENINSIDER_DB` environment variable
2. `database` key in the config file
3. Default: `~/.config/openinsider/openinsider.db` (Linux), `~/Library/Application Support/openinsider/openinsider.db` (macOS), `%APPDATA%\openinsider\openinsider.db` (Windows)

To use a custom path:

```bash
export OPENINSIDER_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 `OpenInsider`:

```python
from openinsider import CeoCfoPurchases25k

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