daily update

This commit is contained in:
David Brazda
2024-10-21 20:57:56 +02:00
parent 132172855a
commit e3da60c647
196 changed files with 1722489 additions and 1134 deletions
+9 -2
View File
@@ -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
+87 -24
View File
@@ -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",
+458 -13
View File
@@ -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"
]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -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
+294
View File
@@ -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