daily update
This commit is contained in:
+9
-2
@@ -37,7 +37,7 @@ dateparser==1.2.0
|
||||
debugpy==1.8.5
|
||||
decorator==5.1.1
|
||||
defusedxml==0.7.1
|
||||
dill==0.3.8
|
||||
dill==0.3.9
|
||||
exceptiongroup==1.2.2
|
||||
exchange_calendars==4.5.5
|
||||
executing==2.0.1
|
||||
@@ -84,7 +84,7 @@ jupyterlab_server==2.27.1
|
||||
jupyterlab_widgets==3.0.13
|
||||
kiwisolver==1.4.5
|
||||
korean-lunar-calendar==0.3.1
|
||||
lightweight_charts @ git+https://github.com/drew2323/lightweight-charts-python.git@7986aa9195d9d3204a998d1a8f5778d95219a08e
|
||||
lightweight_charts @ git+https://github.com/drew2323/lightweight-charts-python.git@c4356bef3a29df29249149fa07160fce48ab8981
|
||||
llvmlite==0.39.1
|
||||
lxml==5.3.0
|
||||
markdown-it-py==3.0.0
|
||||
@@ -96,6 +96,7 @@ mistune==3.0.2
|
||||
ml_dtypes==0.5.0
|
||||
msgpack==1.0.8
|
||||
multidict==6.1.0
|
||||
multiprocess==0.70.17
|
||||
multitasking==0.0.11
|
||||
mypy-extensions==1.0.0
|
||||
nbclient==0.10.0
|
||||
@@ -114,11 +115,15 @@ pandas==2.2.1
|
||||
pandas_market_calendars==4.4.1
|
||||
pandocfilters==1.5.1
|
||||
parso==0.8.4
|
||||
pathos==0.3.3
|
||||
patsy==0.5.6
|
||||
peewee==3.17.6
|
||||
pexpect==4.9.0
|
||||
pillow==10.4.0
|
||||
platformdirs==4.2.2
|
||||
plotly==5.24.0
|
||||
pox==0.3.5
|
||||
ppft==1.7.6.9
|
||||
prometheus_client==0.21.0
|
||||
prompt_toolkit==3.0.47
|
||||
protobuf==5.28.2
|
||||
@@ -169,6 +174,7 @@ soupsieve==2.6
|
||||
SQLAlchemy==2.0.32
|
||||
sseclient-py==1.8.0
|
||||
stack-data==0.6.3
|
||||
statsmodels==0.14.4
|
||||
stratlab_db @ git+https://gitea.stratlab.dev/Stratlab/db.git@0bbe486de7ac410a9375f2ccf7d557a658a662ea
|
||||
stumpy==1.13.0
|
||||
TA-Lib==0.4.32
|
||||
@@ -182,6 +188,7 @@ toolz==0.12.1
|
||||
tornado==6.4.1
|
||||
tqdm==4.66.5
|
||||
traitlets==5.14.3
|
||||
ttools @ git+https://github.com/drew2323/ttools.git@6cfee7aa4c9f1c13efdf2b9dcb54d1ae2106d73d
|
||||
tulipy==0.4.0
|
||||
types-python-dateutil==2.9.0.20240316
|
||||
typing_extensions==4.9.0
|
||||
|
||||
@@ -9,11 +9,33 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"None\n",
|
||||
"Loaded env variables from file None\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Activating profile profile1\n",
|
||||
"</pre>\n"
|
||||
],
|
||||
"text/plain": [
|
||||
"Activating profile profile1\n"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from lightweight_charts import Panel, chart, PlotAccessor\n",
|
||||
"from lightweight_charts import Panel, chart, PlotDFAccessor\n",
|
||||
"from v2realbot.utils.utils import zoneNY\n",
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
@@ -496,7 +518,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -508,25 +530,34 @@
|
||||
"trades_df-SPY-2024-01-01T09:30:00-2024-05-14T16:00:00.parquet\n",
|
||||
"trades_df-BAC-2023-01-01T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-BAC-2024-01-11T09:30:00-2024-01-12T16:00:00.parquet\n",
|
||||
"trades_df-BAC-2024-10-03T09:30:00-2024-10-16T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\n",
|
||||
"trades_df-BAC-2023-01-01T09:30:00-2024-10-02T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\n",
|
||||
"trades_df-BAC-2024-05-15T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-BAC-2024-10-03T09:30:00-2024-10-16T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\n",
|
||||
"ohlcv_df-BAC-2023-01-01T09:30:00-2024-10-02T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\n",
|
||||
"ohlcv_df-BAC-2024-01-01T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-SPY-2024-01-01T09:30:00-2024-05-14T16:00:00.parquet\n",
|
||||
"ohlcv_df-BAC-2024-01-01T09_30_00-2024-05-14T16_00_00-CO4B7VPWUZF-100.parquet\n",
|
||||
"ohlcv_df-BAC-2023-01-01T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-BAC-2023-01-01T09_30_00-2024-05-25T15_30_00-47BCFOPUVWZ-100.parquet\n"
|
||||
"ohlcv_df-BAC-2023-01-01T09_30_00-2024-05-25T15_30_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"<class 'pandas.core.frame.DataFrame'>\n",
|
||||
"DatetimeIndex: 114097 entries, 2024-10-03 09:30:00-04:00 to 2024-10-16 15:59:59-04:00\n",
|
||||
"Data columns (total 10 columns):\n",
|
||||
" # Column Non-Null Count Dtype \n",
|
||||
"--- ------ -------------- ----- \n",
|
||||
" 0 open 114097 non-null float64 \n",
|
||||
" 1 high 114097 non-null float64 \n",
|
||||
" 2 low 114097 non-null float64 \n",
|
||||
" 3 close 114097 non-null float64 \n",
|
||||
" 4 volume 114097 non-null float64 \n",
|
||||
" 5 trades 114097 non-null float64 \n",
|
||||
" 6 updated 114097 non-null datetime64[ns, US/Eastern]\n",
|
||||
" 7 vwap 114097 non-null float64 \n",
|
||||
" 8 buyvolume 114097 non-null float64 \n",
|
||||
" 9 sellvolume 114097 non-null float64 \n",
|
||||
"dtypes: datetime64[ns, US/Eastern](1), float64(9)\n",
|
||||
"memory usage: 9.6 MB\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"440"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
@@ -545,7 +576,8 @@
|
||||
"files = [f for f in os.listdir(dir) if f.endswith(\".parquet\")]\n",
|
||||
"print('\\n'.join(map(str, files)))\n",
|
||||
"#file_name = \"ohlcv_df-BAC-2023-01-01T09_30_00-2024-05-25T15_30_00-47BCFOPUVWZ-100.parquet\"\n",
|
||||
"file_name = \"ohlcv_df-BAC-2023-01-01T09:30:00-2024-10-02T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\"\n",
|
||||
"#file_name = \"ohlcv_df-BAC-2023-01-01T09:30:00-2024-10-02T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\"\n",
|
||||
"file_name = \"ohlcv_df-BAC-2024-10-03T09:30:00-2024-10-16T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\"\n",
|
||||
"ohlcv_df = pd.read_parquet(dir+file_name,engine='pyarrow')\n",
|
||||
"#filter ohlcv_df to certain date range (assuming datetime index)\n",
|
||||
"#ohlcv_df = ohlcv_df.loc[\"2024-02-12 9:30\":\"2024-02-16 16:00\"]\n",
|
||||
@@ -555,9 +587,29 @@
|
||||
"\n",
|
||||
"basic_data = vbt.Data.from_data(vbt.symbol_dict({\"BAC\": ohlcv_df}), tz_convert=zoneNY)\n",
|
||||
"#ohlcv_df= None\n",
|
||||
"basic_data.wrapper.index.normalize().nunique()\n",
|
||||
"bd = basic_data.transform(lambda df: df[:1000000])\n",
|
||||
"bd.data[\"BAC\"].info()\n"
|
||||
"# basic_data.wrapper.index.normalize().nunique()\n",
|
||||
"# bd = basic_data.transform(lambda df: df[:1000000])\n",
|
||||
"basic_data.data[\"BAC\"].info()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"10"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"basic_data.wrapper.index.normalize().nunique()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -569,19 +621,30 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The autoreload extension is already loaded. To reload it, use:\n",
|
||||
" %reload_ext autoreload\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%load_ext autoreload\n",
|
||||
"%autoreload 2\n",
|
||||
"from lib.db import Connection\n",
|
||||
"SYMBOL = \"BAC\"\n",
|
||||
"SCHEMA = \"ohlcv_1s\" #time based 1s other options ohlcv_vol_200 (volume based ohlcv with resolution of 200), ohlcv_renko_20 (renko with 20 bricks size) ...\n",
|
||||
"DB = \"market_data\"\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" con = Connection(db_name=DB, default_schema=SCHEMA, create_db=True)\n",
|
||||
" con.force_rollback_all() # Force cleanup before attempting to save\n",
|
||||
" con.save(basic_data,1000)\n",
|
||||
" con = Connection(db_name=DB, default_schema=SCHEMA, create_db=False)\n",
|
||||
" #con.force_rollback_all() # Force cleanup before attempting to save\n",
|
||||
" con.save(basic_data, chunksize=500)\n",
|
||||
"except Exception as e:\n",
|
||||
" print(f\"Error occurred: {str(e)}\")\n",
|
||||
"\n",
|
||||
|
||||
@@ -29,9 +29,30 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"None\n",
|
||||
"Loaded env variables from file None\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Activating profile profile1\n",
|
||||
"</pre>\n"
|
||||
],
|
||||
"text/plain": [
|
||||
"Activating profile profile1\n"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
@@ -43,6 +64,7 @@
|
||||
"ohlcv_df-BAC-2024-01-11T09:30:00-2024-01-12T16:00:00.parquet\n",
|
||||
"trades_df-BAC-2023-01-01T09:30:00-2024-10-02T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\n",
|
||||
"trades_df-BAC-2024-05-15T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-BAC-2023-01-01T09:30:00-2024-10-02T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\n",
|
||||
"ohlcv_df-BAC-2024-01-01T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-SPY-2024-01-01T09:30:00-2024-05-14T16:00:00.parquet\n",
|
||||
"ohlcv_df-BAC-2024-01-01T09_30_00-2024-05-14T16_00_00-CO4B7VPWUZF-100.parquet\n",
|
||||
@@ -56,7 +78,7 @@
|
||||
"['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -92,8 +114,8 @@
|
||||
"\n",
|
||||
"symbol = \"BAC\"\n",
|
||||
"#datetime in zoneNY \n",
|
||||
"day_start = datetime(2023, 1, 1, 9, 30, 0)\n",
|
||||
"day_stop = datetime(2024, 10, 2, 16, 00, 0)\n",
|
||||
"day_start = datetime(2024, 10, 3, 9, 30, 0)\n",
|
||||
"day_stop = datetime(2024, 10, 16, 16, 00, 0)\n",
|
||||
"day_start = zoneNY.localize(day_start)\n",
|
||||
"day_stop = zoneNY.localize(day_stop)\n",
|
||||
"#filename of trades_df parquet, date are in isoformat but without time zone part\n",
|
||||
@@ -114,30 +136,187 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Contains 10 market days\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Processing market days: 100%|██████████| 10/10 [00:00<00:00, 267.74it/s]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"NOT FOUND. Fetching from remote\n",
|
||||
"NOT FOUND. Fetching from remote\n",
|
||||
"NOT FOUND. Fetching from remote\n",
|
||||
"NOT FOUND. Fetching from remote\n",
|
||||
"NOT FOUND. Fetching from remote\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Fetching data: 0%| | 0/10 [00:00<?, ?it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Remote fetched: is_empty=False 2024-10-03 09:30:00-04:00 2024-10-03 16:00:00-04:00\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1727962200-1727985600.cache.gz\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Fetching data: 10%|█ | 1/10 [00:21<03:12, 21.41s/it]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n",
|
||||
"minsize 100\n",
|
||||
"NOT FOUND. Fetching from remote\n",
|
||||
"Remote fetched: is_empty=False 2024-10-08 09:30:00-04:00 2024-10-08 16:00:00-04:00\n",
|
||||
"Remote fetched: is_empty=False 2024-10-09 09:30:00-04:00 2024-10-09 16:00:00-04:00\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1728394200-1728417600.cache.gz\n",
|
||||
"Remote fetched: is_empty=False 2024-10-07 09:30:00-04:00 2024-10-07 16:00:00-04:00\n",
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n",
|
||||
"minsize 100\n",
|
||||
"NOT FOUND. Fetching from remote\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1728480600-1728504000.cache.gz\n",
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n",
|
||||
"minsize 100\n",
|
||||
"NOT FOUND. Fetching from remote\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1728307800-1728331200.cache.gz\n",
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n",
|
||||
"minsize 100\n",
|
||||
"NOT FOUND. Fetching from remote\n",
|
||||
"Remote fetched: is_empty=False 2024-10-04 09:30:00-04:00 2024-10-04 16:00:00-04:00\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1728048600-1728072000.cache.gz\n",
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Fetching data: 20%|██ | 2/10 [00:32<02:01, 15.24s/it]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"minsize 100\n",
|
||||
"NOT FOUND. Fetching from remote\n",
|
||||
"Remote fetched: is_empty=False 2024-10-10 09:30:00-04:00 2024-10-10 16:00:00-04:00\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1728567000-1728590400.cache.gz\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Fetching data: 60%|██████ | 6/10 [00:47<00:25, 6.40s/it]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n",
|
||||
"minsize 100\n",
|
||||
"Remote fetched: is_empty=False 2024-10-14 09:30:00-04:00 2024-10-14 16:00:00-04:00\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1728912600-1728936000.cache.gz\n",
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n",
|
||||
"minsize 100\n",
|
||||
"Remote fetched: is_empty=False 2024-10-16 09:30:00-04:00 2024-10-16 16:00:00-04:00\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1729085400-1729108800.cache.gz\n",
|
||||
"Remote fetched: is_empty=False 2024-10-11 09:30:00-04:00 2024-10-11 16:00:00-04:00\n",
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n",
|
||||
"minsize 100\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1728653400-1728676800.cache.gz\n",
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Fetching data: 70%|███████ | 7/10 [01:13<00:31, 10.55s/it]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"minsize 100\n",
|
||||
"Remote fetched: is_empty=False 2024-10-15 09:30:00-04:00 2024-10-15 16:00:00-04:00\n",
|
||||
"Saving to Trade CACHE /Users/davidbrazda/Library/Application Support/v2realbot/tradecache/BAC-1728999000-1729022400.cache.gz\n",
|
||||
"excluding conditions ['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Fetching data: 100%|██████████| 10/10 [01:25<00:00, 8.53s/it]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"minsize 100\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from v2realbot.loader.aggregator_vectorized import fetch_daily_stock_trades, fetch_trades_parallel, generate_time_bars_nb, aggregate_trades, fetch_trades_parallel_optimized\n",
|
||||
"#fetch trades in one go\n",
|
||||
"#trades_df = fetch_daily_stock_trades(symbol, day_start, day_stop, exclude_conditions=exclude_conditions, minsize=minsize, force_remote=False, max_retries=5, backoff_factor=1)\n",
|
||||
"#fetch trades in parallel - for longer intervals\n",
|
||||
"#trades_df = fetch_trades_parallel(symbol, day_start, day_stop, exclude_conditions=exclude_conditions, minsize=minsize, force_remote=False, max_workers=None)\n",
|
||||
"trades_df = fetch_trades_parallel(symbol, day_start, day_stop, exclude_conditions=exclude_conditions, minsize=minsize, force_remote=True, max_workers=None)\n",
|
||||
" \n",
|
||||
"##trades_df.info()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#trades_df.to_parquet(file_trades, engine='pyarrow', compression='gzip')"
|
||||
"trades_df.to_parquet(file_trades, engine='pyarrow', compression='gzip')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -156,7 +335,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -170,9 +349,275 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"\"/Users/davidbrazda/Library/Application Support/v2realbot/notebooks/ohlcv_df-BAC-2024-10-03T09:30:00-2024-10-16T16:00:00-['4', '7', 'B', 'C', 'F', 'O', 'P', 'U', 'V', 'W', 'Z']-100.parquet\""
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"file_ohlcv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>open</th>\n",
|
||||
" <th>high</th>\n",
|
||||
" <th>low</th>\n",
|
||||
" <th>close</th>\n",
|
||||
" <th>volume</th>\n",
|
||||
" <th>trades</th>\n",
|
||||
" <th>updated</th>\n",
|
||||
" <th>vwap</th>\n",
|
||||
" <th>buyvolume</th>\n",
|
||||
" <th>sellvolume</th>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>time</th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-03 09:30:00-04:00</th>\n",
|
||||
" <td>38.9800</td>\n",
|
||||
" <td>39.0000</td>\n",
|
||||
" <td>38.940</td>\n",
|
||||
" <td>38.970</td>\n",
|
||||
" <td>249774.0</td>\n",
|
||||
" <td>6.0</td>\n",
|
||||
" <td>2024-10-03 09:30:01.061997-04:00</td>\n",
|
||||
" <td>38.960055</td>\n",
|
||||
" <td>500.0</td>\n",
|
||||
" <td>249088.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-03 09:30:01-04:00</th>\n",
|
||||
" <td>38.9500</td>\n",
|
||||
" <td>39.0001</td>\n",
|
||||
" <td>38.950</td>\n",
|
||||
" <td>39.000</td>\n",
|
||||
" <td>13553.0</td>\n",
|
||||
" <td>44.0</td>\n",
|
||||
" <td>2024-10-03 09:30:02.171691-04:00</td>\n",
|
||||
" <td>38.985179</td>\n",
|
||||
" <td>2133.0</td>\n",
|
||||
" <td>1894.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-03 09:30:02-04:00</th>\n",
|
||||
" <td>38.9992</td>\n",
|
||||
" <td>39.0100</td>\n",
|
||||
" <td>38.990</td>\n",
|
||||
" <td>39.010</td>\n",
|
||||
" <td>4600.0</td>\n",
|
||||
" <td>20.0</td>\n",
|
||||
" <td>2024-10-03 09:30:03.091339-04:00</td>\n",
|
||||
" <td>39.000123</td>\n",
|
||||
" <td>1031.0</td>\n",
|
||||
" <td>797.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-03 09:30:03-04:00</th>\n",
|
||||
" <td>38.9900</td>\n",
|
||||
" <td>39.0400</td>\n",
|
||||
" <td>38.990</td>\n",
|
||||
" <td>39.030</td>\n",
|
||||
" <td>7533.0</td>\n",
|
||||
" <td>36.0</td>\n",
|
||||
" <td>2024-10-03 09:30:04.193646-04:00</td>\n",
|
||||
" <td>39.030827</td>\n",
|
||||
" <td>1733.0</td>\n",
|
||||
" <td>713.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-03 09:30:04-04:00</th>\n",
|
||||
" <td>39.0320</td>\n",
|
||||
" <td>39.0350</td>\n",
|
||||
" <td>39.032</td>\n",
|
||||
" <td>39.035</td>\n",
|
||||
" <td>9142.0</td>\n",
|
||||
" <td>2.0</td>\n",
|
||||
" <td>2024-10-03 09:30:07.260896-04:00</td>\n",
|
||||
" <td>39.032033</td>\n",
|
||||
" <td>9142.0</td>\n",
|
||||
" <td>0.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>...</th>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-16 15:59:55-04:00</th>\n",
|
||||
" <td>42.8100</td>\n",
|
||||
" <td>42.8100</td>\n",
|
||||
" <td>42.810</td>\n",
|
||||
" <td>42.810</td>\n",
|
||||
" <td>8681.0</td>\n",
|
||||
" <td>22.0</td>\n",
|
||||
" <td>2024-10-16 15:59:56.000104-04:00</td>\n",
|
||||
" <td>42.810000</td>\n",
|
||||
" <td>0.0</td>\n",
|
||||
" <td>0.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-16 15:59:56-04:00</th>\n",
|
||||
" <td>42.8150</td>\n",
|
||||
" <td>42.8150</td>\n",
|
||||
" <td>42.810</td>\n",
|
||||
" <td>42.810</td>\n",
|
||||
" <td>4128.0</td>\n",
|
||||
" <td>9.0</td>\n",
|
||||
" <td>2024-10-16 15:59:57.010896-04:00</td>\n",
|
||||
" <td>42.811550</td>\n",
|
||||
" <td>1100.0</td>\n",
|
||||
" <td>603.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-16 15:59:57-04:00</th>\n",
|
||||
" <td>42.8150</td>\n",
|
||||
" <td>42.8150</td>\n",
|
||||
" <td>42.810</td>\n",
|
||||
" <td>42.810</td>\n",
|
||||
" <td>5301.0</td>\n",
|
||||
" <td>20.0</td>\n",
|
||||
" <td>2024-10-16 15:59:58.006387-04:00</td>\n",
|
||||
" <td>42.812493</td>\n",
|
||||
" <td>789.0</td>\n",
|
||||
" <td>1708.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-16 15:59:58-04:00</th>\n",
|
||||
" <td>42.8160</td>\n",
|
||||
" <td>42.8200</td>\n",
|
||||
" <td>42.800</td>\n",
|
||||
" <td>42.800</td>\n",
|
||||
" <td>21469.0</td>\n",
|
||||
" <td>33.0</td>\n",
|
||||
" <td>2024-10-16 15:59:59.088188-04:00</td>\n",
|
||||
" <td>42.809572</td>\n",
|
||||
" <td>542.0</td>\n",
|
||||
" <td>632.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2024-10-16 15:59:59-04:00</th>\n",
|
||||
" <td>42.8087</td>\n",
|
||||
" <td>42.8100</td>\n",
|
||||
" <td>42.800</td>\n",
|
||||
" <td>42.810</td>\n",
|
||||
" <td>26899.0</td>\n",
|
||||
" <td>16.0</td>\n",
|
||||
" <td>2024-10-16 15:59:59.997799-04:00</td>\n",
|
||||
" <td>42.801563</td>\n",
|
||||
" <td>4757.0</td>\n",
|
||||
" <td>16482.0</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"<p>114097 rows × 10 columns</p>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" open high low close volume trades \\\n",
|
||||
"time \n",
|
||||
"2024-10-03 09:30:00-04:00 38.9800 39.0000 38.940 38.970 249774.0 6.0 \n",
|
||||
"2024-10-03 09:30:01-04:00 38.9500 39.0001 38.950 39.000 13553.0 44.0 \n",
|
||||
"2024-10-03 09:30:02-04:00 38.9992 39.0100 38.990 39.010 4600.0 20.0 \n",
|
||||
"2024-10-03 09:30:03-04:00 38.9900 39.0400 38.990 39.030 7533.0 36.0 \n",
|
||||
"2024-10-03 09:30:04-04:00 39.0320 39.0350 39.032 39.035 9142.0 2.0 \n",
|
||||
"... ... ... ... ... ... ... \n",
|
||||
"2024-10-16 15:59:55-04:00 42.8100 42.8100 42.810 42.810 8681.0 22.0 \n",
|
||||
"2024-10-16 15:59:56-04:00 42.8150 42.8150 42.810 42.810 4128.0 9.0 \n",
|
||||
"2024-10-16 15:59:57-04:00 42.8150 42.8150 42.810 42.810 5301.0 20.0 \n",
|
||||
"2024-10-16 15:59:58-04:00 42.8160 42.8200 42.800 42.800 21469.0 33.0 \n",
|
||||
"2024-10-16 15:59:59-04:00 42.8087 42.8100 42.800 42.810 26899.0 16.0 \n",
|
||||
"\n",
|
||||
" updated vwap \\\n",
|
||||
"time \n",
|
||||
"2024-10-03 09:30:00-04:00 2024-10-03 09:30:01.061997-04:00 38.960055 \n",
|
||||
"2024-10-03 09:30:01-04:00 2024-10-03 09:30:02.171691-04:00 38.985179 \n",
|
||||
"2024-10-03 09:30:02-04:00 2024-10-03 09:30:03.091339-04:00 39.000123 \n",
|
||||
"2024-10-03 09:30:03-04:00 2024-10-03 09:30:04.193646-04:00 39.030827 \n",
|
||||
"2024-10-03 09:30:04-04:00 2024-10-03 09:30:07.260896-04:00 39.032033 \n",
|
||||
"... ... ... \n",
|
||||
"2024-10-16 15:59:55-04:00 2024-10-16 15:59:56.000104-04:00 42.810000 \n",
|
||||
"2024-10-16 15:59:56-04:00 2024-10-16 15:59:57.010896-04:00 42.811550 \n",
|
||||
"2024-10-16 15:59:57-04:00 2024-10-16 15:59:58.006387-04:00 42.812493 \n",
|
||||
"2024-10-16 15:59:58-04:00 2024-10-16 15:59:59.088188-04:00 42.809572 \n",
|
||||
"2024-10-16 15:59:59-04:00 2024-10-16 15:59:59.997799-04:00 42.801563 \n",
|
||||
"\n",
|
||||
" buyvolume sellvolume \n",
|
||||
"time \n",
|
||||
"2024-10-03 09:30:00-04:00 500.0 249088.0 \n",
|
||||
"2024-10-03 09:30:01-04:00 2133.0 1894.0 \n",
|
||||
"2024-10-03 09:30:02-04:00 1031.0 797.0 \n",
|
||||
"2024-10-03 09:30:03-04:00 1733.0 713.0 \n",
|
||||
"2024-10-03 09:30:04-04:00 9142.0 0.0 \n",
|
||||
"... ... ... \n",
|
||||
"2024-10-16 15:59:55-04:00 0.0 0.0 \n",
|
||||
"2024-10-16 15:59:56-04:00 1100.0 603.0 \n",
|
||||
"2024-10-16 15:59:57-04:00 789.0 1708.0 \n",
|
||||
"2024-10-16 15:59:58-04:00 542.0 632.0 \n",
|
||||
"2024-10-16 15:59:59-04:00 4757.0 16482.0 \n",
|
||||
"\n",
|
||||
"[114097 rows x 10 columns]"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ohlcv_df"
|
||||
]
|
||||
|
||||
+19219
-243
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
select * from "BAC"
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,404 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import vectorbtpro as vbt
|
||||
import talib
|
||||
from numba import njit
|
||||
from pathlib import Path
|
||||
import scipy
|
||||
import itertools
|
||||
|
||||
FX_MAJOR_LIST = ['EURUSD','AUDNZD','AUDUSD','AUDJPY','EURCHF','EURGBP','EURJPY','GBPCHF','GBPJPY','GBPUSD','NZDUSD','USDCAD','USDCHF','USDJPY','CADJPY','EURAUD','CHFJPY','EURCAD','AUDCAD','AUDCHF','CADCHF','EURNZD','GBPAUD','GBPCAD','GBPNZD','NZDCAD','NZDCHF','NZDJPY']
|
||||
FX_MAJOR_LIST = sorted(FX_MAJOR_LIST)
|
||||
FX_MAJOR_PATH = 'Data/FOREX/oanda/majors_{0}/{1}.csv'
|
||||
|
||||
# Major Currency Pair Loader
|
||||
def get_fx_majors(datapath=FX_MAJOR_PATH, side='bid', start=None, end=None, fillna=True):
|
||||
if side.lower() not in ['bid', 'ask']:
|
||||
raise ValueError('Side *{0}* not recognized. Must be bid or ask'.format(side))
|
||||
print('Loading FOREX {0} major pairs.'.format(side.upper()))
|
||||
if '{0}' in datapath:
|
||||
data = vbt.CSVData.fetch([datapath.format(side.lower(), i) for i in FX_MAJOR_LIST], start=start, end=end)
|
||||
else:
|
||||
data = vbt.CSVData.fetch(['{0}/majors_{1}/{2}.csv'.format(datapath, side, i) for i in FX_MAJOR_LIST], start=start, end=end)
|
||||
|
||||
return data
|
||||
|
||||
# FOREX Position Sizing with ATR
|
||||
def get_fx_position_size(data, init_cash=10_000, risk=0.01, atr=14, sl=1.5):
|
||||
atr = vbt.talib("ATR").run(
|
||||
data.high,
|
||||
data.low,
|
||||
data.close,
|
||||
timeperiod=atr,
|
||||
skipna=True
|
||||
).real.droplevel(0, axis=1)
|
||||
pip_decimal = {i:0.01 if 'JPY' in i else 0.0001 for i in data.close.columns}
|
||||
pip_value = {i:100 if 'JPY' in i else 10_000 for i in data.close.columns}
|
||||
stop_pips = np.ceil(sl*atr/pip_decimal)
|
||||
|
||||
size = ((risk*init_cash/stop_pips)*pip_value)
|
||||
|
||||
return size
|
||||
|
||||
|
||||
# NNFX Dynamic Risk Model
|
||||
@njit
|
||||
def adjust_func_nb(c, atr, sl, tp):
|
||||
position_now = c.last_position[c.col]
|
||||
|
||||
# Check if position is open and needs to be managed
|
||||
if position_now != 0:
|
||||
# Get Current SL & TP Info
|
||||
sl_info = c.last_sl_info[c.col]
|
||||
tp_info = c.last_tp_info[c.col]
|
||||
tp_info.ladder = True
|
||||
tsl_info = c.last_tsl_info[c.col]
|
||||
|
||||
last_order = c.order_records[c.order_counts[c.col] - 1, c.col]
|
||||
|
||||
if last_order.stop_type == -1:
|
||||
# STOP TYPE == -1, User Generate Order
|
||||
# Get Current ATR Value
|
||||
catr = vbt.pf_nb.select_nb(c, atr)
|
||||
|
||||
if not vbt.pf_nb.is_stop_info_active_nb(sl_info):
|
||||
sl_info.stop = catr*vbt.pf_nb.select_nb(c, sl)
|
||||
|
||||
if not vbt.pf_nb.is_stop_info_active_nb(tp_info):
|
||||
tp_info.stop = catr*vbt.pf_nb.select_nb(c, tp)
|
||||
tp_info.exit_size = round(abs(position_now) * 0.5)
|
||||
|
||||
elif last_order.stop_type == vbt.sig_enums.StopType.TP:
|
||||
# STOP TYPE == 3, last fill was a take profit
|
||||
if not vbt.pf_nb.is_stop_info_active_nb(tsl_info):
|
||||
# Set a Trailing Stop for remaining
|
||||
tsl_info.stop = sl_info.stop
|
||||
|
||||
# Deactivate Original Stop
|
||||
sl_info.stop = np.nan
|
||||
|
||||
def get_NNFX_risk(atr_, sl_, tp_):
|
||||
args = {"adjust_func_nb":adjust_func_nb,
|
||||
"adjust_args":(vbt.Rep("atr"), vbt.Rep("sl"), vbt.Rep("tp")),
|
||||
"broadcast_named_args":dict(
|
||||
atr=atr_,
|
||||
sl=sl_,
|
||||
tp=tp_
|
||||
),
|
||||
"use_stops":True}
|
||||
|
||||
return args
|
||||
|
||||
|
||||
# Whites Reality Check for a Single Strategy
|
||||
def col_p_val_WRC(col, means, n, inds):
|
||||
samples = col.values[inds].mean(axis=0)
|
||||
return (samples > means[col.name]).sum()/n
|
||||
|
||||
def get_WRC_p_val(raw_ret, allocations, n=2000):
|
||||
# Detrending & Zero Centering
|
||||
#raw_ret = np.log(data.close/data.open)
|
||||
det_ret = raw_ret - raw_ret.mean(axis=0)
|
||||
det_strat = np.sign(allocations+allocations.shift(1))*det_ret
|
||||
|
||||
# Zero Centering
|
||||
mean_strat = det_strat.mean(axis=0)
|
||||
zero_strat = det_strat - mean_strat
|
||||
|
||||
# Sampling
|
||||
inds = np.random.randint(0, raw_ret.shape[0], size=(raw_ret.shape[0], n))
|
||||
ps = zero_strat.apply(col_p_val_WRC, axis=0, args=(mean_strat, n, inds))
|
||||
|
||||
return ps
|
||||
|
||||
# Monte Carlo Permutation Method (MCP) for Inference Testing
|
||||
def col_p_val_MCP(col, det_ret, means, inds, n):
|
||||
samples = det_ret[col.name[-1]].values[inds]
|
||||
#print(col.values[:, np.newaxis].shape)
|
||||
samples = np.nanmean(samples*col.values[:, np.newaxis], axis=0)
|
||||
return (samples > means[col.name]).sum()/n
|
||||
|
||||
def get_MCP_p_val(raw_ret, allocations, n=2000):
|
||||
# Detrending
|
||||
det_ret = raw_ret - raw_ret.mean(axis=0)
|
||||
allocations = np.sign(allocations + allocations.shift(1))
|
||||
det_strat = allocations*det_ret
|
||||
|
||||
# Zero Centering
|
||||
mean_strat = det_strat.mean(axis=0)
|
||||
|
||||
# Sampling
|
||||
inds = np.tile(np.arange(0, raw_ret.shape[0])[:, np.newaxis], (1, 2000))
|
||||
inds = np.take_along_axis(inds, np.random.randn(*inds.shape).argsort(axis=0), axis=0)
|
||||
ps = allocations.apply(col_p_val_MCP, axis=0, args=(det_ret, mean_strat, inds, n))
|
||||
|
||||
return ps
|
||||
|
||||
def _nonull_df_dict(df, times=True):
|
||||
if times:
|
||||
d = {i:df[i].dropna().to_numpy(dtype=int) for i in df.columns}
|
||||
else:
|
||||
d = {i:df[i].dropna().to_numpy() for i in df.columns}
|
||||
return d
|
||||
|
||||
def col_p_val_MCPH(col, det_ret, means, times, signals, n):
|
||||
# Get Column Specific Holding Times & Signals
|
||||
_times = times[col.name]
|
||||
_signals = signals[col.name]
|
||||
|
||||
# Create Time/Signal Perumutation Arrays
|
||||
index_arr = np.tile(np.arange(0, len(_times))[:, np.newaxis], (1, n))
|
||||
sorter = np.random.randn(*index_arr.shape)
|
||||
index_arr = np.take_along_axis(index_arr, sorter.argsort(axis=0), axis=0)
|
||||
|
||||
# Create Sampling Array
|
||||
_times_perm = _times[index_arr]
|
||||
_signals_perm = _signals[index_arr]
|
||||
_times_flat = _times_perm.flatten('F')
|
||||
_signals_flat = _signals_perm.flatten('F')
|
||||
samples = np.repeat(_signals_flat, _times_flat).reshape((len(col), n), order='F')
|
||||
samples = np.multiply(det_ret[col.name[-1]].values[np.tile(np.arange(0, col.shape[0])[:, np.newaxis], (1, n))], samples)
|
||||
|
||||
samples = np.nanmean(samples, axis=0)
|
||||
|
||||
return (samples > means[col.name]).sum()/n
|
||||
|
||||
def get_MCPH_p_val(raw_ret, allocations, n=2000):
|
||||
# Detrending
|
||||
#raw_ret = np.log(data.close/data.open)
|
||||
det_ret = raw_ret - raw_ret.mean(axis=0)
|
||||
allocations = np.sign(allocations + allocations.shift(1)).fillna(0)
|
||||
det_strat = allocations*det_ret
|
||||
|
||||
# Zero Centering
|
||||
mean_strat = det_strat.mean(axis=0)
|
||||
|
||||
# Strategy Allocation Holding Distribution and Corresponding Signals
|
||||
changes = (allocations == allocations.shift(1))
|
||||
times = changes.cumsum()-changes.cumsum().where(~changes).ffill().fillna(0).astype(int) + 1
|
||||
times = times[times - times.shift(-1, fill_value=1) >= 0]
|
||||
signals = allocations[~times.isnull()]
|
||||
|
||||
# Get Dictionary of Times/Signals
|
||||
times = _nonull_df_dict(times)
|
||||
signals = _nonull_df_dict(signals, times=False)
|
||||
|
||||
# Sampling
|
||||
ps = allocations.apply(col_p_val_MCPH, axis=0, args=(det_ret, mean_strat, times, signals, n))
|
||||
|
||||
return ps
|
||||
|
||||
# Adjusted Returns: Adjusts closes of time series to reflect trade exit prices. Used as input to WRC and MCP statistical tests
|
||||
def get_adjusted_returns(data, pf):
|
||||
# Trade Records
|
||||
records = pf.trades.records_readable[['Column', 'Exit Index', 'Avg Exit Price']]
|
||||
records.Column=records['Column'].apply(lambda x: x[-1])
|
||||
|
||||
close_adj = data.get('Close')
|
||||
for row, value in records.iterrows():
|
||||
close_adj[value['Column']][value['Exit Index']] = value['Avg Exit Price']
|
||||
|
||||
return np.log(close_adj/data.open)
|
||||
|
||||
# Optimized Split
|
||||
def get_optimized_split(tf, frac, n):
|
||||
# Parameter Estimation
|
||||
d = tf/(frac + n*(1 - frac))
|
||||
di = frac*d
|
||||
do = (1-frac)*d
|
||||
|
||||
# Mixed Integer, Linear Optimization
|
||||
c = [-(1/frac - 1), 1]
|
||||
Aeq = [[1, n]]
|
||||
Aub = [[-1, 1],
|
||||
[(1/frac - 1), -1]]
|
||||
beq = [tf]
|
||||
bub = [0, 0]
|
||||
x0_bounds = (di*0.5, di*1.5)
|
||||
x1_bounds = (do*0.5, do*1.5)
|
||||
res = scipy.optimize.linprog(
|
||||
c, A_eq=Aeq, b_eq=beq, A_ub=Aub, b_ub=bub, bounds=(x0_bounds, x1_bounds),
|
||||
integrality=[1, 1],
|
||||
method='highs',
|
||||
options={"disp": True})
|
||||
|
||||
# Solutions
|
||||
di, do = res.x
|
||||
|
||||
# Actual Fraction
|
||||
frac_a = di/(do+di)
|
||||
|
||||
return int(di), int(do), frac_a
|
||||
|
||||
def wfo_split_func(splits, bounds, index, length_IS=20, length_OOS=30):
|
||||
if len(splits) == 0:
|
||||
new_split = (slice(0, length_IS), slice(length_IS, length_OOS+length_IS))
|
||||
else:
|
||||
# Previous split, second set, right bound
|
||||
prev_end = bounds[-1][1][1]
|
||||
|
||||
# Split Calculation
|
||||
new_split = (
|
||||
slice(prev_end-length_IS, prev_end),
|
||||
slice(prev_end, prev_end + length_OOS)
|
||||
)
|
||||
if new_split[-1].stop > len(index):
|
||||
return None
|
||||
return new_split
|
||||
|
||||
def get_wfo_splitter(index, fraction, n):
|
||||
# Generates a splitter based on train/(train+test) fraction and number of folds
|
||||
d_IS, d_OOS, frac = get_optimized_split(len(index), fraction, n)
|
||||
|
||||
# Generate the Splitter
|
||||
splitter = vbt.Splitter.from_split_func(
|
||||
index,
|
||||
wfo_split_func,
|
||||
split_args=(
|
||||
vbt.Rep("splits"),
|
||||
vbt.Rep("bounds"),
|
||||
vbt.Rep("index"),
|
||||
),
|
||||
split_kwargs={
|
||||
'length_IS':d_IS,
|
||||
'length_OOS':d_OOS
|
||||
},
|
||||
set_labels=["IS", "OOS"]
|
||||
)
|
||||
|
||||
return splitter
|
||||
|
||||
# WFO Fold Analysis Splitters
|
||||
def get_wfo_splitters(index, fractions, folds):
|
||||
# Create Combinations of Folds/Fractions
|
||||
combinations = itertools.product(fractions, folds)
|
||||
|
||||
# Generate Splitters
|
||||
splitters = {}
|
||||
splitter_ranges = {}
|
||||
for comb in combinations:
|
||||
splitter = get_wfo_splitter(index, comb[0], comb[1])
|
||||
splitters.update({comb:splitter})
|
||||
splitter_ranges.update({comb:[d_IS, d_OOS, frac]})
|
||||
|
||||
return splitters, splitter_ranges
|
||||
|
||||
# NNFX WFO Trainin Performance Function
|
||||
@vbt.parameterized(merg_func='concat')
|
||||
def strat_perf(data, ind, atr, pos_size, long_signal='long', short_signal='short', metric='sharpe_ratio'):
|
||||
# Simulation
|
||||
pf = vbt.Portfolio.from_signals(
|
||||
data,
|
||||
entries=getattr(ind, long_signal),
|
||||
short_entries=getattr(ind, short_signal),
|
||||
**get_NNFX_risk(atr, 1.5, 1.0),
|
||||
size=pos_size,
|
||||
size_type='amount',
|
||||
init_cash=10_000,
|
||||
delta_format='absolute',
|
||||
price='nextopen',
|
||||
stop_entry_price='fillprice',
|
||||
leverage=np.inf,
|
||||
#fixed_fees=pos_size*data.get('Spread')
|
||||
)
|
||||
result = getattr(pf, metric)
|
||||
return result
|
||||
|
||||
# Walk Forward Optimization Portfolio Simulation
|
||||
def walk_forward_optimization(data, ind, pos_size, atr, splitter, metric='total_return', long_signal='long', short_signal='short', group=True):
|
||||
|
||||
# Calculate Performance on Training Sets
|
||||
train_perf = splitter.apply(
|
||||
strat_perf,
|
||||
vbt.Takeable(data),
|
||||
vbt.Takeable(ind),
|
||||
vbt.Takeable(atr),
|
||||
vbt.Takeable(pos_size),
|
||||
metric=metric,
|
||||
long_signal=long_signal,
|
||||
short_signal=short_signal,
|
||||
_execute_kwargs=dict(
|
||||
show_progress=False,
|
||||
#clear_cache=50,
|
||||
#collect_garbage=50
|
||||
),
|
||||
merge_func='row_stack',
|
||||
set_='IS',
|
||||
execute_kwargs=dict(show_progress=True),
|
||||
jitted=True
|
||||
)
|
||||
|
||||
# Get the Best Parameters
|
||||
exclusions = [i for i in range(len(train_perf.index.names)) if train_perf.index.names[i] not in getattr(ind, long_signal).columns.names]
|
||||
group = train_perf.groupby(['split','symbol'])
|
||||
best = group.idxmax()
|
||||
best[:] = [tuple([i[j] for j in range(len(i)) if j not in exclusions]) for i in best]
|
||||
best = best.droplevel('symbol')
|
||||
|
||||
# Generate the OOS Signals
|
||||
opt_long = []
|
||||
opt_short = []
|
||||
for i in best.index.get_level_values('split').unique():
|
||||
_opt_long = splitter['OOS'].take(getattr(ind, long_signal))[i][best[i]]
|
||||
_opt_short = splitter['OOS'].take(getattr(ind, short_signal))[i][best[i]]
|
||||
|
||||
remove_cols = [i for i in _opt_long.columns.names if i != 'symbol']
|
||||
_opt_long = _opt_long.droplevel(remove_cols, axis=1)
|
||||
_opt_short = _opt_short.droplevel(remove_cols, axis=1)
|
||||
|
||||
opt_long.append(_opt_long)
|
||||
opt_short.append(_opt_short)
|
||||
opt_long = pd.concat(opt_long)
|
||||
opt_short = pd.concat(opt_short)
|
||||
|
||||
# Run the WFO Portfolio
|
||||
group_by = len(opt_long.columns)*[0] if group else None
|
||||
pf = vbt.Portfolio.from_signals(
|
||||
data,
|
||||
entries=opt_long,
|
||||
short_entries=opt_short,
|
||||
**get_NNFX_risk(atr, 1.5, 1.0),
|
||||
size=pos_size,
|
||||
size_type='amount',
|
||||
init_cash=10_000,
|
||||
delta_format='absolute',
|
||||
price='nextopen',
|
||||
stop_entry_price='fillprice',
|
||||
leverage=np.inf,
|
||||
#fixed_fees=pos_size*data.get('Spread'),
|
||||
group_by=group_by
|
||||
)
|
||||
|
||||
return pf
|
||||
|
||||
# WFO Fold Analysis
|
||||
def wfo_fold_analysis(data, ind, pos_size, atr, splitters, metric='total_return', long_signal='long', short_signal='short'):
|
||||
# Create the Results Matrix
|
||||
keys = splitters.keys()
|
||||
fractions = list(set([i[0] for i in keys]))
|
||||
folds = list(set([i[1] for i in keys]))
|
||||
FF, NN = np.meshgrid(fractions, folds)
|
||||
RR = np.zeros_like(FF)
|
||||
|
||||
# Perform the Analysis
|
||||
for key, splitter in splitters.items():
|
||||
# Get the Key Indices
|
||||
idx = np.where((key[0] == FF) & (key[1] == NN))
|
||||
|
||||
# WFO using Splitter
|
||||
print('Performing Walk Forward for train fraction {0} and N = {1}'.format(key[0], key[1]))
|
||||
wfo = walk_forward_optimization(data, ind, pos_size, atr, splitter, metric=metric, long_signal=long_signal, short_signal=short_signal)
|
||||
|
||||
# Correlation
|
||||
rolling_returns = pd.DataFrame(wfo.cumulative_returns)
|
||||
rolling_returns = rolling_returns[rolling_returns != 1.0].dropna()
|
||||
rolling_returns['idx'] = np.arange(0, len(rolling_returns), 1)
|
||||
rolling_returns
|
||||
corr_matrix = rolling_returns.corr()
|
||||
R_sq = corr_matrix.iloc[0, 1]**2
|
||||
|
||||
# Update the Results
|
||||
print(idx[0][0], idx[1][0], R_sq)
|
||||
RR[idx] = R_sq
|
||||
|
||||
return FF, NN, RR
|
||||
@@ -0,0 +1,704 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import vectorbtpro as vbt
|
||||
import talib
|
||||
from numba import njit
|
||||
|
||||
|
||||
# TREND FILTER INDICATOR
|
||||
# Apply Method
|
||||
def tf_apply(close, ema_period, shift_period, smooth_period, tick_val):
|
||||
ma = vbt.nb.ma_1d_nb(close/tick_val, ema_period, wtype=2)
|
||||
rocma = ma - vbt.nb.fshift_1d_nb(ma, shift_period)
|
||||
tf = np.abs(vbt.nb.ma_1d_nb(rocma, smooth_period, wtype=2))
|
||||
return tf
|
||||
# Indicator Factor
|
||||
TrendFilter = vbt.IF(
|
||||
class_name='TrendFilter',
|
||||
short_name='tf',
|
||||
input_names=['close'],
|
||||
param_names=['ema_period', 'shift_period', 'smooth_period', 'tick_val'],
|
||||
output_names=['real']
|
||||
).with_apply_func(
|
||||
tf_apply,
|
||||
takes_1d=True,
|
||||
ema_period=200,
|
||||
shift_period=1,
|
||||
smooth_period=5,
|
||||
tick_val=1e-4
|
||||
)
|
||||
|
||||
# Adaptive Laguerre
|
||||
# Helpers
|
||||
@njit
|
||||
def alag_loop_nb(p, l, warmup):
|
||||
f = np.full(p.shape, np.nan)
|
||||
L0 = np.full(p.shape, np.nan)
|
||||
L1 = np.full(p.shape, np.nan)
|
||||
L2 = np.full(p.shape, np.nan)
|
||||
L3 = np.full(p.shape, np.nan)
|
||||
dir_ = np.full(p.shape, 1)
|
||||
d = np.full(l, np.nan)
|
||||
|
||||
for i in range(3, p.shape[0]):
|
||||
if i < warmup:
|
||||
f[i] = p[i]
|
||||
L0[i] = p[i]
|
||||
L1[i] = p[i-1]
|
||||
L2[i] = p[i-2]
|
||||
L3[i] = p[i-2]
|
||||
else:
|
||||
# Get Differences
|
||||
mi = 0
|
||||
mx = 0
|
||||
a = 0
|
||||
for j in range(l):
|
||||
d[j] = p[i-j] - f[i-j-1]
|
||||
mi = d[j] if d[j] < mi else mi
|
||||
mx = d[j] if d[j] > mx else mx
|
||||
# Min-Max Rescale
|
||||
d = (d - mi)/(mx - mi)
|
||||
a = np.nanmedian(d)
|
||||
|
||||
# Calculation
|
||||
L0[i] = a*p[i] + (1-a)*L0[i-1]
|
||||
L1[i] = -(1 - a) * L0[i] + L0[i-1] + (1 - a) * L1[i-1]
|
||||
L2[i] = -(1 - a) * L1[i] + L1[i-1] + (1 - a) * L2[i-1]
|
||||
L3[i] = -(1 - a) * L2[i] + L2[i-1] + (1 - a) * L3[i-1]
|
||||
f[i] = (L0[i] + 2*L1[i] + 2*L2[i] + L3[i])/6
|
||||
if f[i] < f[i-1]:
|
||||
dir_[i] = -1
|
||||
|
||||
return f, dir_
|
||||
|
||||
def alag_apply(high, low, timeperiod):
|
||||
# Hardcoded 3X Timeperiod Bar Warmup
|
||||
warm = 3*timeperiod
|
||||
|
||||
# Average Price
|
||||
av_price = talib.MEDPRICE(high, low)
|
||||
av_price = vbt.nb.ffill_1d_nb(av_price)
|
||||
|
||||
# Filter
|
||||
filter, direction = alag_loop_nb(av_price, timeperiod, warm)
|
||||
|
||||
# Warmup
|
||||
filter[:warm] = np.nan
|
||||
direction[:warm] = 0
|
||||
|
||||
# Trade Indicators
|
||||
conf = direction > 0
|
||||
long = vbt.nb.crossed_above_1d_nb(direction, np.full(high.shape, 0))
|
||||
short = vbt.nb.crossed_below_1d_nb(direction, np.full(high.shape, 0))
|
||||
|
||||
return filter, direction, long, short, conf
|
||||
|
||||
AdaptiveLaguerre = vbt.IF(
|
||||
class_name='AdaptiveLaguerre',
|
||||
short_name='alag',
|
||||
input_names=['high', 'low'],
|
||||
param_names=['timeperiod'],
|
||||
output_names=['filter', 'direction', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
alag_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=20,
|
||||
)
|
||||
|
||||
class AdaptiveLaguerre(AdaptiveLaguerre):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
|
||||
s_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
filter = self.select_col_from_obj(self.filter, column)
|
||||
direction = self.select_col_from_obj(self.direction, column)
|
||||
|
||||
# Coloring
|
||||
long = pd.Series(np.nan, index=filter.index)
|
||||
short = pd.Series(np.nan, index=filter.index)
|
||||
ups = np.where(direction > 0)[0]
|
||||
downs = np.where(direction < 0)[0]
|
||||
|
||||
# Back Shifting the Start of Each Sequence (proper coloring)
|
||||
# ups
|
||||
upstarts = ups[1:] - ups[:-1]
|
||||
upstarts = np.insert(upstarts, 0, upstarts[0]) == 1
|
||||
upstarts = ups[np.where(~upstarts & np.roll(upstarts,-1))[0]] - 1
|
||||
ups = np.append(ups, upstarts)
|
||||
|
||||
# downs
|
||||
downstarts = downs[1:] - downs[:-1]
|
||||
downstarts = np.insert(downstarts, 0, downstarts[0]) == 1
|
||||
downstarts = downs[np.where(~downstarts & np.roll(downstarts,-1))[0]] - 1
|
||||
downs = np.append(downs, downstarts)
|
||||
|
||||
# Plot Lines
|
||||
long[ups] = filter.iloc[ups]
|
||||
short[downs] = filter.iloc[downs]
|
||||
|
||||
long.vbt.plot(fig=fig, **l_kwargs)
|
||||
short.vbt.plot(fig=fig, **s_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
# AROON UP & DOWN
|
||||
# Helpers
|
||||
def aroon_apply(h, l, t):
|
||||
|
||||
u = 100*vbt.nb.rolling_argmax_1d_nb(h, t, local=True)/(t-1)
|
||||
d = 100*vbt.nb.rolling_argmin_1d_nb(l, t, local=True)/(t-1)
|
||||
|
||||
u[:t] = np.nan
|
||||
d[:t] = np.nan
|
||||
|
||||
# Trade Indicators
|
||||
conf = u > d
|
||||
long = vbt.nb.crossed_above_1d_nb(u, d)
|
||||
short = vbt.nb.crossed_below_1d_nb(u, d)
|
||||
|
||||
return u, d, long, short, conf
|
||||
|
||||
Aroon = vbt.IF(
|
||||
class_name='Aroon',
|
||||
short_name='aroon',
|
||||
input_names=['high', 'low'],
|
||||
param_names=['timeperiod'],
|
||||
output_names=['up', 'down', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
aroon_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=20,
|
||||
)
|
||||
|
||||
# Class
|
||||
class Aroon(Aroon):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
|
||||
s_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
up = self.select_col_from_obj(self.up, column).rename('Aroon(up)')
|
||||
down = self.select_col_from_obj(self.down, column).rename('Aroon(down)')
|
||||
|
||||
up.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
down.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
# Elhers Two Pole Super Smoother
|
||||
# Helpers
|
||||
@njit
|
||||
def tpss_loop(p, l, warmup):
|
||||
f = np.full(p.shape, np.nan)
|
||||
dir_ = np.full(p.shape, 1)
|
||||
|
||||
# Initialize
|
||||
f[:warmup] = p[:warmup]
|
||||
a1 = np.exp(-1.414*3.14159/l)
|
||||
b1 = 2*a1*np.cos(np.deg2rad(1.414*180/l))
|
||||
c2 = b1
|
||||
c3 = -a1*a1
|
||||
c1 = 1 - c2 - c3
|
||||
for i in range(warmup, p.shape[0]):
|
||||
f[i] = c1*p[i] + c2*f[i-1] + c3*f[i-2]
|
||||
if f[i] - f[i-1] < 0:
|
||||
dir_[i] = -1
|
||||
|
||||
return f, dir_
|
||||
|
||||
def tpss_apply(close, timeperiod):
|
||||
# Hardcoded 3X Timeperiod Bar Warmup
|
||||
warm = 3*timeperiod
|
||||
|
||||
# Average Price
|
||||
av_price = (vbt.nb.fshift_1d_nb(close) + close)/2
|
||||
av_price = vbt.nb.ffill_1d_nb(av_price)
|
||||
|
||||
# Filter
|
||||
filter, direction = tpss_loop(av_price, timeperiod, warm)
|
||||
|
||||
# Warmup
|
||||
filter[:warm] = np.nan
|
||||
direction[:warm] = 0
|
||||
|
||||
# Trade Indicators
|
||||
conf = direction > 0
|
||||
long = vbt.nb.crossed_above_1d_nb(direction, np.full(close.shape, 0))
|
||||
short = vbt.nb.crossed_below_1d_nb(direction, np.full(close.shape, 0))
|
||||
|
||||
return filter, direction, long, short, conf
|
||||
|
||||
SuperSmoother = vbt.IF(
|
||||
class_name='SuperSmoother',
|
||||
short_name='tpss',
|
||||
input_names=['close'],
|
||||
param_names=['timeperiod'],
|
||||
output_names=['filter', 'direction', 'long', 'short', 'conf'],
|
||||
attr_settings=dict(
|
||||
filter=dict(dtype=np.float_),
|
||||
direction=dict(dtype=np.float_),
|
||||
long=dict(dtype=np.bool_),
|
||||
short=dict(dtype=np.bool_),
|
||||
conf=dict(dtype=np.bool_),
|
||||
)
|
||||
).with_apply_func(
|
||||
tpss_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=20
|
||||
)
|
||||
|
||||
class SuperSmoother(SuperSmoother):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
|
||||
s_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
filter = self.select_col_from_obj(self.filter, column)
|
||||
direction = self.select_col_from_obj(self.direction, column)
|
||||
|
||||
# Coloring
|
||||
long = pd.Series(np.nan, index=filter.index)
|
||||
short = pd.Series(np.nan, index=filter.index)
|
||||
ups = np.where(direction > 0)[0]
|
||||
downs = np.where(direction < 0)[0]
|
||||
|
||||
# Back Shifting the Start of Each Sequence (proper coloring)
|
||||
# ups
|
||||
upstarts = ups[1:] - ups[:-1]
|
||||
upstarts = np.insert(upstarts, 0, upstarts[0]) == 1
|
||||
upstarts = ups[np.where(~upstarts & np.roll(upstarts,-1))[0]] - 1
|
||||
ups = np.append(ups, upstarts)
|
||||
|
||||
# downs
|
||||
downstarts = downs[1:] - downs[:-1]
|
||||
downstarts = np.insert(downstarts, 0, downstarts[0]) == 1
|
||||
downstarts = downs[np.where(~downstarts & np.roll(downstarts,-1))[0]] - 1
|
||||
downs = np.append(downs, downstarts)
|
||||
|
||||
# Plot Lines
|
||||
long[ups] = filter.iloc[ups]
|
||||
short[downs] = filter.iloc[downs]
|
||||
|
||||
long.vbt.plot(fig=fig, **l_kwargs)
|
||||
short.vbt.plot(fig=fig, **s_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
# Kase Permission Stochastic
|
||||
@njit
|
||||
def kase_loop_nb(TripleK, pstX, alpha):
|
||||
TripleDF = np.full(TripleK.shape[0], 0.0)
|
||||
TripleDS = np.full(TripleK.shape[0], 0.0)
|
||||
|
||||
for i in range(pstX+1, TripleK.shape[0]):
|
||||
TripleDF[i] = TripleDF[i-pstX] + alpha*(TripleK[i] - TripleDF[i-pstX])
|
||||
TripleDS[i] = (2.0*TripleDS[i-pstX] + TripleDF[i]) / 3.0
|
||||
return TripleDF, TripleDS
|
||||
|
||||
@njit
|
||||
def kase_smooth_nb(price, length):
|
||||
# Initialization
|
||||
e0 = np.full(price.shape[0], 0.0)
|
||||
e1 = np.full(price.shape[0], 0.0)
|
||||
e2 = np.full(price.shape[0], 0.0)
|
||||
e3 = np.full(price.shape[0], 0.0)
|
||||
e4 = np.full(price.shape[0], 0.0)
|
||||
alpha = 0.45*(length-1.0)/(0.45*(length-1.0)+2.0)
|
||||
|
||||
# Calculation
|
||||
for i in range(price.shape[0]):
|
||||
if i <= 2:
|
||||
e0[i] = price[i]
|
||||
e1[i] = price[i]
|
||||
e2[i] = price[i]
|
||||
e3[i] = price[i]
|
||||
e4[i] = price[i]
|
||||
else:
|
||||
e0[i] = price[i] + alpha * (e0[i-1] - price[i])
|
||||
e1[i] = (price[i] - e0[i]) * (1 - alpha) + alpha * e1[i-1]
|
||||
e2[i] = e0[i] + e1[i]
|
||||
e3[i] = e2[i] - e4[i-1] * (1-alpha)**2 + (alpha**2) * e3[i-1]
|
||||
e4[i] = e3[i] + e4[i-1]
|
||||
|
||||
return e4
|
||||
|
||||
def kase_apply(h, l, c, pstLength, pstX, pstSmooth, smoothPeriod):
|
||||
|
||||
# Variables
|
||||
lookback = pstLength*pstX
|
||||
alpha = 2.0/(1.0 + pstSmooth)
|
||||
|
||||
# Calculations
|
||||
hh = vbt.nb.rolling_max_1d_nb(h, lookback)
|
||||
ll = vbt.nb.rolling_min_1d_nb(l, lookback)
|
||||
# Triple K
|
||||
TripleK = vbt.nb.fillna_1d_nb(100*(c - ll)/(hh-ll), 0.0)
|
||||
TripleK[TripleK < 0] = 0.0
|
||||
|
||||
# Triple DF, DS
|
||||
TripleDF, TripleDS = kase_loop_nb(TripleK, pstX, alpha)
|
||||
|
||||
# SMA of DF and DS
|
||||
TripleDFs = talib.SMA(TripleDF, pstSmooth)
|
||||
TripleDSs = talib.SMA(TripleDS, pstSmooth)
|
||||
|
||||
# Kase Smoothing
|
||||
pst = kase_smooth_nb(TripleDFs, smoothPeriod)
|
||||
pss = kase_smooth_nb(TripleDSs, smoothPeriod)
|
||||
|
||||
# Signals and Confirmation
|
||||
long = vbt.nb.crossed_above_1d_nb(pst, pss)
|
||||
short = vbt.nb.crossed_above_1d_nb(pss, pst)
|
||||
conf = pst > pss
|
||||
|
||||
return pst, pss, long, short, conf
|
||||
|
||||
KasePermissionStochastic = vbt.IF(
|
||||
class_name='KasePermissionStochastic',
|
||||
short_name='KPSS',
|
||||
input_names=['high', 'low', 'close'],
|
||||
param_names=['pstLength', 'pstX', 'pstSmooth', 'smoothPeriod'],
|
||||
output_names=['pst', 'pss', 'long', 'short', 'conf'],
|
||||
attr_settings=dict(
|
||||
pst=dict(dtype=np.float_),
|
||||
pss=dict(dtype=np.float_),
|
||||
long=dict(dtype=np.bool_),
|
||||
short=dict(dtype=np.bool_),
|
||||
conf=dict(dtype=np.bool_),
|
||||
)
|
||||
).with_apply_func(
|
||||
kase_apply,
|
||||
takes_1d=True,
|
||||
pstLength=9,
|
||||
pstX=5,
|
||||
pstSmooth=3,
|
||||
smoothPeriod=10
|
||||
)
|
||||
class KasePermissionStochastic(KasePermissionStochastic):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
pst_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
|
||||
pss_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
pst_kwargs = pst_kwargs if pst_kwargs else {}
|
||||
pss_kwargs = pss_kwargs if pss_kwargs else {}
|
||||
|
||||
pst = self.select_col_from_obj(self.pst, column).rename('KPSS-pst')
|
||||
pss = self.select_col_from_obj(self.pss, column).rename('KPSS-pss')
|
||||
long = self.select_col_from_obj(self.long, column).rename('Long')
|
||||
short = self.select_col_from_obj(self.short, column).rename('Short')
|
||||
|
||||
pst.vbt.plot(fig=fig, **pst_kwargs, **layout_kwargs)
|
||||
pss.vbt.plot(fig=fig, **pss_kwargs, **layout_kwargs)
|
||||
long.vbt.signals.plot_as_entries(fig=fig, y=pst, **layout_kwargs)
|
||||
short.vbt.signals.plot_as_exits(fig=fig, y=pss, **layout_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
## TREND LORD
|
||||
def trend_lord_apply(c, period):
|
||||
|
||||
# Variables
|
||||
sqrt_period = round(np.sqrt(period))
|
||||
|
||||
# Calculations
|
||||
ma = vbt.nb.ma_1d_nb(c, period, wtype=vbt.enums.WType.Weighted)
|
||||
tl = vbt.nb.ma_1d_nb(ma, sqrt_period, wtype=vbt.enums.WType.Weighted)
|
||||
|
||||
# Signals
|
||||
dir = np.sign(tl - vbt.nb.fshift_1d_nb(tl))
|
||||
long = vbt.nb.crossed_above_1d_nb(dir, np.full(c.shape[0], 0.0))
|
||||
short = vbt.nb.crossed_below_1d_nb(dir, np.full(c.shape[0], 0.0))
|
||||
conf = dir > 0
|
||||
|
||||
return tl, dir, long, short, conf
|
||||
|
||||
TrendLord = vbt.IF(
|
||||
class_name='TrendLord',
|
||||
short_name='tl',
|
||||
input_names=['close'],
|
||||
param_names=['timeperiod'],
|
||||
output_names=['tl', 'direction', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
trend_lord_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=20
|
||||
)
|
||||
|
||||
class TrendLord(TrendLord):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
|
||||
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
tl = self.select_col_from_obj(self.tl, column)
|
||||
direction = self.select_col_from_obj(self.direction, column)
|
||||
|
||||
# Coloring
|
||||
long = pd.Series(np.nan, index=tl.index)
|
||||
short = pd.Series(np.nan, index=tl.index)
|
||||
ups = np.where(direction > 0)[0]
|
||||
downs = np.where(direction < 0)[0]
|
||||
|
||||
# Plot Lines
|
||||
long[ups] = tl.iloc[ups]
|
||||
short[downs] = tl.iloc[downs]
|
||||
|
||||
long.vbt.barplot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
short.vbt.barplot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
## Cyber Cycle
|
||||
@njit
|
||||
def cyber_cycle_apply(c, a):
|
||||
|
||||
# Variables
|
||||
c = vbt.nb.ffill_1d_nb(c)
|
||||
smooth = np.full(c.shape[0], 0.0)
|
||||
cycle = np.full(c.shape[0], 0.0)
|
||||
trigger = np.full(c.shape[0], 0.0)
|
||||
|
||||
# Calculations
|
||||
for i in range(0, c.shape[0]):
|
||||
if i < 4:
|
||||
cycle[i] = (c[i] - 2*c[i-1] + c[i-2])/4
|
||||
smooth[i] = (c[i] + 2*c[i-1] + 2*c[i-2] + c[i-3])/6
|
||||
trigger[i] = cycle[i-1]
|
||||
else:
|
||||
smooth[i] = (c[i] + 2*c[i-1] + 2*c[i-2] + c[i-3])/6
|
||||
cycle[i] = ((1 - 0.5*a)**2)*(smooth[i] - 2*smooth[i-1] + smooth[i-2]) + 2*(1-a)*cycle[i-1] - ((1-a)**2)*cycle[i-2]
|
||||
trigger[i] = cycle[i-1]
|
||||
|
||||
# Remove Early Convergence Period
|
||||
cycle[:30] = 0.0
|
||||
trigger[:30] = 0.0
|
||||
|
||||
# Signals
|
||||
long = vbt.nb.crossed_above_1d_nb(cycle, trigger)
|
||||
short = vbt.nb.crossed_below_1d_nb(cycle, trigger)
|
||||
conf = cycle > trigger
|
||||
|
||||
return cycle, trigger, long, short, conf
|
||||
|
||||
CyberCycle = vbt.IF(
|
||||
class_name='CyberCycle',
|
||||
short_name='cc',
|
||||
input_names=['close'],
|
||||
param_names=['alpha'],
|
||||
output_names=['cycle', 'trigger', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
cyber_cycle_apply,
|
||||
takes_1d=True,
|
||||
alpha=0.7
|
||||
)
|
||||
|
||||
class CyberCycle(CyberCycle):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
|
||||
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
cycle = self.select_col_from_obj(self.cycle, column)
|
||||
trigger = self.select_col_from_obj(self.trigger, column)
|
||||
|
||||
cycle.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
trigger.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
## Inverse Fisher of the Cyber Cycle
|
||||
def icycle_apply(c, a, p, s, level=0.5):
|
||||
# Calculate Cyber Cycle
|
||||
cycle, _, _, _, _ = cyber_cycle_apply(c, a)
|
||||
|
||||
# Scaling
|
||||
h = vbt.nb.rolling_max_1d_nb(cycle, p)
|
||||
l = vbt.nb.rolling_min_1d_nb(cycle, p)
|
||||
cycle = (2*5.0)*((cycle-l)/(h-l))-5.0
|
||||
|
||||
# Smoothing
|
||||
cycle = talib.EMA(cycle, s)
|
||||
|
||||
# Inverse Fisher of the Cyber Cycle
|
||||
icc = (np.exp(2*cycle)-1.0)/(np.exp(2*cycle)+1.0)
|
||||
|
||||
# Signals
|
||||
long = vbt.nb.crossed_above_1d_nb(icc, np.full(cycle.shape[0], -1*level))
|
||||
short = vbt.nb.crossed_below_1d_nb(icc, np.full(cycle.shape[0], level))
|
||||
long_conf = icc < -1*level
|
||||
short_conf = icc > level
|
||||
|
||||
return icc, long, short, long_conf, short_conf
|
||||
|
||||
iCyberCycle = vbt.IF(
|
||||
class_name='iCyberCycle',
|
||||
short_name='icc',
|
||||
input_names=['close'],
|
||||
param_names=['alpha', 'period', 'smoothing'],
|
||||
output_names=['icycle', 'long', 'short', 'long_conf', 'short_conf']
|
||||
).with_apply_func(
|
||||
icycle_apply,
|
||||
takes_1d=True,
|
||||
alpha=0.7,
|
||||
period=10,
|
||||
smoothing=9
|
||||
)
|
||||
|
||||
class iCyberCycle(iCyberCycle):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
c_kwargs=dict(trace_kwargs=dict(marker=dict(color='yellow'))),
|
||||
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
|
||||
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
c_kwargs = c_kwargs if c_kwargs else {}
|
||||
|
||||
icycle = self.select_col_from_obj(self.icycle, column)
|
||||
long_level = pd.DataFrame(np.full(len(icycle), -0.5), index=icycle.index)
|
||||
short_level = pd.DataFrame(np.full(len(icycle), 0.5), index=icycle.index)
|
||||
|
||||
icycle.vbt.plot(fig=fig, **c_kwargs, **layout_kwargs)
|
||||
long_level.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
short_level.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
|
||||
|
||||
return fig
|
||||
|
||||
# Trend Trigger Factor
|
||||
def t3_ma_apply(c, l, a=0.7):
|
||||
|
||||
# Variables
|
||||
c1 = -a**3
|
||||
c2 = 3*a**2 + 3*a**3
|
||||
c3 = -6*a**2 - 3*a - 3*a**3
|
||||
c4 = 1 + 3*a + a**3 + 3*a**2
|
||||
|
||||
# Calculation
|
||||
e1 = talib.EMA(c, l)
|
||||
e2 = talib.EMA(e1, l)
|
||||
e3 = talib.EMA(e2, l)
|
||||
e4 = talib.EMA(e3, l)
|
||||
e5 = talib.EMA(e4, l)
|
||||
e6 = talib.EMA(e5, l)
|
||||
T3 = c1*e6 + c2*e5 + c3*e4 + c4*e3
|
||||
|
||||
return T3
|
||||
def ttf_apply(c, l, t3=True, t3_period=3, b=0.7):
|
||||
|
||||
# Fill Nan
|
||||
c = vbt.nb.ffill_1d_nb(c)
|
||||
|
||||
# Variables
|
||||
c_s = vbt.nb.fshift_1d_nb(c, l)
|
||||
|
||||
# Caluclations
|
||||
buyPower = vbt.nb.rolling_max_1d_nb(c, l) - vbt.nb.rolling_min_1d_nb(c_s, l)
|
||||
sellPower = vbt.nb.rolling_max_1d_nb(c_s, l) - vbt.nb.rolling_min_1d_nb(c, l)
|
||||
ttf = 100*((buyPower-sellPower)/(0.5*(buyPower+sellPower)))
|
||||
|
||||
# T3 Smoothing
|
||||
if t3:
|
||||
ttf = t3_ma_apply(ttf, t3_period, a=b)
|
||||
|
||||
# Signnals
|
||||
long = vbt.nb.crossed_above_1d_nb(ttf, np.full(c.shape[0], 0.0))
|
||||
short = vbt.nb.crossed_below_1d_nb(ttf, np.full(c.shape[0], 0.0))
|
||||
conf = ttf > 0
|
||||
|
||||
return ttf, long, short, conf
|
||||
|
||||
TrendTriggerFactor = vbt.IF(
|
||||
class_name='TrendTriggerFactor',
|
||||
short_name='ttf',
|
||||
input_names=['close'],
|
||||
param_names=['timeperiod', 't3', 't3_period', 'b'],
|
||||
output_names=['ttf', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
ttf_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=15,
|
||||
t3=True,
|
||||
t3_period=3,
|
||||
b=0.7
|
||||
)
|
||||
|
||||
class TrendTriggerFactor(TrendTriggerFactor):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
|
||||
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
|
||||
n_kwargs=dict(trace_kwargs=dict(marker=dict(color='rgba(255, 255, 0, 0.1)'))),
|
||||
fig=None,
|
||||
signal=True,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
n_kwargs = n_kwargs if n_kwargs else {}
|
||||
|
||||
ttf = self.select_col_from_obj(self.ttf, column)
|
||||
|
||||
if not signal:
|
||||
ttf.vbt.plot(fig=fig, **n_kwargs, **layout_kwargs)
|
||||
else:
|
||||
long = pd.DataFrame(np.full(len(ttf), 100), index=ttf.index)
|
||||
short = pd.DataFrame(np.full(len(ttf), -100), index=ttf.index)
|
||||
neutral = pd.DataFrame(np.full(len(ttf), np.nan), index=ttf.index)
|
||||
|
||||
long_entry = self.select_col_from_obj(self.long, column).fillna(False)
|
||||
short_entry = self.select_col_from_obj(self.short, column).fillna(False)
|
||||
|
||||
long_idx = long_entry
|
||||
short_idx = short_entry
|
||||
pre_long_idx = long_idx.shift(-1)
|
||||
pre_short_idx = short_idx.shift(-1)
|
||||
|
||||
neutral[long_idx==True] = 100
|
||||
neutral[pre_long_idx==True] = -100
|
||||
neutral[short_idx==True] = -100
|
||||
neutral[pre_short_idx==True] = 100
|
||||
|
||||
long[~self.select_col_from_obj(self.conf, column)] = np.nan
|
||||
short[self.select_col_from_obj(self.conf, column)] = np.nan
|
||||
|
||||
long.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
short.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
neutral.vbt.plot(fig=fig, **n_kwargs, **layout_kwargs)
|
||||
|
||||
|
||||
return fig
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,855 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Markov Variance Switching\n",
|
||||
"\n",
|
||||
"# 2: Leveraged Exchange Traded Funds\n",
|
||||
"\n",
|
||||
"Kim, C., Nelson, C., and Startz, R. (1998). Testing for mean reversion in heteroskedastic data based on Gibbs-sampling-augmented randomization. Journal of Empirical Finance, (5)2, pp.131-154.\n",
|
||||
"\n",
|
||||
"**Author:** shittles\n",
|
||||
"\n",
|
||||
"**Created:** 2024-10-17\n",
|
||||
"\n",
|
||||
"**Modified:** 2024-10-17\n",
|
||||
"\n",
|
||||
"## Sources\n",
|
||||
"- https://www.statsmodels.org/v0.11.1/examples/notebooks/generated/markov_autoregression.html\n",
|
||||
"- https://www.proshares.com/our-etfs/leveraged-and-inverse/upro\n",
|
||||
"- https://www.bogleheads.org/forum/viewtopic.php?t=272007\n",
|
||||
"- https://www.bogleheads.org/forum/viewtopic.php?t=288192\n",
|
||||
"- https://www.reddit.com/r/LETFs/comments/14lubaz/finally_an_accurate_backtesting_model/\n",
|
||||
"- https://www.reddit.com/r/mauerstrassenwetten/comments/sivtas/zahlgrafs_exzellente_abenteuer_teil_4/\n",
|
||||
"- https://code.launchpad.net/zgea\n",
|
||||
"\n",
|
||||
"## Changelog\n",
|
||||
"- Modified Markov variance switching notebook for portfolio optimisation (2h - 2024-10-17).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd\n",
|
||||
"import plotly.express as px\n",
|
||||
"import plotly.graph_objects as go\n",
|
||||
"import statsmodels.api as sm\n",
|
||||
"\n",
|
||||
"from pykalman import KalmanFilter\n",
|
||||
"from sklearn.compose import make_column_transformer\n",
|
||||
"from sklearn.preprocessing import RobustScaler\n",
|
||||
"\n",
|
||||
"from vectorbtpro import *"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vbt.settings.set_theme(\"dark\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Ingestion\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Use the fund, not the index, because its going to be part of a portfolio.\n",
|
||||
"data = vbt.YFData.pull(\n",
|
||||
" [\"SPY\", \"UPRO\"], start=\"50 years ago\", end=\"today\", timeframe=\"daily\", tz=\"UTC\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"data.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.data[\"SPY\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.data[\"UPRO\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.plot(symbol=\"SPY\", yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.plot(symbol=\"UPRO\", yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Cleaning\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.features"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.get_feature(\"Dividends\").any()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.get_feature(\"Stock Splits\").any()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.get_feature(\"Capital Gains\").any()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = data.remove_features([\"Capital Gains\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# A post global financial crisis backtest probably isn't long enough.\n",
|
||||
"data = data.transform(lambda df: df.loc[\"June 25th 2009\" < df.index])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# I don't need to resample the data since it's sourced from the same exchange.\n",
|
||||
"# data = data.resample(\"daily\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Modelling\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.log_returns.vbt.plot().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Pre-processing\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"column_transformer = make_column_transformer(\n",
|
||||
" (RobustScaler(), [\"SPY\"]),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"sr_log_returns_scaled = pd.Series(\n",
|
||||
" data=column_transformer.fit_transform(pd.DataFrame(data.log_returns[\"SPY\"])).ravel(),\n",
|
||||
" index=data.index,\n",
|
||||
" name=\"SPY\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_log_returns_scaled.vbt.plot().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Markov Regression\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"k_regimes_kns = 3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"kns = sm.tsa.MarkovRegression(\n",
|
||||
" sr_log_returns_scaled, k_regimes=k_regimes_kns, trend=\"n\", switching_variance=True\n",
|
||||
")\n",
|
||||
"results_kns = kns.fit()\n",
|
||||
"\n",
|
||||
"results_kns.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"results_kns.filtered_marginal_probabilities # using data until time t (excluding time t+1, ..., T)\n",
|
||||
"# results_kns.smoothed_marginal_probabilities # using data until time T"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_subplots(\n",
|
||||
" rows=k_regimes_kns,\n",
|
||||
" cols=1,\n",
|
||||
" y_title=\"Filtered Marginal Variance Regime Probabilities\",\n",
|
||||
" # y_title=\"Smoothed Marginal Variance Regime Probabilities\",\n",
|
||||
" shared_xaxes=True,\n",
|
||||
" subplot_titles=[\n",
|
||||
" \"Low-variance\",\n",
|
||||
" \"Medium-variance\",\n",
|
||||
" \"High-variance\",\n",
|
||||
" ], # order changes dependent on fit\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"for i in range(k_regimes_kns):\n",
|
||||
" fig = results_kns.filtered_marginal_probabilities[i].vbt.plot(\n",
|
||||
" # fig = results_kns.smoothed_marginal_probabilities[i].vbt.plot(\n",
|
||||
" add_trace_kwargs=dict(row=i + 1, col=1), fig=fig\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"fig.update_layout(showlegend=False)\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def plot_annotated_line(\n",
|
||||
" fig: go.Figure,\n",
|
||||
" x: pd.Series,\n",
|
||||
" y: pd.Series,\n",
|
||||
" classes: pd.Series,\n",
|
||||
" dict_class_colours: dict,\n",
|
||||
" dict_class_labels: dict,\n",
|
||||
") -> go.Figure:\n",
|
||||
" \"\"\"Plot a line chart where each trace is coloured based on its class.\n",
|
||||
"\n",
|
||||
" Yes, plotly really doesn't support this out of the box.\n",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
" fig: Figure.\n",
|
||||
" x: Indices.\n",
|
||||
" y: Close prices.\n",
|
||||
" classes: Regimes.\n",
|
||||
" dict_class_colours: In the format {class: colour}\n",
|
||||
" dict_class_labels: In the format {class: label}\n",
|
||||
"\n",
|
||||
" Returns:\n",
|
||||
" fig: The figure.\n",
|
||||
" \"\"\"\n",
|
||||
" # Plot each segment in its corresponding color.\n",
|
||||
" for i in range(len(x) - 1):\n",
|
||||
" fig.add_trace(\n",
|
||||
" go.Scatter(\n",
|
||||
" x=x[i : i + 2],\n",
|
||||
" y=y[i : i + 2],\n",
|
||||
" mode=\"lines\",\n",
|
||||
" line=dict(color=dict_class_colours[classes[i]], width=2),\n",
|
||||
" showlegend=False, # added manually\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Label each regime.\n",
|
||||
" for regime, colour in dict_class_colours.items():\n",
|
||||
" fig.add_trace(\n",
|
||||
" go.Scatter(\n",
|
||||
" x=[None],\n",
|
||||
" y=[None],\n",
|
||||
" mode=\"lines\",\n",
|
||||
" line=dict(color=colour, width=2),\n",
|
||||
" name=dict_class_labels[regime],\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return fig"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_variance_regime_forecasts = results_kns.filtered_marginal_probabilities.idxmax(\n",
|
||||
" axis=1\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"sr_variance_regime_predictions = results_kns.smoothed_marginal_probabilities.idxmax(\n",
|
||||
" axis=1\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_variance_regime_forecasts.vbt.plot().show()\n",
|
||||
"# sr_variance_regime_predictions.vbt.plot().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# order changes dependent on fit\n",
|
||||
"dict_variance_regime_labels = {\n",
|
||||
" 0: \"Low\",\n",
|
||||
" 1: \"Medium\",\n",
|
||||
" 2: \"High\",\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"dict_variance_regime_colours = {\n",
|
||||
" 0: \"green\",\n",
|
||||
" 1: \"orange\",\n",
|
||||
" 2: \"red\",\n",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"fig = plot_annotated_line(\n",
|
||||
" fig,\n",
|
||||
" data.index,\n",
|
||||
" np.log(data.data[\"SPY\"][\"Close\"]),\n",
|
||||
" sr_variance_regime_forecasts,\n",
|
||||
" # sr_variance_regime_predictions,\n",
|
||||
" dict_variance_regime_colours,\n",
|
||||
" dict_variance_regime_labels,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig.update_layout(\n",
|
||||
" title=\"Filtered Variance Regime Labels\",\n",
|
||||
" # title=\"Smoothed Variance Regime Labels\",\n",
|
||||
" xaxis_title=\"Date\",\n",
|
||||
" yaxis_title=\"Log Close\",\n",
|
||||
" showlegend=True,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Kalman Filter\n",
|
||||
"Experiment with smoothing the regime probabilities.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def kalman_smooth(column: np.array) -> np.array:\n",
|
||||
" \"\"\"Apply a Kalman filter to the column.\n",
|
||||
" \n",
|
||||
" The Kalman filter class cannot handle the NaNs created by aligning symbols\n",
|
||||
" indices, so only apply it to the relevent slice of the array.\n",
|
||||
" \"\"\"\n",
|
||||
" # index = column.index\n",
|
||||
" # column = column.loc[column.first_valid_index() : column.last_valid_index()]\n",
|
||||
"\n",
|
||||
" # Filter out NaNs at the start and end of the column.\n",
|
||||
" valid_mask = ~np.isnan(column)\n",
|
||||
"\n",
|
||||
" if not valid_mask.any():\n",
|
||||
" # If all values are NaN, return an array of NaNs with the same length.\n",
|
||||
" return np.full_like(column, np.nan)\n",
|
||||
"\n",
|
||||
" # Get the index of the first occurrence of the maximum value in the array.\n",
|
||||
" first_valid = valid_mask.argmax()\n",
|
||||
" # Reverse the array to find the index of the last occurence.\n",
|
||||
" last_valid = len(valid_mask) - valid_mask[::-1].argmax()\n",
|
||||
"\n",
|
||||
" column = column[first_valid:last_valid]\n",
|
||||
"\n",
|
||||
" kf = KalmanFilter(initial_state_mean=0, n_dim_obs=1)\n",
|
||||
" kf = kf.em(column, n_iter=5)\n",
|
||||
"\n",
|
||||
" smoothed_state_means, _ = kf.smooth(column)\n",
|
||||
"\n",
|
||||
" # return pd.Series(\n",
|
||||
" # data=smoothed_state_means.ravel(),\n",
|
||||
" # index=column.index,\n",
|
||||
" # name=column.name,\n",
|
||||
" # ).reindex(index)\n",
|
||||
" result = np.full(len(valid_mask), np.nan)\n",
|
||||
" result[first_valid:last_valid] = smoothed_state_means.ravel()\n",
|
||||
"\n",
|
||||
" return result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 29,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"Kalman = vbt.IF(\n",
|
||||
" class_name=\"Kalman\",\n",
|
||||
" short_name=\"kf\",\n",
|
||||
" input_names=[\"column\"],\n",
|
||||
" output_names=[\"smoothed_state_means\"],\n",
|
||||
").with_apply_func(\n",
|
||||
" kalman_smooth,\n",
|
||||
" takes_1d=True,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ssm = Kalman.run(results_kns.filtered_marginal_probabilities)\n",
|
||||
"\n",
|
||||
"ssm.kf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_subplots(\n",
|
||||
" rows=k_regimes_kns,\n",
|
||||
" cols=1,\n",
|
||||
" y_title=\"Kalman Filtered Marginal Variance Regime (Not) Probabilities\",\n",
|
||||
" shared_xaxes=True,\n",
|
||||
" subplot_titles=[\n",
|
||||
" \"Low-variance\",\n",
|
||||
" \"Medium-variance\",\n",
|
||||
" \"High-variance\",\n",
|
||||
" ], # order changes dependent on fit\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"for i in range(k_regimes_kns):\n",
|
||||
" fig = ssm.kf[i].vbt.plot(\n",
|
||||
" add_trace_kwargs=dict(row=i + 1, col=1), fig=fig\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"fig.update_layout(showlegend=False)\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 32,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_variance_regime_kalman_forecasts = ssm.kf.idxmax(axis=1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_variance_regime_kalman_forecasts.vbt.plot().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"fig = plot_annotated_line(\n",
|
||||
" fig,\n",
|
||||
" data.index,\n",
|
||||
" np.log(data.data[\"SPY\"][\"Close\"]),\n",
|
||||
" sr_variance_regime_kalman_forecasts,\n",
|
||||
" dict_variance_regime_colours,\n",
|
||||
" dict_variance_regime_labels,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig.update_layout(\n",
|
||||
" title=\"Kalman Filtered Variance Regime Labels\",\n",
|
||||
" xaxis_title=\"Date\",\n",
|
||||
" yaxis_title=\"Log Close\",\n",
|
||||
" showlegend=True,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Backtest\n",
|
||||
"\n",
|
||||
"### 100% SPX Allocation\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf = vbt.Portfolio.from_holding(close=data.data[\"SPY\"][\"Close\"])\n",
|
||||
"\n",
|
||||
"pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot(yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"pf.drawdowns.plot(yaxis=dict(type=\"log\"), fig=fig)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"pf.plot_underwater(pct_scale=True, fig=fig)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 100% UPRO Allocation\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf = vbt.Portfolio.from_holding(\n",
|
||||
" close=data.data[\"UPRO\"][\"Close\"], bm_close=data.data[\"SPY\"][\"Close\"]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot(yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"pf.drawdowns.plot(yaxis=dict(type=\"log\"), fig=fig)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Nice!\n",
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"pf.plot_underwater(pct_scale=True, fig=fig)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Filtered Marginal Probability Allocation\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ws_points = data.wrapper.get_index_points(every=\"W\")\n",
|
||||
"\n",
|
||||
"ws_points"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ws_timestamps = data.wrapper.index[ws_points]\n",
|
||||
"\n",
|
||||
"ws_timestamps"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"symbol_wrapper = data.get_symbol_wrapper(freq=\"1D\") \n",
|
||||
"\n",
|
||||
"allocations = symbol_wrapper.fill()\n",
|
||||
"\n",
|
||||
"allocations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"allocations[\"UPRO\"] = results_kns.filtered_marginal_probabilities[0]\n",
|
||||
"allocations[\"SPY\"] = 1 - allocations[\"UPRO\"]\n",
|
||||
"\n",
|
||||
"allocations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf = vbt.Portfolio.from_orders(\n",
|
||||
" close=data.get(\"Close\"),\n",
|
||||
" bm_close=data.data[\"SPY\"][\"Close\"],\n",
|
||||
" size=allocations,\n",
|
||||
" size_type=\"targetpercent\",\n",
|
||||
" group_by=True, \n",
|
||||
" cash_sharing=True,\n",
|
||||
" call_seq=\"auto\" \n",
|
||||
")\n",
|
||||
"\n",
|
||||
"pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# sim_alloc = pf.get_asset_value(group_by=False).vbt / pf.value\n",
|
||||
"\n",
|
||||
"# sim_alloc.vbt.plot(\n",
|
||||
"# trace_kwargs=dict(stackgroup=\"one\"),\n",
|
||||
"# use_gl=False\n",
|
||||
"# ).show()\n",
|
||||
"\n",
|
||||
"pf.plot_allocations().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot(yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.drawdowns.plot(yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot_underwater(pct_scale=True).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3d7e090f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "71212d49",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code analyzes the SPY ticker data from Yahoo Finance to identify and study 3-day losing streaks. It calculates daily returns, identifies losing streaks, and tracks the days since the last streak. The code further computes future returns following these streaks to evaluate market behavior. This is useful for understanding market patterns and predicting future performance based on historical data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3e33fd4d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"import yfinance as yf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "207fb52c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define the ticker symbol and download historical data from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f6c02468",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ticker = \"SPY\"\n",
|
||||
"data = yf.download(ticker)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2564ee80",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate daily returns by finding the difference between consecutive closing prices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bab6fda1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"return\"] = data[\"Close\"].diff()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b9b2fb9f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Identify days with negative returns to find losing days in the dataset"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fe5059b6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"down\"] = data[\"return\"] < 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0dcd7b43",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Identify 3-day losing streaks by checking for three consecutive losing days"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e0dc811b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"3_day_losing_streak\"] = (\n",
|
||||
" data[\"down\"] & data[\"down\"].shift(1) & data[\"down\"].shift(2)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f20e4853",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Initialize a column to track the number of days since the last 3-day losing streak"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "766171ef",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"days_since_last_streak\"] = np.nan"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3c417464",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Iterate over the data to calculate the days since the last 3-day losing streak"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "49416f01",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"last_streak_day = -np.inf # Initialize with a very large negative value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2f6d58c2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for i in range(len(data)):\n",
|
||||
" if data[\"3_day_losing_streak\"].iloc[i]:\n",
|
||||
" if i - last_streak_day >= 42: # Check if it's been at least 42 trading days\n",
|
||||
" data.loc[data.index[i], \"days_since_last_streak\"] = i - last_streak_day\n",
|
||||
" last_streak_day = i"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b11167c0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Filter the data to show only the occurrences that meet the criteria"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8910af75",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result = data.dropna(subset=[\"days_since_last_streak\"]).copy()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "870198e1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate future returns following the identified streaks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0fcde2dc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result[\"next_1_day_return\"] = data[\"Close\"].shift(-1) / data[\"Close\"] - 1\n",
|
||||
"result[\"next_5_day_return\"] = data[\"Close\"].shift(-5) / data[\"Close\"] - 1\n",
|
||||
"result[\"next_10_day_return\"] = data[\"Close\"].shift(-10) / data[\"Close\"] - 1\n",
|
||||
"result[\"next_21_day_return\"] = data[\"Close\"].shift(-21) / data[\"Close\"] - 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "10250edf",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Print the mean future returns for different time horizons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "62d635bc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"cols = [\n",
|
||||
" \"next_1_day_return\",\n",
|
||||
" \"next_5_day_return\",\n",
|
||||
" \"next_10_day_return\",\n",
|
||||
" \"next_21_day_return\"\n",
|
||||
"]\n",
|
||||
"print(result[cols].mean())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "374238ed",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the proportion of positive returns for the different time horizons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9cc1f42b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result[cols].gt(0).mean().plot.bar()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7b4b3cbe",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Display the proportion of positive returns for the different time horizons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5538d15f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result[cols].gt(0).mean()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b190dc9f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "719d0221",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d1a07cd6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code prices a European call option using QuantLib. It sets up market conditions including spot price, volatility, dividend rate, and risk-free rate. The code then calculates the option price and delta using the Black-Scholes-Merton model. It also demonstrates delta hedging by adjusting the stock position in response to changes in the spot price. This is useful for option pricing and risk management in financial markets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "d439df1a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import QuantLib as ql"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6cd4da78",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Set the evaluation date for the pricing model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "25131917",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"today = ql.Date(15, 1, 2023)\n",
|
||||
"ql.Settings.instance().evaluationDate = today"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bf75bb46",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define the option parameters including expiry date, strike price, and type"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "a50af380",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"expiry = ql.Date(15, 7, 2023)\n",
|
||||
"strike_price = 100\n",
|
||||
"option_type = ql.Option.Call"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d1ac359b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the payoff and exercise objects for the European option"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "783d1bff",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"payoff = ql.PlainVanillaPayoff(option_type, strike_price)\n",
|
||||
"exercise = ql.EuropeanExercise(expiry)\n",
|
||||
"european_option = ql.VanillaOption(payoff, exercise)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "61ee097c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define market parameters including spot price, volatility, dividend rate, and risk-free rate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "db0a54a6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spot_price = 100\n",
|
||||
"volatility = 0.2 # 20%\n",
|
||||
"dividend_rate = 0.01 # 1%\n",
|
||||
"risk_free_rate = 0.05 # 5%"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "436e4502",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create handles for market conditions such as spot price, volatility, dividend yield, and risk-free rate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "e283fafc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))\n",
|
||||
"volatility_handle = ql.BlackVolTermStructureHandle(\n",
|
||||
" ql.BlackConstantVol(today, ql.NullCalendar(), ql.QuoteHandle(ql.SimpleQuote(volatility)), ql.Actual365Fixed())\n",
|
||||
")\n",
|
||||
"dividend_handle = ql.YieldTermStructureHandle(\n",
|
||||
" ql.FlatForward(today, ql.QuoteHandle(ql.SimpleQuote(dividend_rate)), ql.Actual365Fixed())\n",
|
||||
")\n",
|
||||
"risk_free_handle = ql.YieldTermStructureHandle(\n",
|
||||
" ql.FlatForward(today, ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)), ql.Actual365Fixed())\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8604c96c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the Black-Scholes-Merton process using the defined market conditions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "a4d0ca38",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"bsm_process = ql.BlackScholesMertonProcess(spot_handle, dividend_handle, risk_free_handle, volatility_handle)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f4d1e624",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Price the option using the analytic European engine and calculate its Net Present Value (NPV)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "f619b803",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Option Price: 6.56\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"european_option.setPricingEngine(ql.AnalyticEuropeanEngine(bsm_process))\n",
|
||||
"option_price = european_option.NPV()\n",
|
||||
"print(f\"Option Price: {option_price:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c61ab297",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the option delta, which measures sensitivity to changes in the spot price"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "0b38ea69",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Option Delta: 0.58\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"delta = european_option.delta()\n",
|
||||
"print(f\"Option Delta: {delta:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7a406a89",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Compute the initial stock position required for delta hedging"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "ce3c6064",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Initial Stock Position: 58.08\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"stock_position = delta * spot_price\n",
|
||||
"print(f\"Initial Stock Position: {stock_position:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0d5db52f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Simulate a change in the spot price and update the spot handle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "ff0ec270",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"new_spot_price = 105\n",
|
||||
"spot_handle = ql.QuoteHandle(ql.SimpleQuote(new_spot_price))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2ef23741",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Recalculate the option price and delta with the new spot price"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "5ab13208",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"New Option Price: 6.56\n",
|
||||
"New Option Delta: 0.58\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"new_option_price = european_option.NPV()\n",
|
||||
"new_delta = european_option.delta()\n",
|
||||
"print(f\"New Option Price: {new_option_price:.2f}\")\n",
|
||||
"print(f\"New Option Delta: {new_delta:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f15a5f31",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Adjust the stock position for delta hedging based on the new delta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "976bac17",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Adjustment in Stock Position: 2.90\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"new_stock_position = new_delta * new_spot_price\n",
|
||||
"hedge_adjustment = new_stock_position - stock_position\n",
|
||||
"print(f\"Adjustment in Stock Position: {hedge_adjustment:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "19006af5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "357aedc8-fef7-42d9-a5ac-0f973488a6ec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,294 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c1d5e21b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ed31c33c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code calculates and visualizes the theta of a European call option over time until expiration. It sets up the necessary financial instruments using QuantLib, including the option, interest rate curve, dividend yield curve, and volatility surface. The theta is calculated for each day leading up to expiration and stored in a list. Finally, the theta values are plotted against the days to expiration, providing insight into the option's time decay."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2ce21918",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import QuantLib as ql\n",
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "687a3623",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define option parameters"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4fbd7243",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"expiry_date = ql.Date(15, 6, 2023)\n",
|
||||
"strike_price = 100\n",
|
||||
"spot_price = 105\n",
|
||||
"volatility = 0.2\n",
|
||||
"risk_free_rate = 0.01\n",
|
||||
"dividend_yield = 0.02"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "49f2c17d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Set up the QuantLib calendar and day count convention"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "59c625a5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"calendar = ql.UnitedStates(ql.UnitedStates.NYSE)\n",
|
||||
"day_count = ql.Actual365Fixed()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "462a8820",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the QuantLib objects for the option"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "402532e0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"exercise = ql.EuropeanExercise(expiry_date)\n",
|
||||
"payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike_price)\n",
|
||||
"option = ql.VanillaOption(payoff, exercise)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2af0a464",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the interest rate curve"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e1e92d44",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"risk_free_rate_handle = ql.YieldTermStructureHandle(\n",
|
||||
" ql.FlatForward(0, calendar, ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)), day_count)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b90496fb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the dividend yield curve"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "60284e6e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"dividend_yield_handle = ql.YieldTermStructureHandle(\n",
|
||||
" ql.FlatForward(0, calendar, ql.QuoteHandle(ql.SimpleQuote(dividend_yield)), day_count)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5ed9af74",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the volatility surface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4f0b4be8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"volatility_handle = ql.BlackVolTermStructureHandle(\n",
|
||||
" ql.BlackConstantVol(0, calendar, ql.QuoteHandle(ql.SimpleQuote(volatility)), day_count)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c976b34a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the Black-Scholes process"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2a6dd5ce",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))\n",
|
||||
"bs_process = ql.BlackScholesMertonProcess(\n",
|
||||
" spot_handle, \n",
|
||||
" dividend_yield_handle, \n",
|
||||
" risk_free_rate_handle, \n",
|
||||
" volatility_handle\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a97ac96c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define the range of days to expiration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "813206a1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"days_to_expiry = range(365, 15, -1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a0944d10",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"theta_values = []"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fab6d8f2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate theta for each day"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "12acf9e4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for days in days_to_expiry:\n",
|
||||
" expiry_date = calendar.advance(ql.Date().todaysDate(), ql.Period(int(days), ql.Days))\n",
|
||||
" exercise = ql.EuropeanExercise(expiry_date)\n",
|
||||
" option = ql.VanillaOption(payoff, exercise)\n",
|
||||
" \n",
|
||||
" # Set up the pricing engine\n",
|
||||
" engine = ql.AnalyticEuropeanEngine(bs_process)\n",
|
||||
" option.setPricingEngine(engine)\n",
|
||||
" \n",
|
||||
" # Calculate theta\n",
|
||||
" theta = option.theta() / 365\n",
|
||||
" theta_values.append(theta)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b61c5992",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the theta values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "edd4d555",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.figure(figsize=(10, 6))\n",
|
||||
"plt.plot(days_to_expiry, theta_values, label='Theta')\n",
|
||||
"plt.xlabel('Days to Expiration')\n",
|
||||
"plt.ylabel('Theta')\n",
|
||||
"plt.title('Option Theta over Time to Expiration')\n",
|
||||
"plt.gca().invert_xaxis()\n",
|
||||
"ticks = range(365, 15, -50)\n",
|
||||
"plt.xticks(ticks)\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ec3156e1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user