Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ All notable changes to **oipd** will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2025-09-18
## [Unreleased]
### Added
- **Cryptocurrency Options Support**: Added Bybit vendor integration for crypto options (BTC, ETH, etc.)
- New `crypto` installation option: `pip install oipd[crypto]`
- Bybit API integration via `pybit` library
- Crypto-specific examples and documentation
- overhauled user API interface. Now, users interface using the RND() class, and input arguments using MarketInputs and ModelParams
- integrated plotting functionality
- integrated convenient functions to access results
- handles dividend yield and schedule
- integrated yfinance support for automated options data pulling, and retrieval of dividend info
- refactored folder structure - seperated RND from options-pricing functionality
- integrated put-call parity -> finds market-implied forward price, and implied dividend yield, replaced ITM call options with OTM puts converted to synthetic calls to reduce noise
- integrated Black-76 pricing model compatible with forwards
- removed KDE and fit_kde() argument
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,15 @@ est = RND.from_ticker("AAPL", market)
# 3 ─ access results and plots
est.prob_at_or_above(120) # P(price >= $120)
est.prob_below(100) # P(price < $100)
est.plot() # plot probability and cumulative distribution functions
est.plot() # plot probability and cumulative distribution functions

# For cryptocurrency options (Bybit)
crypto_market = MarketInputs(
valuation_date=date.today(),
expiry_date=date(2025, 12, 26),
risk_free_rate=0.04,
)
crypto_est = RND.from_ticker("BTC", crypto_market, vendor="bybit")
```

OIPD also **supports manual CSV or DataFrame uploads**.
Expand Down Expand Up @@ -85,7 +93,8 @@ Join the [Discord community](https://discord.gg/pWVrmQWk) to share ideas, discus
# Current Roadmap

Convenience features:
- integrate other data vendors (Alpaca, Deribit) for automatic stock and crypto options data fetching
- ✅ integrate crypto options data fetching (Bybit implemented)
- integrate additional data vendors (Alpaca, Deribit) for more coverage

Algorithmic improvements:
- implement no-arbitrage checks
Expand Down
30 changes: 23 additions & 7 deletions TECHNICAL_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ This document complements the high-level `README.md`. Here you’ll find:

## 1. Installation flavours

| Use-case | Command |
| --------------------------------- | --------------------------- |
| Full installation (with yfinance) | `pip install oipd` |
| Core maths only (no data vendors) | `pip install oipd[minimal]` |
| Contributor install | `pip install oipd[dev]` |
| Use-case | Command |
| ----------------------------------- | --------------------------- |
| Full installation (all vendors) | `pip install oipd` |
| Core maths only (no data vendors) | `pip install oipd[minimal]` |
| Crypto markets only (Bybit) | `pip install oipd[crypto]` |
| Contributor install | `pip install oipd[dev]` |

Requires at minimum **Python 3.10+**.

Expand All @@ -32,7 +33,8 @@ The `RND` class is the high-level facade that users interact with. It has three

1. **CSV files**: `RND.from_csv(path, market)`
2. **DataFrames**: `RND.from_dataframe(df, market)`
3. **Live data**: `RND.from_ticker("AAPL", market)`
3. **Live data**: `RND.from_ticker("AAPL", market)` for equity options (Yahoo Finance)
4. **Crypto options**: `RND.from_ticker("BTC", market, vendor="bybit")` for cryptocurrency options

`RND` takes the mandatory MarketInputs parameter, and an optional ModelParams parameter.

Expand Down Expand Up @@ -85,9 +87,23 @@ Main class that fits risk-neutral density models from options data and provides
```python
RND.from_csv(path, market, model, column_mapping={"YourHeader": "oipd_field"}) # Maps CSV headers to OIPD fields
RND.from_dataframe(df, market, model, column_mapping={"YourHeader": "oipd_field"}) # Maps DataFrame columns to OIPD fields
RND.from_ticker("AAPL", market, vendor="yfinance", ...) # (auto-fetches from vendors, only YFinance currently integrated)
RND.from_ticker("AAPL", market, vendor="yfinance", ...) # Equity options via Yahoo Finance
RND.from_ticker("BTC", market, vendor="bybit", ...) # Crypto options via Bybit API
```

### 2.5 Supported Data Vendors

| Vendor | Market Type | Supported Assets | Installation |
|-----------|------------------|----------------------|----------------------|
| yfinance | Equity Options | US stocks, ETFs | Default |
| bybit | Crypto Options | BTC, ETH, SOL, etc. | Requires `pybit` |

**Crypto Options Notes:**
- Cryptocurrency options don't have dividends (dividend_yield defaults to 0)
- Uses spot price from crypto exchanges as underlying price
- Risk-free rate should reflect the funding cost in crypto markets
- Expiry dates follow exchange-specific formats (Bybit: "30DEC22")

#### Expected Data Format

OIPD expects the following columns in your data:
Expand Down
129 changes: 129 additions & 0 deletions examples/crypto_options_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""
Example: Analyzing Bitcoin Options Implied Probability Distribution using Bybit

This example demonstrates how to use OIPD with cryptocurrency options data
from Bybit to extract risk-neutral probability distributions.
"""

from datetime import date, timedelta
import matplotlib.pyplot as plt

from oipd import RND, MarketInputs

def main():
"""Main example function."""

print("🚀 OIPD Crypto Options Example - Bitcoin via Bybit")
print("=" * 60)

# Step 1: Define market parameters for Bitcoin options
# Note: For crypto, risk-free rate should reflect funding costs in crypto markets
market = MarketInputs(
valuation_date=date.today(),
expiry_date=date.today() + timedelta(days=30), # 30 days from now
risk_free_rate=0.05, # 5% annual rate (adjust based on crypto funding rates)
)

print(f"📅 Analysis Date: {market.valuation_date}")
print(f"📅 Expiry Date: {market.expiry_date}")
print(f"💰 Risk-free Rate: {market.risk_free_rate:.1%}")
print()

try:
# Step 2: List available expiry dates for Bitcoin options
print("📋 Available Bitcoin option expiry dates:")
expiry_dates = RND.list_expiry_dates("BTCUSDT", vendor="bybit")
for i, expiry in enumerate(expiry_dates[:10]): # Show first 10
print(f" {i+1:2d}. {expiry}")
if len(expiry_dates) > 10:
print(f" ... and {len(expiry_dates) - 10} more")
print()

# Step 3: Use the first available expiry date
if expiry_dates:
target_expiry = expiry_dates[0]
market = MarketInputs(
valuation_date=date.today(),
expiry_date=date.fromisoformat(target_expiry),
risk_free_rate=0.05,
)

print(f"🎯 Analyzing Bitcoin options expiring on {target_expiry}")
print("⏳ Fetching data from Bybit...")

# Step 4: Fetch Bitcoin options data and estimate RND
est = RND.from_ticker("BTC", market, vendor="bybit")

print("✅ Data fetched successfully!")
print()

# Step 5: Display summary
print("📊 Market Summary:")
print(est.summary())
print()

# Step 6: Calculate key probabilities
current_price = est.market.underlying_price

# Calculate probability ranges
prob_above_current = est.prob_at_or_above(current_price * 1.1) # +10%
prob_below_current = est.prob_below(current_price * 0.9) # -10%
prob_stable = 1 - prob_above_current - prob_below_current # ±10%

print("🎲 Probability Analysis:")
print(f" P(BTC ≥ ${current_price * 1.1:,.0f}) = {prob_above_current:.1%}")
print(f" P(BTC ≤ ${current_price * 0.9:,.0f}) = {prob_below_current:.1%}")
print(f" P(${current_price * 0.9:,.0f} < BTC < ${current_price * 1.1:,.0f}) = {prob_stable:.1%}")
print()

# Step 7: Generate probability distribution plot
print("📈 Generating probability distribution plot...")

fig = est.plot(
kind="both",
figsize=(12, 6),
title=f"Bitcoin Options Implied Probability Distribution\nExpiry: {target_expiry}",
source="Data: Bybit API via OIPD",
show_current_price=True
)

# Save the plot
plt.tight_layout()
plt.savefig("bitcoin_probability_distribution.png", dpi=300, bbox_inches='tight')
print("💾 Plot saved as 'bitcoin_probability_distribution.png'")

# Step 8: Export results to CSV
est.to_csv("bitcoin_rnd_results.csv")
print("💾 Results exported to 'bitcoin_rnd_results.csv'")
print()

# Step 9: Advanced analysis - quartiles
df_results = est.to_frame()

# Find quartile prices
q25_price = est.prices[est.cdf >= 0.25][0] if any(est.cdf >= 0.25) else est.prices[-1]
q50_price = est.prices[est.cdf >= 0.50][0] if any(est.cdf >= 0.50) else est.prices[-1]
q75_price = est.prices[est.cdf >= 0.75][0] if any(est.cdf >= 0.75) else est.prices[-1]

print("📊 Price Quartiles (Market Expectations):")
print(f" 25th percentile: ${q25_price:,.0f}")
print(f" 50th percentile: ${q50_price:,.0f} (median)")
print(f" 75th percentile: ${q75_price:,.0f}")
print()

print("✨ Analysis complete! Check the generated files for detailed results.")

else:
print("❌ No Bitcoin options expiry dates found on Bybit")

except Exception as e:
print(f"❌ Error occurred: {e}")
print("\n💡 Troubleshooting tips:")
print(" 1. Ensure you have pybit installed: pip install pybit")
print(" 2. Check your internet connection")
print(" 3. Verify Bybit API is accessible")
print(" 4. Try a different expiry date")


if __name__ == "__main__":
main()
17 changes: 17 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from oipd import RND, MarketInputs
from datetime import date

# 1 ─ point to a ticker and provide market info
crypto_market = MarketInputs(
valuation_date=date.today(),
expiry_date=date(2025, 12, 26),
risk_free_rate=0.04,
)
crypto_est = RND.from_ticker("ETH", crypto_market, vendor="bybit")
# 2 - run estimator, auto fetching data from Yahoo Finance
# est = RND.from_ticker("AAPL", market)

# 3 ─ access results and plots
est.prob_at_or_above(4500) # P(price >= $120)
est.prob_below(4700) # P(price < $100)
est.plot() # plot probability and cumulative distribution functions
2 changes: 1 addition & 1 deletion oipd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
FillMode, resolve_market
)

__version__ = "0.1.0"
__version__ = "1.0.3"

__all__ = [
# Core functions
Expand Down
1 change: 1 addition & 0 deletions oipd/vendor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ def get_reader(name: str):
# Pre-register built-in vendors
# ------------------------------------------------------------------
register("yfinance", "oipd.vendor.yfinance.reader")
register("bybit", "oipd.vendor.bybit.reader")
3 changes: 3 additions & 0 deletions oipd/vendor/bybit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Bybit vendor module for OIPD."""

__all__ = ["Reader", "BybitError"]
Loading
Loading