diff --git a/REFACTOR_PLAN.md b/REFACTOR_PLAN.md new file mode 100644 index 0000000..09f0374 --- /dev/null +++ b/REFACTOR_PLAN.md @@ -0,0 +1,517 @@ +# Refactor Plan: Portfolio, Option, Future Classes + Centralize Constants + +## Context + +The codebase has significant technical debt: no type hints, duplicated constants across 9 files, magic-index data structures, poor variable names, and complex methods. This plan targets the three core classes (Portfolio, Option, Future) and centralizes constants. + +**User decisions:** +- Full rename everywhere (no backwards-compat aliases — rename `K`, `char`, etc. across ALL callers) +- Use `str, Enum` types now for option_type, barrier_style, etc. +- One PR with multiple commits (one commit per step) + +**Dependencies required:** `pip install numpy pandas scipy plotly sqlalchemy joblib` + +**Run tests with:** `python -m unittest discover -s tests -p "test_*.py" -v` + +--- + +## Step 1: Create `scripts/constants.py` + Wire Up Imports Everywhere + +**New file:** `scripts/constants.py` +**Modify:** all files that duplicate constants + +### 1a. Contents of constants.py + +**`ProductSpec` NamedTuple** — backwards-compatible with index access (`[0]`, `[-1]`, `[2:]`): +```python +from typing import NamedTuple, Dict +from enum import Enum + +class ProductSpec(NamedTuple): + dollar_mult: float + lot_mult: float + futures_tick: float + options_tick: float + pnl_mult: float + +multipliers: Dict[str, ProductSpec] = { + 'LH': ProductSpec(22.046, 18.143881, 0.025, 1, 400), + 'LSU': ProductSpec(1, 50, 0.1, 10, 50), + 'QC': ProductSpec(1.2153, 10, 1, 25, 12.153), + 'SB': ProductSpec(22.046, 50.802867, 0.01, 0.25, 1120), + 'CC': ProductSpec(1, 10, 1, 50, 10), + 'CT': ProductSpec(22.046, 22.679851, 0.01, 1, 500), + 'KC': ProductSpec(22.046, 17.009888, 0.05, 2.5, 375), + 'W': ProductSpec(0.3674333, 136.07911, 0.25, 10, 50), + 'S': ProductSpec(0.3674333, 136.07911, 0.25, 10, 50), + 'C': ProductSpec(0.393678571428571, 127.007166832986, 0.25, 10, 50), + 'BO': ProductSpec(22.046, 27.215821, 0.01, 0.5, 600), + 'LC': ProductSpec(22.046, 18.143881, 0.025, 1, 400), + 'LRC': ProductSpec(1, 10, 1, 50, 10), + 'KW': ProductSpec(0.3674333, 136.07911, 0.25, 10, 50), + 'SM': ProductSpec(1.1023113, 90.718447, 0.1, 5, 100), + 'COM': ProductSpec(1.0604, 50, 0.25, 2.5, 53.02), + 'CA': ProductSpec(1.0604, 50, 0.25, 1, 53.02), + 'MW': ProductSpec(0.3674333, 136.07911, 0.25, 10, 50), +} +``` + +**Other dicts:** `month_to_sym`, `sym_to_month`, `contract_mths`, `op_ticksize` + +**Scalar constants:** `RANDOM_SEED = 7`, `DECADE = 10`, `TIMESTEP = 1/365`, `BREAKEVEN_FACTOR = 2.8` + +**str Enums** (use `str, Enum` base so `BarrierStyle.AMERICAN == 'amer'` is `True`): +```python +class OptionType(str, Enum): + CALL = 'call' + PUT = 'put' + +class BarrierStyle(str, Enum): + AMERICAN = 'amer' + EUROPEAN = 'euro' + +class BarrierDirection(str, Enum): + UP = 'up' + DOWN = 'down' + +class SecurityType(str, Enum): + OPTION = 'option' + FUTURE = 'future' + +class PositionFlag(str, Enum): + OTC = 'OTC' + HEDGE = 'hedge' +``` + +### 1b. Bug fixes in contract_mths + +**Missing comma in LC entry (Python string concat creates `'VZ'` instead of `'V', 'Z'`):** +- `scripts/simulation.py` line 89 +- `scripts/fetch_data.py` line 33 +- `demos/option_demo.py` line 58 +- `tests/test_prep_data.py` (has same bug) +- `wip.py` (has same bug) +- CORRECT in: `scripts/util.py` line 66, `scripts/prep_data.py` line 61 + +**CT months inconsistency:** `fetch_data.py` has `['H', 'K', 'N', 'V', 'Z']` (5 months) while all other files have `['H', 'K', 'N', 'Z']` (4 months). Use the 4-month version. + +### 1c. Where constants are currently duplicated + +**multipliers** (9 copies): +- `scripts/simulation.py` lines 36-56 +- `scripts/calc.py` lines 57-76 +- `scripts/classes.py` lines 16-35 +- `scripts/hedge.py` lines 14-33 +- `scripts/util.py` lines 18-37 +- `scripts/portfolio.py` lines 25-44 +- `scripts/prep_data.py` lines 71-90 +- `tests/test_calc.py` lines 9-28 (**KEEP LOCAL** — uses `LCC`/`OBM` legacy codes) +- `demos/option_demo.py` lines 10-29 (out of scope) + +**month_to_sym** (4 copies): simulation.py:100, util.py:44, prep_data.py:37, option_demo.py:36 +**sym_to_month** (4 copies): simulation.py:102, util.py:46, prep_data.py:39, option_demo.py:38 +**contract_mths** (5+ copies): simulation.py:76-96, util.py:53-73, prep_data.py:48-68, fetch_data.py:20-40, option_demo.py:45-65 +**op_ticksize** (2 copies): simulation.py:58-74, hedge.py:35-51 +**seed = 7** (6 copies): simulation.py:109, calc.py:84, portfolio.py:47, util.py:39, prep_data.py:30, option_demo.py:31 +**decade = 10** (4 copies): simulation.py:104, util.py:48, prep_data.py:41, option_demo.py:40 + +### 1d. What each file needs to import + +| File | Imports needed | +|------|---------------| +| `scripts/classes.py` | `multipliers` | +| `scripts/calc.py` | `multipliers`, `RANDOM_SEED` | +| `scripts/portfolio.py` | `multipliers`, `RANDOM_SEED`, `BREAKEVEN_FACTOR` | +| `scripts/simulation.py` | `multipliers`, `contract_mths`, `month_to_sym`, `sym_to_month`, `op_ticksize`, `RANDOM_SEED`, `DECADE`, `TIMESTEP` | +| `scripts/hedge.py` | `multipliers`, `op_ticksize` | +| `scripts/util.py` | `multipliers`, `contract_mths`, `month_to_sym`, `sym_to_month`, `RANDOM_SEED`, `DECADE` | +| `scripts/prep_data.py` | `multipliers`, `contract_mths`, `month_to_sym`, `sym_to_month`, `RANDOM_SEED`, `DECADE` | +| `scripts/fetch_data.py` | `contract_mths` | + +After replacing, keep `np.random.seed(RANDOM_SEED)` at module level in each file that currently has `np.random.seed(seed)`. + +### Risk: LOW + +--- + +## Step 2: Refactor `Future` class in `scripts/classes.py` + +Currently lines 586-668. + +### Changes: +1. **Type hints** on all methods +2. **Fix mutable default:** `instructions={}` on line 608 → remove parameter entirely (it's never read in the method body, and grep confirms no caller passes it) +3. **Use `SecurityType.FUTURE`** for `self.desc` +4. **Move calc imports to module top level** (currently lazy-imported in 4 Option methods). Verified safe: `calc.py` does NOT import `classes.py` — no circular dependency. +5. Clean up `__str__` + +### Current Future API (preserve all): +- `__init__(month, price, product, shorted=None, lots=1000, ordering=None)` +- `get_price()`, `update_price(price)`, `get_month()`, `get_lots()`, `get_product()` +- `get_desc()`, `get_delta()`, `get_uid()`, `get_ordering()`, `set_ordering(i)` +- `decrement_ordering(i)`, `update_lots(lots)` + +### Risk: LOW + +--- + +## Step 3: Refactor `Option` class — Full Rename Across Codebase + +Currently lines 38-584 in `scripts/classes.py`. + +### 3a. Attribute renames (at source AND all callers) + +| Old name | New name | Grep to find all references | +|----------|----------|-----------------------------| +| `self.K` / `.K` | `self.strike` / `.strike` | `grep -rn '\.K[^a-zA-Z]' scripts/ tests/` (careful: K appears in month codes too) | +| `self.char` / `.char` | `self.option_type` / `.option_type` | `grep -rn '\.char' scripts/ tests/` | +| `self.direc` / `.direc` | `self.direction` / `.direction` | `grep -rn '\.direc\b' scripts/ tests/` and `grep -rn 'direc=' scripts/ tests/` | +| `self.ki` / `.ki` | `self.knock_in` / `.knock_in` | `grep -rn '\.ki\b' scripts/ tests/` and `grep -rn 'ki=' scripts/ tests/` | +| `self.ko` / `.ko` | `self.knock_out` / `.knock_out` | `grep -rn '\.ko\b' scripts/ tests/` and `grep -rn 'ko=' scripts/ tests/` | +| `self.bvol` / `.bvol` | `self.barrier_vol` / `.barrier_vol` | `grep -rn '\.bvol\b' scripts/ tests/` and `grep -rn 'bvol=' scripts/ tests/` (careful: don't match bvol2) | +| `self.bvol2` / `.bvol2` | `self.barrier_vol2` / `.barrier_vol2` | `grep -rn '\.bvol2' scripts/ tests/` and `grep -rn 'bvol2=' scripts/ tests/` | +| `self.r` | Remove entirely — inline `0` | Only used inside `classes.py` in calls to calc functions | +| Constructor param `char=` | `option_type=` | Also rename in `_compute_greeks()` and `_compute_value()` param names in `calc.py` if they use `char` | + +**IMPORTANT for `ki`/`ko`/`bvol`/`bvol2`:** These are also **keyword arguments** passed to `_compute_greeks()` and `_compute_value()` in `calc.py`. Check if `calc.py` function signatures use the same param names — if so, those must be renamed too, along with all callers passing them as kwargs. + +### 3b. Type hints on all Option methods + +Add `from __future__ import annotations` or use string quotes for forward references to `Future`. + +### 3c. Extract duplicated Greeks computation + +**`init_greeks()`** (lines 324-370) and **`update_greeks()`** (lines 372-419) both contain: +```python +ttms = [self.tau] if self.bullet else self.dailies +for tau in ttms: + delta, gamma, theta, vega = _compute_greeks(self.char, self.K, tau, ...) + d += delta; g += gamma; t += theta; v += vega +``` + +Extract to: +```python +def _sum_greeks_over_ttms(self, vol: float, barrier_vol, barrier_vol2) -> tuple: + """Compute aggregate greeks across all TTM slices.""" + product = self.get_product() + spot = self.underlying.get_price() + ttms = [self.tau] if self.bullet else self.dailies + total = [0.0, 0.0, 0.0, 0.0] + for ttm in ttms: + d, g, t, v = _compute_greeks( + self.option_type, self.strike, ttm, vol, spot, 0, product, + self.payoff, self.lots, ki=self.knock_in, ko=self.knock_out, + barrier=self.barrier, direction=self.direction, + order=self.ordering, bvol=barrier_vol, bvol2=barrier_vol2, + dbarrier=self.dbarrier) + total[0] += d; total[1] += g; total[2] += t; total[3] += v + return tuple(total) +``` + +### 3d. Simplify `check_active()` (lines 245-313, 69 lines → ~30 lines) + +**Current structure:** deeply nested if/else with state mutations scattered throughout. + +**Proposed structure:** +```python +def check_active(self) -> bool: + spot = self.underlying.get_price() + if self.check_expired(): + return False + if self.knockedin: + return True + if self.knockedout: + return self.barrier == BarrierStyle.EUROPEAN and self.tau > 0 + if self.knock_in is not None: + self._update_knock_in_state(spot) + if self.knock_out is not None: + return self._update_knock_out_state(spot) + return True # vanilla: active until expiry + +def _update_knock_in_state(self, spot: float) -> None: + if self.direction == 'up': + self.knockedin = spot >= self.knock_in + elif self.direction == 'down': + self.knockedin = spot <= self.knock_in + if self.barrier == 'amer' and self.knockedin: + self.knock_in = None + self.knock_out = None + self.barrier = None + self.direction = None + +def _update_knock_out_state(self, spot: float) -> bool: + if self.barrier == 'amer': + if self.direction == 'up': + active = spot < self.knock_out + else: + active = spot > self.knock_out + self.knockedout = not active + if self.knockedout: + self.dailies = [] + self.expired = True + return active + elif self.barrier == 'euro': + if self.direction == 'up': + self.knockedout = spot >= self.knock_out + else: + self.knockedout = spot <= self.knock_out + return True # Euro KO stays active until expiry + return True +``` + +**CRITICAL:** `check_active()` mutates `self.knockedin` and `self.knockedout` as side effects. The refactored version must preserve this exactly. + +### 3e. Simplify `moneyness()` (lines 488-539, 52 lines → ~20 lines) + +Extract helper: +```python +def _is_itm(self, spot: float) -> bool: + if self.option_type == 'call': + return self.strike < spot + return self.strike > spot +``` + +Then `moneyness()` becomes: +```python +def moneyness(self) -> int: + active = self.check_active() + if self.knockedout: + return -1 + self.active = active + if not active: + return -1 + spot = self.underlying.get_price() + if self.strike == spot: + return 0 + # Barrier KO check + if self.knock_out is not None and self.knockedout: + return -1 + # Barrier KI check — not yet knocked in means OTM + if self.knock_in is not None and not self.knockedin: + return -1 + return 1 if self._is_itm(spot) else -1 +``` + +### 3f. Cleanup +- Remove debug `print()` statements in `init_greeks()` error handler (lines 348-360) +- Remove all commented-out code +- Use `SecurityType.OPTION` for `self.desc` + +### Files to update for the renames + +For each rename, grep and update ALL references. Key files: + +- `scripts/classes.py` — source of truth +- `scripts/simulation.py` — accesses `.K`, `.char`, `.ki`, `.ko`, `.direc`, `.bvol`, `.bvol2` on Option objects throughout the simulation loop +- `scripts/hedge.py` — creates options and accesses barrier attributes +- `scripts/util.py` — factory functions pass `ki=`, `ko=`, `bvol=`, `direc=` as kwargs to Option constructor; also accesses `.K`, `.char` +- `scripts/calc.py` — function signatures use `char` param name, `ki=`, `ko=`, `bvol=`, `bvol2=` as params. **Must rename these function params too** and update all callers. +- `scripts/signals.py` — check for any attribute access +- `tests/test_options.py` — constructs Options with `ki=`, `ko=`, `direc=`, `barrier=`; accesses `.K`, `.char` +- `tests/test_calc.py` — calls calc functions with `ki=`, `ko=` kwargs +- `tests/test_portfolio.py` — constructs Options with `ki=`, `ko=`, `direc=`; accesses `.K` +- `tests/test_hedge.py` — constructs Options and accesses attributes +- `tests/test_simulation.py` — may reference option attributes + +### Risk: MEDIUM-HIGH + +--- + +## Step 4: Refactor `Portfolio` class in `scripts/portfolio.py` + +Currently 1092 lines. + +### 4a. Fix bugs + +1. **Line 242 — `update_sec_lots` wrong list for hedge options:** + ```python + # BUG: else branch should be self.hedge_options, not self.hedge_futures + ops = self.OTC_options if flag == 'OTC' else self.hedge_futures + # FIX: + ops = self.OTC_options if flag == 'OTC' else self.hedge_options + ``` + +2. **Lines 247/249 — desc comparison case mismatch (dead code):** + ```python + # BUG: 'Option'/'Future' uppercase never matches get_desc() which returns lowercase + if s.desc == 'Option' and s not in ops: + # FIX: + if s.desc == 'option' and s not in ops: + ``` + +3. **Line 296 — `init_sec_by_month` writes to builtin `dict` instead of local `dic`:** + ```python + # BUG: + dict[prod] = {} + # FIX: + dic[prod] = {} + ``` + +4. **Line 301 — `update_greeks_by_month` missing `flag` argument:** + ```python + # BUG: + self.update_greeks_by_month(prod, month, sec, True) + # FIX: + self.update_greeks_by_month(prod, month, sec, True, iden) + ``` + +### 4b. Type hints + cleanup + +- Add type hints to all methods +- Remove unused import: `from timeit import default_timer as timer` (line 13) +- Remove ~15 commented-out debug print statements (lines 323-324, 344, 431-432, 438, 442, 556, 660, 665, 679, 683) +- Remove unused `breakeven` params: `flag=None, conv=None` (line 1014, never read) + +### 4c. Rename local variables + +These are method-local, not public API: +- `dic` → `positions` or `target_dict` (used in ~20 methods) +- `pdt` → `product` (used in ~12 methods) +- `mth` → `month` (used in ~10 methods) +- `d3` → `net_greeks_dict` (line 580) +- `bes` → `breakevens` (line 1017) +- `op` / `ft` → `options` / `futures` (in local scope) +- `s` → `element` or `security` (lines 244, 923, 926) + +### 4d. Extract flag-based dispatch + +The pattern below appears ~10 times: +```python +if flag == 'OTC': + dic = self.OTC + op = self.OTC_options + ft = self.OTC_futures +elif flag == 'hedge': + dic = self.hedges + op = self.hedge_options + ft = self.hedge_futures +``` + +Extract to: +```python +def _get_position_lists(self, flag: str): + """Return (options_deque, futures_list, positions_dict) for the given flag.""" + if flag == 'OTC': + return self.OTC_options, self.OTC_futures, self.OTC + return self.hedge_options, self.hedge_futures, self.hedges +``` + +Locations: lines 282-289, 389-394, 411-416, 490-499, 653, 725-728, 764-767, 772-777 + +### 4e. Simplify `compute_net_greeks()` (lines 313-383, 70 → ~20 lines) + +Current code has 3 separate loops (common products, OTC-unique products, hedge-unique products) with nearly identical logic. Replace with: + +```python +def compute_net_greeks(self) -> None: + result = {} + all_products = set(self.OTC) | set(self.hedges) + for product in all_products: + result[product] = {} + otc_data = self.OTC.get(product, {}) + hedge_data = self.hedges.get(product, {}) + for month in set(otc_data) | set(hedge_data): + otc_greeks = otc_data[month][2:] if month in otc_data and otc_data[month][0] else [0, 0, 0, 0] + hedge_greeks = hedge_data[month][2:] if month in hedge_data and hedge_data[month][0] else [0, 0, 0, 0] + result[product][month] = list(map(add, otc_greeks, hedge_greeks)) + if not result[product]: + del result[product] + self.net_greeks = result +``` + +### 4f. Decompose `update_sec_by_month()` (lines 474-622, 150 lines) + +This method does 3 completely different things depending on parameters. Split: + +```python +def update_sec_by_month(self, added, flag, update=None): + """Thin dispatcher — preserves original call signature.""" + if update is not None: + self._refresh_all_greeks(flag) + elif added: + self._add_securities_to_positions(flag) + else: + self._remove_securities_from_positions(flag) + +def _add_securities_to_positions(self, flag: str) -> None: + # Current lines 504-527 + +def _remove_securities_from_positions(self, flag: str) -> None: + # Current lines 529-575 + +def _refresh_all_greeks(self, flag: str) -> None: + # Current lines 578-622 +``` + +### 4g. Use `BREAKEVEN_FACTOR` constant + +Line 1030: replace magic `2.8` with `BREAKEVEN_FACTOR` imported from constants. + +### 4h. Fix `remove_expired()` potential double-add (lines 446-472) + +OTC loop uses `if`/`if` (option could be added twice), hedge loop uses `if`/`elif` (correct). Make both consistent with `elif`: +```python +# OTC loop (lines 450-460): +if sec.barrier == 'amer': + if sec.knockedout: + explist['OTC'].append(sec) +elif sec.check_expired(): # was 'if', should be 'elif' + explist['OTC'].append(sec) +``` + +### Risk: MEDIUM-HIGH + +--- + +## Step 5: Final Test Verification + +1. Run full test suite: `python -m unittest discover -s tests -p "test_*.py" -v` +2. Verify all tests pass +3. If any test references old attribute names, update them + +--- + +## Commit Plan (Single PR) + +| Commit | Scope | Risk | +|--------|-------|------| +| 1 | `scripts/constants.py` + wire imports in all 8 consumer files | LOW | +| 2 | Future class: type hints, remove dead param, SecurityType enum | LOW | +| 3 | Option class: full rename (K→strike etc) across ~11 files, type hints, simplify check_active/moneyness, extract _sum_greeks_over_ttms | MEDIUM-HIGH | +| 4 | Portfolio class: 4 bug fixes, type hints, variable renames, extract _get_position_lists, simplify compute_net_greeks, decompose update_sec_by_month | MEDIUM-HIGH | +| 5 | Test updates + final verification | LOW | + +Run full test suite after each commit. + +--- + +## Key Reference: Option Constructor (Before → After) + +**Before:** +```python +Option(strike, tau, char, vol, underlying, payoff, shorted, month, + direc=None, barrier=None, lots=1000, bullet=True, + ki=None, ko=None, rebate=0, ordering=1e5, settlement='futures', + bvol=None, bvol2=None, dailies=None) +``` + +**After:** +```python +Option(strike, tau, option_type, vol, underlying, payoff, shorted, month, + direction=None, barrier=None, lots=1000, bullet=True, + knock_in=None, knock_out=None, rebate=0, ordering=1e5, settlement='futures', + barrier_vol=None, barrier_vol2=None, dailies=None) +``` + +Note: `strike` stays the same (constructor param was always `strike`, just stored as `self.K`). + +--- + +## Key Reference: calc.py Function Signatures to Rename + +These functions use `ki`, `ko`, `bvol`, `bvol2` as keyword params: + +- `_compute_greeks(char, K, tau, vol, s, r, product, payoff, lots, ki=, ko=, barrier=, direction=, order=, bvol=, bvol2=, dbarrier=)` +- `_compute_value(char, tau, vol, K, s, r, payoff, ki=, ko=, barrier=, d=, product=, bvol=, bvol2=, dbarrier=)` + +Rename params in signatures AND all call sites. diff --git a/scripts/calc.py b/scripts/calc.py index f2d66d9..7946f49 100644 --- a/scripts/calc.py +++ b/scripts/calc.py @@ -47,33 +47,7 @@ import numpy as np from scipy.interpolate import interp1d import copy - - -# Dictionary of multipliers for greeks/pnl calculation. -# format = 'product' : [dollar_mult, lot_mult, futures_tick, -# options_tick, pnl_mult] - -# TODO: read this in during prep_data -multipliers = { - 'LH': [22.046, 18.143881, 0.025, 1, 400], - 'LSU': [1, 50, 0.1, 10, 50], - 'QC': [1.2153, 10, 1, 25, 12.153], - 'SB': [22.046, 50.802867, 0.01, 0.25, 1120], - 'CC': [1, 10, 1, 50, 10], - 'CT': [22.046, 22.679851, 0.01, 1, 500], - 'KC': [22.046, 17.009888, 0.05, 2.5, 375], - 'W': [0.3674333, 136.07911, 0.25, 10, 50], - 'S': [0.3674333, 136.07911, 0.25, 10, 50], - 'C': [0.393678571428571, 127.007166832986, 0.25, 10, 50], - 'BO': [22.046, 27.215821, 0.01, 0.5, 600], - 'LC': [22.046, 18.143881, 0.025, 1, 400], - 'LRC': [1, 10, 1, 50, 10], - 'KW': [0.3674333, 136.07911, 0.25, 10, 50], - 'SM': [1.1023113, 90.718447, 0.1, 5, 100], - 'COM': [1.0604, 50, 0.25, 2.5, 53.02], - 'CA': [1.0604, 50, 0.25, 1, 53.02], - 'MW': [0.3674333, 136.07911, 0.25, 10, 50] -} +from .constants import multipliers, RANDOM_SEED # TODO: Include brokerage for options/futures, and bid-ask spread for options. # TODO: Bid-ask for options will vary depending on configuration. i.e. < @@ -81,50 +55,23 @@ # spreads. # filepath = 'data_loc.txt' -seed = 7 -np.random.seed(seed) +np.random.seed(RANDOM_SEED) ##################################################################### ##################### Option pricing formulas ####################### ##################################################################### -def _compute_value(char, tau, vol, K, s, r, payoff, ki=None, ko=None, - barrier=None, d=None, product=None, bvol=None, bvol2=None, - dbarrier=None): - '''Wrapper function that computes value of option. - - Outputs: Price of the option - - Args: - char (str): call/put - tau (float): ttm in years - vol (float): vol - K (float): strike - s (float): spot - r (float): interest rate - payoff (str): american/european option. irrelevant param. - ki (float, optional): knock-in barrier level. - ko (float, optional): knock out barrier level - barrier (str, optional): barrier type (american or euro) - d (str, optional): direction (up or donw) - product (str, optional): product - bvol (float, optional): barrier volatility - bvol2 (float, optional): digital barrier vol - dbarrier (float, optional): digital barrier. - - Returns: - TYPE: price of option based on inputs passed in. - ''' +def _compute_value(char, tau, vol, K, s, r, payoff, knock_in=None, + knock_out=None, barrier=None, d=None, product=None, + barrier_vol=None, barrier_vol2=None, dbarrier=None): + '''Wrapper function that computes value of option.''' # expiry case if tau <= 0 or np.isclose(tau, 0): val = max(s-K, 0) if char == 'call' else max(K-s, 0) - # print('t = 0 intrinsic value: ', val) return val # vanilla option case if barrier is None: - # currently american == european since it's never optimal to exercise - # before expiry. if payoff == 'amer': return _bsm_euro(char, tau, vol, K, s, r) elif payoff == 'euro': @@ -132,10 +79,13 @@ def _compute_value(char, tau, vol, K, s, r, payoff, ki=None, ko=None, # barrier option case else: if barrier == 'amer': - return _barrier_amer(char, tau, vol, K, s, r, payoff, d, ki, ko) + return _barrier_amer(char, tau, vol, K, s, r, payoff, d, + knock_in, knock_out) elif barrier == 'euro': return _barrier_euro(char, tau, vol, K, s, r, payoff, d, - ki, ko, product, bvol=bvol, bvol2=bvol2, + knock_in, knock_out, product, + barrier_vol=barrier_vol, + barrier_vol2=barrier_vol2, dbarrier=dbarrier) @@ -220,8 +170,8 @@ def get_barrier_vol(df, tau, call_put_id, barlevel, vol_id): # NOTE: Currently follows implementation taken from PnP Excel source code, # and so only accounts for ECUI, ECUO, EPDI, EPDO options. def _barrier_euro(char, tau, vol, k, s, r, payoff, direction, - ki, ko, product, rebate=0, bvol=None, bvol2=None, - dbarrier=None): + knock_in, knock_out, product, rebate=0, + barrier_vol=None, barrier_vol2=None, dbarrier=None): """ Pricing model for options with European barriers. Inputs: @@ -244,35 +194,31 @@ def _barrier_euro(char, tau, vol, k, s, r, payoff, direction, 1) Price """ - barlevel = ki if ki else ko - - if dbarrier is None: - # print('dbarrier is None; computing') - barlevel = ki if ki is not None else ko + barlevel = knock_in if knock_in else knock_out + + if dbarrier is None: + barlevel = knock_in if knock_in is not None else knock_out ticksize = multipliers[product][-3] dbarrier = barlevel - ticksize if direction == 'up' else barlevel + ticksize - # case when barrier vol is not in vol surface; raise error. - # if bvol is None: - # raise ValueError('Improper Data: Barrier vol not on vol surface.') ticksize = multipliers[product][2] dpo = abs(k - barlevel)/ticksize - if ko: + if knock_out: c1 = _compute_value(char, tau, vol, k, s, r, payoff) - c2 = _compute_value(char, tau, bvol, barlevel, s, r, payoff) - # digital_option(char, tau, vol, dbarvol, k, dbar, s, r, payoff, product) - c3 = digital_option(char, tau, bvol, bvol2, barlevel, dbarrier, - s, r, payoff, product) * dpo + c2 = _compute_value(char, tau, barrier_vol, barlevel, s, r, payoff) + c3 = digital_option(char, tau, barrier_vol, barrier_vol2, barlevel, + dbarrier, s, r, payoff, product) * dpo val = c1 - c2 - c3 - elif ki: - c1 = _compute_value(char, tau, bvol, barlevel, s, r, payoff) - c2 = dpo * digital_option(char, tau, bvol, bvol2, barlevel, - dbarrier, s, r, payoff, product) + elif knock_in: + c1 = _compute_value(char, tau, barrier_vol, barlevel, s, r, payoff) + c2 = dpo * digital_option(char, tau, barrier_vol, barrier_vol2, + barlevel, dbarrier, s, r, payoff, product) val = c1 + c2 return val -def _barrier_amer(char, tau, vol, k, s, r, payoff, direction, ki, ko, rebate=0): +def _barrier_amer(char, tau, vol, k, s, r, payoff, direction, knock_in, + knock_out, rebate=0): """ Pricing model for options with american barrers. Currently, payoff is assumed to be European; consequently _compute_value defaults to computing the value of a European vanilla option. @@ -305,7 +251,7 @@ def _barrier_amer(char, tau, vol, k, s, r, payoff, direction, ki, ko, rebate=0): b = 0 mu = (b - ((vol**2)/2))/(vol**2) lambd = sqrt(mu**2 + 2*r/vol**2) - h = ki if ki else ko + h = knock_in if knock_in else knock_out x1 = log(s/k)/(vol * sqrt(tau)) + (1 + mu)*vol*sqrt(tau) x2 = log(s/h)/(vol * sqrt(tau)) + (1 + mu)*vol*sqrt(tau) @@ -326,109 +272,109 @@ def _barrier_amer(char, tau, vol, k, s, r, payoff, direction, ki, ko, rebate=0): if char == 'call': if direction == 'up': # call up in - if ki: - if s >= ki: + if knock_in: + if s >= knock_in: return _compute_value(char, tau, vol, k, s, r, payoff) - elif s < ki and k >= ki and tau > 0: + elif s < knock_in and k >= knock_in and tau > 0: return A + E - elif s < ki and k >= ki and tau == 0: + elif s < knock_in and k >= knock_in and tau == 0: return 0 - elif s < ki and k < ki and tau > 0: + elif s < knock_in and k < knock_in and tau > 0: return B - C + D + E - elif s < ki and k < ki and tau == 0: + elif s < knock_in and k < knock_in and tau == 0: return 0 # call_up_out - if ko: - if s >= ko: + if knock_out: + if s >= knock_out: return rebate * exp(-r*tau) - elif s < ko and k >= ko and tau > 0: + elif s < knock_out and k >= knock_out and tau > 0: return F - elif s < ko and k >= ko and tau == 0: + elif s < knock_out and k >= knock_out and tau == 0: return _compute_value(char, tau, vol, k, s, r, payoff) - elif s < ko and k < ko and tau > 0: + elif s < knock_out and k < knock_out and tau > 0: return A - B + C - D + F - elif s < ko and k < ko and tau == 0: + elif s < knock_out and k < knock_out and tau == 0: return _compute_value(char, tau, vol, k, s, r, payoff) if direction == 'down': - if ki: + if knock_in: # call_down_in - if s <= ki: + if s <= knock_in: return _compute_value(char, tau, vol, k, s, r, payoff) - elif s > ki and k >= ki and tau > 0: + elif s > knock_in and k >= knock_in and tau > 0: return C + E - elif s > ki and k >= ki and tau == 0: + elif s > knock_in and k >= knock_in and tau == 0: return 0 - elif s > ki and k < ki and tau > 0: + elif s > knock_in and k < knock_in and tau > 0: return A - B + D + E - elif s > ki and k < ki and tau == 0: + elif s > knock_in and k < knock_in and tau == 0: return 0 - if ko: + if knock_out: # call_down_out - if s < ko: + if s < knock_out: return rebate*exp(-r*tau) - elif s > ko and k >= ko and tau > 0: + elif s > knock_out and k >= knock_out and tau > 0: return A - C + F - elif s > ko and k >= ko and tau == 0: + elif s > knock_out and k >= knock_out and tau == 0: return _compute_value(char, tau, vol, k, s, r, payoff) - elif s > ko and k < ko and tau > 0: + elif s > knock_out and k < knock_out and tau > 0: return B - D + F - elif s > ko and k < ko and tau == 0: + elif s > knock_out and k < knock_out and tau == 0: return _compute_value(char, tau, vol, k, s, r, payoff) # put options elif char == 'put': if direction == 'up': - if ki: + if knock_in: # put_up_in - if s >= ki: + if s >= knock_in: return _compute_value(char, tau, vol, k, s, r, payoff) - elif s < ki and k >= ki and tau > 0: + elif s < knock_in and k >= knock_in and tau > 0: return A - B + D + E - elif s < ki and k >= ki and tau == 0: + elif s < knock_in and k >= knock_in and tau == 0: return 0 - elif s < ki and k < ki and tau > 0: + elif s < knock_in and k < knock_in and tau > 0: return C + E - elif s < ki and k < ki and tau == 0: + elif s < knock_in and k < knock_in and tau == 0: return 0 - if ko: + if knock_out: # put_up_out - if s >= ko: + if s >= knock_out: return rebate * exp(-r*tau) - elif s < ko and k >= ko and tau > 0: + elif s < knock_out and k >= knock_out and tau > 0: return B - D + F - elif s < ko and k >= ko and tau == 0: + elif s < knock_out and k >= knock_out and tau == 0: return _compute_value(char, tau, vol, k, s, r, payoff) - elif s < ko and k < ko and tau > 0: + elif s < knock_out and k < knock_out and tau > 0: return A - C + F - elif s < ko and k < ko and tau == 0: + elif s < knock_out and k < knock_out and tau == 0: return _compute_value(char, tau, vol, k, s, r, payoff) if direction == 'down': - if ki: + if knock_in: # put_down_in - if s <= ki: + if s <= knock_in: return _compute_value(char, tau, vol, k, s, r, payoff) - elif s > ki and k >= ki and tau > 0: + elif s > knock_in and k >= knock_in and tau > 0: return B - C + D + E - elif s > ki and k >= ki and tau == 0: + elif s > knock_in and k >= knock_in and tau == 0: return 0 - elif s > ki and k < ki and tau > 0: + elif s > knock_in and k < knock_in and tau > 0: return A + E - elif s > ki and k < ki and tau == 0: + elif s > knock_in and k < knock_in and tau == 0: return 0 - if ko: + if knock_out: # put_down_out - if s <= ko: + if s <= knock_out: return rebate * exp(-r*tau) - elif s > ko and k > ko and tau > 0: + elif s > knock_out and k > knock_out and tau > 0: return A - B + C - D + F - elif s > ko and k > ko and tau == 0: + elif s > knock_out and k > knock_out and tau == 0: return _compute_value(char, tau, vol, k, s, r, payoff) - elif s > ko and k < ko and tau > 0: + elif s > knock_out and k < knock_out and tau > 0: return F - elif s > ko and k < ko and tau == 0: + elif s > knock_out and k < knock_out and tau == 0: return _compute_value(char, tau, vol, k, s, r, payoff) ########################################################################## @@ -588,70 +534,38 @@ def digital_greeks(char, k, dbar, tau, vol, vol2, s, r, product, payoff, lots): ##################### Greek-related formulas ################################ ############################################################################# def _compute_greeks(char, K, tau, vol, s, r, product, payoff, lots, - ki=None, ko=None, barrier=None, direction=None, - order=None, bvol=None, bvol2=None, dbarrier=None): - """ Wrapper method. Filters for the necessary condition, and feeds - inputs to the relevant computational engine. Computes the greeks of - various option profiles. Currently, american and european greeks and - pricing are assumed to be the same. - - Inputs: 1) char : call or put - 2) K : strike - 3) tau : time to expiry - 4) vol : volatility (sigma) - 5) s : price of underlying - 6) r : interest - 7) product: underlying commodity. - 8) payoff : american or european option. - 9) lots : number of lots. - 10) barrier: american or european barrier. - 11) ki : knockin barrier level - 12) ko : knockout barrier level - 13) direction : direction (up or down) - 14) order : - 15) bvol : - 16) bvol2 : vol of the digital barrier used in european barrier pricing. - 17) dbarrier: digital barrier used in european barrier pricing. - - Outputs: 1) delta : dC/dS - 2) gamma : d^2C/dS^2 - 3) theta : dC/dt - 4) vega : dC/dvol - """ + knock_in=None, knock_out=None, barrier=None, + direction=None, order=None, barrier_vol=None, + barrier_vol2=None, dbarrier=None): + '''Wrapper that computes greeks for various option profiles.''' - # european options if tau == 0: - # print('tau == 0 case') gamma, theta, vega = 0, 0, 0 if char == 'call': - # in the money delta = 1 if K < s else 0 if char == 'put': delta = -1 if K > s else 0 return delta, gamma, theta, vega if payoff == 'euro' or payoff == 'amer': - # vanilla case if barrier is None: - # print('vanilla case') return _euro_vanilla_greeks( char, K, tau, vol, s, r, product, lots) elif barrier == 'amer': - # print('amer barrier case') - # greeks for european options with american barrier. return _euro_barrier_amer_greeks(char, tau, vol, K, s, r, payoff, - direction, product, ki, ko, lots) + direction, product, knock_in, + knock_out, lots) elif barrier == 'euro': - # print('euro barrier case') - # greeks for european options with european barrier. - if dbarrier is None: - # print('dbarrier is None; computing') - barlevel = ki if ki is not None else ko + if dbarrier is None: + barlevel = knock_in if knock_in is not None else knock_out ticksize = multipliers[product][-3] dbarrier = barlevel - ticksize if direction == 'up' else barlevel + ticksize return _euro_barrier_euro_greeks(char, tau, vol, K, s, r, payoff, - direction, product, ki, ko, lots, - order=order, bvol=bvol, bvol2=bvol2, + direction, product, knock_in, + knock_out, lots, + order=order, + barrier_vol=barrier_vol, + barrier_vol2=barrier_vol2, dbarrier=dbarrier) @@ -729,7 +643,7 @@ def _amer_vanilla_greeks(char, K, tau, vol, s, r, product, lots): def _euro_barrier_amer_greeks(char, tau, vol, k, s, r, payoff, direction, - product, ki, ko, lots, rebate=0): + product, knock_in, knock_out, lots, rebate=0): """Computes greeks of european options with american barriers. Args: @@ -759,14 +673,14 @@ def _euro_barrier_amer_greeks(char, tau, vol, k, s, r, payoff, direction, # computing delta # char, tau, vol, k, s, r, payoff, direction, ki, ko, rebate=0 - init = _barrier_amer(char, tau, vol, k, s, - r, payoff, direction, ki, ko) + init = _barrier_amer(char, tau, vol, k, s, + r, payoff, direction, knock_in, knock_out) del1 = _barrier_amer(char, tau, vol, k, s+change_spot, - r, payoff, direction, ki, ko) + r, payoff, direction, knock_in, knock_out) del2 = _barrier_amer(char, tau, vol, k, max(0, s-change_spot), - r, payoff, direction, ki, ko) + r, payoff, direction, knock_in, knock_out) delta = (del1 - del2)/(2*change_spot) # computing gamma @@ -775,18 +689,18 @@ def _euro_barrier_amer_greeks(char, tau, vol, k, s, r, payoff, direction, # computing vega v1 = _barrier_amer(char, tau, vol+change_vol, k, s, r, - payoff, direction, ki, ko) + payoff, direction, knock_in, knock_out) tvol = max(0, vol - change_vol) v2 = _barrier_amer(char, tau, tvol, k, s, r, - payoff, direction, ki, ko) + payoff, direction, knock_in, knock_out) vega = (v1 - v2)/(2*change_vol) if tau > 0 else 0 # computing theta t1 = init ctau = 0.0001 if tau-change_tau <= 0 else tau-change_tau t2 = _barrier_amer(char, ctau, vol, k, s, r, - payoff, direction, ki, ko) + payoff, direction, knock_in, knock_out) theta = (t2 - t1)/change_tau if tau > 0 else 0 # scaling greeks to retrieve dollar value. delta, gamma, theta, vega = greeks_scaled( @@ -796,54 +710,26 @@ def _euro_barrier_amer_greeks(char, tau, vol, k, s, r, payoff, direction, # NOTE: follows PnP implementation. Only supports ECUO, ECUI, EPDO, EPDI def _euro_barrier_euro_greeks(char, tau, vol, k, s, r, payoff, direction, - product, ki, ko, lots, order=None, rebate=0, bvol=None, - bvol2=None, dbarrier=None): - """Computes greeks of european options with american barriers. - - Args: - char (str): Call or Put. - tau (double): time to expiry in years - vol (float): strike volatility - k (double): strike - s (double): price of underlying_id - r (double): interest rate - payoff (str): american or european exercise. 'amer' or 'euro.' - direction (str): direction of the barrier. 'up' or 'down' - product (str): underlying product. eg: 'C' - ki (double): knock-in value. - ko (double): knock-out value - lots (double): number of lots. - order (int, optional): C1 C2 etc. - rebate (int, optional): payback if option fails to knock in / knocks out. - bvol (None, optional): barrier - bvol2 (TYPE, optional): Description - dbarrier (TYPE, optional): Description - - Returns: - delta, gamma, theta, vega: greeks of this instrument. - """ - barlevel = ki if ki else ko - - # print(tau, vol, k, s, r, direction, ki, ko, lots, bvol, bvol2, dbarrier) - - if dbarrier is None: - # print('dbarrier is None; computing') - barlevel = ki if ki is not None else ko + product, knock_in, knock_out, lots, order=None, + rebate=0, barrier_vol=None, barrier_vol2=None, + dbarrier=None): + '''Computes greeks of european options with european barriers.''' + barlevel = knock_in if knock_in else knock_out + + if dbarrier is None: + barlevel = knock_in if knock_in is not None else knock_out ticksize = multipliers[product][-3] dbarrier = barlevel - ticksize if direction == 'up' else barlevel + ticksize - # case when barrier vol is not in vol surface; raise error. - # if bvol is None: - # raise ValueError('Improper Data: Barrier vol not on vol surface.') ticksize = multipliers[product][2] dpo = abs(k - barlevel) / ticksize - if ko: + if knock_out: g1 = np.array(_compute_greeks( char, k, tau, vol, s, r, product, payoff, lots)) g2 = np.array(_compute_greeks( - char, barlevel, tau, bvol, s, r, product, payoff, lots)) - # digital_greeks(char, k, dbar, tau, vol, vol2, s, r, product, payoff, lots): - g3 = np.array(digital_greeks(char, barlevel, dbarrier, tau, bvol, bvol2, + char, barlevel, tau, barrier_vol, s, r, product, payoff, lots)) + g3 = np.array(digital_greeks(char, barlevel, dbarrier, tau, + barrier_vol, barrier_vol2, s, r, product, payoff, lots)) * dpo greeks = g1 - g2 - g3 d = greeks[0] @@ -851,11 +737,12 @@ def _euro_barrier_euro_greeks(char, tau, vol, k, s, r, payoff, direction, t = greeks[2] v = greeks[3] - elif ki: + elif knock_in: g1 = np.array(_compute_greeks( - char, barlevel, tau, bvol, s, r, product, payoff, lots)) + char, barlevel, tau, barrier_vol, s, r, product, payoff, lots)) g2 = dpo * \ - np.array(digital_greeks(char, barlevel, dbarrier, tau, bvol, bvol2, + np.array(digital_greeks(char, barlevel, dbarrier, tau, + barrier_vol, barrier_vol2, s, r, product, payoff, lots)) greeks = g1 + g2 d = greeks[0] @@ -909,7 +796,7 @@ def compute_strike_from_delta(option, delta1=None, vol=None, s=None, tau=None, c delta = 1e-5 if delta == 0 else delta delta = 0.99 if delta == 1 else delta # find strike corresponding to this delta in prev_date data - char = option.char if option else char + char = option.option_type if option else char D = norm.ppf(delta) if char == 'call' else -norm.ppf(delta) if np.isnan(D): print('[dvol 2] D IS NAN') diff --git a/scripts/classes.py b/scripts/classes.py index 75584f2..0907d7c 100644 --- a/scripts/classes.py +++ b/scripts/classes.py @@ -10,169 +10,88 @@ """ -# lots = 10 import numpy as np - -multipliers = { - 'LH': [22.046, 18.143881, 0.025, 1, 400], - 'LSU': [1, 50, 0.1, 10, 50], - 'QC': [1.2153, 10, 1, 25, 12.153], - 'SB': [22.046, 50.802867, 0.01, 0.25, 1120], - 'CC': [1, 10, 1, 50, 10], - 'CT': [22.046, 22.679851, 0.01, 1, 500], - 'KC': [22.046, 17.009888, 0.05, 2.5, 375], - 'W': [0.3674333, 136.07911, 0.25, 10, 50], - 'S': [0.3674333, 136.07911, 0.25, 10, 50], - 'C': [0.393678571428571, 127.007166832986, 0.25, 10, 50], - 'BO': [22.046, 27.215821, 0.01, 0.5, 600], - 'LC': [22.046, 18.143881, 0.025, 1, 400], - 'LRC': [1, 10, 1, 50, 10], - 'KW': [0.3674333, 136.07911, 0.25, 10, 50], - 'SM': [1.1023113, 90.718447, 0.1, 5, 100], - 'COM': [1.0604, 50, 0.25, 2.5, 53.02], - 'CA': [1.0604, 50, 0.25, 1, 53.02], - 'MW': [0.3674333, 136.07911, 0.25, 10, 50] -} +from .constants import multipliers +from .calc import _compute_greeks, _compute_value, _compute_iv class Option: - """ - Attributes: - - -- Implementation - specific attributes -- - - active (bool): True if option contributes greeks, false otherwise. - barrier (str/None): three potential inputs: None, amer or euro (for vanilla, american and european barriers respectively) - dbarrier (float/None): used for European barriers. this is the value of the secondary digital strike based on which the - digital component is priced. - bullet (bool): True if option has bullet payoff, False if option is Daily - desc (str): description. defaults to 'option'. - direc (str): 'up' or 'down'. to be used for barrier options. - expired (bool): True if option has expired, False otherwise. - knockedin (bool): True if option is has a knock-in barrier that has been breached, False otherwise. - knockedout (bool): True if option has a knock-out barrier that has been breached, False otherwise. - ko (float): Knock-out barrier level. - ki (float): Knock-in barrier level. - partners (set): Set containing other option objects for composite actions. if this option object is contract or delta-rolled, all options - in this set are also contract/delta rolled. - strike_type (str): callstrike or putstrike, depending on relationship of strike to current spot price. - - ----------------------------------------- - - -- Conventional Attributes -- - - K (float): strike - lots (float): lottage - month (str): Option month. The first component of C H8.Z8. - ordering (int): C1, C2 etc. - payoff (str): amer/euro. currently doesn't matter, since all options are evaluated like european options. - price (float): option price, computed using black scholes. - product (str): Product this option is on. - r (int): interest rate, defaults to 0 - rebate (float): rebate value payed when a barrier is breached. defaults to 0. - settlement (str): cash or future - shorted (bool): True if short position, False otherwise. - bvol (float): Barrier vol - bvol2 (float): barrier vol of the second ticksize-differing strike. used only for euro barriers. - char (str): call or put - tau (float): time to maturity in years. - underlying (object): Underlying Futures object. - vol (float): strike vol - delta (float): delta of this option (between 0 and 1) - gamma (TYPE): gamma - theta (TYPE): theta - vega (TYPE): vega - - ---------------------------------------- - - Notes: - 1_ ki, ko, bullet, direc and barrier default to None and must be - expressly overridden if an exotic option is desired. - 2_ knockedin and knockedout should be set ONLY for options that - are knockin and knockout options respectively. the program assumes - this assignment is done correctly. - 3_ greeks are computed using black scholes, unmodified. - - """ - - def __init__(self, strike, tau, char, vol, underlying, payoff, shorted, - month, direc=None, barrier=None, lots=1000, bullet=True, - ki=None, ko=None, rebate=0, ordering=1e5, settlement='futures', - bvol=None, bvol2=None, dailies=None): + def __init__(self, strike: float, tau: float, option_type: str, vol: float, + underlying: 'Future', payoff: str, shorted: bool, month: str, + direction: str = None, barrier: str = None, lots: float = 1000, + bullet: bool = True, knock_in: float = None, + knock_out: float = None, rebate: float = 0, + ordering: float = 1e5, settlement: str = 'futures', + barrier_vol: float = None, barrier_vol2: float = None, + dailies: list = None) -> None: self.month = month self.barrier = barrier self.payoff = payoff self.underlying = underlying - self.bullet = bullet # daily = list of bullets. + self.bullet = bullet self.dailies = dailies - # get the ttm list of all constituent daily options if this option is daily. self.lots = lots self.desc = 'option' - self.ki = ki - self.ko = ko - # defaults to None. Is set upon first check_active call. + self.knock_in = knock_in + self.knock_out = knock_out self.knockedout = None - # defaults to None. Is set upon first check_active call. self.knockedin = None - self.char = char - self.K = strike + self.option_type = option_type + self.strike = strike self.dbarrier = None - # set the digital barrier, if applicable. if self.barrier == 'euro': - mult = -1 if char == 'call' else 1 - product = self.underlying.get_product() + mult = -1 if option_type == 'call' else 1 + product = self.underlying.get_product() ticksize = multipliers[product][2] - barlevel = ki if ki is not None else ko + barlevel = knock_in if knock_in is not None else knock_out self.dbarrier = barlevel + mult * ticksize - self.direc = direc + self.direction = direction self.tau = tau - self.bvol = bvol - self.bvol2 = bvol2 + self.barrier_vol = barrier_vol + self.barrier_vol2 = barrier_vol2 self.vol = vol - self.r = 0 self.shorted = shorted self.price = self.get_price() self.ordering = ordering self.init_greeks() self.active = self.check_active() - self.expired = False # defaults to false. + self.expired = False self.rebate = rebate self.product = self.get_product() self.settlement = settlement - self.strike_type = 'callstrike' if self.K >= self.underlying.get_price() else 'putstrike' + self.strike_type = 'callstrike' if self.strike >= self.underlying.get_price() else 'putstrike' self.partners = set() - self.prev_dailies = None + self.prev_dailies = None - def __str__(self): + def __str__(self) -> str: string = '<<' string += self.product + ' ' + self.month + \ '.' + self.underlying.get_month() + ' ' - string += str(self.K) + ' ' + string += str(self.strike) + ' ' if self.barrier is None: string += 'Vanilla ' elif self.barrier == 'euro': string += 'E' - string += 'C' if self.char == 'call' else 'P' - if self.ki: - if self.direc == 'up': + string += 'C' if self.option_type == 'call' else 'P' + if self.knock_in: + if self.direction == 'up': string += 'UI ' - if self.direc == 'down': + if self.direction == 'down': string += 'DI ' - string += str(self.ki) - if self.ko: - if self.direc == 'up': + string += str(self.knock_in) + if self.knock_out: + if self.direction == 'up': string += 'UO' - if self.direc == 'down': + if self.direction == 'down': string += 'DO' - string += ' ' + str(self.ko) - # price = self.get_price() + string += ' ' + str(self.knock_out) string += ' S ' if self.shorted else ' L ' string += str(self.underlying.get_price()) - mult = -1 if self.shorted else 1 + mult = -1 if self.shorted else 1 string += ' | lots - ' + str(int(self.lots*mult)) + ' |' string += ' ttm - ' + str(round(self.tau * 365)) + ' |' string += ' order - [c_' + str(self.ordering) + '] |' @@ -180,276 +99,225 @@ def __str__(self): string += ' delta - ' + str(abs(self.delta / self.lots)) + ' |' string += ' vol - ' + str(self.vol) + ' |' string += ' bvol - ' - string += str(self.bvol) if self.bvol is not None else 'None' + string += str(self.barrier_vol) if self.barrier_vol is not None else 'None' string += ' | ' - string += ' | dbarrier - %s | ' % self.dbarrier - string += 'bvol2 - ' + str(self.bvol2) if self.bvol2 is not None else 'None' + string += ' | dbarrier - %s | ' % self.dbarrier + string += 'bvol2 - ' + str(self.barrier_vol2) if self.barrier_vol2 is not None else 'None' string += ' | ' string += ' len_ttms - %s | ' % len(self.dailies) if self.dailies is not None else '0' string += ' | strike type: ' + str(self.strike_type) + ' ' string += '>>' return string - def set_strike(self, strike): - self.K = strike + def set_strike(self, strike: float) -> None: + self.strike = strike self.update() - def set_partners(self, ops): + def set_partners(self, ops: set) -> None: self.partners = ops - def set_ordering(self, val): + def set_ordering(self, val: float) -> None: self.ordering = val - def get_ordering(self): + def get_ordering(self) -> float: return self.ordering - def decrement_ordering(self, i): + def decrement_ordering(self, i: float) -> None: self.ordering -= i - # check expiration self.expired = self.check_expired() - def get_op_month(self): + def get_op_month(self) -> str: return self.month - def update_bvol(self, vol, vol2=None): - self.bvol = vol + def update_bvol(self, vol: float, vol2: float = None) -> None: + self.barrier_vol = vol if vol2 is not None: - self.bvol2 = vol2 + self.barrier_vol2 = vol2 - def get_greek(self, name): + def get_greek(self, name: str) -> float: if name == 'delta': - return self.delta + return self.delta if name == 'vega': - return self.vega + return self.vega if name == 'gamma': - return self.gamma + return self.gamma if name == 'theta': return self.theta - def get_m2m(self): + def get_m2m(self) -> float: mult = -1 if self.shorted else 1 if self.is_bullet(): return self.lots * self.price * multipliers[self.product][-1] * mult else: return self.lots * multipliers[self.product][-1] * (self.price * len(self.get_ttms())) * mult - def get_ttms(self): + def get_ttms(self) -> list: return self.dailies - def set_ttms(self, lst): - self.dailies = lst + def set_ttms(self, lst: list) -> None: + self.dailies = lst - def is_bullet(self): + def is_bullet(self) -> bool: return self.bullet - def check_active(self): - """Checks to see if this option object is active, i.e. if it has any value/contributes greeks. - Cases are as follows: - 1) Knock-in barrier options are considered always active until expiry. - 2) Knock-out options with an american barrier are considered inactive when barrier is hit. - 3) Knock-out options with a European barrier are considered always active until expiry. - 4) Vanilla options are always active until expiry. - """ - s = self.underlying.get_price() + def check_active(self) -> bool: + spot = self.underlying.get_price() expired = self.check_expired() - # expired case if expired: return False - # base cases: if already knocked in or knocked out, return - # appropriately. if self.knockedin: return True if self.knockedout: - # first case: check for expiry. if self.tau == 0: return False - # second: american barrier. ko = deactivate. elif self.barrier == 'amer': return False - # final case: Euro barrier. active till exp. else: return True - # barrier cases - if self.ki: - # all knockin options contribute greeks/have value until expiry. - active = True if not expired else False - if self.direc == 'up': - self.knockedin = True if (s >= self.ki) else False - if self.direc == 'down': - self.knockedin = True if (s <= self.ki) else False - # if AKI is hit, set to vanilla parameters. - if self.barrier == 'amer': - if self.knockedin: - self.ki, self.ko, self.barrier, self.direc = None, None, None, None - - if self.ko: + if self.knock_in: + active = not expired + if self.direction == 'up': + self.knockedin = (spot >= self.knock_in) + if self.direction == 'down': + self.knockedin = (spot <= self.knock_in) + if self.barrier == 'amer' and self.knockedin: + self.knock_in, self.knock_out, self.barrier, self.direction = None, None, None, None + + if self.knock_out: if self.barrier == 'amer': - # american up and out - if self.direc == 'up': - active = False if s >= self.ko else True - # american down and out - if self.direc == 'down': - active = False if s <= self.ko else True + if self.direction == 'up': + active = spot < self.knock_out + if self.direction == 'down': + active = spot > self.knock_out self.knockedout = not active - # if knocked out, remove all elements from self.dailies if self.knockedout: self.dailies = [] self.expired = True - - # european knockout are active until expiry. elif self.barrier == 'euro': - active = True if not expired else False - # european up and out - if self.direc == 'up': - # print('Euro Up Out Hit') - self.knockedout = True if (s >= self.ko) else False - # european down and out - if self.direc == 'down': - self.knockedout = True if (s <= self.ko) else False + active = not expired + if self.direction == 'up': + self.knockedout = (spot >= self.knock_out) + if self.direction == 'down': + self.knockedout = (spot <= self.knock_out) else: - # vanilla case. true till expiry - active = True if not expired else False + active = not expired return active - def get_underlying(self): + def get_underlying(self) -> 'Future': return self.underlying - def get_month(self): + def get_month(self) -> str: return self.underlying.get_month() - def get_desc(self): + def get_desc(self) -> str: return self.desc - def init_greeks(self): - from .calc import _compute_greeks - # initializes relevant greeks. only used once, when initializing Option - # object. - delta, gamma, theta, vega = 0, 0, 0, 0 + def _sum_greeks_over_ttms(self, vol: float, barrier_vol: float, + barrier_vol2: float) -> tuple: product = self.get_product() - # print(product) - s = self.underlying.get_price() - # print(s) + spot = self.underlying.get_price() + ttms = [self.tau] if self.bullet else self.dailies + d, g, t, v = 0, 0, 0, 0 + for ttm in ttms: + delta, gamma, theta, vega = \ + _compute_greeks(self.option_type, self.strike, ttm, vol, + spot, 0, product, self.payoff, self.lots, + knock_in=self.knock_in, knock_out=self.knock_out, + barrier=self.barrier, direction=self.direction, + order=self.ordering, barrier_vol=barrier_vol, + barrier_vol2=barrier_vol2, + dbarrier=self.dbarrier) + d += delta + g += gamma + t += theta + v += vega + return d, g, t, v + + def init_greeks(self) -> None: try: - ttms = [self.tau] if self.bullet else self.dailies - for ttm in ttms: - d, g, t, v = \ - _compute_greeks(self.char, self.K, self.tau, self.vol, - s, self.r, product, self.payoff, self.lots, - ki=self.ki, ko=self.ko, barrier=self.barrier, - direction=self.direc, order=self.ordering, - bvol=self.bvol, bvol2=self.bvol2, - dbarrier=self.dbarrier) - delta += d - gamma += g - theta += t - vega += v + delta, gamma, theta, vega = self._sum_greeks_over_ttms( + self.vol, self.barrier_vol, self.barrier_vol2) except TypeError as e: - print('char: ', self.char) - print('strike: ', self.K) - print('tau: ', self.tau) - print('vol: ', self.vol) - print('s: ', s) - print('r: ', self.r) - print('product: ', product) - print('payoff: ', self.payoff) - print('ki: ', self.ki) - print('ko: ', self.ko) - print('barrier: ', self.barrier) - print('direction: ', self.direc) - raise TypeError(getattr(e, 'message')) from e + raise TypeError(str(e)) from e if self.shorted: - # print('shorted!') delta, gamma, theta, vega = -delta, -gamma, -theta, -vega self.delta = delta self.gamma = gamma self.theta = theta self.vega = vega - # return delta, gamma, theta, vega - def update_greeks(self, vol=None, bvol=None, bvol2=None): - from .calc import _compute_greeks - # method that updates greeks given new values of s, vol and tau, and subsequently updates value. - # used in passage of time step. - sigma, b_sigma, b_sigma2 = None, None, None + def update_greeks(self, vol: float = None, barrier_vol: float = None, + barrier_vol2: float = None) -> None: active = self.check_active() self.active = active if active: sigma = vol if vol is not None else self.vol - b_sigma = bvol if bvol is not None else self.bvol - b_sigma2 = bvol2 if bvol2 is not None else self.bvol2 - - product = self.get_product() - s = self.underlying.get_price() - - d, g, t, v = 0,0,0,0 - ttms = [self.tau] if self.bullet else self.dailies + b_sigma = barrier_vol if barrier_vol is not None else self.barrier_vol + b_sigma2 = barrier_vol2 if barrier_vol2 is not None else self.barrier_vol2 - # print('b_sigma: ', b_sigma) - # print('b_sigma2: ', b_sigma2) + d, g, t, v = 0, 0, 0, 0 + ttms = [self.tau] if self.bullet else self.dailies + product = self.get_product() + spot = self.underlying.get_price() for tau in ttms: delta, gamma, theta, vega = \ - _compute_greeks(self.char, self.K, tau, sigma, - s, self.r, product, self.payoff, - self.lots, ki=self.ki, ko=self.ko, - barrier=self.barrier, direction=self.direc, - order=self.ordering, bvol=b_sigma, bvol2=b_sigma2, + _compute_greeks(self.option_type, self.strike, tau, sigma, + spot, 0, product, self.payoff, + self.lots, knock_in=self.knock_in, + knock_out=self.knock_out, + barrier=self.barrier, direction=self.direction, + order=self.ordering, barrier_vol=b_sigma, + barrier_vol2=b_sigma2, dbarrier=self.dbarrier) - # account for shorting if self.shorted: delta, gamma, theta, vega = -delta, -gamma, -theta, -vega - - d += delta - g += gamma - t += theta - v += vega + d += delta + g += gamma + t += theta + v += vega self.delta, self.gamma, self.theta, self.vega = d, g, t, v - self.vol = sigma - self.bvol = b_sigma - self.bvol2 = b_sigma2 - + self.barrier_vol = b_sigma + self.barrier_vol2 = b_sigma2 self.price = self.compute_price() - self.strike_type = 'callstrike' if self.K >= s else 'putstrike' + self.strike_type = 'callstrike' if self.strike >= spot else 'putstrike' else: self.zero_option() - def greeks(self): - # getter method for greeks. preserves abstraction barrier. updates just - # in case price of underlying has changed. + def greeks(self) -> tuple: self.update_greeks() return self.delta, self.gamma, self.theta, self.vega - def compute_vol(self, underlying, price, strike, tau, r): - # computes implied vol from market price data. only holds for vanilla - # options. - from .calc import _compute_iv + def compute_vol(self, underlying: float, price: float, strike: float, + tau: float, r: float) -> float: product = self.get_product() if self.barrier is None: return _compute_iv(underlying, price, strike, tau, r, product) - def compute_price(self): - from .calc import _compute_value - # computes the value of this structure from relevant information. - ttms = [self.tau] if self.bullet else self.dailies + def compute_price(self) -> float: + ttms = [self.tau] if self.bullet else self.dailies s = self.underlying.get_price() product = self.underlying.get_product() - val = 0 + val = 0 for tau in ttms: - val += _compute_value(self.char, tau, self.vol, self.K, s, self.r, - self.payoff, ki=self.ki, ko=self.ko, barrier=self.barrier, - d=self.direc, product=product, bvol=self.bvol, - bvol2=self.bvol2, dbarrier=self.dbarrier) - # handle the edge case where self.ttms is empty in the event of a daily KO - val = val/len(ttms) if val != 0 else 0 + val += _compute_value(self.option_type, tau, self.vol, self.strike, + s, 0, self.payoff, + knock_in=self.knock_in, knock_out=self.knock_out, + barrier=self.barrier, + d=self.direction, product=product, + barrier_vol=self.barrier_vol, + barrier_vol2=self.barrier_vol2, + dbarrier=self.dbarrier) + val = val/len(ttms) if val != 0 else 0 self.price = val return val - def get_price(self): + def get_price(self) -> float: active = self.check_active() self.active = active if self.active: @@ -459,153 +327,100 @@ def get_price(self): else: return 0 - def update_tau(self, diff): + def update_tau(self, diff: float) -> None: self.tau -= diff if not self.bullet: if diff >= 0: - self.prev_dailies = self.dailies - self.dailies = [x - diff if x-diff > 0 else 0 - for x in self.dailies] + self.prev_dailies = self.dailies + self.dailies = [x - diff if x-diff > 0 else 0 + for x in self.dailies] else: - # this is only ever called when reversing a timestep. if self.prev_dailies is not None: - self.dailies = self.prev_dailies - self.prev_dailies = None - - def remove_expired_dailies(self): + self.dailies = self.prev_dailies + self.prev_dailies = None + + def remove_expired_dailies(self) -> None: self.dailies = [x for x in self.dailies if not np.isclose(x, 0)] - - def get_product(self): + + def get_product(self) -> str: return self.underlying.get_product() - def exercise(self): + def exercise(self) -> bool: worth = self.moneyness() - if (worth is not None) and (worth > 0): - return True - else: - return False + return (worth is not None) and (worth > 0) - def moneyness(self): - """Checks to see the 'moneyness' of the option. + def _is_itm(self, spot: float) -> bool: + if self.option_type == 'call': + return self.strike < spot + return self.strike > spot - Returns: - int: returns 1, 0, -1 for ITM, ATM and OTM options respectively, - pr None if check_active() returns False - """ + def moneyness(self) -> int: active = self.check_active() - # degenerate case: knocked out. if self.knockedout: return -1 self.active = active - if active: - s = self.underlying.get_price() - # vanilla option case. - # at the money - if self.K == s: - return 0 - # call - if self.char == 'call': - if self.barrier is None: - return 1 if self.K < s else -1 - # KO barrier case. - if self.ko is not None: - if self.knockedout: - return -1 - else: - return 1 if self.K < s else -1 - # KI case. - if self.ki is not None: - if self.knockedin: - return 1 if self.K < s else -1 - else: - return -1 - - elif self.char == 'put': - if self.barrier is None: - return 1 if self.K > s else -1 - # KO barrier case. - if self.ko is not None: - if self.knockedout: - return -1 - else: - return 1 if self.K > s else -1 - # KI case. - if self.ki is not None: - if self.knockedin: - return 1 if self.K > s else -1 - else: - return -1 - else: + if not active: + return -1 + spot = self.underlying.get_price() + if self.strike == spot: + return 0 + if self.knock_out is not None and self.knockedout: + return -1 + if self.knock_in is not None and not self.knockedin: return -1 + return 1 if self._is_itm(spot) else -1 - def update(self): + def update(self) -> None: self.update_greeks() self.price = self.get_price() - self.strike_type = 'callstrike' if self.K >= self.underlying.get_price() else 'putstrike' + self.strike_type = 'callstrike' if self.strike >= self.underlying.get_price() else 'putstrike' - def zero_option(self): - # check to see if the option is in the money. + def zero_option(self) -> None: self.delta, self.gamma, self.theta, self.vega = 0, 0, 0, 0 if self.exercise(): - if self.char == 'call' and self.K < self.underlying.get_price(): - self.delta = 1 if not self.shorted else -1 - if self.char == 'put' and self.K > self.underlying.get_price(): + if self.option_type == 'call' and self.strike < self.underlying.get_price(): + self.delta = 1 if not self.shorted else -1 + if self.option_type == 'put' and self.strike > self.underlying.get_price(): self.delta = -1 if not self.shorted else 1 - self.delta *= self.lots - - def check_expired(self): + self.delta *= self.lots + + def check_expired(self) -> bool: if self.bullet: - ret = True if (np.isclose(self.tau, 0) or self.tau <= - 0) else False + ret = np.isclose(self.tau, 0) or self.tau <= 0 else: - ret = True if not self.dailies else False + ret = not self.dailies self.expired = ret return ret - def update_lots(self, lots): + def update_lots(self, lots: float) -> None: self.lots = lots self.update_greeks() - def get_strike_type(self): + def get_strike_type(self) -> str: return self.strike_type - def get_vol_id(self): + def get_vol_id(self) -> str: return self.get_product() + ' ' + self.get_op_month() + '.' + self.get_month() - def get_uid(self): + def get_uid(self) -> str: return self.underlying.get_uid() - def get_properties(self): + def get_properties(self) -> dict: return {'month': self.month, 'barrier': self.barrier, 'payoff': self.payoff, - 'underlying': self.underlying, 'lots': self.lots, 'ki': self.ki, - 'ko': self.ko, 'direc': self.direc, 'strike': self.K, 'char': self.char, - 'vol': self.vol, 'shorted': self.shorted, 'ordering': self.ordering, - 'rebate': self.rebate, 'bvol': self.bvol, 'bvol2': self.bvol2, 'settlement': self.settlement} + 'underlying': self.underlying, 'lots': self.lots, + 'knock_in': self.knock_in, 'knock_out': self.knock_out, + 'direction': self.direction, 'strike': self.strike, + 'option_type': self.option_type, 'vol': self.vol, + 'shorted': self.shorted, 'ordering': self.ordering, + 'rebate': self.rebate, 'barrier_vol': self.barrier_vol, + 'barrier_vol2': self.barrier_vol2, 'settlement': self.settlement} class Future: - ''' - Class representing a Future object. Instance variables are: - 1) month : the contract month. - 2) price : the quoted price of the future. - 3) desc : string description of the object - 4) lots : number of lots represented by each future contract. - 5) product : the commodity of this future. - 6) shorted : bool indicating whether this future is being shorted or long - 7) delta : delta contribution of this future. 1 if shorted=False, -1 otherwise. - - Instance Methods: - 1) get_desc : returns 'future' - 2) get_price : returns price of the future. - 3) update_price : updates the price based on inputted data. - 5) get_month : returns contract month. - 6) get_lots : returns lot size - 7) get_product : returns the name of this contract (i.e. the commodity) - - ''' - - def __init__(self, month, price, product, shorted=None, lots=1000, ordering=None, instructions={}): + def __init__(self, month: str, price: float, product: str, + shorted: bool = None, lots: float = 1000, + ordering: int = None) -> None: self.product = product self.ordering = ordering self.lots = lots @@ -619,7 +434,7 @@ def __init__(self, month, price, product, shorted=None, lots=1000, ordering=None mult = -1 if shorted else 1 self.delta = 1 * lots * mult - def __str__(self): + def __str__(self) -> str: string = self.product + ' ' + self.month + ' ' string += str(self.price) string += ' S' if self.shorted else ' L' @@ -627,41 +442,40 @@ def __str__(self): string += ' [c_' + str(self.ordering) + ']' return string - def get_ordering(self): + def get_ordering(self) -> int: return self.ordering - def set_ordering(self, i): + def set_ordering(self, i: int) -> None: self.ordering = i - def decrement_ordering(self, i): + def decrement_ordering(self, i: int) -> None: self.ordering -= i - def get_price(self): + def get_price(self) -> float: return self.price - def get_desc(self): + def get_desc(self) -> str: return self.desc - def update_price(self, price): - # updates the price of the future object + def update_price(self, price: float) -> None: self.price = price - def get_month(self): + def get_month(self) -> str: return self.month - def get_lots(self): + def get_lots(self) -> float: return self.lots - def get_product(self): + def get_product(self) -> str: return self.product - def update_lots(self, lots): + def update_lots(self, lots: float) -> None: self.lots = lots mult = -1 if self.shorted else 1 self.delta = 1 * lots * mult - def get_delta(self): + def get_delta(self) -> float: return self.delta - def get_uid(self): + def get_uid(self) -> str: return self.product + ' ' + self.month diff --git a/scripts/constants.py b/scripts/constants.py new file mode 100644 index 0000000..ef842d7 --- /dev/null +++ b/scripts/constants.py @@ -0,0 +1,117 @@ +""" +Centralized constants for the portfolio simulation system. + +This module is the single source of truth for multipliers, contract months, +month/symbol mappings, tick sizes, and other constants that were previously +duplicated across multiple files. +""" + +from typing import NamedTuple, Dict +from enum import Enum + + +class ProductSpec(NamedTuple): + dollar_mult: float + lot_mult: float + futures_tick: float + options_tick: float + pnl_mult: float + + +multipliers: Dict[str, ProductSpec] = { + 'LH': ProductSpec(22.046, 18.143881, 0.025, 1, 400), + 'LSU': ProductSpec(1, 50, 0.1, 10, 50), + 'QC': ProductSpec(1.2153, 10, 1, 25, 12.153), + 'SB': ProductSpec(22.046, 50.802867, 0.01, 0.25, 1120), + 'CC': ProductSpec(1, 10, 1, 50, 10), + 'CT': ProductSpec(22.046, 22.679851, 0.01, 1, 500), + 'KC': ProductSpec(22.046, 17.009888, 0.05, 2.5, 375), + 'W': ProductSpec(0.3674333, 136.07911, 0.25, 10, 50), + 'S': ProductSpec(0.3674333, 136.07911, 0.25, 10, 50), + 'C': ProductSpec(0.393678571428571, 127.007166832986, 0.25, 10, 50), + 'BO': ProductSpec(22.046, 27.215821, 0.01, 0.5, 600), + 'LC': ProductSpec(22.046, 18.143881, 0.025, 1, 400), + 'LRC': ProductSpec(1, 10, 1, 50, 10), + 'KW': ProductSpec(0.3674333, 136.07911, 0.25, 10, 50), + 'SM': ProductSpec(1.1023113, 90.718447, 0.1, 5, 100), + 'COM': ProductSpec(1.0604, 50, 0.25, 2.5, 53.02), + 'CA': ProductSpec(1.0604, 50, 0.25, 1, 53.02), + 'MW': ProductSpec(0.3674333, 136.07911, 0.25, 10, 50), +} + +# Dictionary mapping month number to symbol and vice versa +month_to_sym = {1: 'F', 2: 'G', 3: 'H', 4: 'J', 5: 'K', 6: 'M', + 7: 'N', 8: 'Q', 9: 'U', 10: 'V', 11: 'X', 12: 'Z'} +sym_to_month = {'F': 1, 'G': 2, 'H': 3, 'J': 4, 'K': 5, + 'M': 6, 'N': 7, 'Q': 8, 'U': 9, 'V': 10, 'X': 11, 'Z': 12} + +# Contract months for each commodity +# NOTE: LC previously had a missing-comma bug ('V' 'Z' -> 'VZ'). Fixed here. +contract_mths = { + 'LH': ['G', 'J', 'K', 'M', 'N', 'Q', 'V', 'Z'], + 'LSU': ['H', 'K', 'Q', 'V', 'Z'], + 'QC': ['H', 'K', 'N', 'U', 'Z'], + 'SB': ['H', 'K', 'N', 'V'], + 'CC': ['H', 'K', 'N', 'U', 'Z'], + 'CT': ['H', 'K', 'N', 'Z'], + 'KC': ['H', 'K', 'N', 'U', 'Z'], + 'W': ['H', 'K', 'N', 'U', 'Z'], + 'S': ['F', 'H', 'K', 'N', 'Q', 'U', 'X'], + 'C': ['H', 'K', 'N', 'U', 'Z'], + 'BO': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], + 'LC': ['G', 'J', 'M', 'Q', 'V', 'Z'], + 'LRC': ['F', 'H', 'K', 'N', 'U', 'X'], + 'KW': ['H', 'K', 'N', 'U', 'Z'], + 'SM': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], + 'COM': ['G', 'K', 'Q', 'X'], + 'CA': ['H', 'K', 'U', 'Z'], + 'MW': ['H', 'K', 'N', 'U', 'Z'], +} + +# Options tick sizes (used for hedging strike selection) +op_ticksize = { + 'QC': 1, + 'CC': 1, + 'SB': 0.01, + 'LSU': 0.05, + 'KC': 0.01, + 'DF': 1, + 'CT': 0.01, + 'C': 0.125, + 'S': 0.125, + 'SM': 0.05, + 'BO': 0.005, + 'W': 0.125, + 'MW': 0.125, + 'KW': 0.125, +} + +RANDOM_SEED = 7 +DECADE = 10 +TIMESTEP = 1 / 365 +BREAKEVEN_FACTOR = 2.8 + + +class OptionType(str, Enum): + CALL = 'call' + PUT = 'put' + + +class BarrierStyle(str, Enum): + AMERICAN = 'amer' + EUROPEAN = 'euro' + + +class BarrierDirection(str, Enum): + UP = 'up' + DOWN = 'down' + + +class SecurityType(str, Enum): + OPTION = 'option' + FUTURE = 'future' + + +class PositionFlag(str, Enum): + OTC = 'OTC' + HEDGE = 'hedge' diff --git a/scripts/fetch_data.py b/scripts/fetch_data.py index 25f05e6..5946718 100644 --- a/scripts/fetch_data.py +++ b/scripts/fetch_data.py @@ -17,27 +17,7 @@ from joblib import Parallel, delayed import getpass -contract_mths = { - - 'LH': ['G', 'J', 'K', 'M', 'N', 'Q', 'V', 'Z'], - 'LSU': ['H', 'K', 'Q', 'V', 'Z'], - 'QC': ['H', 'K', 'N', 'U', 'Z'], - 'SB': ['H', 'K', 'N', 'V'], - 'CC': ['H', 'K', 'N', 'U', 'Z'], - 'CT': ['H', 'K', 'N', 'V', 'Z'], - 'KC': ['H', 'K', 'N', 'U', 'Z'], - 'W': ['H', 'K', 'N', 'U', 'Z'], - 'S': ['F', 'H', 'K', 'N', 'Q', 'U', 'X'], - 'C': ['H', 'K', 'N', 'U', 'Z'], - 'BO': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], - 'LC': ['G', 'J', 'M', 'Q', 'V' 'Z'], - 'LRC': ['F', 'H', 'K', 'N', 'U', 'X'], - 'KW': ['H', 'K', 'N', 'U', 'Z'], - 'SM': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], - 'COM': ['G', 'K', 'Q', 'X'], - 'CA': ['H', 'K', 'U', 'Z'], - 'MW': ['H', 'K', 'N', 'U', 'Z'] -} +from .constants import contract_mths def pull_settlement_data(pdt, start_date=None, end_date=None, write_dump=False, @@ -59,7 +39,7 @@ def pull_settlement_data(pdt, start_date=None, end_date=None, write_dump=False, assert (start_date is not None or end_date is not None) or write_dump print('starting clock..') - t = time.clock() + t = time.perf_counter() user = getpass.getuser() password = getpass.getpass() @@ -94,7 +74,7 @@ def pull_settlement_data(pdt, start_date=None, end_date=None, write_dump=False, # '_raw_data.csv', index=False) print('finished pulling data') - print('elapsed: ', time.clock() - t) + print('elapsed: ', time.perf_counter() - t) add = 1 if len(pdt) == 2 else 0 @@ -554,7 +534,7 @@ def pull_intraday_data(pdts, start_date=None, end_date=None, filepath='', contra """ overnight_pdts = {'BO', 'C', 'KW', 'S', 'SM', 'W', 'CT', 'MW'} - t = time.clock() + t = time.perf_counter() par = True if len(pdts) > 1 else False @@ -568,7 +548,7 @@ def pull_intraday_data(pdts, start_date=None, end_date=None, filepath='', contra overnight_pdts=overnight_pdts) for pdt in pdts) df = pd.concat(res) - print('elapsed: ', time.clock() - t) + print('elapsed: ', time.perf_counter() - t) return df diff --git a/scripts/hedge.py b/scripts/hedge.py index ab772f0..b353a96 100644 --- a/scripts/hedge.py +++ b/scripts/hedge.py @@ -10,45 +10,7 @@ from .util import create_straddle, create_underlying, create_strangle, create_vanilla_option from .calc import _compute_value from .hedge_mods import TrailingStop, HedgeParser - -multipliers = { - 'LH': [22.046, 18.143881, 0.025, 1, 400], - 'LSU': [1, 50, 0.1, 10, 50], - 'QC': [1.2153, 10, 1, 25, 12.153], - 'SB': [22.046, 50.802867, 0.01, 0.25, 1120], - 'CC': [1, 10, 1, 50, 10], - 'CT': [22.046, 22.679851, 0.01, 1, 500], - 'KC': [22.046, 17.009888, 0.05, 2.5, 375], - 'W': [0.3674333, 136.07911, 0.25, 10, 50], - 'S': [0.3674333, 136.07911, 0.25, 10, 50], - 'C': [0.393678571428571, 127.007166832986, 0.25, 10, 50], - 'BO': [22.046, 27.215821, 0.01, 0.5, 600], - 'LC': [22.046, 18.143881, 0.025, 1, 400], - 'LRC': [1, 10, 1, 50, 10], - 'KW': [0.3674333, 136.07911, 0.25, 10, 50], - 'SM': [1.1023113, 90.718447, 0.1, 5, 100], - 'COM': [1.0604, 50, 0.25, 2.5, 53.02], - 'CA': [1.0604, 50, 0.25, 1, 53.02], - 'MW': [0.3674333, 136.07911, 0.25, 10, 50] -} - -op_ticksize = { - - 'QC': 1, - 'CC': 1, - 'SB': 0.01, - 'LSU': 0.05, - 'KC': 0.01, - 'DF': 1, - 'CT': 0.01, - 'C': 0.125, - 'S': 0.125, - 'SM': 0.05, - 'BO': 0.005, - 'W': 0.125, - 'MW': 0.125, - 'KW': 0.125 -} +from .constants import multipliers, op_ticksize class Hedge: @@ -718,21 +680,21 @@ def hedge(self, flag, product, loc, greekval, target): if self.book: for op in ops: try: - cpi = 'C' if op.char == 'call' else 'P' + cpi = 'C' if op.option_type == 'call' else 'P' df = self.settlements settle_vol = df[(df.vol_id == op.get_vol_id()) & (df.call_put_id == cpi) & - (df.strike == op.K)].vol.values[0] + (df.strike == op.strike)].vol.values[0] except IndexError as e: print('scripts.hedge - book vol case: cannot find vol: ', - op.get_vol_id(), cpi, op.K) + op.get_vol_id(), cpi, op.strike) settle_vol = op.vol # print(op.get_vol_id() + ' settle_vol: ', settle_vol) # print('op.book vol: ', op.vol) - true_value = _compute_value(op.char, op.tau, settle_vol, op.K, - op.underlying.get_price(), 0, 'amer', ki=op.ki, - ko=op.ko, barrier=op.barrier, d=op.direc, - product=op.get_product(), bvol=op.bvol) + true_value = _compute_value(op.option_type, op.tau, settle_vol, op.strike, + op.underlying.get_price(), 0, 'amer', knock_in=op.knock_in, + knock_out=op.knock_out, barrier=op.barrier, d=op.direction, + product=op.get_product(), barrier_vol=op.barrier_vol) # print('op value basis settlements: ', true_value) pnl_mult = multipliers[op.get_product()][-1] diff = (true_value - op.get_price()) * op.lots * pnl_mult diff --git a/scripts/portfolio.py b/scripts/portfolio.py index 7f08fd8..5472eb0 100644 --- a/scripts/portfolio.py +++ b/scripts/portfolio.py @@ -10,42 +10,14 @@ """ -from timeit import default_timer as timer from operator import add import pprint import numpy as np from collections import deque import copy +from .constants import multipliers, RANDOM_SEED, BREAKEVEN_FACTOR - -# Dictionary of multipliers for greeks/pnl calculation. -# format = 'product' : [dollar_mult, lot_mult, futures_tick, -# options_tick, pnl_mult] - -multipliers = { - 'LH': [22.046, 18.143881, 0.025, 1, 400], - 'LSU': [1, 50, 0.1, 10, 50], - 'QC': [1.2153, 10, 1, 25, 12.153], - 'SB': [22.046, 50.802867, 0.01, 0.25, 1120], - 'CC': [1, 10, 1, 50, 10], - 'CT': [22.046, 22.679851, 0.01, 1, 500], - 'KC': [22.046, 17.009888, 0.05, 2.5, 375], - 'W': [0.3674333, 136.07911, 0.25, 10, 50], - 'S': [0.3674333, 136.07911, 0.25, 10, 50], - 'C': [0.393678571428571, 127.007166832986, 0.25, 10, 50], - 'BO': [22.046, 27.215821, 0.01, 0.5, 600], - 'LC': [22.046, 18.143881, 0.025, 1, 400], - 'LRC': [1, 10, 1, 50, 10], - 'KW': [0.3674333, 136.07911, 0.25, 10, 50], - 'SM': [1.1023113, 90.718447, 0.1, 5, 100], - 'COM': [1.0604, 50, 0.25, 2.5, 53.02], - 'CA': [1.0604, 50, 0.25, 1, 53.02], - 'MW': [0.3674333, 136.07911, 0.25, 10, 50] -} - - -seed = 7 -np.random.seed(seed) +np.random.seed(RANDOM_SEED) # TODO: Abstract away reliance on greeks by month; should be able to # accept any other convention as well. @@ -168,6 +140,19 @@ def __str__(self): return str(pprint.pformat(r_dict)) + def _get_position_lists(self, flag): + """Returns (options_list, futures_list, positions_dict) for the given flag. + + Args: + flag (str): 'OTC' or 'hedge' + + Returns: + tuple: (options deque, futures list, positions dict) + """ + if flag == 'OTC': + return self.OTC_options, self.OTC_futures, self.OTC + return self.hedge_options, self.hedge_futures, self.hedges + def set_families(self, lst): self.families = lst @@ -239,14 +224,14 @@ def update_sec_lots(self, sec, flag, lots): lots (TYPE): list of lot values, where lots[i] corresponds to the new lot value of sec[i] """ - ops = self.OTC_options if flag == 'OTC' else self.hedge_futures + ops = self.OTC_options if flag == 'OTC' else self.hedge_options fts = self.OTC_futures if flag == 'OTC' else self.hedge_futures for s in sec: # sanity checks: make sure the security is present in the relevant # list selected by flag - if s.desc == 'Option' and s not in ops: + if s.desc == 'option' and s not in ops: raise ValueError('This option is not in the portfolio.') - elif s.desc == 'Future' and s not in fts: + elif s.desc == 'future' and s not in fts: raise ValueError('This future is not in the portfolio.') else: s.update_lots(lots[sec.index(s)]) @@ -277,28 +262,19 @@ def init_sec_by_month(self, iden): Returns: None: Initializes the relevant data structures. """ - # initialize dictionaries based on whether securities are OTC or - # hedge. - if iden == 'OTC': - op = self.OTC_options - ft = self.OTC_futures - dic = self.OTC - elif iden == 'hedge': - op = self.hedge_options - ft = self.hedge_futures - dic = self.hedges + op, ft, dic = self._get_position_lists(iden) # add in options for sec in op: month = sec.get_month() prod = sec.get_product() if prod not in dic: - dict[prod] = {} + dic[prod] = {} if month not in dic[prod]: dic[prod][month] = [set([sec]), set(), 0, 0, 0, 0] else: dic[prod][month][0].add(sec) - self.update_greeks_by_month(prod, month, sec, True) + self.update_greeks_by_month(prod, month, sec, True, iden) # add in futures for sec in ft: month = sec.get_month() @@ -311,74 +287,30 @@ def init_sec_by_month(self, iden): dic[prod][month][1].add(sec) def compute_net_greeks(self): - ''' Computes net greeks organized hierarchically according to product and - month. Updates net_greeks by using OTC and hedges. ''' + """Computes net greeks organized by product and month. + Net greeks = OTC greeks + hedge greeks for each product-month.""" final_dic = {} - common_products = set(self.OTC.keys()) & set( - self.hedges.keys()) - OTC_products_unique = set(self.OTC.keys()) - common_products - hedge_products_unique = set(self.hedges.keys()) - common_products - - # print('OTCs: ', self.OTC) - # print('hedges: ', self.hedges) - - # dealing with common products - for product in common_products: - # checking existence. - if product not in final_dic: - final_dic[product] = {} - # instantiating variables to make it neater. - OTCdata = self.OTC[product] - hedgedata = self.hedges[product] - common_months = set(OTCdata.keys()) & set( - hedgedata.keys()) - # finding unique months within this product. - OTC_unique_mths = set( - OTCdata.keys()) - common_months - hedges_unique_mths = set( - hedgedata.keys()) - common_months - # dealing with common months - for month in common_months: - OTC_greeks = OTCdata[month][2:] - # print('DEBUG: OTC greeks: ', OTC_greeks) - hedge_greeks = hedgedata[month][2:] - net = list(map(add, OTC_greeks, hedge_greeks)) - final_dic[product][month] = net - # dealing with non overlapping months - for month in OTC_unique_mths: - # checking if month has options. - if OTCdata[month][0]: - final_dic[product][month] = OTCdata[month][2:] - for month in hedges_unique_mths: - # checking if month has options. - if hedgedata[month][0]: - final_dic[product][month] = hedgedata[month][2:] - - # dealing with non-overlapping products - for product in OTC_products_unique: - data = self.OTC[product] - # checking existence - if product not in final_dic: - final_dic[product] = {} - # iterating over all months corresponding to non-overlapping - # product for which we have OTC positions - for month in data: - # checking if month has options. - if data[month][0]: - final_dic[product][month] = data[month][2:] - - for product in hedge_products_unique: - data = self.hedges[product] - # checking existence - if product not in final_dic: - final_dic[product] = {} - # iterating over all months corresponding to non-overlapping - # product for which we have hedge positions. - for month in data: - # checking if month has options. - if data[month][0]: - final_dic[product][month] = data[month][2:] + all_products = set(self.OTC.keys()) | set(self.hedges.keys()) + + for product in all_products: + final_dic[product] = {} + otc_data = self.OTC.get(product, {}) + hedge_data = self.hedges.get(product, {}) + all_months = set(otc_data.keys()) | set(hedge_data.keys()) + + for month in all_months: + otc_greeks = otc_data[month][2:] if month in otc_data else [0, 0, 0, 0] + hedge_greeks = hedge_data[month][2:] if month in hedge_data else [0, 0, 0, 0] + # only include month if it has options in at least one side + has_otc_options = month in otc_data and otc_data[month][0] + has_hedge_options = month in hedge_data and hedge_data[month][0] + if has_otc_options or has_hedge_options: + final_dic[product][month] = list(map(add, otc_greeks, hedge_greeks)) + + # remove empty products + if not final_dic[product]: + del final_dic[product] self.net_greeks = final_dic @@ -386,19 +318,11 @@ def add_security(self, security, flag): # adds a security into the portfolio, and updates relevant lists and # adjusts greeks of the portfolio. - if flag == 'OTC': - op = self.OTC_options - ft = self.OTC_futures - elif flag == 'hedge': - op = self.hedge_options - ft = self.hedge_futures + op, ft, _ = self._get_position_lists(flag) for sec in security: if sec.get_desc() == 'option': - try: - op.append(sec) - except UnboundLocalError: - print('flag: ', flag) + op.append(sec) elif sec.get_desc() == 'future': ft.append(sec) @@ -408,21 +332,14 @@ def add_security(self, security, flag): def remove_security(self, security, flag): # removes a security from the portfolio, updates relevant list and # adjusts greeks of the portfolio. - if flag == 'OTC': - op = self.OTC_options - ft = self.OTC_futures - elif flag == 'hedge': - op = self.hedge_options - ft = self.hedge_futures + op, ft, _ = self._get_position_lists(flag) self.toberemoved.extend(security) - # if not listonly: self.update_sec_by_month(False, flag) for sec in security: if sec.get_desc() == 'option': op.remove(sec) elif sec.get_desc() == 'future': ft.remove(sec) - # return -1 if self.families: for sec in security: if sec.get_desc() == 'option': @@ -487,16 +404,8 @@ def update_sec_by_month(self, added, flag, update=None): Notes: this method does 90% of all the heavy lifting in the portfolio class. Don't mess with this unless you know EXACTLY what each part is doing. ''' - if flag == 'OTC': - dic = self.OTC - op = self.OTC_options - ft = self.OTC_futures - other = self.hedges - elif flag == 'hedge': - dic = self.hedges - op = self.hedge_options - ft = self.hedge_futures - other = self.OTC + op, ft, dic = self._get_position_lists(flag) + other = self.hedges if flag == 'OTC' else self.OTC # adding/removing security to portfolio if update is None: @@ -769,15 +678,8 @@ def get_securities_monthly(self, flag): return dic def get_securities(self, flag): - if flag == 'OTC': - op = self.OTC_options - ft = self.OTC_futures - else: - op = self.hedge_options - ft = self.hedge_futures - lst1 = op.copy() - lst2 = ft.copy() - return (lst1, lst2) + op, ft, _ = self._get_position_lists(flag) + return (op.copy(), ft.copy()) def get_all_options(self, pdt=None, mth=None): """Gets all options, hedge and OTC @@ -944,8 +846,8 @@ def net_vega_pos(self, month, pdt=None): all_ops = [op for op in all_ops if op.get_product() == pdt] if not all_ops: return 0, 0 - call_op_vega = sum([op.vega for op in all_ops if op.char == 'call']) - put_op_vega = sum([op.vega for op in all_ops if op.char == 'put']) + call_op_vega = sum([op.vega for op in all_ops if op.option_type =='call']) + put_op_vega = sum([op.vega for op in all_ops if op.option_type =='put']) return call_op_vega, put_op_vega @@ -1027,7 +929,7 @@ def breakeven(self, flag=None, conv=None): else: thetas.append(theta) gammas.append(gamma) - bes[pdt][mth] = (((2.8*theta)/gamma) ** 0.5) / \ + bes[pdt][mth] = (((BREAKEVEN_FACTOR*theta)/gamma) ** 0.5) / \ multipliers[pdt][0] return bes diff --git a/scripts/prep_data.py b/scripts/prep_data.py index c55cdf1..abb9330 100644 --- a/scripts/prep_data.py +++ b/scripts/prep_data.py @@ -27,67 +27,15 @@ import copy import datetime as dt import os -seed = 7 -np.random.seed(seed) +from .constants import (multipliers, contract_mths, month_to_sym, sym_to_month, + RANDOM_SEED, DECADE) + +np.random.seed(RANDOM_SEED) # setting pandas warning levels pd.options.mode.chained_assignment = None -# Dictionary mapping month to symbols and vice versa -month_to_sym = {1: 'F', 2: 'G', 3: 'H', 4: 'J', 5: 'K', 6: 'M', - 7: 'N', 8: 'Q', 9: 'U', 10: 'V', 11: 'X', 12: 'Z'} -sym_to_month = {'F': 1, 'G': 2, 'H': 3, 'J': 4, 'K': 5, - 'M': 6, 'N': 7, 'Q': 8, 'U': 9, 'V': 10, 'X': 11, 'Z': 12} -decade = 10 - -# specifies the filepath for the read-in file. -# filepath = 'portfolio_specs.txt' - -# details contract months for each commodity. used in the continuation -# assignment. -contract_mths = { - - 'LH': ['G', 'J', 'K', 'M', 'N', 'Q', 'V', 'Z'], - 'LSU': ['H', 'K', 'Q', 'V', 'Z'], - 'QC': ['H', 'K', 'N', 'U', 'Z'], - 'SB': ['H', 'K', 'N', 'V'], - 'CC': ['H', 'K', 'N', 'U', 'Z'], - 'CT': ['H', 'K', 'N', 'Z'], - 'KC': ['H', 'K', 'N', 'U', 'Z'], - 'W': ['H', 'K', 'N', 'U', 'Z'], - 'S': ['F', 'H', 'K', 'N', 'Q', 'U', 'X'], - 'C': ['H', 'K', 'N', 'U', 'Z'], - 'BO': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], - 'LC': ['G', 'J', 'M', 'Q', 'V', 'Z'], - 'LRC': ['F', 'H', 'K', 'N', 'U', 'X'], - 'KW': ['H', 'K', 'N', 'U', 'Z'], - 'SM': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], - 'COM': ['G', 'K', 'Q', 'X'], - 'CA': ['H', 'K', 'U', 'Z'], - 'MW': ['H', 'K', 'N', 'U', 'Z'] -} - - -multipliers = { - 'LH': [22.046, 18.143881, 0.025, 1, 400], - 'LSU': [1, 50, 0.1, 10, 50], - 'QC': [1.2153, 10, 1, 25, 12.153], - 'SB': [22.046, 50.802867, 0.01, 0.25, 1120], - 'CC': [1, 10, 1, 50, 10], - 'CT': [22.046, 22.679851, 0.01, 1, 500], - 'KC': [22.046, 17.009888, 0.05, 2.5, 375], - 'W': [0.3674333, 136.07911, 0.25, 10, 50], - 'S': [0.3674333, 136.07911, 0.25, 10, 50], - 'C': [0.393678571428571, 127.007166832986, 0.25, 10, 50], - 'BO': [22.046, 27.215821, 0.01, 0.5, 600], - 'LC': [22.046, 18.143881, 0.025, 1, 400], - 'LRC': [1, 10, 1, 50, 10], - 'KW': [0.3674333, 136.07911, 0.25, 10, 50], - 'SM': [1.1023113, 90.718447, 0.1, 5, 100], - 'COM': [1.0604, 50, 0.25, 2.5, 53.02], - 'CA': [1.0604, 50, 0.25, 1, 53.02], - 'MW': [0.3674333, 136.07911, 0.25, 10, 50] -} +decade = DECADE ############################################################### @@ -401,9 +349,9 @@ def prep_portfolio(voldata, pricedata, filepath=None, spec=None): bvoldata, f_name, tau, volflag, barlevel, ordering) opt = Option(strike, tau, char, vol, underlying, - payoff, shorted=shorted, month=opmth, direc=direc, + payoff, shorted=shorted, month=opmth, direction=direc, barrier=barriertype, lots=lots, bullet=bullet, - ki=ki, ko=ko, ordering=ordering, bvol=bvol) + knock_in=ki, knock_out=ko, ordering=ordering, barrier_vol=bvol) oplist[flag].append(opt) @@ -472,21 +420,21 @@ def handle_dailies(dic, sim_start): # print(len(taus)) strike, char, vol, underlying, payoff, shorted, month, ordering, lots, settlement \ - = params['strike'], params['char'], params['vol'], params['underlying'], \ + = params['strike'], params['option_type'], params['vol'], params['underlying'], \ params['payoff'], params['shorted'], params['month'], \ params['ordering'], params['lots'],\ - params['settlement'], params['bvol'], params['bvol2'] + params['settlement'], params['barrier_vol'], params['barrier_vol2'] # barrier params direc, barrier, ki, ko, rebate, bvol, bvol2 = \ - params['direc'], params['barrier'], params[ - 'ki'], params['ko'], params['rebate'], params['bvol'], params['bvol2'] + params['direction'], params['barrier'], params[ + 'knock_in'], params['knock_out'], params['rebate'], params['barrier_vol'], params['barrier_vol2'] # creating the bullets corresponding to this daily option. for tau in taus: ui = copy.deepcopy(underlying) - op_i = Option(strike, tau, char, vol, ui, payoff, shorted, month, direc=direc, - barrier=barrier, lots=lots, bullet=False, ki=ki, ko=ko, rebate=rebate, - ordering=ordering, settlement=settlement, bvol=bvol, bvol2=bvol2) + op_i = Option(strike, tau, char, vol, ui, payoff, shorted, month, direction=direc, + barrier=barrier, lots=lots, bullet=False, knock_in=ki, knock_out=ko, rebate=rebate, + ordering=ordering, settlement=settlement, barrier_vol=bvol, barrier_vol2=bvol2) bullets.append(op_i) lst.extend(bullets) @@ -1054,7 +1002,7 @@ def timestep_recon(df): else: date_range = pd.to_datetime(df.value_date.unique()) for date in date_range: - t = time.clock() + t = time.perf_counter() tdf = df[df.value_date == date] grps = tdf.groupby('underlying_id') # isolate the group with the least data @@ -1081,7 +1029,7 @@ def timestep_recon(df): dic_lst.extend(other_data) print('datalen: ', list(zip(*ords))[1]) print('%s completed' % (date.strftime('%Y-%m-%d'))) - print('elapsed: ', time.clock() - t) + print('elapsed: ', time.perf_counter() - t) print('--------------------------------------') fdf = pd.DataFrame.from_records(dic_lst) diff --git a/scripts/signals.py b/scripts/signals.py index d8480e8..dae2127 100644 --- a/scripts/signals.py +++ b/scripts/signals.py @@ -206,7 +206,7 @@ def liquidate_position(pf, vol_id, char, greek, greekval, signal, pdt = vol_id.split()[0] opmth = vol_id.split()[1].split('.')[0] ftmth = vol_id.split()[1].split('.')[1] - relevant_ops = deque([x for x in pf.OTC_options if x.char == char and + relevant_ops = deque([x for x in pf.OTC_options if x.option_type == char and x.get_product() == pdt and x.get_month() == ftmth and x.get_op_month() == opmth]) index = indices[greek] @@ -305,7 +305,7 @@ def liquidate_position(pf, vol_id, char, greek, greekval, signal, ftmth = vol_id.split()[1].split('.')[1] print('vol_id, pdt, opmth, ftmth, char: ', vol_id, pdt, opmth, ftmth, char) new_ops = deque([x for x in pf.OTC_options if - (x.char == char and x.get_product() == pdt and + (x.option_type == char and x.get_product() == pdt and x.get_month() == ftmth and x.get_op_month() == opmth)]) print('ops after liquidation: ', str([str(x) for x in new_ops])) @@ -334,7 +334,7 @@ def close_position(pf, vol_id, char): opmth = vol_id.split()[1].split('.')[0] ftmth = vol_id.split()[1].split('.')[1] ops = [x for x in pf.OTC_options if x.get_product() == pdt and - x.get_month() == ftmth and x.get_op_month() == opmth and x.char == char] + x.get_month() == ftmth and x.get_op_month() == opmth and x.option_type == char] for op in ops: cost += op.get_price() if op.shorted else -op.get_price() @@ -394,7 +394,7 @@ def add_position(pf, signal, vdf, pdf, vol_id, char, strike, date, print('signals.add_position - maintain flag triggered.') desired_level = abs(newpos) * greekval relevant_ops = [x for x in pf.OTC_options if x.get_product() == pdt and - x.get_month() == ftmth and x.get_op_month() == opmth and x.char == char] + x.get_month() == ftmth and x.get_op_month() == opmth and x.option_type == char] current_level = abs(sum([op.greeks()[index] for op in relevant_ops])) print('signals.add_position - current_level: ', current_level) print('signals.add_position - desired_level: ', desired_level) @@ -419,7 +419,7 @@ def add_position(pf, signal, vdf, pdf, vol_id, char, strike, date, # debug statements: pf.add_security(tobeadded, 'OTC') relevant_ops = [x for x in pf.OTC_options if x.get_product() == pdt and - x.get_month() == ftmth and x.get_op_month() == opmth and x.char == char] + x.get_month() == ftmth and x.get_op_month() == opmth and x.option_type == char] current_level = sum([op.greeks()[index] for op in relevant_ops]) print('new current level: ', current_level) @@ -451,7 +451,7 @@ def maintain_position(pf, pdf, vdf, vol_id, char, strike, pos, lots, pdt = vol_id.split()[0] opmth = vol_id.split()[1].split('.')[0] ftmth = vol_id.split()[1].split('.')[1] - relevant_ops = [x for x in pf.OTC_options if x.char == char and + relevant_ops = [x for x in pf.OTC_options if x.option_type == char and x.get_product() == pdt and x.get_month() == ftmth and x.get_op_month() == opmth] index = indices[greek] @@ -488,7 +488,7 @@ def maintain_position(pf, pdf, vdf, vol_id, char, strike, pos, lots, pf, cost = add_position(pf, sig, vdf, pdf, vol_id, char, strike, date, np.nan, greek, diff, slippage=slippage, brokerage=brokerage) # sanity checking. - new_ops = [x for x in pf.OTC_options if x.char == char and + new_ops = [x for x in pf.OTC_options if x.option_type == char and x.get_product() == pdt and x.get_month() == ftmth and x.get_op_month() == opmth] diff --git a/scripts/simulation.py b/scripts/simulation.py index 027463b..9bb4ef0 100644 --- a/scripts/simulation.py +++ b/scripts/simulation.py @@ -29,85 +29,13 @@ ########################################################################### ######################## initializing variables ########################### ########################################################################### -# Dictionary of multipliers for greeks/pnl calculation. -# format = 'product' : [dollar_mult, lot_mult, futures_tick, -# options_tick, pnl_mult] - -multipliers = { - - 'LH': [22.046, 18.143881, 0.025, 1, 400], - 'LSU': [1, 50, 0.1, 10, 50], - 'QC': [1.2153, 10, 1, 25, 12.153], - 'SB': [22.046, 50.802867, 0.01, 0.25, 1120], - 'CC': [1, 10, 1, 50, 10], - 'CT': [22.046, 22.679851, 0.01, 1, 500], - 'KC': [22.046, 17.009888, 0.05, 2.5, 375], - 'W': [0.3674333, 136.07911, 0.25, 10, 50], - 'S': [0.3674333, 136.07911, 0.25, 10, 50], - 'C': [0.393678571428571, 127.007166832986, 0.25, 10, 50], - 'BO': [22.046, 27.215821, 0.01, 0.5, 600], - 'LC': [22.046, 18.143881, 0.025, 1, 400], - 'LRC': [1, 10, 1, 50, 10], - 'KW': [0.3674333, 136.07911, 0.25, 10, 50], - 'SM': [1.1023113, 90.718447, 0.1, 5, 100], - 'COM': [1.0604, 50, 0.25, 2.5, 53.02], - 'CA': [1.0604, 50, 0.25, 1, 53.02], - 'MW': [0.3674333, 136.07911, 0.25, 10, 50] -} - -op_ticksize = { - - 'QC': 1, - 'CC': 1, - 'SB': 0.01, - 'LSU': 0.05, - 'KC': 0.01, - 'DF': 1, - 'CT': 0.01, - 'C': 0.125, - 'S': 0.125, - 'SM': 0.05, - 'BO': 0.005, - 'W': 0.125, - 'MW': 0.125, - 'KW': 0.125 -} - -contract_mths = { - - 'LH': ['G', 'J', 'K', 'M', 'N', 'Q', 'V', 'Z'], - 'LSU': ['H', 'K', 'Q', 'V', 'Z'], - 'QC': ['H', 'K', 'N', 'U', 'Z'], - 'SB': ['H', 'K', 'N', 'V'], - 'CC': ['H', 'K', 'N', 'U', 'Z'], - 'CT': ['H', 'K', 'N', 'Z'], - 'KC': ['H', 'K', 'N', 'U', 'Z'], - 'W': ['H', 'K', 'N', 'U', 'Z'], - 'S': ['F', 'H', 'K', 'N', 'Q', 'U', 'X'], - 'C': ['H', 'K', 'N', 'U', 'Z'], - 'BO': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], - 'LC': ['G', 'J', 'M', 'Q', 'V' 'Z'], - 'LRC': ['F', 'H', 'K', 'N', 'U', 'X'], - 'KW': ['H', 'K', 'N', 'U', 'Z'], - 'SM': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], - 'COM': ['G', 'K', 'Q', 'X'], - 'CA': ['H', 'K', 'U', 'Z'], - 'MW': ['H', 'K', 'N', 'U', 'Z'] -} - - -# Dictionary mapping month to symbols and vice versa -month_to_sym = {1: 'F', 2: 'G', 3: 'H', 4: 'J', 5: 'K', 6: 'M', - 7: 'N', 8: 'Q', 9: 'U', 10: 'V', 11: 'X', 12: 'Z'} -sym_to_month = {'F': 1, 'G': 2, 'H': 3, 'J': 4, 'K': 5, - 'M': 6, 'N': 7, 'Q': 8, 'U': 9, 'V': 10, 'X': 11, 'Z': 12} -decade = 10 - - -# passage of time -timestep = 1 / 365 -seed = 7 -np.random.seed(seed) +from .constants import (multipliers, op_ticksize, contract_mths, + month_to_sym, sym_to_month, DECADE, TIMESTEP, + RANDOM_SEED) + +decade = DECADE +timestep = TIMESTEP +np.random.seed(RANDOM_SEED) ######################################################################## ######################################################################## @@ -175,8 +103,8 @@ def run_simulation(voldata, pricedata, pf, flat_vols=False, flat_price=False, blockPrint() ##### timers ##### - e1 = time.clock() - t = time.clock() + e1 = time.perf_counter() + t = time.perf_counter() ################## ########### initializing pnl variables ########### @@ -731,7 +659,7 @@ def run_simulation(voldata, pricedata, pf, flat_vols=False, flat_price=False, 'signal']], on=['value_date', 'vol_id']) - elapsed = time.clock() - t + elapsed = time.perf_counter() - t print('Time elapsed: ', elapsed) @@ -766,7 +694,7 @@ def run_simulation(voldata, pricedata, pf, flat_vols=False, flat_price=False, print('Max Drawdown [net]: ', min(np.diff(net_daily_values))) # time elapsed 2 # - e2 = time.clock() - e1 + e2 = time.perf_counter() - e1 print('SIMULATION RUNTIME: ', e2) print('######################################################') @@ -1000,7 +928,7 @@ def feed_data(voldf, pdf, pf, init_val, brokerage=None, # update option attributes by feeding in vol. for op in pf.get_all_options(): # info reqd: strike, order, product, tau - strike = op.K + strike = op.strike bvol2, b_vol, strike_vol = None, None, None # interpolate or round? currently rounding, interpolation easy. ticksize = multipliers[op.get_product()][-2] @@ -1022,7 +950,7 @@ def feed_data(voldf, pdf, pf, init_val, brokerage=None, try: if op.barrier is not None: - barlevel = op.ki if op.ki is not None else op.ko + barlevel = op.knock_in if op.knock_in is not None else op.knock_out # b_val = voldf[(voldf.pdt == product) & (voldf.strike == barlevel) & # (voldf.vol_id == vid) & (voldf.call_put_id == cpi)] # print('inputs: ', product, barlevel, vid, cpi) @@ -1036,15 +964,15 @@ def feed_data(voldf, pdf, pf, init_val, brokerage=None, except (IndexError, ValueError): print('### BARRIER VOLATILITY DATA MISSING ###') - b_vol = op.bvol - bvol2 = op.bvol2 + b_vol = op.barrier_vol + bvol2 = op.barrier_vol2 # print('bvol: ', b_vol) # print('bvol2: ', bvol2) vol_change = save_vol_change(op, strike_vol, vol_change, 'strike') vol_change = save_vol_change(op, b_vol, vol_change, 'bvol') vol_change = save_vol_change(op, bvol2, vol_change, 'bvol2') - op.update_greeks(vol=strike_vol, bvol=b_vol, bvol2=bvol2) + op.update_greeks(vol=strike_vol, barrier_vol=b_vol, barrier_vol2=bvol2) pf.refresh() @@ -1058,13 +986,13 @@ def save_vol_change(op, new_vol, vol_change_dic, flag): volid = op.get_vol_id() if flag == 'strike': old_vol = op.vol - strike = op.K + strike = op.strike elif flag == 'bvol': - old_vol = op.bvol - strike = op.ki if op.ki is not None else op.ko + old_vol = op.barrier_vol + strike = op.knock_in if op.knock_in is not None else op.knock_out elif flag == 'bvol2': - old_vol = op.bvol2 - strike = op.ki if op.ki is not None else op.ko + old_vol = op.barrier_vol2 + strike = op.knock_in if op.knock_in is not None else op.knock_out try: vol_change = new_vol - old_vol except TypeError as e: @@ -1130,18 +1058,18 @@ def handle_barriers(vdf, pdf, ft, val, pf, date): volid = op.get_vol_id() # print('vdf Z8: ', vdf) - vanop = create_vanilla_option(vdf, pdf, volid, op.char, op.shorted, - lots=op.lots, vol=op.vol, strike=op.K, + vanop = create_vanilla_option(vdf, pdf, volid, op.option_type, op.shorted, + lots=op.lots, vol=op.vol, strike=op.strike, bullet=op.bullet) # case 1: knockin case - option is not currently knocked in. - if op.ki is not None: + if op.knock_in is not None: # print('simulation.handle_barriers - knockin case') # case 1-1: di option, val is below barrier. - if op.direc == 'down' and val <= op.ki: + if op.direction == 'down' and val <= op.knock_in: # print('simulation.handle_barriers - down-in case ' + str(op)) step = ft_ticksize - elif op.direc == 'up' and val >= op.ki: + elif op.direction == 'up' and val >= op.knock_in: # print('simulation.handle_barriers - up-in case ' + str(op)) step = -ft_ticksize # knockin case with no action @@ -1151,7 +1079,7 @@ def handle_barriers(vdf, pdf, ft, val, pf, date): # get delta after converting to vanilla option. # round barrier to closest future ticksize price. - ft_price = round(round(op.ki / ft_ticksize) * ft_ticksize, 2) + ft_price = round(round(op.knock_in / ft_ticksize) * ft_ticksize, 2) print('ftprice: ', ft_price) vanop.underlying.update_price(ft_price) van_delta = vanop.greeks()[0] @@ -1178,7 +1106,7 @@ def handle_barriers(vdf, pdf, ft, val, pf, date): ret = pf, 0, [fts] # case 2: knockout. - elif op.ko is not None: + elif op.knock_out is not None: # print('simulation.handle_barriers - knockout case') if op.knockedout: print('simulation.handle_barriers - knockedout') @@ -1186,10 +1114,10 @@ def handle_barriers(vdf, pdf, ft, val, pf, date): else: # case 1: updating price to val will initiate a knockout - if op.direc == 'up' and val > op.ko: + if op.direction == 'up' and val > op.knock_out: # print('simulation.handle_barriers - up-out case ' + str(op)) step = -ft_ticksize - elif op.direc == 'down' and val < op.ko: + elif op.direction == 'down' and val < op.knock_out: # print('simulation.handle_barriers - down-out case ' + str(op)) step = ft_ticksize else: @@ -1197,7 +1125,7 @@ def handle_barriers(vdf, pdf, ft, val, pf, date): return pf, 0, [] ft_price = round( - round((op.ko + step) / ft_ticksize) * ft_ticksize, 2) + round((op.knock_out + step) / ft_ticksize) * ft_ticksize, 2) bar_op.underlying.update_price(ft_price) # print('future price: ', ft_price) delta_diff = bar_op.delta @@ -1241,7 +1169,7 @@ def handle_exercise(pf, brokerage=None, slippage=None): if pf.empty() or pf.ops_empty(): return 0, pf, False, [] exercised = False - # t = time.clock() + # t = time.perf_counter() profit = 0 tol = 1/365 # handle options exercise @@ -1277,8 +1205,8 @@ def handle_exercise(pf, brokerage=None, slippage=None): # calculating the net profit from this exchange. product = op.get_product() pnl_mult = multipliers[product][-1] - ftprice, strike = op.get_underlying().get_price(), op.K - oppnl = op.lots * (ftprice - strike) * pnl_mult if op.char == 'call' else op.lots * (strike - ftprice) * pnl_mult + ftprice, strike = op.get_underlying().get_price(), op.strike + oppnl = op.lots * (ftprice - strike) * pnl_mult if op.option_type == 'call' else op.lots * (strike - ftprice) * pnl_mult if op.shorted: oppnl = -oppnl print('profit/loss on this exercise: ', oppnl) @@ -1290,7 +1218,7 @@ def handle_exercise(pf, brokerage=None, slippage=None): # print('handle_exercise - tobeadded: ', [str(x) for x in tobeadded]) # print('handle_exercise - options exercised: ', exercised) # print('handle_exercise - net exercise profit: ', profit) - # print('handle exercise time: ', time.clock() - t) + # print('handle exercise time: ', time.perf_counter() - t) return profit, pf, exercised, tobeadded @@ -1589,9 +1517,9 @@ def contract_roll(pf, op, vdf, pdf, date, flag, slippage=None): else: r_delta = d_cond if strike is None: - strike = op.K + strike = op.strike - newop = create_vanilla_option(vdf, pdf, new_vol_id, op.char, op.shorted, + newop = create_vanilla_option(vdf, pdf, new_vol_id, op.option_type, op.shorted, date, lots=lots, strike=strike, delta=r_delta) # cost is > 0 if newop.price > op.price @@ -1860,7 +1788,7 @@ def hedge_delta_roll_simple(pf, vdf, pdf, brokerage=None, slippage=None, book=Fa # case: option has already been processed due to its partner being # processed. print('simulation.hedge_delta_roll - option: ', op.get_product(), - op.char, round(abs(op.delta / op.lots), 2)) + op.option_type, round(abs(op.delta / op.lots), 2)) if op in toberemoved: print('option already handled') continue @@ -1872,7 +1800,7 @@ def hedge_delta_roll_simple(pf, vdf, pdf, brokerage=None, slippage=None, book=Fa if (diff < bounds[0]) or (diff > bounds[1]): # if delta > bounds[1] or delta < bounds[0]: print('rolling delta: ', op.get_product(), - op.char, round(abs(op.delta / op.lots), 2)) + op.option_type, round(abs(op.delta / op.lots), 2)) newop, old_op, rcost = delta_roll(pf, op, roll_val, vdf, pdf, flag, slippage=slippage, brokerage=brokerage, book=book, settlements=settlements) @@ -1962,7 +1890,7 @@ def hedge_delta_roll_comp(fpf, vdf, pdf, brokerage=None, slippage=None, book=Fal if (diff < bounds[0]) or (diff > bounds[1]): # if delta > bounds[1] or delta < bounds[0]: print('rolling delta: ', op.get_product(), - op.char, round(abs(op.delta / op.lots), 2)) + op.option_type, round(abs(op.delta / op.lots), 2)) newop, old_op, rcost = delta_roll(pf, op, roll_val, vdf, pdf, flag, slippage=slippage, brokerage=brokerage, book=book, settlements=settlements) @@ -1972,7 +1900,7 @@ def hedge_delta_roll_comp(fpf, vdf, pdf, brokerage=None, slippage=None, book=Fal # if rolling option, roll all partners as well. for opx in op.partners: print('rolling delta: ', opx.get_product(), - opx.char, round(abs(opx.delta / opx.lots), 2)) + opx.option_type, round(abs(opx.delta / opx.lots), 2)) if opx in pf.get_all_options(): tar = pf else: @@ -2024,7 +1952,7 @@ def delta_roll(pf, op, roll_val, vdf, pdf, flag, slippage=None, print('family rolling conds: ', pf.hedge_params['delta']) cost = 0 vol_id = op.get_product() + ' ' + op.get_op_month() + '.' + op.get_month() - newop = create_vanilla_option(vdf, pdf, vol_id, op.char, op.shorted, + newop = create_vanilla_option(vdf, pdf, vol_id, op.option_type, op.shorted, lots=op.lots, delta=roll_val) # handle expenses: brokerage and old op price - new op price @@ -2051,21 +1979,21 @@ def delta_roll(pf, op, roll_val, vdf, pdf, flag, slippage=None, print('volid, book vol: ', op.get_vol_id(), op.vol) try: - cpi = 'C' if op.char == 'call' else 'P' + cpi = 'C' if op.option_type == 'call' else 'P' df = settlements settle_vol = df[(df.vol_id == op.get_vol_id()) & (df.call_put_id == cpi) & - (df.strike == op.K)].vol.values[0] + (df.strike == op.strike)].vol.values[0] except IndexError as e: print('scripts.simulaton.delta_roll - book vol case: cannot find vol: ', - op.get_vol_id(), cpi, op.K) + op.get_vol_id(), cpi, op.strike) settle_vol = op.vol print('volid, settle vol: ', op.get_vol_id(), settle_vol) - true_value = _compute_value(newop.char, newop.tau, settle_vol, newop.K, - newop.underlying.get_price(), 0, 'amer', ki=newop.ki, - ko=newop.ko, barrier=newop.barrier, d=newop.direc, - product=newop.get_product(), bvol=newop.bvol) + true_value = _compute_value(newop.option_type, newop.tau, settle_vol, newop.strike, + newop.underlying.get_price(), 0, 'amer', knock_in=newop.knock_in, + knock_out=newop.knock_out, barrier=newop.barrier, d=newop.direction, + product=newop.get_product(), barrier_vol=newop.barrier_vol) print('op value basis settlements: ', true_value) pnl_mult = multipliers[newop.get_product()][-1] @@ -2165,13 +2093,13 @@ def write_log(pf, drawdown_limit, date, dailpnl, dailynet, grosspnl, netpnl, dai # pos = 'long' if not op.shorted else 'short' oplots = -op.lots if op.shorted else op.lots opvol = op.vol - strike = op.K + strike = op.strike vol_id = op.get_vol_id() tau = round(op.tau * 365) - + underlying_id = op.get_uid() ftprice = settlement_prices[underlying_id] - dvol = vol_change[op.get_vol_id()][op.K] if vol_change else 0 + dvol = vol_change[op.get_vol_id()][op.strike] if vol_change else 0 dprice = price_change[op.get_uid()] dic = pf.get_aggregated_greeks() @@ -2195,7 +2123,7 @@ def write_log(pf, drawdown_limit, date, dailpnl, dailynet, grosspnl, netpnl, dai 'op_vega', 'txn_costs'] if op.barrier is not None: - barlevel = op.ki if op.ki is not None else op.ko + barlevel = op.knock_in if op.knock_in is not None else op.knock_out knockedin = op.knockedin knockedout = op.knockedout bvol_change = vol_change[op.get_vol_id()][barlevel] diff --git a/scripts/util.py b/scripts/util.py index 6e4d04f..da39954 100644 --- a/scripts/util.py +++ b/scripts/util.py @@ -14,63 +14,13 @@ from .calc import compute_strike_from_delta, get_vol_from_delta, get_vol_at_strike import sys from pandas.tseries.offsets import BDay +from .constants import (multipliers, contract_mths, month_to_sym, sym_to_month, + RANDOM_SEED, DECADE) -multipliers = { - 'LH': [22.046, 18.143881, 0.025, 1, 400], - 'LSU': [1, 50, 0.1, 10, 50], - 'QC': [1.2153, 10, 1, 25, 12.153], - 'SB': [22.046, 50.802867, 0.01, 0.25, 1120], - 'CC': [1, 10, 1, 50, 10], - 'CT': [22.046, 22.679851, 0.01, 1, 500], - 'KC': [22.046, 17.009888, 0.05, 2.5, 375], - 'W': [0.3674333, 136.07911, 0.25, 10, 50], - 'S': [0.3674333, 136.07911, 0.25, 10, 50], - 'C': [0.393678571428571, 127.007166832986, 0.25, 10, 50], - 'BO': [22.046, 27.215821, 0.01, 0.5, 600], - 'LC': [22.046, 18.143881, 0.025, 1, 400], - 'LRC': [1, 10, 1, 50, 10], - 'KW': [0.3674333, 136.07911, 0.25, 10, 50], - 'SM': [1.1023113, 90.718447, 0.1, 5, 100], - 'COM': [1.0604, 50, 0.25, 2.5, 53.02], - 'CA': [1.0604, 50, 0.25, 1, 53.02], - 'MW': [0.3674333, 136.07911, 0.25, 10, 50] -} - -seed = 7 -np.random.seed(seed) +np.random.seed(RANDOM_SEED) pd.options.mode.chained_assignment = None -# Dictionary mapping month to symbols and vice versa -month_to_sym = {1: 'F', 2: 'G', 3: 'H', 4: 'J', 5: 'K', 6: 'M', - 7: 'N', 8: 'Q', 9: 'U', 10: 'V', 11: 'X', 12: 'Z'} -sym_to_month = {'F': 1, 'G': 2, 'H': 3, 'J': 4, 'K': 5, - 'M': 6, 'N': 7, 'Q': 8, 'U': 9, 'V': 10, 'X': 11, 'Z': 12} -decade = 10 - - -# details contract months for each commodity. used in the continuation -# assignment. -contract_mths = { - - 'LH': ['G', 'J', 'K', 'M', 'N', 'Q', 'V', 'Z'], - 'LSU': ['H', 'K', 'Q', 'V', 'Z'], - 'QC': ['H', 'K', 'N', 'U', 'Z'], - 'SB': ['H', 'K', 'N', 'V'], - 'CC': ['H', 'K', 'N', 'U', 'Z'], - 'CT': ['H', 'K', 'N', 'Z'], - 'KC': ['H', 'K', 'N', 'U', 'Z'], - 'W': ['H', 'K', 'N', 'U', 'Z'], - 'S': ['F', 'H', 'K', 'N', 'Q', 'U', 'X'], - 'C': ['H', 'K', 'N', 'U', 'Z'], - 'BO': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], - 'LC': ['G', 'J', 'M', 'Q', 'V', 'Z'], - 'LRC': ['F', 'H', 'K', 'N', 'U', 'X'], - 'KW': ['H', 'K', 'N', 'U', 'Z'], - 'SM': ['F', 'H', 'K', 'N', 'Q', 'U', 'V', 'Z'], - 'COM': ['G', 'K', 'Q', 'X'], - 'CA': ['H', 'K', 'U', 'Z'], - 'MW': ['H', 'K', 'N', 'U', 'Z'] -} +decade = DECADE def create_underlying(pdt, ftmth, pdf, date=None, flag='settlement', ftprice=None, shorted=False, lots=None): @@ -457,9 +407,9 @@ def create_barrier_option(vdf, pdf, volid, char, strike, shorted, barriertype, d # print('tau: ', tau) op1 = Option(strike, tau, char, vol, ft, payoff, shorted, opmth, - direc=direction, barrier=barriertype, lots=lots_req, - bullet=bullet, ki=ki, ko=ko, rebate=rebate, ordering=ft.get_ordering(), - bvol=bvol, bvol2=bvol2, dailies=dailies) + direction=direction, barrier=barriertype, lots=lots_req, + bullet=bullet, knock_in=ki, knock_out=ko, rebate=rebate, ordering=ft.get_ordering(), + barrier_vol=bvol, barrier_vol2=bvol2, dailies=dailies) return op1 @@ -521,11 +471,11 @@ def create_butterfly(char, volid, vdf, pdf, date, shorted, **kwargs): mid_op2 = create_vanilla_option( vdf, pdf, volid, char, not shorted, date, delta=mid_delta, strike=mid_strike, lots=lot3) - print('mid strikes: ', mid_op1.K, mid_op2.K) + print('mid strikes: ', mid_op1.strike, mid_op2.strike) if dist is not None: - lower_strike = mid_op2.K - dist - upper_strike = mid_op2.K + dist + lower_strike = mid_op2.strike - dist + upper_strike = mid_op2.strike + dist lower_op = create_vanilla_option( vdf, pdf, volid, char, shorted, date, strike=lower_strike, lots=lot1) @@ -782,7 +732,7 @@ def create_skew(volid, vdf, pdf, date, shorted, delta, **kwargs): for op in ops: print('util.create_skew - strike, char, short: ', - op.K, op.char, op.shorted) + op.strike, op.option_type, op.shorted) return ops @@ -1244,15 +1194,15 @@ def mark_to_vols(pfx, vdf, dup=False, pdt=None): for op in ops: ticksize = multipliers[op.get_product()][-2] vid = op.get_vol_id() - cpi = 'C' if op.char == 'call' else 'P' - strike = round(round(op.K / ticksize) * ticksize, 2) + cpi = 'C' if op.option_type == 'call' else 'P' + strike = round(round(op.strike / ticksize) * ticksize, 2) try: vol = vdf[(vdf.vol_id == vid) & (vdf.call_put_id == cpi) & (vdf.strike == strike)].vol.values[0] except IndexError as e: print("scripts.util.mark_to_vols: cannot find vol with inputs ", - vid, cpi, op.K, strike) + vid, cpi, op.strike, strike) print('scripts.mark_to_vols: debug1 = ', vdf[(vdf.vol_id == vid)]) print('scripts.mark_to_vols: debug2 = ', vdf[(vdf.vol_id == vid) & (vdf.call_put_id == cpi)]) @@ -1279,8 +1229,8 @@ def compute_market_minus(pf, vdf): for op in newpf.get_all_options(): ticksize = multipliers[op.get_product()][-2] vid = op.get_vol_id() - cpi = 'C' if op.char == 'call' else 'P' - strike = round(round(op.K / ticksize) * ticksize, 2) + cpi = 'C' if op.option_type == 'call' else 'P' + strike = round(round(op.strike / ticksize) * ticksize, 2) try: vol = vdf[(vdf.vol_id == vid) & (vdf.call_put_id == cpi) & @@ -1289,7 +1239,7 @@ def compute_market_minus(pf, vdf): except IndexError: print('scripts.calc.market_minus - data not found. ', - vid, cpi, op.K, strike) + vid, cpi, op.strike, strike) print('date: ', vdf.value_date.unique()) val = pf.compute_value() - newpf.compute_value() mm = abs(val) diff --git a/tests/test_calc.py b/tests/test_calc.py index 4aab959..bb8945c 100644 --- a/tests/test_calc.py +++ b/tests/test_calc.py @@ -355,10 +355,10 @@ def test_euro_barrier_greeks(): d1, g1, t1, v1 = deltas[i], gammas[i], thetas[i], vegas[i] try: d, g, t, v = _euro_barrier_euro_greeks( - char, tau, vol, k, s, 0, payoff, direc, 'C', ki, ko, lots, bvol=bvol, bvol2=bvol) + char, tau, vol, k, s, 0, payoff, direc, 'C', ki, ko, lots, barrier_vol=bvol, barrier_vol2=bvol) except TypeError: print(_euro_barrier_euro_greeks( - char, tau, vol, k, s, 0, payoff, direc, 'C', ki, ko, lots, bvol=bvol, bvol2=bvol) is None) + char, tau, vol, k, s, 0, payoff, direc, 'C', ki, ko, lots, barrier_vol=bvol, barrier_vol2=bvol) is None) # g1, t1, v1 = g1/dollar_mult, t1*dollar_mult, v1*dollar_mult try: assert np.isclose(d, d1) @@ -401,7 +401,7 @@ def test_euro_barrier_pricing(): direction = directions[i] payoff = 'amer' val = _barrier_euro(char, tau, vol, k, s, 0, - payoff, direction, ki, ko, product, bvol=vol, bvol2=vol) + payoff, direction, ki, ko, product, barrier_vol=vol, barrier_vol2=vol) try: assert np.isclose(val, actual) except AssertionError: diff --git a/tests/test_dailies.py b/tests/test_dailies.py index 42f1291..7461ea9 100644 --- a/tests/test_dailies.py +++ b/tests/test_dailies.py @@ -33,40 +33,40 @@ def create_test_suite(): put = create_vanilla_option(vdf, pdf, volid, 'put', False, lots=1, bullet=False, strike=strike) ecuo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'euro', 'up', bullet=False, ko=up_bar, + 'euro', 'up', bullet=False, knock_out=up_bar, lots=1) ecui = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'euro', 'up', bullet=False, ki=up_bar, + 'euro', 'up', bullet=False, knock_in=up_bar, lots=1) epdo = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'euro', 'down', bullet=False, ko=down_bar, + 'euro', 'down', bullet=False, knock_out=down_bar, lots=1) epdi = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'euro', 'down', bullet=False, ki=down_bar, + 'euro', 'down', bullet=False, knock_in=down_bar, lots=1) cuo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'up', bullet=False, ko=up_bar, + 'amer', 'up', bullet=False, knock_out=up_bar, lots=1) cui = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'up', bullet=False, ki=up_bar, + 'amer', 'up', bullet=False, knock_in=up_bar, lots=1) cdo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'down', bullet=False, ko=down_bar, + 'amer', 'down', bullet=False, knock_out=down_bar, lots=1) cdi = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'down', bullet=False, ki=down_bar, + 'amer', 'down', bullet=False, knock_in=down_bar, lots=1) puo = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'amer', 'up', bullet=False, ko=up_bar, + 'amer', 'up', bullet=False, knock_out=up_bar, lots=1) pui = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'amer', 'up', bullet=False, ki=up_bar, + 'amer', 'up', bullet=False, knock_in=up_bar, lots=1) pdo = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'amer', 'down', bullet=False, ko=down_bar, + 'amer', 'down', bullet=False, knock_out=down_bar, lots=1) pdi = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'amer', 'down', bullet=False, ki=down_bar, + 'amer', 'down', bullet=False, knock_in=down_bar, lots=1) ops = {'call': call, 'put': put, 'ecuo': ecuo, 'ecui': ecui, 'epdo': epdo, 'epdi': epdi, 'cuo': cuo, 'cui': cui, 'cdo': cdo, 'cdi': cdi, @@ -79,7 +79,7 @@ def create_test_suite(): def test_euro_knockin(): #### ECUI test #### ecui = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'euro', 'up', bullet=False, ki=up_bar, + 'euro', 'up', bullet=False, knock_in=up_bar, lots=1) # option should be active, not expired, OTM, not knockedin. @@ -120,7 +120,7 @@ def test_euro_knockin(): #### EPDI test #### epdi = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'euro', 'down', bullet=False, ki=down_bar, + 'euro', 'down', bullet=False, knock_in=down_bar, lots=1) assert epdi.check_active() assert not epdi.check_expired() @@ -161,7 +161,7 @@ def test_euro_knockin(): def test_euro_knockout(): #### ECUO Test #### ecuo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'euro', 'up', bullet=False, ko=up_bar, + 'euro', 'up', bullet=False, knock_out=up_bar, lots=1) # option should be active, not expired, OTM, not knockedout. assert ecuo.check_active() @@ -202,7 +202,7 @@ def test_euro_knockout(): #### Test EPDO #### epdo = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'euro', 'down', bullet=False, ko=down_bar, + 'euro', 'down', bullet=False, knock_out=down_bar, lots=1) # option should be active, not expired, ITM, not knockedout assert epdo.check_active() @@ -232,7 +232,7 @@ def test_euro_knockout(): def test_cui(): cui = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'up', bullet=False, ki=up_bar, + 'amer', 'up', bullet=False, knock_in=up_bar, lots=1) assert cui.moneyness() == -1 assert not cui.exercise() @@ -275,7 +275,7 @@ def test_cui(): def test_pui(): pui = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'amer', 'up', bullet=False, ki=up_bar, + 'amer', 'up', bullet=False, knock_in=up_bar, lots=1) assert pui.moneyness() == -1 assert not pui.exercise() @@ -321,7 +321,7 @@ def test_pui(): def test_cdi(): cdi = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'down', bullet=False, ki=down_bar, + 'amer', 'down', bullet=False, knock_in=down_bar, lots=1) assert cdi.moneyness() == -1 assert not cdi.exercise() @@ -367,7 +367,7 @@ def test_cdi(): def test_pdi(): pdi = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'amer', 'down', bullet=False, ki=down_bar, + 'amer', 'down', bullet=False, knock_in=down_bar, lots=1) assert pdi.moneyness() == -1 assert not pdi.exercise() @@ -404,7 +404,7 @@ def test_pdi(): def test_cuo(): cuo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'up', bullet=False, ko=up_bar, + 'amer', 'up', bullet=False, knock_out=up_bar, lots=1) assert cuo.moneyness() == -1 assert not cuo.exercise() @@ -434,7 +434,7 @@ def test_cuo(): def test_cdo(): cdo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'down', bullet=False, ko=down_bar, + 'amer', 'down', bullet=False, knock_out=down_bar, lots=1) assert cdo.moneyness() == -1 @@ -465,7 +465,7 @@ def test_cdo(): def test_puo(): puo = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'amer', 'up', bullet=False, ko=up_bar, + 'amer', 'up', bullet=False, knock_out=up_bar, lots=1) assert puo.moneyness() == 1 assert puo.exercise() @@ -495,7 +495,7 @@ def test_puo(): def test_pdo(): pdo = create_barrier_option(vdf, pdf, volid, 'put', strike, False, - 'amer', 'down', bullet=False, ko=down_bar, + 'amer', 'down', bullet=False, knock_out=down_bar, lots=1) assert pdo.moneyness() == 1 assert pdo.exercise() @@ -525,7 +525,7 @@ def test_pdo(): def test_remove_expired(): cdo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'down', bullet=False, ko=down_bar, + 'amer', 'down', bullet=False, knock_out=down_bar, lots=1) assert all([x > 0 for x in cdo.get_ttms()]) init_len = len(cdo.get_ttms()) @@ -538,7 +538,7 @@ def test_remove_expired(): def test_expiry(): cdo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'down', bullet=False, ko=down_bar, + 'amer', 'down', bullet=False, knock_out=down_bar, lots=1) assert not cdo.check_expired() cdo.update_tau(1 / 365) @@ -562,7 +562,7 @@ def test_expiry(): def test_reverse_timestep(): cdo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'down', bullet=False, ko=down_bar, + 'amer', 'down', bullet=False, knock_out=down_bar, lots=1) init_ttms = deepcopy(cdo.get_ttms()) # print('init_ttms: ', np.array(init_ttms)*365) @@ -580,7 +580,7 @@ def test_reverse_timestep(): assert np.array_equal(new_ttms, init_ttms) cdo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'down', bullet=False, ko=down_bar, + 'amer', 'down', bullet=False, knock_out=down_bar, lots=1) init_ttms = deepcopy(cdo.get_ttms()) cdo.update_tau(2 / 365) @@ -605,7 +605,7 @@ def test_reverse_timestep(): raise AssertionError from e cdo = create_barrier_option(vdf, pdf, volid, 'call', strike, False, - 'amer', 'down', bullet=False, ko=down_bar, + 'amer', 'down', bullet=False, knock_out=down_bar, lots=1) init_ttms = deepcopy(cdo.get_ttms()) # print('init :', np.array(init_ttms) * 365) diff --git a/tests/test_hedge.py b/tests/test_hedge.py index d34c2f5..abb7e6b 100644 --- a/tests/test_hedge.py +++ b/tests/test_hedge.py @@ -343,7 +343,7 @@ def test_add_hedges(): assert total_theta < 5265 assert engine.satisfied() chars = set(['call', 'put']) - actchars = set([op.char for op in ops]) + actchars = set([op.option_type for op in ops]) assert actchars == chars # testing exp repr diff --git a/tests/test_options.py b/tests/test_options.py index ac69245..a82b3bf 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -27,14 +27,14 @@ def test_check_active_ko_american(): # testing up and out # test 1: up and out at 50, spot at 30. op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc=direc, barrier=barrier, bullet=False, ko=50, + direction=direc, barrier=barrier, bullet=False, knock_out=50, dailies=ttms) assert op.check_active() == True assert op.knockedout == False # test 2: up and out at 20, spot at 30 op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc=direc, barrier=barrier, bullet=False, ko=20, dailies=ttms) + direction=direc, barrier=barrier, bullet=False, knock_out=20, dailies=ttms) assert op.check_active() == False assert op.knockedout == True @@ -51,7 +51,7 @@ def test_check_active_ko_american(): direc2 = 'down' barrier2 = 'amer' op2 = Option(strike2, tau, 'call', vol, ft2, payoff, False, 'Z7', - direc=direc2, barrier=barrier2, bullet=False, ko=50, dailies=ttms) + direction=direc2, barrier=barrier2, bullet=False, knock_out=50, dailies=ttms) assert op2.check_active() == True assert op2.knockedout == False @@ -71,7 +71,7 @@ def test_check_active_ki_american(): direc = 'down' barrier = 'amer' op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc=direc, barrier=barrier, bullet=False, ki=20, dailies=ttms) + direction=direc, barrier=barrier, bullet=False, knock_in=20, dailies=ttms) # testing down and in # test 1: down and in at 20, spot at 30. @@ -93,7 +93,7 @@ def test_check_active_ki_american(): ft2 = Future('march', 30, 'C') direc2 = 'up' op2 = Option(strike, tau, 'call', vol, ft2, payoff, False, 'Z7', - direc=direc2, barrier=barrier, ki=50, dailies=ttms) + direction=direc2, barrier=barrier, knock_in=50, dailies=ttms) assert op2.check_active() == True assert op2.knockedin == False @@ -119,7 +119,7 @@ def test_check_active_ki_euro(): direc = 'down' barrier = 'amer' op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc=direc, barrier=barrier, bullet=True, ki=200, dailies=ttms) + direction=direc, barrier=barrier, bullet=True, knock_in=200, dailies=ttms) # testing down and in # test 1: down and in at 20, spot at 30. @@ -146,7 +146,7 @@ def test_check_active_ki_euro(): ft2 = Future('march', 300, 'C') direc2 = 'up' op2 = Option(strike, tau, 'call', vol, ft2, payoff, False, 'Z7', - direc=direc2, barrier=barrier, ki=500, dailies=ttms) + direction=direc2, barrier=barrier, knock_in=500, dailies=ttms) assert op2.check_active() == True assert op2.knockedin == False @@ -168,7 +168,7 @@ def test_get_underlying(): vol = 0.2 payoff = 'euro' op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - 'up', barrier='amer', bullet=False, ko=50, dailies=ttms) + direction='up', barrier='amer', bullet=False, knock_out=50, dailies=ttms) assert op.get_underlying() == ft @@ -179,7 +179,7 @@ def test_get_desc(): vol = 0.2 payoff = 'euro' op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc='up', barrier='amer', bullet=False, ko=50, dailies=ttms) + direction='up', barrier='amer', bullet=False, knock_out=50, dailies=ttms) assert op.get_desc() == 'option' @@ -190,7 +190,7 @@ def test_update_tau(): vol = 0.2 payoff = 'euro' op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc='up', barrier='amer', bullet=False, ko=50, dailies=ttms) + direction='up', barrier='amer', bullet=False, knock_out=50, dailies=ttms) op.update_tau(0.1) assert op.tau == (327/365) - 0.1 @@ -202,7 +202,7 @@ def test_get_product(): vol = 0.2 payoff = 'euro' op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc='up', barrier='amer', bullet=False, ko=50, dailies=ttms) + direction='up', barrier='amer', bullet=False, knock_out=50, dailies=ttms) assert op.get_product() == 'C' @@ -232,7 +232,7 @@ def test_moneyness_american(): vol = 0.2 payoff = 'euro' op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc='up', barrier='amer', bullet=False, ko=50, dailies=ttms) + direction='up', barrier='amer', bullet=False, knock_out=50, dailies=ttms) # at the money assert op.moneyness() == 0 # in the moneu @@ -256,7 +256,7 @@ def test_moneyness_american(): strike2 = 20 ft2 = Future('march', 20, 'C') op2 = Option(strike2, tau, 'put', vol, ft2, payoff, False, 'Z7', - direc='up', barrier='amer', bullet=False, ko=50, dailies=ttms) + direction='up', barrier='amer', bullet=False, knock_out=50, dailies=ttms) assert op2.moneyness() == 0 ft2.update_price(30) assert op2.moneyness() == -1 @@ -273,7 +273,7 @@ def test_zero_options(): vol = 0.2 payoff = 'euro' op = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc='up', barrier='amer', bullet=True, ko=50) + direction='up', barrier='amer', bullet=True, knock_out=50) delta, gamma, theta, vega = op.greeks() initial_val = op.get_price() assert initial_val > 0 @@ -298,7 +298,7 @@ def test_barrier_options(): payoff = 'euro' vanop = Option(strike, tau, 'call', vol, ft, 'amer', False, 'Z7') barOp = Option(strike, tau, 'call', vol, ft, payoff, False, 'Z7', - direc='up', barrier='amer', bullet=True, ki=40) + direction='up', barrier='amer', bullet=True, knock_in=40) d1, g1, t1, v1 = vanop.greeks() p1 = vanop.get_price() d2, g2, t2, v2 = barOp.greeks() @@ -334,7 +334,7 @@ def test_barrier_options2(): payoff2 = 'euro' vanop2 = Option(strike2, tau2, 'call', vol2, ft2, payoff2, False, 'Z7') barOp2 = Option(strike2, tau2, 'call', vol2, ft2, payoff2, False, 'Z7', - direc='up', barrier='amer', bullet=False, ko=36, dailies=ttms) + direction='up', barrier='amer', bullet=False, knock_out=36, dailies=ttms) d1, g1, t1, v1 = vanop2.greeks() p3 = vanop2.get_price() d2, g2, t2, v2 = barOp2.greeks() diff --git a/tests/test_portfolio.py b/tests/test_portfolio.py index a79cea6..6c088a4 100644 --- a/tests/test_portfolio.py +++ b/tests/test_portfolio.py @@ -52,9 +52,9 @@ def generate_portfolio(): op2 = Option( 29, 0.2156100288506942, 'call', 0.45176132048500206, ft2, 'amer', False, 'Z7') op3 = Option(30, 0.21534276294769317, 'call', 0.14464169782291536, - ft3, 'amer', True, 'Z7', direc='up', barrier='amer', bullet=True, ko=35) + ft3, 'amer', True, 'Z7', direction='up', barrier='amer', bullet=True, knock_out=35) op4 = Option(33, 0.22365510948646386, 'put', 0.18282926924909026, - ft4, 'amer', False, 'Z7', direc='down', barrier='amer', bullet=True, ki=28) + ft4, 'amer', False, 'Z7', direction='down', barrier='amer', bullet=True, knock_in=28) op5 = Option( 32, 0.010975090692443346, 'put', 0.8281728247909962, ft5, 'amer', True, 'Z7') @@ -214,9 +214,9 @@ def test_remove_security_futures(): # checking equality of options assert mar_option1.underlying.get_price( ) == mar_option2.underlying.get_price() - assert mar_option1.K == mar_option2.K + assert mar_option1.strike == mar_option2.strike assert mar_option1.tau == mar_option2.tau - assert mar_option1.char == mar_option2.char + assert mar_option1.option_type == mar_option2.option_type assert mar_option1.price == mar_option2.price assert mar_option1.get_product() == mar_option2.get_product() @@ -273,9 +273,9 @@ def test_remove_security_options(): # checking equality of options assert mar_option1.underlying.get_price( ) == mar_option2.underlying.get_price() - assert mar_option1.K == mar_option2.K + assert mar_option1.strike == mar_option2.strike assert mar_option1.tau == mar_option2.tau - assert mar_option1.char == mar_option2.char + assert mar_option1.option_type == mar_option2.option_type assert mar_option1.price == mar_option2.price assert mar_option1.get_product() == mar_option2.get_product() diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 0259ee8..173cbd4 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -55,12 +55,12 @@ def generate_portfolio(flag): 'K7', ordering=2) op3 = Option(300, 0.473972602739726, 'call', 0.14464169782291536, - ft3, 'amer', short, 'N7', direc='up', barrier='amer', bullet=True, - ko=350, ordering=2) + ft3, 'amer', short, 'N7', direction='up', barrier='amer', bullet=True, + knock_out=350, ordering=2) op4 = Option(330, 0.473972602739726, 'put', 0.18282926924909026, - ft4, 'amer', short, 'N7', direc='down', barrier='amer', bullet=True, - ki=280, ordering=2) + ft4, 'amer', short, 'N7', direction='down', barrier='amer', bullet=True, + knock_in=280, ordering=2) op5 = Option( 320, 0.473972602739726, 'put', 0.8281728247909962, ft5, 'amer', short, 'N7', ordering=2)