Skip to content

Commit f769bd2

Browse files
authored
Merge pull request #65 from tyrneh/create-actions-pipeline
Add CI pipeline
2 parents 5bd2f57 + 03faa70 commit f769bd2

18 files changed

Lines changed: 520 additions & 306 deletions

.github/workflows/ci.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["**"]
8+
9+
env:
10+
PYTHON_VERSION: "3.12"
11+
12+
jobs:
13+
lint:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Python
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: ${{ env.PYTHON_VERSION }}
24+
25+
- name: Install dependencies
26+
run: |
27+
python -m pip install --upgrade pip
28+
pip install -r requirements.txt
29+
30+
- name: Run formatter
31+
run: black --check .
32+
33+
# - name: Run linter
34+
# run: flake8 .
35+
36+
test:
37+
runs-on: ubuntu-latest
38+
needs: lint # only run tests if lint passes
39+
40+
steps:
41+
- name: Checkout code
42+
uses: actions/checkout@v4
43+
44+
- name: Set up Python
45+
uses: actions/setup-python@v5
46+
with:
47+
python-version: ${{ env.PYTHON_VERSION }}
48+
49+
- name: Install dependencies
50+
run: |
51+
python -m pip install --upgrade pip
52+
pip install -r requirements.txt
53+
54+
- name: Run tests
55+
run: pytest

oipd/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616

1717
from oipd.estimator import RND, ModelParams, RNDResult
1818
from oipd.market_inputs import (
19-
MarketInputs, VendorSnapshot, ResolvedMarket,
20-
FillMode, resolve_market
19+
MarketInputs,
20+
VendorSnapshot,
21+
ResolvedMarket,
22+
FillMode,
23+
resolve_market,
2124
)
2225

2326
__version__ = "0.1.0"

oipd/core/parity.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ def infer_forward_from_atm(
6363
"strike": row["strike"],
6464
"call_price": row["call_price"],
6565
"put_price": row["put_price"],
66-
"distance_from_underlying": abs(row["strike"] - underlying_price),
66+
"distance_from_underlying": abs(
67+
row["strike"] - underlying_price
68+
),
6769
}
6870
)
6971

@@ -185,7 +187,12 @@ def apply_put_call_parity(
185187
put_data = row if pd.notna(put_price) and put_price > 0 else None
186188

187189
_process_strike_prices(
188-
results, strike, call_data, put_data, forward_price, discount_factor
190+
results,
191+
strike,
192+
call_data,
193+
put_data,
194+
forward_price,
195+
discount_factor,
189196
)
190197

191198
elif "last_price" in options_df.columns and "option_type" in options_df.columns:
@@ -204,7 +211,12 @@ def apply_put_call_parity(
204211
put_row = put_data.iloc[0] if len(put_data) > 0 else None
205212

206213
_process_strike_prices(
207-
results, strike, call_row, put_row, forward_price, discount_factor
214+
results,
215+
strike,
216+
call_row,
217+
put_row,
218+
forward_price,
219+
discount_factor,
208220
)
209221
else:
210222
raise ValueError(
@@ -343,7 +355,10 @@ def _process_strike_prices(
343355

344356

345357
def _convert_put_to_call(
346-
put_price: float, strike: float, forward_price: float, discount_factor: float
358+
put_price: float,
359+
strike: float,
360+
forward_price: float,
361+
discount_factor: float,
347362
) -> float:
348363
"""
349364
Convert put price to synthetic call price using put-call parity.
@@ -489,7 +504,9 @@ def preprocess_with_parity(
489504

490505
try:
491506
# Apply parity preprocessing
492-
forward_price = infer_forward_from_atm(options_df, underlying_price, discount_factor)
507+
forward_price = infer_forward_from_atm(
508+
options_df, underlying_price, discount_factor
509+
)
493510
cleaned_df = apply_put_call_parity(options_df, forward_price, discount_factor)
494511
return cleaned_df
495512

oipd/core/pdf.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,13 +330,20 @@ def _calculate_IV(
330330
elif solver_method == "brent":
331331
iv_solver_scalar = _bs_iv_brent_method
332332
else:
333-
raise ValueError("Invalid solver_method. Choose either 'newton' or 'brent'.")
333+
raise ValueError(
334+
"Invalid solver_method. Choose either 'newton' or 'brent'."
335+
)
334336

335337
q = dividend_yield
336338
iv_values = np.fromiter(
337339
(
338340
iv_solver_scalar(
339-
p, underlying_price, k, years_to_expiry, r=risk_free_rate, q=q
341+
p,
342+
underlying_price,
343+
k,
344+
years_to_expiry,
345+
r=risk_free_rate,
346+
q=q,
340347
)
341348
for p, k in zip(prices_arr, strikes_arr)
342349
),

oipd/estimator.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
from oipd.core.parity import preprocess_with_parity
1919
from oipd.io import CSVReader, DataFrameReader
2020
from oipd.vendor import get_reader
21-
from oipd.pricing.utils import prepare_dividends, implied_dividend_yield_from_forward
21+
from oipd.pricing.utils import (
22+
prepare_dividends,
23+
implied_dividend_yield_from_forward,
24+
)
2225
from oipd.market_inputs import (
2326
MarketInputs,
2427
VendorSnapshot,
@@ -320,7 +323,8 @@ def __init__(
320323
# Most readers accept cache flags; if not, Python will ignore unexpected kwargs.
321324
try:
322325
self._reader = reader_cls(
323-
cache_enabled=cache_enabled, cache_ttl_minutes=cache_ttl_minutes
326+
cache_enabled=cache_enabled,
327+
cache_ttl_minutes=cache_ttl_minutes,
324328
)
325329
except TypeError:
326330
self._reader = reader_cls()
@@ -363,7 +367,9 @@ def dividend_schedule(self) -> Optional[pd.DataFrame]:
363367

364368

365369
def _estimate(
366-
options_data: pd.DataFrame, resolved_market: ResolvedMarket, model: ModelParams
370+
options_data: pd.DataFrame,
371+
resolved_market: ResolvedMarket,
372+
model: ModelParams,
367373
) -> tuple[np.ndarray, np.ndarray, np.ndarray, Dict[str, Any]]:
368374
"""Run the core RND estimation given fully validated input data."""
369375

oipd/graphics/matplot.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ def generate_pdf_figure(
6363
if current_price:
6464
label = f"{current_price:.2f}"
6565
line = ax.axvline(
66-
x=current_price, color="green", linestyle=":", label=label, linewidth=0.75
66+
x=current_price,
67+
color="green",
68+
linestyle=":",
69+
label=label,
70+
linewidth=0.75,
6771
)
6872
# calculate the offset so that it is centered in the current range
6973
bottom, top = ax.get_ylim()
@@ -135,7 +139,11 @@ def generate_cdf_figure(
135139
if current_price:
136140
label = f"{current_price:.2f}"
137141
line = ax.axvline(
138-
x=current_price, color="green", linestyle=":", label=label, linewidth=0.75
142+
x=current_price,
143+
color="green",
144+
linestyle=":",
145+
label=label,
146+
linewidth=0.75,
139147
)
140148
_label_line_no_warnings(line, x=current_price, yoffset=0.35)
141149
if quartiles:

oipd/graphics/publication.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,12 @@ def plot_rnd(
176176
else:
177177
# CDF only - no legend for individual plot
178178
ax1.plot(
179-
prices, cdf, color=cdf_color, linewidth=1.5, alpha=0.9, **plot_kwargs
179+
prices,
180+
cdf,
181+
color=cdf_color,
182+
linewidth=1.5,
183+
alpha=0.9,
184+
**plot_kwargs,
180185
)
181186
ax1.set_xlabel("Price at expiry", fontsize=11)
182187
ax1.set_ylabel(

oipd/io/csv_reader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ def _transform_data(self, cleaned_data: DataFrame) -> DataFrame:
6464
6565
Returns:
6666
Transformed DataFrame
67-
67+
6868
Note:
69-
Common validation (NaN checks, negative prices, etc.) is handled
69+
Common validation (NaN checks, negative prices, etc.) is handled
7070
by the base class _validate_data() method.
7171
"""
7272
# CSV-specific transformations can be added here if needed

oipd/io/dataframe_reader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ def _transform_data(self, cleaned_data: DataFrame) -> DataFrame:
4040
4141
Raises:
4242
ValueError: If insufficient data points
43-
43+
4444
Note:
45-
Common validation (NaN checks, negative prices, etc.) is handled
45+
Common validation (NaN checks, negative prices, etc.) is handled
4646
by the base class _validate_data() method.
4747
"""
4848
# Ensure we have enough data points

oipd/io/reader.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ def _clean_data(self, raw_data: DataFrame) -> DataFrame:
9090

9191
if missing_optional:
9292
warnings.warn(
93-
f"Optional columns not present: {missing_optional}.", UserWarning
93+
f"Optional columns not present: {missing_optional}.",
94+
UserWarning,
9495
)
9596

9697
# Create a copy to avoid modifying the original data

0 commit comments

Comments
 (0)