Initial commit
This commit is contained in:
commit
9767adb7de
|
|
@ -0,0 +1,39 @@
|
|||
name: Test
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: run tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [ "3.10" ]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
coverage run -m pytest -v -s
|
||||
|
||||
- name: Generate Coverage Report
|
||||
run: |
|
||||
coverage report -m
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,2 @@
|
|||
[tool.ruff]
|
||||
line-length = 100
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[pytest]
|
||||
pythonpath = .
|
||||
testpaths = src
|
||||
python_files = *_test.py
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
numpy==2.2.3
|
||||
pandas==2.2.3
|
||||
pytest==8.3.5
|
||||
scipy==1.15.2
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#%% md
|
||||
## Answer 1 - Option
|
||||
|
||||
* Implied Volatility (IV) provides a prediction of the future, while Historical Volatility (HV) provides an observation of the past.
|
||||
* IV is derived from option pricing models while HV is calculated directly from historical price data.
|
||||
* IV provides a subjective measure on future volatility, while HV provides an objective measure of baseline volatility.
|
||||
|
||||
## Answer 2 - VaR
|
||||
|
||||
Value-at-Risk (VaR) is a measurement of the maximum potential loss in value of a portfolio over a defined period (e.g. 1 day) and for a given confidence interval (e.g. 95%).
|
||||
|
||||
There are various methods of calculation that mainly differ in the way they approximate portfolio volatility.
|
||||
|
||||
The simplest method (parametric) assumes that portfolio volatility is constant and thus return variability can be approximated using a normal distribution. It requires very little data (only the current portfolio value and a measure of volatility, e.g. annual volatility of the portfolio) and is easily computed but has practical limitations due to its assumptions.
|
||||
|
||||
A more complex method is the Peaks Over Threshold (POT) method. This method requires more data, specifically containing sufficient extreme events, as it uses historical excesses in portfolio returns to extrapolate risk beyond historical observations.
|
||||
In other words, it estimates the chance of future excesses in returns.
|
||||
This estimation is dependent on the method's parameters, so stress testing is needed make sure the parameters are aptly chosen.
|
||||
|
||||
Because VaR is used in risk management, and specifically to reduce portfolio risk if it's deemed to large, a method that specifically models extreme events (i.e. "tail focus") is preferable.
|
||||
|
||||
## Answer 3 - Option
|
||||
|
||||
Refer to `options.py` for the implementation and `options_test.py` for both unit- and end-to-end tests.
|
||||
|
||||
## Answer 4 - VaR
|
||||
|
||||
Refer to `var.py` for the implementation and `var_test.py` for both unit- and end-to-end tests.
|
||||
#%%
|
||||
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
date,ccy-1,ccy-2
|
||||
14-11-2019,1.1684427,0.886564121
|
||||
13-11-2019,1.165976797,0.884329678
|
||||
12-11-2019,1.16603118,0.883470271
|
||||
11-11-2019,1.166901992,0.87753938
|
||||
8-11-2019,1.160725686,0.877658927
|
||||
7-11-2019,1.160712213,0.876270592
|
||||
6-11-2019,1.162466288,0.877654906
|
||||
5-11-2019,1.162020521,0.876693114
|
||||
4-11-2019,1.158050769,0.881600987
|
||||
1-11-2019,1.159084323,0.881911985
|
||||
31-10-2019,1.160995205,0.885700368
|
||||
30-10-2019,1.156992283,0.886839305
|
||||
29-10-2019,1.160671797,0.878271562
|
||||
28-10-2019,1.159164938,0.879816998
|
||||
25-10-2019,1.157420803,0.881290209
|
||||
24-10-2019,1.157126161,0.880902044
|
||||
23-10-2019,1.158761979,0.88163985
|
||||
22-10-2019,1.15928588,0.878464444
|
||||
21-10-2019,1.164903779,0.87788605
|
||||
18-10-2019,1.157889862,0.879198171
|
||||
17-10-2019,1.155081202,0.883197174
|
||||
16-10-2019,1.158426394,0.879700902
|
||||
15-10-2019,1.157836236,0.881329044
|
||||
14-10-2019,1.141630705,0.885151582
|
||||
11-10-2019,1.148791471,0.88210647
|
||||
10-10-2019,1.115299681,0.879971841
|
||||
9-10-2019,1.112743134,0.875465091
|
||||
8-10-2019,1.114876918,0.876001927
|
||||
7-10-2019,1.12083749,0.876270592
|
||||
4-10-2019,1.119870991,0.876193814
|
||||
3-10-2019,1.129318231,0.876616261
|
||||
2-10-2019,1.124062813,0.876923751
|
||||
1-10-2019,1.119733056,0.874431619
|
||||
30-9-2019,1.128935752,0.873667657
|
||||
27-9-2019,1.124492573,0.880824452
|
||||
26-9-2019,1.12731946,0.877654906
|
||||
25-9-2019,1.129496809,0.876424189
|
||||
24-9-2019,1.130867236,0.872600349
|
||||
23-9-2019,1.133758437,0.873896705
|
||||
20-9-2019,1.135151089,0.868809731
|
||||
19-9-2019,1.129484052,0.867829558
|
||||
18-9-2019,1.128579006,0.871269876
|
||||
17-9-2019,1.129905201,0.872105699
|
||||
16-9-2019,1.129292724,0.874087671
|
||||
13-9-2019,1.124151266,0.87753938
|
||||
12-9-2019,1.123458883,0.878734622
|
||||
11-9-2019,1.122258883,0.87966221
|
||||
10-9-2019,1.118918677,0.879894413
|
||||
9-9-2019,1.116220922,0.88051422
|
||||
6-9-2019,1.11477749,0.878811846
|
||||
5-9-2019,1.116245842,0.880630531
|
||||
4-9-2019,1.105534305,0.877308418
|
||||
3-9-2019,1.103046615,0.874469853
|
||||
2-9-2019,1.099601944,0.87507623
|
||||
30-8-2019,1.103022281,0.875273523
|
||||
29-8-2019,1.10282765,0.87001914
|
||||
28-8-2019,1.103387399,0.871991629
|
||||
27-8-2019,1.010123245,0.874928912
|
||||
26-8-2019,1.099747058,0.876501008
|
||||
23-8-2019,1.104899123,0.878889084
|
||||
22-8-2019,1.105546527,0.880630531
|
||||
21-8-2019,1.093469798,0.882768362
|
||||
20-8-2019,1.094235567,0.886839305
|
||||
19-8-2019,1.093637219,0.884955752
|
||||
16-8-2019,1.093744873,0.886014265
|
||||
15-8-2019,1.090655266,0.886839305
|
||||
14-8-2019,1.082508822,0.887941751
|
||||
13-8-2019,1.078865034,0.884564352
|
||||
12-8-2019,1.077156737,0.882339966
|
||||
9-8-2019,1.07820199,0.881057269
|
||||
8-8-2019,1.083247576,0.881290209
|
||||
7-8-2019,1.083505791,0.881911985
|
||||
6-8-2019,1.087961704,0.881367883
|
||||
5-8-2019,1.085422772,0.88028169
|
||||
2-8-2019,1.091381361,0.878618811
|
||||
1-8-2019,1.098490674,0.876232202
|
||||
31-7-2019,1.096234287,0.878194432
|
||||
30-7-2019,1.090905124,0.883119177
|
||||
29-7-2019,1.098719991,0.884603476
|
||||
26-7-2019,1.113337787,0.883587365
|
||||
25-7-2019,1.119670369,0.890194507
|
||||
24-7-2019,1.121642084,0.890234132
|
||||
23-7-2019,1.11564808,0.890947969
|
||||
22-7-2019,1.113647753,0.88691796
|
||||
19-7-2019,1.114243373,0.884173298
|
||||
18-7-2019,1.112978442,0.884642604
|
||||
17-7-2019,1.107162232,0.883002208
|
||||
16-7-2019,1.106304831,0.882028666
|
||||
15-7-2019,1.111296327,0.881406725
|
||||
12-7-2019,1.116196004,0.880902044
|
||||
11-7-2019,1.114293037,0.88028169
|
||||
10-7-2019,1.1105805,0.886367665
|
||||
9-7-2019,1.112433671,0.883197174
|
||||
8-7-2019,1.115672974,0.886485528
|
||||
5-7-2019,1.114504157,0.888967908
|
||||
4-7-2019,1.114789918,0.88999644
|
||||
3-7-2019,1.113635351,0.890323
|
||||
2-7-2019,1.115038525,0.891067053
|
||||
1-7-2019,1.117006423,0.893535272
|
||||
28-6-2019,1.114050958,0.890511599
|
||||
27-6-2019,1.115050958,0.891583452
|
||||
26-6-2019,1.114541422,0.89098766
|
||||
25-6-2019,1.117355889,0.887981175
|
||||
24-6-2019,1.117218573,0.88711466
|
||||
21-6-2019,1.121453404,0.888809884
|
||||
20-6-2019,1.124985938,0.88711466
|
||||
19-6-2019,1.126164172,0.883821645
|
||||
18-6-2019,1.120925436,0.884407889
|
||||
17-6-2019,1.118480656,0.884955752
|
||||
14-6-2019,1.123204277,0.884760009
|
||||
13-6-2019,1.125365744,0.889363216
|
||||
12-6-2019,1.124163903,0.889086464
|
||||
11-6-2019,1.124075448,0.888533475
|
||||
10-6-2019,1.121239643,0.892299456
|
||||
7-6-2019,1.125163149,0.892538379
|
||||
6-6-2019,1.127116161,0.8942312
|
||||
5-6-2019,1.130467222,0.89512332
|
||||
4-6-2019,1.12847712,0.89561596
|
||||
3-6-2019,1.127471982,0.8952134
|
||||
31-5-2019,1.1281843,0.889600569
|
||||
30-5-2019,1.131528922,0.894134478
|
||||
29-5-2019,1.133542661,0.893894699
|
||||
28-5-2019,1.134404211,0.893255918
|
||||
27-5-2019,1.132964742,0.893854749
|
||||
24-5-2019,1.132618274,0.892538379
|
||||
23-5-2019,1.135976372,0.89047195
|
||||
22-5-2019,1.134803282,0.889263216
|
||||
21-5-2019,1.14310536,0.889363216
|
||||
20-5-2019,1.140706097,0.891901534
|
||||
17-5-2019,1.14087528,0.892259648
|
||||
16-5-2019,1.144348065,0.89465444
|
||||
15-5-2019,1.147802532,0.896137647
|
||||
14-5-2019,1.152113552,0.89561596
|
||||
13-5-2019,1.154921119,0.894374385
|
||||
10-5-2019,1.158842548,0.896619744
|
||||
9-5-2019,1.159393405,0.896901206
|
||||
8-5-2019,1.160294715,0.892458724
|
||||
7-5-2019,1.16638479,0.893575194
|
||||
6-5-2019,1.169590643,0.894854586
|
||||
3-5-2019,1.172525385,0.897504936
|
||||
2-5-2019,1.165324601,0.898714838
|
||||
1-5-2019,1.163995297,0.897021887
|
||||
30-4-2019,1.16132379,0.892618049
|
||||
29-4-2019,1.157407407,0.89031339
|
||||
26-4-2019,1.57284634,0.887862914
|
||||
25-4-2019,1.568324628,0.886406949
|
||||
24-4-2019,1.156590833,0.882067566
|
||||
23-4-2019,1.154227937,0.883704489
|
||||
22-4-2019,1.152963693,0.883743538
|
||||
19-4-2019,1.156122826,0.883431247
|
||||
18-4-2019,1.156925355,0.887232721
|
||||
17-4-2019,1.154521105,0.890868597
|
||||
16-4-2019,1.155214639,0.890789239
|
||||
15-4-2019,1.159729551,0.894054537
|
||||
12-4-2019,1.158158065,0.891583452
|
||||
11-4-2019,1.160739159,0.885778821
|
||||
10-4-2019,1.163277651,0.883314195
|
||||
9-4-2019,1.157420803,0.878040214
|
||||
8-4-2019,1.158721698,0.878425861
|
||||
5-4-2019,1.159218687,0.879043601
|
||||
4-4-2019,1.166412,0.879584836
|
||||
3-4-2019,1.171687347,0.878889084
|
||||
2-4-2019,1.164225674,0.883041194
|
||||
1-4-2019,1.170686022,0.885269122
|
||||
29-3-2019,1.168346532,0.885367665
|
||||
28-3-2019,1.164646006,0.886367665
|
||||
27-3-2019,1.172374175,0.891106755
|
||||
26-3-2019,1.171440286,0.891543708
|
||||
25-3-2019,1.165555504,0.892379083
|
||||
22-3-2019,1.171550078,0.888888889
|
||||
21-3-2019,1.150483203,0.888730892
|
||||
20-3-2019,1.161737029,0.888691402
|
||||
19-3-2019,1.169057389,0.887862914
|
||||
18-3-2019,1.165949608,0.891106755
|
||||
15-3-2019,1.17249789,0.890828916
|
||||
14-3-2019,1.174853437,0.891662951
|
||||
13-3-2019,1.169590643,0.891106755
|
||||
12-3-2019,1.161642563,0.891027355
|
||||
11-3-2019,1.166520852,0.896860987
|
||||
8-3-2019,1.158506916,0.897424392
|
||||
7-3-2019,1.16780138,0.896097495
|
||||
6-3-2019,1.162007019,0.898795614
|
||||
5-3-2019,1.159810255,0.898311175
|
||||
4-3-2019,1.163372384,0.897786955
|
||||
1-3-2019,1.16295297,0.90244334
|
||||
28-2-2019,1.16512094,0.90440445
|
||||
27-2-2019,1.171508903,0.900738606
|
||||
26-2-2019,1.165311022,0.894054537
|
||||
25-2-2019,1.151768541,0.893974611
|
||||
22-2-2019,1.151330939,0.891662951
|
||||
21-2-2019,1.151410478,0.89273758
|
||||
20-2-2019,1.151012891,0.892777431
|
||||
19-2-2019,1.149729239,0.89146423
|
||||
18-2-2019,1.143301397,0.893694982
|
||||
15-2-2019,1.140693085,0.896860987
|
||||
14-2-2019,1.133118796,0.900252071
|
||||
13-2-2019,1.14153948,0.900576369
|
||||
12-2-2019,1.140133852,0.901225667
|
||||
11-2-2019,1.140914329,0.901794571
|
||||
8-2-2019,1.142739604,0.901388138
|
||||
7-2-2019,1.142400183,0.902445628
|
||||
6-2-2019,1.139211666,0.900009
|
||||
5-2-2019,1.134198349,0.899806542
|
||||
4-2-2019,1.144885225,0.901243
|
||||
1-2-2019,1.14187839,0.902934537
|
||||
31-1-2019,1.143379831,0.903628067
|
||||
30-1-2019,1.144112398,0.906043309
|
||||
29-1-2019,1.148349572,0.911701691
|
||||
28-1-2019,1.15036409,0.912325518
|
||||
25-1-2019,1.155374804,0.906618314
|
||||
24-1-2019,1.148567162,0.905387053
|
||||
23-1-2019,1.14825064,0.905223138
|
||||
22-1-2019,1.140797189,0.904486252
|
||||
21-1-2019,1.134751773,0.905715062
|
||||
18-1-2019,1.135383078,0.909338911
|
||||
17-1-2019,1.134134032,0.906700517
|
||||
16-1-2019,1.128515325,0.902282775
|
||||
15-1-2019,1.119482351,0.909256228
|
||||
14-1-2019,1.125707789,0.904977376
|
||||
11-1-2019,1.117143687,0.90424089
|
||||
10-1-2019,1.108696616,0.904568069
|
||||
9-1-2019,1.107125459,0.908265213
|
||||
8-1-2019,1.112644087,0.90962735
|
||||
7-1-2019,1.1141813,0.91124248
|
||||
4-1-2019,1.115026092,0.912575287
|
||||
3-1-2019,1.106060103,0.913993236
|
||||
2-1-2019,1.110592834,0.913408842
|
||||
31-12-2018,1.117156167,0.916674306
|
||||
28-12-2018,1.109299256,0.916086479
|
||||
27-12-2018,1.107910481,0.913575735
|
||||
26-12-2018,1.110975325,0.910332271
|
||||
24-12-2018,1.114305453,0.910829766
|
||||
21-12-2018,1.1105805,0.909545682
|
||||
20-12-2018,1.106831363,0.912825194
|
||||
19-12-2018,1.10702741,0.910705341
|
||||
18-12-2018,1.111716379,0.908182726
|
||||
17-12-2018,1.112334679,0.905879156
|
||||
14-12-2018,1.111790539,0.907029478
|
||||
13-12-2018,1.112099644,0.906084356
|
||||
12-12-2018,1.113833816,0.904813608
|
||||
11-12-2018,1.106427236,0.899523253
|
||||
10-12-2018,1.101770545,0.897585495
|
||||
7-12-2018,1.12027245,0.897182846
|
||||
6-12-2018,1.123048133,0.898391879
|
||||
5-12-2018,1.123078133,0.899523253
|
||||
4-12-2018,1.1212145,0.900414191
|
||||
3-12-2018,1.122498232,0.901631954
|
||||
30-11-2018,1.122384843,0.901550667
|
||||
29-11-2018,1.122120359,0.899604174
|
||||
28-11-2018,1.130671732,0.899118864
|
||||
27-11-2018,1.128387985,0.896539358
|
||||
26-11-2018,1.130965845,0.895255148
|
||||
23-11-2018,1.128795575,0.896458987
|
||||
22-11-2018,1.128349788,0.903097625
|
||||
21-11-2018,1.121730157,0.903056847
|
||||
20-11-2018,1.123898579,0.912825194
|
||||
19-11-2018,1.247645328,0.910705341
|
||||
16-11-2018,1.126722477,0.900414191
|
||||
15-11-2018,1.129598879,0.905879156
|
||||
14-11-2018,1.14998045,0.895255148
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import pandas as pd
|
||||
import numpy as np
|
||||
from scipy.stats import norm
|
||||
|
||||
|
||||
def as_df(option):
|
||||
"""Creates a dataframe holding the option, and calculates time to maturity"""
|
||||
df = pd.DataFrame([option])
|
||||
df["T"] = (df["t1"] - df["t0"]) / np.timedelta64(365, "D")
|
||||
return df
|
||||
|
||||
|
||||
def black_scholes_option_price_spot(S, K, T, r, sigma, option_type="call"):
|
||||
"""Calculates option price using Black-Scholes formula with spot price assuming zero dividend"""
|
||||
sigma_scaled = sigma * np.sqrt(T)
|
||||
d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / sigma_scaled
|
||||
d2 = d1 - sigma_scaled
|
||||
|
||||
if option_type == "call":
|
||||
return S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
|
||||
return K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1) # put
|
||||
|
||||
|
||||
def black_scholes_option_price_forward(F, K, T, r, sigma, option_type="call"):
|
||||
"""Calculates option price using Black-Scholes formula with forward price assuming zero dividend"""
|
||||
sigma_scaled = sigma * np.sqrt(T)
|
||||
d1 = (np.log(F / K) + (0.5 * sigma**2) * T) / sigma_scaled
|
||||
d2 = d1 - sigma_scaled
|
||||
|
||||
discount = np.exp(-r * T)
|
||||
|
||||
if option_type == "call":
|
||||
return discount * (F * norm.cdf(d1) - K * norm.cdf(d2))
|
||||
return discount * (K * norm.cdf(-d2) - F * norm.cdf(-d1)) # put
|
||||
|
||||
|
||||
def forward_price(S, T, r):
|
||||
"""Calculates the forward price assuming zero dividend"""
|
||||
return S * np.exp(r * T)
|
||||
|
||||
|
||||
def moneyness(strike_price, current_price, option_type="call", threshold=0.005):
|
||||
"""Calculates if an option is ITM, ATM, or OTM"""
|
||||
fuzzy_price = threshold * strike_price
|
||||
|
||||
if option_type == "put":
|
||||
if current_price < strike_price - fuzzy_price:
|
||||
return "ITM"
|
||||
if current_price > strike_price + fuzzy_price:
|
||||
return "OTM"
|
||||
return "ATM"
|
||||
|
||||
if option_type == "call":
|
||||
if current_price > strike_price + fuzzy_price:
|
||||
return "ITM"
|
||||
if current_price < strike_price - fuzzy_price:
|
||||
return "OTM"
|
||||
return "ATM"
|
||||
|
||||
return ""
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
import pytest
|
||||
import pandas as pd
|
||||
from pandas.testing import assert_series_equal
|
||||
|
||||
from . import options
|
||||
|
||||
|
||||
# https://analystprep.com/study-notes/actuarial-exams/soa/ifm-investment-and-financial-markets/black-scholes-option-pricing-model/
|
||||
@pytest.mark.parametrize(
|
||||
"S, K, T, r, sigma, option_type, expected", [(100, 90, 0.5, 0.10, 0.25, "call", 16.11)]
|
||||
)
|
||||
def test_black_scholes_option_price_spot(S, K, T, r, sigma, option_type, expected):
|
||||
assert (
|
||||
abs(options.black_scholes_option_price_spot(S, K, T, r, sigma, option_type)) - expected
|
||||
< 0.05
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"F, K, T, r, sigma, option_type, expected", [(100, 90, 0.5, 0.10, 0.25, "call", 16.11)]
|
||||
)
|
||||
def test_black_scholes_option_price_forward(F, K, T, r, sigma, option_type, expected):
|
||||
assert (
|
||||
abs(options.black_scholes_option_price_forward(F, K, T, r, sigma, option_type)) - expected
|
||||
< 0.05
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"current_price, strike_price, option_type, expected",
|
||||
[
|
||||
(1, 10, "call", "ITM"),
|
||||
(10, 1, "call", "OTM"),
|
||||
(10, 10, "call", "ATM"),
|
||||
(1, 10, "put", "OTM"),
|
||||
(10, 1, "put", "ITM"),
|
||||
(10, 10, "put", "ATM"),
|
||||
],
|
||||
)
|
||||
def test_moneyness(current_price, strike_price, option_type, expected):
|
||||
assert options.moneyness(current_price, strike_price, option_type) == expected
|
||||
|
||||
|
||||
def test_end_to_end():
|
||||
option = {
|
||||
"t0": pd.Timestamp("2024-11-23"),
|
||||
"t1": pd.Timestamp("2025-05-10"),
|
||||
"S": 19,
|
||||
"K": 17,
|
||||
"r": 0.005,
|
||||
"sigma": 0.3,
|
||||
}
|
||||
options_df = options.as_df(option)
|
||||
|
||||
expected = {
|
||||
"F": 19.04367,
|
||||
"C_F": 2.70,
|
||||
"C_S": 2.70,
|
||||
"P_F": 0.66,
|
||||
"P_PCP": 0.66,
|
||||
}
|
||||
expected_df = pd.DataFrame([expected])
|
||||
|
||||
assert_series_equal(
|
||||
options.forward_price(options_df["S"], options_df["T"], options_df["r"]),
|
||||
expected_df["F"],
|
||||
check_names=False,
|
||||
check_exact=False,
|
||||
)
|
||||
|
||||
assert_series_equal(
|
||||
options.black_scholes_option_price_spot(
|
||||
options_df["S"],
|
||||
options_df["K"],
|
||||
options_df["T"],
|
||||
options_df["r"],
|
||||
options_df["sigma"],
|
||||
"call",
|
||||
),
|
||||
expected_df["C_S"],
|
||||
rtol=0.01,
|
||||
check_names=False,
|
||||
check_exact=False,
|
||||
)
|
||||
|
||||
assert_series_equal(
|
||||
options.black_scholes_option_price_forward(
|
||||
options.forward_price(options_df["S"], options_df["T"], options_df["r"]),
|
||||
options_df["K"],
|
||||
options_df["T"],
|
||||
options_df["r"],
|
||||
options_df["sigma"],
|
||||
"call",
|
||||
),
|
||||
expected_df["C_F"],
|
||||
rtol=0.01,
|
||||
check_names=False,
|
||||
check_exact=False,
|
||||
)
|
||||
|
||||
assert_series_equal(
|
||||
options.black_scholes_option_price_forward(
|
||||
options.forward_price(options_df["S"], options_df["T"], options_df["r"]),
|
||||
options_df["K"],
|
||||
options_df["T"],
|
||||
options_df["r"],
|
||||
options_df["sigma"],
|
||||
"put",
|
||||
),
|
||||
expected_df["P_F"],
|
||||
rtol=0.01,
|
||||
check_names=False,
|
||||
check_exact=False,
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import numpy as np
|
||||
|
||||
|
||||
def calculate_returns(prices, horizon=1, sort="ascending"):
|
||||
"""Calculate (simple) returns from price action"""
|
||||
step = 1 if sort == "ascending" else -1
|
||||
if horizon == 1:
|
||||
returns = prices / prices.shift(step) - 1
|
||||
else:
|
||||
returns = np.exp(np.log(prices / prices.shift(step)) * np.sqrt(horizon)) - 1
|
||||
return returns.dropna()
|
||||
|
||||
|
||||
def var(portfolio, prices, sort="ascending"):
|
||||
"""Calculate VaR 1-day for portfolio"""
|
||||
returns = calculate_returns(prices, 1, sort)
|
||||
pnl = portfolio.dot(returns.T)
|
||||
daily_pnl = pnl.sum(axis=0)
|
||||
daily_pnl_asc = daily_pnl.sort_values(ascending=True)
|
||||
return 0.4 * daily_pnl_asc[1] + 0.6 * daily_pnl_asc[2]
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import os
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from pandas.testing import assert_series_equal
|
||||
|
||||
from . import var
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prices, horizon, ascending, expected",
|
||||
[
|
||||
([1, 2, 3, 4, 5], 1, "ascending", [1.0, 0.5, 0.333333, 0.25]),
|
||||
([5, 4, 3, 2, 1], 1, "descending",[0.25, 0.333333, 0.5, 1.0])
|
||||
],
|
||||
)
|
||||
def test_calculate_returns(prices, horizon, ascending, expected):
|
||||
assert_series_equal(
|
||||
pd.Series(var.calculate_returns(pd.DataFrame(prices), horizon, ascending).squeeze()),
|
||||
pd.Series(expected),
|
||||
check_names=False,
|
||||
check_exact=False,
|
||||
check_index=False,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"portfolio, prices, expected",
|
||||
[
|
||||
({"FX-1": [100.00]}, {"FX-1": [1, 1.1, 0.9, 0.8]}, -6.909090),
|
||||
({"FX-1": [100.00]}, {"FX-1": [1, 0.9, 0.8, 0.8]}, -10.66666),
|
||||
],
|
||||
)
|
||||
def test_var(portfolio, prices, expected):
|
||||
assert var.var(pd.DataFrame(portfolio), pd.DataFrame(prices)) == pytest.approx(expected)
|
||||
|
||||
|
||||
def test_end_to_end():
|
||||
portfolio = pd.DataFrame([{"ccy-1": 153084.81, "ccy-2": 95891.51}])
|
||||
fx_prices = pd.read_csv(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "var_fx_prices.csv"),
|
||||
parse_dates=["date"],
|
||||
index_col="date",
|
||||
dtype=float,
|
||||
)
|
||||
fx_prices.sort_index(inplace=True, ascending=True)
|
||||
assert var.var(portfolio, fx_prices) == pytest.approx(-13572.73)
|
||||
Loading…
Reference in New Issue