Metadata-Version: 2.4
Name: asxshorts
Version: 0.1.0
Summary: Lightweight Python client to download official ASX short position daily CSVs with local caching
Project-URL: Homepage, https://github.com/ay-mich/asxshorts
Project-URL: Documentation, https://ay-mich.github.io/asxshorts/asxshorts.html
Project-URL: Repository, https://github.com/ay-mich/asxshorts
Project-URL: Issues, https://github.com/ay-mich/asxshorts/issues
Project-URL: Changelog, https://github.com/ay-mich/asxshorts/blob/main/CHANGELOG.md
Author-email: ay-mich <aydenmich@gmail.com>
Maintainer-email: ay-mich <aydenmich@gmail.com>
License: MIT
License-File: LICENSE
Keywords: asx,australia,csv,data,finance,short-selling
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: pydantic-settings>=2.0.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: python-dateutil>=2.9.0
Requires-Dist: requests>=2.32.0
Requires-Dist: rich>=13.0.0
Requires-Dist: typer>=0.12.0
Provides-Extra: all
Requires-Dist: bandit>=1.7.5; extra == 'all'
Requires-Dist: bandit>=1.8.6; extra == 'all'
Requires-Dist: build>=1.0.0; extra == 'all'
Requires-Dist: commitizen>=3.13.0; extra == 'all'
Requires-Dist: mkdocs-material>=9.5.0; extra == 'all'
Requires-Dist: mkdocs>=1.5.0; extra == 'all'
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == 'all'
Requires-Dist: mypy>=1.8.0; extra == 'all'
Requires-Dist: pandas>=2.0.0; extra == 'all'
Requires-Dist: pdoc>=15.0.4; extra == 'all'
Requires-Dist: pip-audit>=2.9.0; extra == 'all'
Requires-Dist: polars>=1.0.0; extra == 'all'
Requires-Dist: pre-commit>=3.6.0; extra == 'all'
Requires-Dist: pre-commit>=4.3.0; extra == 'all'
Requires-Dist: pydocstyle>=6.3.0; extra == 'all'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'all'
Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
Requires-Dist: pytest-mock>=3.12.0; extra == 'all'
Requires-Dist: pytest>=8.0.0; extra == 'all'
Requires-Dist: ruff>=0.14.1; extra == 'all'
Requires-Dist: ruff>=0.6.0; extra == 'all'
Requires-Dist: safety>=2.3.5; extra == 'all'
Requires-Dist: twine>=5.0.0; extra == 'all'
Requires-Dist: twine>=6.2.0; extra == 'all'
Requires-Dist: types-python-dateutil>=2.9.0; extra == 'all'
Requires-Dist: types-requests>=2.32.0; extra == 'all'
Requires-Dist: vulture>=2.14; extra == 'all'
Provides-Extra: dev
Requires-Dist: bandit>=1.7.5; extra == 'dev'
Requires-Dist: bandit>=1.8.6; extra == 'dev'
Requires-Dist: build>=1.0.0; extra == 'dev'
Requires-Dist: commitizen>=3.13.0; extra == 'dev'
Requires-Dist: mypy>=1.8.0; extra == 'dev'
Requires-Dist: pdoc>=15.0.4; extra == 'dev'
Requires-Dist: pip-audit>=2.9.0; extra == 'dev'
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
Requires-Dist: pre-commit>=4.3.0; extra == 'dev'
Requires-Dist: pydocstyle>=6.3.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.14.1; extra == 'dev'
Requires-Dist: ruff>=0.6.0; extra == 'dev'
Requires-Dist: safety>=2.3.5; extra == 'dev'
Requires-Dist: twine>=5.0.0; extra == 'dev'
Requires-Dist: twine>=6.2.0; extra == 'dev'
Requires-Dist: types-python-dateutil>=2.9.0; extra == 'dev'
Requires-Dist: types-requests>=2.32.0; extra == 'dev'
Requires-Dist: vulture>=2.14; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5.0; extra == 'docs'
Requires-Dist: mkdocs>=1.5.0; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == 'docs'
Provides-Extra: pandas
Requires-Dist: pandas>=2.0.0; extra == 'pandas'
Provides-Extra: polars
Requires-Dist: polars>=1.0.0; extra == 'polars'
Description-Content-Type: text/markdown

# asxshorts

[![PyPI version](https://badge.fury.io/py/asxshorts.svg)](https://badge.fury.io/py/asxshorts)
[![Python versions](https://img.shields.io/pypi/pyversions/asxshorts.svg)](https://pypi.org/project/asxshorts/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![CI](https://github.com/ay-mich/asxshorts/workflows/CI/badge.svg)](https://github.com/ay-mich/asxshorts/actions)

Lightweight Python client to download official ASIC short position daily CSVs across a date range, with local caching.

## Features

- 🚀 **Simple API**: Fetch short selling data with just a few lines of code
- 💾 **Local Caching**: Automatic file-based caching with atomic operations
- 🔄 **Retry Logic**: Built-in exponential backoff for robust data fetching
- 📊 **Multiple Formats**: Typed models + dicts, optional pandas/polars adapters
- 🖥️ **CLI Interface**: Command-line tool for quick data access
- 🛡️ **Type Safe**: Full type hints and mypy compatibility
- ⚡ **Minimal Dependencies**: Only requires `requests`, `python-dateutil`, and `typer`

## Installation

```bash
# Basic installation
pip install asxshorts

# With pandas support
pip install asxshorts[pandas]

# With polars support
pip install asxshorts[polars]

# Development installation
pip install asxshorts[dev]
```

## Quick Start

### Python API

```python
from datetime import date
from asxshorts import ShortsClient

# Create client
client = ShortsClient()

# Fetch data for a specific date
res = client.fetch_day(date(2024, 1, 15))
print(f"Found {res.record_count} records (from_cache={res.from_cache})")

# Fetch data for a date range
rng = client.fetch_range(
    start=date(2024, 1, 15),
    end=date(2024, 1, 19)
)
print(f"Total records: {rng.total_records}")

# Each record is a dictionary with normalized fields
for record in res.records[:3]:
    d = record.report_date
    print(f"{d}: {record.asx_code} - {record.percent_short}")
```

### Pandas Integration

```python
from asxshorts.adapters import create_pandas_adapter, to_pandas

# Create pandas adapter
adapter = create_pandas_adapter()

# Fetch as DataFrame via adapter
df = adapter.fetch_day_df(date(2024, 1, 15))
print(df.head())

# Or convert existing records
df2 = to_pandas([r.model_dump() for r in res.records])

# Date range as DataFrame
df = adapter.fetch_range_df(
    start=date(2024, 1, 15),
    end=date(2024, 1, 19)
)
```

### Polars Integration

```python
from asxshorts.adapters import create_polars_adapter, to_polars

# Create polars adapter
adapter = create_polars_adapter()

# Fetch as Polars DataFrame
df = adapter.fetch_day_df(date(2024, 1, 15))
print(df.head())

# Or convert existing records
df2 = to_polars([r.model_dump() for r in res.records])
```

### Command Line Interface

```bash
# Fetch data for a specific date
asxshorts fetch 2024-01-15

# Fetch yesterday's data
asxshorts fetch yesterday

# Fetch date range and save to file
asxshorts range 2024-01-15 2024-01-19 --output data.json

# Show cache statistics
asxshorts cache stats

# Clear cache
asxshorts cache clear

# Clean up old cache files
asxshorts cache cleanup --max-age 30
```

## Configuration

### Environment Variables

```bash
# Custom cache directory
export asxshorts_CACHE_DIR="/path/to/cache"

# Custom base URL
export asxshorts_BASE_URL="https://download.asic.gov.au"

# Custom user agent
export asxshorts_USER_AGENT="MyApp/1.0"
```

### Client Configuration

```python
from asxshorts import ShortsClient

client = ShortsClient(
    cache_dir="/custom/cache/path",
    timeout=30.0,
    retries=5,
    backoff=1.0
)
```

## Data Format

Each record contains the following normalized fields:

```python
{
    "report_date": "2024-01-15",   # date
    "asx_code": "ABC",            # ASX code
    "company_name": "…",          # optional
    "short_sold": 1000000,         # int
    "issued_shares": 10000000,     # int
    "percent_short": 10.0          # float
}
```

## Caching

- Files are cached in `~/.cache/asxshorts/` by default
- Cache uses atomic writes with file locking for thread safety
- Cached files are named by date: `2024-01-15.csv`
- Use `force=True` to bypass cache and fetch fresh data

## Error Handling

```python
from asxshorts import ShortsClient
from asxshorts.errors import NotFoundError, FetchError, RateLimitError

client = ShortsClient()

try:
    records = client.fetch_day(date(2024, 1, 15))
except NotFoundError:
    print("No data available for this date")
except RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after} seconds")
except FetchError as e:
    print(f"Failed to fetch data: {e}")
```

## Contributing

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

## License

MIT License - see LICENSE file for details.

---

**Note**: This package resolves daily CSV URLs via the official ASIC short-selling index. Please respect ASIC/ASX terms and usage limits.
