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

View File

@ -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
}

View File

@ -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

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
}

View File

@ -0,0 +1,267 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ec44d369",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "8e794167",
"metadata": {},
"source": [
"This code simulates a trading strategy to assess the risk of ruin. It defines functions to simulate individual trades, an entire trading strategy, and calculate the risk of ruin over multiple simulations. The parameters include initial capital, win probability, average win/loss amounts, and the number of trades. The output is a plot showing how the probability of a winning trade affects the risk of ruin. This can help traders understand the robustness of their trading strategy."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "23e1fe3a",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"id": "8a7ec979",
"metadata": {},
"source": [
"Simulate a single trade with given win probability and average win/loss amounts"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "23261ffb",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def simulate_trade(win_prob, avg_win, avg_loss):\n",
" \"\"\"\n",
" Simulate a single trade with given win probability and average win/loss amounts.\n",
" \n",
" Parameters\n",
" ----------\n",
" win_prob : float\n",
" Probability of a winning trade\n",
" avg_win : float\n",
" Average amount won per winning trade\n",
" avg_loss : float\n",
" Average amount lost per losing trade\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" The result of the trade, positive for win and negative for loss\n",
" \"\"\"\n",
"\n",
" # Determine the trade outcome based on win probability and return the result\n",
" if np.random.rand() < win_prob:\n",
" return avg_win\n",
" else:\n",
" return -avg_loss"
]
},
{
"cell_type": "markdown",
"id": "f6a99690",
"metadata": {},
"source": [
"Simulate the entire trading strategy over a given number of trades"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8bfa85ae",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def simulate_trading_strategy(initial_capital, trades, win_prob, avg_win, avg_loss):\n",
" \"\"\"\n",
" Simulate the entire trading strategy over a given number of trades.\n",
" \n",
" Parameters\n",
" ----------\n",
" initial_capital : float\n",
" Starting capital for the trading strategy\n",
" trades : int\n",
" Number of trades to simulate\n",
" win_prob : float\n",
" Probability of a winning trade\n",
" avg_win : float\n",
" Average amount won per winning trade\n",
" avg_loss : float\n",
" Average amount lost per losing trade\n",
" \n",
" Returns\n",
" -------\n",
" list\n",
" Capital history as a list of capital values after each trade\n",
" \"\"\"\n",
"\n",
" # Initialize capital and history list\n",
" capital = initial_capital\n",
" capital_history = [capital]\n",
"\n",
" # Simulate each trade and update capital\n",
" for _ in range(trades):\n",
" capital += simulate_trade(win_prob, avg_win, avg_loss)\n",
" capital_history.append(capital)\n",
"\n",
" return capital_history"
]
},
{
"cell_type": "markdown",
"id": "79c3ffec",
"metadata": {},
"source": [
"Calculate the risk of ruin over a number of trading simulations"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11a634e0",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def calculate_risk_of_ruin(initial_capital, trades, win_prob, avg_win, avg_loss, simulations=100):\n",
" \"\"\"\n",
" Calculate the risk of ruin over a number of trading simulations.\n",
" \n",
" Parameters\n",
" ----------\n",
" initial_capital : float\n",
" Starting capital for the trading strategy\n",
" trades : int\n",
" Number of trades to simulate\n",
" win_prob : float\n",
" Probability of a winning trade\n",
" avg_win : float\n",
" Average amount won per winning trade\n",
" avg_loss : float\n",
" Average amount lost per losing trade\n",
" simulations : int, optional\n",
" Number of simulations to run (default is 100)\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" Proportion of simulations where the capital went to zero or below\n",
" \"\"\"\n",
"\n",
" # Initialize ruin count\n",
" ruin_count = 0\n",
"\n",
" # Run the specified number of simulations\n",
" for _ in range(simulations):\n",
" capital_history = simulate_trading_strategy(initial_capital, trades, win_prob, avg_win, avg_loss)\n",
" if min(capital_history) <= 0:\n",
" ruin_count += 1\n",
"\n",
" return ruin_count / simulations"
]
},
{
"cell_type": "markdown",
"id": "38b50779",
"metadata": {},
"source": [
"Define initial parameters for the trading simulation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b0a2e66",
"metadata": {},
"outputs": [],
"source": [
"initial_capital = 10000\n",
"average_win = 110\n",
"average_loss = 100\n",
"trades = 1000"
]
},
{
"cell_type": "markdown",
"id": "93284971",
"metadata": {},
"source": [
"Calculate risk of ruin for varying win probabilities"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b492dda9",
"metadata": {},
"outputs": [],
"source": [
"risk_of_ruins = []\n",
"steps = range(30, 60)\n",
"for step in steps:\n",
" win_probability = step / 100\n",
" risk_of_ruin = calculate_risk_of_ruin(initial_capital, trades, win_probability, average_win, average_loss)\n",
" risk_of_ruins.append(risk_of_ruin)"
]
},
{
"cell_type": "markdown",
"id": "d5657ff2",
"metadata": {},
"source": [
"Plot the risk of ruin versus probability of a winning trade"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a649045d",
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10, 6))\n",
"plt.plot(steps, risk_of_ruins, label='Risk of ruin')\n",
"plt.xlabel('Probability of a winning trade')\n",
"plt.ylabel('Risk of ruin')\n",
"plt.title(\"Probability of ruin versus probability of a winning trade\")\n",
"plt.grid(True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "b6ef45a0",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,163 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "2b725ab1",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "c686b10c",
"metadata": {},
"source": [
"This code calculates the expectancy ratio of a series of trades. The expectancy ratio measures the average expected return per trade by considering the win rate, loss rate, and average profit/loss of trades. It is useful in financial trading to evaluate the performance of a trading strategy. The input is a DataFrame of trades with profit or loss values. The output is a single expectancy ratio value."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "652ddb60",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "f54264f9",
"metadata": {},
"source": [
"Define a function to calculate the expectancy ratio of trades."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d056583b",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def calculate_expectancy_ratio(trades):\n",
" \"\"\"Calculate the expectancy ratio of trades.\n",
" \n",
" This function computes the average expected return for a series of trades \n",
" by considering their win rate, loss rate, and average profit/loss.\n",
" \n",
" Parameters\n",
" ----------\n",
" trades : pd.DataFrame\n",
" DataFrame containing trade information with a 'Profit' column.\n",
" \n",
" Returns\n",
" -------\n",
" expectancy_ratio : float\n",
" The calculated expectancy ratio.\n",
" \"\"\"\n",
" \n",
" # Calculate the number of trades\n",
" num_trades = len(trades)\n",
" \n",
" # Separate winning and losing trades\n",
" winners = trades[trades['Profit'] > 0]\n",
" losers = trades[trades['Profit'] <= 0]\n",
" \n",
" # Calculate win rate and loss rate\n",
" win_rate = len(winners) / num_trades\n",
" loss_rate = len(losers) / num_trades\n",
" \n",
" # Calculate average profit for winning trades and average loss for losing trades\n",
" avg_win = winners['Profit'].mean()\n",
" avg_loss = losers['Profit'].mean()\n",
" \n",
" # Compute the expectancy ratio\n",
" expectancy_ratio = (win_rate * avg_win) + (loss_rate * avg_loss)\n",
" \n",
" return expectancy_ratio"
]
},
{
"cell_type": "markdown",
"id": "2611f9e5",
"metadata": {},
"source": [
"Create a dictionary with trade data including trade numbers and corresponding profits/losses."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c26a120",
"metadata": {},
"outputs": [],
"source": [
"trade_data = {\n",
" 'Trade': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],\n",
" 'Profit': [100, -50, 200, -100, 300, -150, 400, -200, 500, -250]\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "b84b5061",
"metadata": {},
"source": [
"Convert the trade data dictionary into a pandas DataFrame."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f057971",
"metadata": {},
"outputs": [],
"source": [
"trades = pd.DataFrame(trade_data)"
]
},
{
"cell_type": "markdown",
"id": "4ce371e5",
"metadata": {},
"source": [
"Calculate the expectancy ratio using the defined function and print the result."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4086f225",
"metadata": {},
"outputs": [],
"source": [
"expectancy_ratio = calculate_expectancy_ratio(trades)\n",
"print(f\"Expectancy Ratio: {expectancy_ratio}\")"
]
},
{
"cell_type": "markdown",
"id": "49299264",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,246 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "1ed30b1e",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "550cc5f3",
"metadata": {},
"source": [
"This code downloads historical price data for SPY and AAPL from Yahoo Finance, calculates daily returns, and computes the Sharpe ratio for these returns. It includes a function to determine the Sharpe ratio, adjusting for a daily benchmark return. The code then plots the rolling 30-day Sharpe ratio for AAPL and visualizes the histogram of these Sharpe ratios. Additionally, it compares the rolling 30-day Sharpe ratio of AAPL against SPY and plots the histogram of the differences."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dcabe4c7",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "46f6a31c",
"metadata": {},
"source": [
"Download historical price data for SPY and AAPL from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a83f550",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download([\"SPY\", \"AAPL\"], start=\"2020-01-01\", end=\"2022-07-31\")"
]
},
{
"cell_type": "markdown",
"id": "2018e200",
"metadata": {},
"source": [
"Extract adjusted closing prices for SPY and AAPL"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c977a8bc",
"metadata": {},
"outputs": [],
"source": [
"closes = data['Adj Close']\n",
"spy_returns = closes.SPY.pct_change().dropna()\n",
"aapl_returns = closes.AAPL.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "9741f742",
"metadata": {},
"source": [
"Define a function to calculate the Sharpe ratio of a strategy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f618f290",
"metadata": {},
"outputs": [],
"source": [
"def sharpe_ratio(returns, adjustment_factor=0.0):\n",
" \"\"\"\n",
" Determines the Sharpe ratio of a strategy.\n",
" \n",
" Parameters\n",
" ----------\n",
" returns : pd.Series or np.ndarray\n",
" Daily returns of the strategy, noncumulative.\n",
" adjustment_factor : int, float\n",
" Constant daily benchmark return throughout the period.\n",
"\n",
" Returns\n",
" -------\n",
" sharpe_ratio : float\n",
"\n",
" Note\n",
" -----\n",
" See https://en.wikipedia.org/wiki/Sharpe_ratio for more details.\n",
" \"\"\"\n",
"\n",
" # Adjust returns by subtracting the benchmark return\n",
"\n",
" returns_risk_adj = returns - adjustment_factor\n",
"\n",
" # Print the annualized standard deviation of the risk-adjusted returns\n",
"\n",
" print(returns_risk_adj.std() * np.sqrt(252))\n",
"\n",
" # Return the annualized Sharpe ratio\n",
"\n",
" return (\n",
" returns_risk_adj.mean() / returns_risk_adj.std()\n",
" ) * np.sqrt(252)"
]
},
{
"cell_type": "markdown",
"id": "035cea96",
"metadata": {},
"source": [
"Calculate the Sharpe ratio for SPY daily returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "23dfb39c",
"metadata": {},
"outputs": [],
"source": [
"sharpe_ratio(spy_returns)"
]
},
{
"cell_type": "markdown",
"id": "19aebd83",
"metadata": {},
"source": [
"Calculate the Sharpe ratio for AAPL daily returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12292244",
"metadata": {},
"outputs": [],
"source": [
"sharpe_ratio(aapl_returns)"
]
},
{
"cell_type": "markdown",
"id": "77252a24",
"metadata": {},
"source": [
"Plot the rolling 30-day Sharpe ratio for AAPL"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "75215b9c",
"metadata": {},
"outputs": [],
"source": [
"aapl_returns.rolling(30).apply(sharpe_ratio).plot()"
]
},
{
"cell_type": "markdown",
"id": "5ac119f9",
"metadata": {},
"source": [
"Plot the histogram of the rolling 30-day Sharpe ratios for AAPL"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bae6269a",
"metadata": {},
"outputs": [],
"source": [
"aapl_returns.rolling(30).apply(sharpe_ratio).hist(bins=50)"
]
},
{
"cell_type": "markdown",
"id": "3b70d6ff",
"metadata": {},
"source": [
"Compare the rolling 30-day Sharpe ratio of AAPL against SPY and plot the histogram of the differences"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0796ca2e",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" aapl_returns.rolling(30).apply(sharpe_ratio)\n",
" - spy_returns.rolling(30).apply(sharpe_ratio)\n",
").hist(bins=50)"
]
},
{
"cell_type": "markdown",
"id": "cef5bf53",
"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
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,414 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3fd3d3a8",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "97330215",
"metadata": {},
"outputs": [],
"source": [
"from llama_index.llms.openai import OpenAI"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "dc086cc8",
"metadata": {},
"outputs": [],
"source": [
"from llama_index.core import (\n",
" StorageContext,\n",
" VectorStoreIndex,\n",
" SimpleDirectoryReader,\n",
" load_index_from_storage,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "93549f19",
"metadata": {},
"outputs": [],
"source": [
"from llama_index.core.tools import QueryEngineTool, ToolMetadata\n",
"from llama_index.core.query_engine import SubQuestionQueryEngine\n",
"from dotenv import load_dotenv"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "15d43e1f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"load_dotenv()"
]
},
{
"cell_type": "markdown",
"id": "ca35fe7d",
"metadata": {},
"source": [
"### Configure the language model and load the document"
]
},
{
"cell_type": "markdown",
"id": "fd2c5bd0",
"metadata": {},
"source": [
"First, we configure the language model with specific parameters and load the document."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "30c58e66",
"metadata": {},
"outputs": [],
"source": [
"llm = OpenAI(temperature=0, model_name=\"gpt-4o\")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "d92fcf7f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loaded NVDA 10-K with 80 pages\n"
]
}
],
"source": [
"doc = SimpleDirectoryReader(input_files=[\"nvda.pdf\"]).load_data()\n",
"print(f\"Loaded NVDA 10-K with {len(doc)} pages\")"
]
},
{
"cell_type": "markdown",
"id": "a7615156",
"metadata": {},
"source": [
"We set the language model to use the GPT-4 model with a temperature of 0 for deterministic responses. The model is configured to use an unlimited number of tokens. We then load the NVDA 10-K document from a PDF file and print the number of pages loaded."
]
},
{
"cell_type": "markdown",
"id": "d2c67a6e",
"metadata": {},
"source": [
"### Create an index to enable querying of the document"
]
},
{
"cell_type": "markdown",
"id": "ba9502c6",
"metadata": {},
"source": [
"Next, we create an index from the loaded document to facilitate efficient querying."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "22eff79d",
"metadata": {},
"outputs": [],
"source": [
"index = VectorStoreIndex.from_documents(doc)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "4651c1a9",
"metadata": {},
"outputs": [],
"source": [
"engine = index.as_query_engine(similarity_top_k=3)"
]
},
{
"cell_type": "markdown",
"id": "77f2ee27",
"metadata": {},
"source": [
"We create a VectorStoreIndex from the loaded document, which enables us to perform similarity searches. We then set up a query engine with a similarity search parameter to return the top 3 most relevant results for each query."
]
},
{
"cell_type": "markdown",
"id": "d36f8d48",
"metadata": {},
"source": [
"### Query specific financial information from the document"
]
},
{
"cell_type": "markdown",
"id": "1fefd8e4",
"metadata": {},
"source": [
"Now, we can use the query engine to extract specific financial information from the document."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "a0ff7054",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The revenue of NVIDIA in the last period reported was $30,040 million for the three months ended July 28, 2024, as shown on page 3 of the document.\n"
]
}
],
"source": [
"response = await engine.aquery(\"What is the revenue of NVDIA in the last period reported? Answer in millions with page reference. Include the period.\")\n",
"print(response)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "4c70926e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"NVIDIA's fiscal period begins on the last Sunday in January.\n"
]
}
],
"source": [
"response = await engine.aquery(\"What is the beginning and end date of NVIDA's fiscal period?\")\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"id": "625a2cd7",
"metadata": {},
"source": [
"We use the query engine to asynchronously ask questions about NVIDIA's financial report. The first query asks for the revenue in the last reported period, including the page reference. The second query asks for the beginning and end dates of NVIDIA's fiscal period. The responses are printed to the console."
]
},
{
"cell_type": "markdown",
"id": "c589df9b",
"metadata": {},
"source": [
"### Set up a tool for sub-question querying"
]
},
{
"cell_type": "markdown",
"id": "9b5b7372",
"metadata": {},
"source": [
"We will now set up a tool to handle more complex queries by breaking them down into sub-questions."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "c880739e",
"metadata": {},
"outputs": [],
"source": [
"query_engine_tool = [\n",
" QueryEngineTool(\n",
" query_engine=engine,\n",
" metadata=ToolMetadata(name='nvda_10k', description='Provides information about NVDA financials for year 2024')\n",
" )\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "99229480",
"metadata": {},
"outputs": [],
"source": [
"s_engine = SubQuestionQueryEngine.from_defaults(query_engine_tools=query_engine_tool)"
]
},
{
"cell_type": "markdown",
"id": "d3f0d7e2",
"metadata": {},
"source": [
"We create a list of QueryEngineTool objects with metadata describing the tool's function. We then initialize a SubQuestionQueryEngine with the list of tools. This engine can break down complex queries into smaller, more manageable sub-questions."
]
},
{
"cell_type": "markdown",
"id": "5a0e20f5",
"metadata": {},
"source": [
"### Perform complex queries on customer segments and risks"
]
},
{
"cell_type": "markdown",
"id": "268e67df",
"metadata": {},
"source": [
"Finally, we perform more complex queries on the document to extract detailed information about customer segments and business risks."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "194b22a8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Generated 2 sub questions.\n",
"\u001b[1;3;38;2;237;90;200m[nvda_10k] Q: What are the customer segments that grew the fastest in terms of revenue in 2024?\n",
"\u001b[0m\u001b[1;3;38;2;90;149;237m[nvda_10k] Q: Which geographies showed the highest growth in revenue for NVDA in 2024?\n",
"\u001b[0m\u001b[1;3;38;2;237;90;200m[nvda_10k] A: Networking revenue grew the fastest in terms of revenue in 2024.\n",
"\u001b[0m\u001b[1;3;38;2;90;149;237m[nvda_10k] A: Data Center revenue showed the highest growth in revenue for NVDA in 2024.\n",
"\u001b[0mNetworking revenue grew the fastest in terms of revenue in 2024, while Data Center revenue showed the highest growth in revenue for NVDA in the same year.\n"
]
}
],
"source": [
"response = await s_engine.aquery(\"Compare and contrast the customer segments and geographies that grew the fastest\")\n",
"print(response)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "56bc8833",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Generated 1 sub questions.\n",
"\u001b[1;3;38;2;237;90;200m[nvda_10k] Q: What risks are highlighted in NVDA's 10-K document for the year 2024?\n",
"\u001b[0m\u001b[1;3;38;2;237;90;200m[nvda_10k] A: Long manufacturing lead times, uncertain supply and component availability, failure to estimate customer demand accurately, mismatches between supply and demand, product shortages, excess inventory, and the impact of changes in product development cycles, competing technologies, business and economic conditions, government lockdowns, technology advancements, and other factors on revenue and supply levels.\n",
"\u001b[0mThe risks highlighted in the document for NVIDIA's business include long manufacturing lead times, uncertain supply and component availability, failure to estimate customer demand accurately, mismatches between supply and demand, product shortages, excess inventory, and the impact of changes in product development cycles, competing technologies, business and economic conditions, government lockdowns, technology advancements, and other factors on revenue and supply levels.\n"
]
}
],
"source": [
"response = await s_engine.aquery(\"What risks to NVDIA's business are highlighted in the document?\")\n",
"print(response)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "2c0c2b4e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Generated 2 sub questions.\n",
"\u001b[1;3;38;2;237;90;200m[nvda_10k] Q: What are the key risks highlighted in the NVDA 10K document?\n",
"\u001b[0m\u001b[1;3;38;2;90;149;237m[nvda_10k] Q: How does NVDA plan to mitigate the risks mentioned in the 10K document?\n",
"\u001b[0m\u001b[1;3;38;2;237;90;200m[nvda_10k] A: The key risks highlighted in the NVDA 10K document include potential challenges related to manufacturing lead times, uncertain supply and component availability, inaccurate estimation of customer demand leading to mismatches between supply and demand, product shortages or excess inventory, inability to secure sufficient commitments for capacity, impeded ability to sell products if necessary components are unavailable, extended lead times on orders, increased product costs due to securing future supply, and the impact of various factors on underestimating or overestimating customer demand.\n",
"\u001b[0m\u001b[1;3;38;2;90;149;237m[nvda_10k] A: NVDA plans to mitigate the risks mentioned in the 10K document by increasing supply and capacity purchases with existing and new suppliers to support their demand projections. Additionally, they aim to accurately estimate customer demand to avoid mismatches between supply and demand, which have previously harmed their financial results. They may enter into long-term supply agreements and capacity commitments to address their business needs and secure sufficient commitments for capacity. Furthermore, they acknowledge the potential impact of factors such as changes in product development cycles, competitor actions, economic conditions, and technology advancements on their revenue and strive to manage these uncertainties effectively.\n",
"\u001b[0mNVDIA sees the risks highlighted in the document impacting financial performance through potential challenges related to manufacturing lead times, uncertain supply and component availability, inaccurate estimation of customer demand leading to mismatches between supply and demand, product shortages or excess inventory, inability to secure sufficient commitments for capacity, impeded ability to sell products if necessary components are unavailable, extended lead times on orders, increased product costs due to securing future supply, and the impact of various factors on underestimating or overestimating customer demand. These risks could lead to financial implications such as decreased revenue, increased costs, reduced profitability, and potential negative effects on overall financial results.\n"
]
}
],
"source": [
"response = await s_engine.aquery(\"How does NVDIA see the risks highlighted in the document impacting financial performance?\")\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"id": "b1ac62ee",
"metadata": {},
"source": [
"We use the sub-question query engine to ask complex questions about NVIDIA's customer segments and geographies and the business risks highlighted in the document. The engine breaks these questions into smaller sub-questions, processes them, and compiles the responses. Each response is then printed to the console."
]
},
{
"cell_type": "markdown",
"id": "b47d32f0",
"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": "d6d8773b-4b58-43c0-829c-c56fd603703d",
"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
}

View File

@ -0,0 +1,459 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "607fd8fb",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "49879d7a",
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"from vectorbtpro import *\n",
"import pandas as pd\n",
"import scipy.stats as st\n",
"import statsmodels.tsa.stattools as ts \n",
"import numpy as np\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "a726d955",
"metadata": {},
"source": [
"### Load and save S&P 500 tickers and their data"
]
},
{
"cell_type": "markdown",
"id": "8eed1e5d",
"metadata": {},
"source": [
"First, we load the S&P 500 tickers from Wikipedia and save their historical data if it doesn't already exist. We will store the data in an HDF5 file format. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f481675b",
"metadata": {},
"outputs": [],
"source": [
"sp500_tickers = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')[0]['Symbol'].tolist()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "95b7da8c",
"metadata": {},
"outputs": [],
"source": [
"COINT_FILE = \"coint_pvalues.pickle\"\n",
"POOL_FILE = \"data_pool.h5\"\n",
"START = \"2015-01-01\"\n",
"END = \"2023-12-31\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8be1d20a",
"metadata": {},
"outputs": [],
"source": [
"if not vbt.file_exists(POOL_FILE):\n",
" with vbt.ProgressBar(total=len(sp500_tickers)) as pbar:\n",
" collected = 0\n",
" for symbol in sp500_tickers:\n",
" try:\n",
" data = vbt.YFData.pull(\n",
" symbol,\n",
" start=START,\n",
" end=END,\n",
" silence_warnings=True,\n",
" )\n",
" data.to_hdf(POOL_FILE)\n",
" collected += 1\n",
" except:\n",
" pass\n",
" pbar.set_prefix(f\"{symbol} ({collected})\")\n",
" pbar.update()"
]
},
{
"cell_type": "markdown",
"id": "4f1e195c",
"metadata": {},
"source": [
"We load the S&P 500 tickers from Wikipedia using pandas. We then check if the data file already exists. If it does not, we initialize a progress bar and start collecting historical data for each ticker using the vectorbtpro library's YFData.pull method. The collected data is then saved into an HDF5 file. If there is an error while collecting data for a ticker, we simply pass and move to the next ticker."
]
},
{
"cell_type": "markdown",
"id": "9870389a",
"metadata": {},
"source": [
"### Filter and select valid symbols from the data"
]
},
{
"cell_type": "markdown",
"id": "ad74b67c",
"metadata": {},
"source": [
"Next, we load the saved data, filter out any symbols with missing data, and keep only valid symbols. This ensures we work with complete datasets."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4896730c",
"metadata": {},
"outputs": [],
"source": [
"data = vbt.HDFData.pull(\n",
" POOL_FILE,\n",
" start=START,\n",
" end=END,\n",
" silence_warnings=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4f8e9058",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"data = data.select_symbols([\n",
" k\n",
" for k, v in data.data.items()\n",
" if not v.isnull().any().any()\n",
"])"
]
},
{
"cell_type": "markdown",
"id": "9cac3f6d",
"metadata": {},
"source": [
"We load the saved data from the HDF5 file. We then iterate over each symbol's data and check for missing values. If a symbol has any missing values, it is excluded. This ensures that we only work with complete datasets, which is crucial for accurate analysis."
]
},
{
"cell_type": "markdown",
"id": "cada47c4",
"metadata": {},
"source": [
"### Identify cointegrated pairs of stocks"
]
},
{
"cell_type": "markdown",
"id": "6e2ea5ba",
"metadata": {},
"source": [
"Now, we identify pairs of stocks that are cointegrated. Cointegration helps us find pairs of stocks that have a stable relationship over time, which is essential for pairs trading strategies."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36050b45",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"@vbt.parameterized(\n",
" merge_func=\"concat\",\n",
" engine=\"pathos\",\n",
" distribute=\"chunks\",\n",
" n_chunks=\"auto\"\n",
")\n",
"def coint_pvalue(close, s1, s2):\n",
" return ts.coint(np.log(close[s1]), np.log(close[s2]))[1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5863250c",
"metadata": {},
"outputs": [],
"source": [
"if not vbt.file_exists(COINT_FILE):\n",
" coint_pvalues = coint_pvalue(\n",
" data.close,\n",
" vbt.Param(data.symbols, condition=\"s1 != s2\"),\n",
" vbt.Param(data.symbols)\n",
" )\n",
" vbt.save(coint_pvalues, COINT_FILE)\n",
"else:\n",
" coint_pvalues = vbt.load(COINT_FILE)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "85224b4e",
"metadata": {},
"outputs": [],
"source": [
"coint_pvalues = coint_pvalues.sort_values()\n",
"coint_pvalues.head(20)"
]
},
{
"cell_type": "markdown",
"id": "3397e3ea",
"metadata": {},
"source": [
"We define a function to calculate the cointegration p-value between two stock price series. We use the parameterized decorator to parallelize the computation. If the cointegration p-values file does not exist, we calculate the p-values for all pairs of stocks and save the results. Otherwise, we load the saved p-values. We then sort the p-values in ascending order and display the top 20 pairs with the lowest p-values, indicating the strongest cointegration."
]
},
{
"cell_type": "markdown",
"id": "019e2328",
"metadata": {},
"source": [
"### Analyze and visualize the selected stock pair"
]
},
{
"cell_type": "markdown",
"id": "4e5cfedc",
"metadata": {},
"source": [
"Finally, we choose a specific pair of stocks, analyze their price relationship, and visualize their spread and z-score. This helps us understand their mean-reverting behavior for potential trading opportunities."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7ea967b",
"metadata": {},
"outputs": [],
"source": [
"S1, S2 = \"WYNN\", \"DVN\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "839b843a",
"metadata": {},
"outputs": [],
"source": [
"data.plot(column=\"Close\", symbol=[S1, S2], base=1).show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "edabec9b",
"metadata": {},
"outputs": [],
"source": [
"S1_log = np.log(data.get(\"Close\", S1))\n",
"S2_log = np.log(data.get(\"Close\", S2))\n",
"log_diff = (S2_log - S1_log).rename(\"Log diff\")\n",
"fig = log_diff.vbt.plot()\n",
"fig.add_hline(y=log_diff.mean(), line_color=\"yellow\", line_dash=\"dot\")\n",
"fig.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc73a803",
"metadata": {},
"outputs": [],
"source": [
"data = vbt.YFData.pull(\n",
" [S1, S2],\n",
" start=START,\n",
" end=END,\n",
" silence_warnings=True,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1e36a43",
"metadata": {},
"outputs": [],
"source": [
"UPPER = st.norm.ppf(1 - 0.05 / 2)\n",
"LOWER = -st.norm.ppf(1 - 0.05 / 2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "40980b4a",
"metadata": {},
"outputs": [],
"source": [
"S1_close = data.get(\"Close\", S1)\n",
"S2_close = data.get(\"Close\", S2)\n",
"ols = vbt.OLS.run(S1_close, S2_close, window=vbt.Default(21))\n",
"spread = ols.error.rename(\"Spread\")\n",
"zscore = ols.zscore.rename(\"Z-score\")\n",
"print(pd.concat((spread, zscore), axis=1))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "81afd692",
"metadata": {},
"outputs": [],
"source": [
"upper_crossed = zscore.vbt.crossed_above(UPPER)\n",
"lower_crossed = zscore.vbt.crossed_below(LOWER)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eb36548f",
"metadata": {},
"outputs": [],
"source": [
"fig = zscore.vbt.plot()\n",
"fig.add_hline(y=UPPER, line_color=\"orangered\", line_dash=\"dot\")\n",
"fig.add_hline(y=0, line_color=\"yellow\", line_dash=\"dot\")\n",
"fig.add_hline(y=LOWER, line_color=\"limegreen\", line_dash=\"dot\")\n",
"upper_crossed.vbt.signals.plot_as_exits(zscore, fig=fig)\n",
"lower_crossed.vbt.signals.plot_as_entries(zscore, fig=fig)\n",
"fig.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e650b931",
"metadata": {},
"outputs": [],
"source": [
"long_entries = data.symbol_wrapper.fill(False)\n",
"short_entries = data.symbol_wrapper.fill(False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8571c745",
"metadata": {},
"outputs": [],
"source": [
"short_entries.loc[upper_crossed, S1] = True\n",
"long_entries.loc[upper_crossed, S2] = True\n",
"long_entries.loc[lower_crossed, S1] = True\n",
"short_entries.loc[lower_crossed, S2] = True"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66168c29",
"metadata": {},
"outputs": [],
"source": [
"pf = vbt.Portfolio.from_signals(\n",
" data,\n",
" entries=long_entries,\n",
" short_entries=short_entries,\n",
" size=10,\n",
" size_type=\"valuepercent100\",\n",
" group_by=True,\n",
" cash_sharing=True,\n",
" call_seq=\"auto\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5332cb0",
"metadata": {},
"outputs": [],
"source": [
"pf.stats()"
]
},
{
"cell_type": "markdown",
"id": "de8b48d9",
"metadata": {},
"source": [
"We select two specific stocks and plot their closing prices. We then calculate their log-price difference and plot it to analyze their mean-reverting behavior. We pull the latest data for the selected stocks and calculate the spread and z-score using a rolling window OLS regression. We identify the points where the z-score crosses above or below the thresholds and plot these signals. Finally, we define long and short entry signals and create a portfolio based on these signals to evaluate its performance."
]
},
{
"cell_type": "markdown",
"id": "bc7c7218",
"metadata": {},
"source": [
"### Your next steps"
]
},
{
"cell_type": "markdown",
"id": "9b35e7cf-3a73-488f-bbe3-b818906bb040",
"metadata": {},
"source": [
"Try changing the selected stock pair to explore different cointegrated pairs. You can also adjust the z-score thresholds to see how it affects your trading signals. Experiment with different window sizes in the OLS regression to find an optimal setting for your strategy."
]
},
{
"cell_type": "markdown",
"id": "1af444c5",
"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
}

View File

@ -0,0 +1,236 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "40c4b691",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "e8acbb3a",
"metadata": {},
"source": [
"## Load stock data and calculate daily returns"
]
},
{
"cell_type": "markdown",
"id": "ec0a3524-f34d-47c1-9d0d-4c214e667a05",
"metadata": {},
"source": [
"We start by loading stock data for three companies: Apple (AAPL), Microsoft (MSFT), and Google (GOOGL). We will use the adjusted closing prices from January 1, 2020, to January 1, 2022. Then, we calculate the daily returns for these stocks."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd38b0ce",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import yfinance as yf\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "98a88cab",
"metadata": {},
"outputs": [],
"source": [
"tickers = ['AAPL', 'MSFT', 'GOOGL']\n",
"data = yf.download(tickers, start='2020-01-01', end='2022-01-01')['Adj Close']"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "92f195d8",
"metadata": {},
"outputs": [],
"source": [
"returns = data.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "29257797",
"metadata": {},
"source": [
"We use the yf.download method from the yfinance library to fetch adjusted closing prices for the specified tickers and date range. The pct_change method calculates the daily returns, which represent the percentage change in stock prices from one day to the next. The dropna method ensures that we remove any missing values from our data. This step is crucial for accurate calculations in subsequent steps."
]
},
{
"cell_type": "markdown",
"id": "9d9ce685",
"metadata": {},
"source": [
"## Calculate portfolio returns"
]
},
{
"cell_type": "markdown",
"id": "7f4974cc-4fae-4684-ab89-5b1f170bf5e1",
"metadata": {},
"source": [
"Next, we will calculate the returns of a portfolio consisting of the three stocks. We assign specific weights to each stock and compute the portfolio returns based on these weights."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "915c359b",
"metadata": {},
"outputs": [],
"source": [
"weights = np.array([0.4, 0.4, 0.2])\n",
"portfolio_returns = returns.dot(weights)"
]
},
{
"cell_type": "markdown",
"id": "3bec93fa",
"metadata": {},
"source": [
"We create an array of weights that represent the proportion of the portfolio allocated to each stock: 40% to Apple, 40% to Microsoft, and 20% to Google. We then calculate the portfolio returns by taking the dot product of the daily returns and the weights. This gives us a time series of portfolio returns, which is the weighted sum of the individual stock returns for each day."
]
},
{
"cell_type": "markdown",
"id": "4901e227",
"metadata": {},
"source": [
"## Calculate Sortino Ratio"
]
},
{
"cell_type": "markdown",
"id": "8082f1f7-97d0-413e-8b8e-80447bd43342",
"metadata": {},
"source": [
"We will now calculate the Sortino Ratio for the portfolio. The Sortino Ratio measures the risk-adjusted return, focusing only on downside risk. We first determine the excess returns over a risk-free rate and then compute the downside deviation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bc4451fd",
"metadata": {},
"outputs": [],
"source": [
"risk_free_rate = 0.01\n",
"target_return = 0\n",
"excess_return = portfolio_returns - risk_free_rate / 252\n",
"downside_returns = excess_return[excess_return < 0]\n",
"downside_deviation = np.std(downside_returns)\n",
"sortino_ratio = np.mean(excess_return) / downside_deviation"
]
},
{
"cell_type": "markdown",
"id": "3a039805",
"metadata": {},
"source": [
"We set the risk-free rate at 1% and assume a target return of 0. The excess return is calculated by subtracting the daily risk-free rate from the portfolio returns. We identify the downside returns, which are the negative excess returns, and compute the standard deviation of these downside returns to get the downside deviation. Finally, we calculate the Sortino Ratio by dividing the mean excess return by the downside deviation. This ratio helps us understand the portfolio's performance relative to the downside risk."
]
},
{
"cell_type": "markdown",
"id": "dffa0116",
"metadata": {},
"source": [
"## Plot portfolio returns and visualize the downside risk"
]
},
{
"cell_type": "markdown",
"id": "45c15282-dddb-4f40-b31d-1101a2c8f643",
"metadata": {},
"source": [
"Lastly, we will plot the portfolio returns and highlight the periods where the returns are below the target return. This will help us visualize the downside risk."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "94f2cf01",
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(12, 6))\n",
"plt.plot(portfolio_returns.index, portfolio_returns, label='Portfolio Returns')\n",
"downside_returns = portfolio_returns[portfolio_returns < target_return]\n",
"plt.fill_between(downside_returns.index, downside_returns, alpha=0.5, color='red', label='Downside Returns')\n",
"plt.title('Portfolio Returns with Downside Risk Highlighted')\n",
"plt.xlabel('Date')\n",
"plt.ylabel('Returns')\n",
"plt.legend()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "1f318296",
"metadata": {},
"source": [
"We create a plot with portfolio returns on the y-axis and dates on the x-axis. We use the plot method from matplotlib to visualize the portfolio returns. We then identify the dates where the returns are below the target return and highlight these periods using the fill_between method with a red fill color. The plot includes a title and labels for the axes, and a legend to distinguish between the portfolio returns and downside returns. This visualization helps us easily identify periods of negative performance."
]
},
{
"cell_type": "markdown",
"id": "32baf792",
"metadata": {},
"source": [
"## Your next steps"
]
},
{
"cell_type": "markdown",
"id": "dd15bfe8-42e1-4f91-ba77-123b3dc5211b",
"metadata": {},
"source": [
"Try changing the weights assigned to the stocks and observe how the portfolio returns and the Sortino Ratio change. You can also experiment with different time periods to see how the portfolio would have performed in various market conditions. This will give you a deeper understanding of portfolio performance and risk management."
]
},
{
"cell_type": "markdown",
"id": "5e461cd3",
"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
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,458 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fd999901",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "986105e8",
"metadata": {},
"outputs": [],
"source": [
"import threading\n",
"import time\n",
"from typing import Dict, Optional"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b1b21d3d",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"from ibapi.client import EClient\n",
"from ibapi.common import BarData\n",
"from ibapi.contract import Contract\n",
"from ibapi.order import Order\n",
"from ibapi.wrapper import EWrapper"
]
},
{
"cell_type": "markdown",
"id": "655e0e34",
"metadata": {},
"source": [
"### Calculate Donchian Channels for price data"
]
},
{
"cell_type": "markdown",
"id": "712afa36",
"metadata": {
"lines_to_next_cell": 2
},
"source": [
"This section defines a function to calculate Donchian Channels for given price data over a specified period. It calculates the upper and lower bands and optionally the middle line."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c9b4cbb0",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"def donchian_channel(df: pd.DataFrame, period: int = 30) -> pd.DataFrame:\n",
"\n",
" df[\"upper\"] = df[\"high\"].rolling(window=period).max()\n",
"\n",
" df[\"lower\"] = df[\"low\"].rolling(window=period).min()\n",
"\n",
" df[\"mid\"] = (df[\"upper\"] + df[\"lower\"]) / 2\n",
"\n",
" return df"
]
},
{
"cell_type": "markdown",
"id": "2467633a",
"metadata": {},
"source": [
"This function takes a DataFrame containing price data and a period for the calculation. It computes the upper band as the highest high over the period and the lower band as the lowest low. The middle line is calculated as the average of the upper and lower bands. Finally, the function returns the DataFrame with the new columns added."
]
},
{
"cell_type": "markdown",
"id": "193cbda0",
"metadata": {},
"source": [
"### Create a class to interact with Interactive Brokers API"
]
},
{
"cell_type": "markdown",
"id": "59e6e825",
"metadata": {
"lines_to_next_cell": 2
},
"source": [
"This section defines a TradingApp class that interacts with the Interactive Brokers (IB) API. This class handles connections, data retrieval, and order placement."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef93770e",
"metadata": {},
"outputs": [],
"source": [
"class TradingApp(EClient, EWrapper):\n",
"\n",
" def __init__(self) -> None:\n",
"\n",
" EClient.__init__(self, self)\n",
" self.data: Dict[int, pd.DataFrame] = {}\n",
" self.nextOrderId: Optional[int] = None\n",
"\n",
" def error(\n",
" self, reqId: int, errorCode: int, errorString: str, advanced: any\n",
" ) -> None:\n",
"\n",
" print(f\"Error: {reqId}, {errorCode}, {errorString}\")\n",
"\n",
" def nextValidId(self, orderId: int) -> None:\n",
"\n",
" super().nextValidId(orderId)\n",
" self.nextOrderId = orderId\n",
"\n",
" def get_historical_data(self, reqId: int, contract: Contract) -> pd.DataFrame:\n",
"\n",
" self.data[reqId] = pd.DataFrame(columns=[\"time\", \"high\", \"low\", \"close\"])\n",
" self.data[reqId].set_index(\"time\", inplace=True)\n",
" self.reqHistoricalData(\n",
" reqId=reqId,\n",
" contract=contract,\n",
" endDateTime=\"\",\n",
" durationStr=\"1 D\",\n",
" barSizeSetting=\"1 min\",\n",
" whatToShow=\"MIDPOINT\",\n",
" useRTH=0,\n",
" formatDate=2,\n",
" keepUpToDate=False,\n",
" chartOptions=[],\n",
" )\n",
" time.sleep(5)\n",
" return self.data[reqId]\n",
"\n",
" def historicalData(self, reqId: int, bar: BarData) -> None:\n",
"\n",
" df = self.data[reqId]\n",
"\n",
" df.loc[pd.to_datetime(bar.date, unit=\"s\"), [\"high\", \"low\", \"close\"]] = [\n",
" bar.high,\n",
" bar.low,\n",
" bar.close,\n",
" ]\n",
"\n",
" df = df.astype(float)\n",
"\n",
" self.data[reqId] = df\n",
"\n",
" @staticmethod\n",
" def get_contract(symbol: str) -> Contract:\n",
"\n",
" contract = Contract()\n",
" contract.symbol = symbol\n",
" contract.secType = \"STK\"\n",
" contract.exchange = \"SMART\"\n",
" contract.currency = \"USD\"\n",
" return contract\n",
"\n",
" def place_order(\n",
" self, contract: Contract, action: str, order_type: str, quantity: int\n",
" ) -> None:\n",
"\n",
" order = Order()\n",
" order.action = action\n",
" order.orderType = order_type\n",
" order.totalQuantity = quantity\n",
"\n",
" self.placeOrder(self.nextOrderId, contract, order)\n",
" self.nextOrderId += 1\n",
" print(\"Order placed\")"
]
},
{
"cell_type": "markdown",
"id": "4ac4d04c",
"metadata": {},
"source": [
"The TradingApp class extends EClient and EWrapper to interact with the IB API. It initializes the client and wrapper components and sets up data storage. The error method handles API errors, while nextValidId sets the next valid order ID. The get_historical_data method requests historical market data for a given contract, storing it in a DataFrame. The historicalData method processes and stores the received data. The get_contract method creates a stock contract, and the place_order method places trades using the provided contract, action, order type, and quantity."
]
},
{
"cell_type": "markdown",
"id": "dd46c3c3",
"metadata": {},
"source": [
"### Connect the trading app and request data"
]
},
{
"cell_type": "markdown",
"id": "b4f05869",
"metadata": {},
"source": [
"This section connects the TradingApp to the IB API and requests historical data for a specified stock. It also calculates Donchian Channels for the acquired data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6916d705",
"metadata": {},
"outputs": [],
"source": [
"app = TradingApp()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "46aa8c0a",
"metadata": {},
"outputs": [],
"source": [
"app.connect(\"127.0.0.1\", 7497, clientId=5)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e4c3e56e",
"metadata": {},
"outputs": [],
"source": [
"threading.Thread(target=app.run, daemon=True).start()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e7ceb258",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"while True:\n",
" if isinstance(app.nextOrderId, int):\n",
" print(\"connected\")\n",
" break\n",
" else:\n",
" print(\"waiting for connection\")\n",
" time.sleep(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "57f829af",
"metadata": {},
"outputs": [],
"source": [
"nvda = TradingApp.get_contract(\"NVDA\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "815602fb",
"metadata": {},
"outputs": [],
"source": [
"data = app.get_historical_data(99, nvda)\n",
"data.tail()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "75f32b38",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"donchian = donchian_channel(data, period=30)\n",
"donchian.tail()"
]
},
{
"cell_type": "markdown",
"id": "09e50c35",
"metadata": {},
"source": [
"The code creates an instance of the TradingApp class and connects it to the IB API using the specified IP address, port, and client ID. It then starts the app on a separate thread to allow code execution to continue. A loop checks for a successful connection by verifying the nextOrderId. Once connected, it defines a contract for the stock symbol NVDA and requests historical data for the last trading day using a specified request ID. The Donchian Channels are then calculated for the acquired data."
]
},
{
"cell_type": "markdown",
"id": "8b65adae",
"metadata": {},
"source": [
"### Implement trading logic based on Donchian Channels"
]
},
{
"cell_type": "markdown",
"id": "3061633c",
"metadata": {},
"source": [
"This section implements trading logic based on the Donchian Channels. It checks for breakouts and places buy or sell orders accordingly."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f4d5dd6f",
"metadata": {},
"outputs": [],
"source": [
"period = 30"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "848bb8d0",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"while True:\n",
"\n",
" print(\"Getting data for contract...\")\n",
" data = app.get_historical_data(99, nvda)\n",
"\n",
" if len(data) < period:\n",
" print(f\"There are only {len(data)} bars of data, skipping...\")\n",
" continue\n",
"\n",
" print(\"Computing the Donchian Channel...\")\n",
" donchian = donchian_channel(data, period=period)\n",
"\n",
" last_price = data.iloc[-1].close\n",
"\n",
" upper, lower = donchian[[\"upper\", \"lower\"]].iloc[-1]\n",
"\n",
" print(\n",
" f\"Check if last price {last_price} is outside the channels {upper} and {lower}\"\n",
" )\n",
"\n",
" if last_price >= upper:\n",
" print(\"Breakout detected, going long...\")\n",
" app.place_order(nvda, \"BUY\", \"MKT\", 10)\n",
"\n",
" elif last_price <= lower:\n",
" print(\"Breakout detected, going short...\")\n",
" app.place_order(nvda, \"SELL\", \"MKT\", 10)"
]
},
{
"cell_type": "markdown",
"id": "31f5b652",
"metadata": {},
"source": [
"The code sets the period for the Donchian Channels to 30. It enters an infinite loop to continuously request data and check for trading opportunities. It retrieves historical data for the NVDA contract and skips further processing if there is insufficient data. It then calculates the Donchian Channels and gets the last traded price. The code compares the last price with the upper and lower channels to detect breakouts. If a breakout to the upside is detected, it places a buy market order for 10 shares. If a breakout to the downside is detected, it places a sell market order for 10 shares."
]
},
{
"cell_type": "markdown",
"id": "89402165",
"metadata": {},
"source": [
"### Disconnect the trading app"
]
},
{
"cell_type": "markdown",
"id": "92b4fc23",
"metadata": {},
"source": [
"This section disconnects the TradingApp from the IB API once the trading logic is complete."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c771f63a",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"app.disconnect()"
]
},
{
"cell_type": "markdown",
"id": "a4d25159",
"metadata": {},
"source": [
"The code disconnects the TradingApp from the IB API. This ensures that the connection is properly closed once the trading logic is complete. It is important to disconnect to avoid leaving open connections, which can lead to issues with the API or unwanted data usage."
]
},
{
"cell_type": "markdown",
"id": "d0733028",
"metadata": {},
"source": [
"### Your next steps"
]
},
{
"cell_type": "markdown",
"id": "2221da63",
"metadata": {},
"source": [
"Try changing the stock symbol to a different one to see how the Donchian Channels and trading logic perform with other stocks. Experiment with different periods for the Donchian Channels to observe how the results change. Consider modifying the order quantity to suit your trading strategy better."
]
},
{
"cell_type": "markdown",
"id": "9961ff12",
"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
}

View File

@ -0,0 +1,226 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3216dcbe",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "2348156b",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import yfinance as yf\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "054e4261",
"metadata": {},
"source": [
"## Retrieve historical stock prices for selected assets"
]
},
{
"cell_type": "markdown",
"id": "6dc9ad89-8631-4802-821b-399c956158bd",
"metadata": {},
"source": [
"We start by collecting historical stock prices for a set of specified assets. We will use the `yfinance` library to download this data."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "65f895d0",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"[*********************100%%**********************] 5 of 5 completed\n"
]
}
],
"source": [
"prices = yf.download([\"FDN\", \"FTXL\", \"FXD\", \"FXR\", \"HDEF\"], start=\"2020-01-01\")"
]
},
{
"cell_type": "markdown",
"id": "b258dbd9-5a22-46b4-ae2c-ce99a23291ec",
"metadata": {},
"source": [
"We use the `yfinance` library to download historical price data for five different assets, starting from January 1, 2020. This data includes all available prices up to the current date. The data is returned in a structured format, with separate columns for different types of prices, such as open, high, low, close, and adjusted close. We're interested in the adjusted close prices, which account for corporate actions like dividends and stock splits."
]
},
{
"cell_type": "markdown",
"id": "dbeb7368",
"metadata": {},
"source": [
"## Calculate daily returns for each asset"
]
},
{
"cell_type": "markdown",
"id": "79820113-91f4-49f1-97cf-17ebd716c7e8",
"metadata": {},
"source": [
"Next, we calculate the daily returns for each asset. This helps us understand how the price of each asset changes from one day to the next."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "d61f22ee",
"metadata": {},
"outputs": [],
"source": [
"returns = (\n",
" prices[\"Adj Close\"]\n",
" .pct_change()\n",
" .dropna()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "fa2b88c8-f9c7-43e3-9d5d-c36c50225c67",
"metadata": {},
"source": [
"We calculate the daily returns by taking the percentage change of the adjusted close prices from one day to the next. The `pct_change()` function is used to compute these changes. We drop any missing values that may arise from calculating percentage changes, particularly at the start of the dataset. The result is a new dataset where each value represents the daily return of an asset expressed as a percentage."
]
},
{
"cell_type": "markdown",
"id": "63911f78",
"metadata": {},
"source": [
"## Determine expected returns and covariance matrix"
]
},
{
"cell_type": "markdown",
"id": "5267166d-e2c1-45b0-bda3-6f0f340a0f2f",
"metadata": {},
"source": [
"We now compute the expected returns for each asset and the covariance matrix of the returns. The expected return gives us an average sense of how much we can expect each asset to return. The covariance matrix helps us understand how the returns of the different assets move in relation to each other."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "754a1cb1-4498-4a8a-8a08-b6b3069c2ddd",
"metadata": {},
"outputs": [],
"source": [
"expected_returns = np.mean(returns, axis=0)\n",
"cov_matrix = np.cov(returns, rowvar=False)"
]
},
{
"cell_type": "markdown",
"id": "c6176ff7-22df-4e56-9c12-f9a1bbccf300",
"metadata": {},
"source": [
"We calculate the expected returns using the mean of the daily returns for each asset. This gives us a single number for each asset representing its average daily return over the entire period. The covariance matrix is computed using `np.cov`, which takes the returns data and calculates how the returns of different assets change together. The result is a matrix where each element represents the covariance between a pair of assets, helping us understand their return correlations."
]
},
{
"cell_type": "markdown",
"id": "9f2556d4",
"metadata": {},
"source": [
"## Evaluate the portfolio's risk through variance and standard deviation"
]
},
{
"cell_type": "markdown",
"id": "b09aac17-c051-4214-a4ee-e38bbab9fc2e",
"metadata": {},
"source": [
"Finally, we define a portfolio with equal weights for each asset and calculate its variance and standard deviation. These metrics will help us assess the risk associated with the portfolio."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "c961c89e",
"metadata": {},
"outputs": [],
"source": [
"weights = np.array([0.2] * 5)\n",
"portfolio_variance = np.dot(weights.T, np.dot(cov_matrix, weights))\n",
"portfolio_std_dev = np.sqrt(portfolio_variance) * np.sqrt(252)"
]
},
{
"cell_type": "markdown",
"id": "5872f502-98b0-436a-bf0d-6e635bf8979d",
"metadata": {},
"source": [
"We define equal weights of 0.2 for each asset, assuming we want to allocate the same proportion of our investment to each. The portfolio variance is calculated using a mathematical formula that combines the weights and the covariance matrix. This calculation results in a single value representing the total risk of the portfolio based on the variance of its assets. To get the annualized standard deviation, we take the square root of the portfolio variance and multiply it by the square root of 252, the typical number of trading days in a year. The standard deviation provides a measure of how much the portfolio's return might fluctuate annually."
]
},
{
"cell_type": "markdown",
"id": "ec9e6505",
"metadata": {},
"source": [
"## Your next steps"
]
},
{
"cell_type": "markdown",
"id": "3996c26e-d565-4724-8128-d6ce7672bf74",
"metadata": {},
"source": [
"Try modifying the weight distribution among the assets to see how it affects the portfolio's risk. You can adjust the weights in the `weights` array, making some assets more dominant than others. Observe how changes in asset allocation impact the portfolio's variance and standard deviation, allowing you to explore different risk-return profiles. Experimenting with different weights helps in understanding the dynamics of portfolio optimization."
]
},
{
"cell_type": "markdown",
"id": "6928a33e",
"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
}

View File

@ -0,0 +1,269 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "6ae95e22",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "d03b11d6",
"metadata": {},
"source": [
"This code downloads historical price data for specific stocks and calculates a portfolio's performance. It uses Yahoo Finance data to get adjusted close prices for QQQ, AAPL, and AMZN. The code constructs a simple portfolio of equal shares in AAPL and AMZN, computes the portfolio's value, daily returns, and cumulative returns over time. It then compares the portfolio's cumulative returns to a benchmark (QQQ). Finally, it calculates the information ratio to evaluate the portfolio's performance relative to the benchmark."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "df51ef5b",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import yfinance as yf"
]
},
{
"cell_type": "markdown",
"id": "d1365087",
"metadata": {},
"source": [
"Download historical price data for QQQ, AAPL, and AMZN from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c4473af",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download([\"QQQ\", \"AAPL\", \"AMZN\"], start=\"2020-01-01\", end=\"2022-07-31\")"
]
},
{
"cell_type": "markdown",
"id": "a22d784e",
"metadata": {},
"source": [
"Extract adjusted close prices for the downloaded data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab04ef0d",
"metadata": {},
"outputs": [],
"source": [
"closes = data['Adj Close']\n",
"benchmark_returns = closes.QQQ.pct_change()"
]
},
{
"cell_type": "markdown",
"id": "7788fe03",
"metadata": {},
"source": [
"Construct a simple portfolio with equal shares of AAPL and AMZN"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ac0ea69",
"metadata": {},
"outputs": [],
"source": [
"aapl_position = closes.AAPL * 50\n",
"amzn_position = closes.AMZN * 50"
]
},
{
"cell_type": "markdown",
"id": "82e0e793",
"metadata": {},
"source": [
"Compute the portfolio value over time by summing the positions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e5c9614",
"metadata": {},
"outputs": [],
"source": [
"portfolio_value = aapl_position + amzn_position"
]
},
{
"cell_type": "markdown",
"id": "75f062eb",
"metadata": {},
"source": [
"Calculate the portfolio's daily profit and loss (PnL)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "98e59e8c",
"metadata": {},
"outputs": [],
"source": [
"portfolio_pnl = (\n",
" (aapl_position - aapl_position.shift()) \n",
" + (amzn_position - amzn_position.shift())\n",
")"
]
},
{
"cell_type": "markdown",
"id": "410af899",
"metadata": {},
"source": [
"Compute the portfolio's daily return by dividing PnL by the portfolio value"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e17de052",
"metadata": {},
"outputs": [],
"source": [
"portfolio_returns = (portfolio_pnl / portfolio_value)\n",
"portfolio_returns.name = \"Port\""
]
},
{
"cell_type": "markdown",
"id": "6349bff8",
"metadata": {},
"source": [
"Create cumulative returns for both the portfolio and the benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea4a9b96",
"metadata": {},
"outputs": [],
"source": [
"portfolio_cumulative_returns = (portfolio_returns.fillna(0.0) + 1).cumprod()\n",
"benchmark_cumulative_returns = (benchmark_returns.fillna(0.0) + 1).cumprod()"
]
},
{
"cell_type": "markdown",
"id": "1382ec20",
"metadata": {},
"source": [
"Plot the cumulative returns of the portfolio against the benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9ba13165",
"metadata": {},
"outputs": [],
"source": [
"portfolio_cumulative_returns = (portfolio_returns.fillna(0.0) + 1).cumprod()\n",
"benchmark_cumulative_returns = (benchmark_returns.fillna(0.0) + 1).cumprod()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9e6d8de3",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"pd.concat([portfolio_cumulative_returns, benchmark_cumulative_returns], axis=1).plot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8454d5ed",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def information_ratio(portfolio_returns, benchmark_returns):\n",
" \"\"\"\n",
" Determines the information ratio of a strategy.\n",
" \n",
" Parameters\n",
" ----------\n",
" portfolio_returns : pd.Series or np.ndarray\n",
" Daily returns of the strategy, noncumulative.\n",
" benchmark_returns : int, float\n",
" Daily returns of the benchmark or factor, noncumulative.\n",
"\n",
" Returns\n",
" -------\n",
" information_ratio : float\n",
"\n",
" Note\n",
" -----\n",
" See https://en.wikipedia.org/wiki/Information_ratio for more details.\n",
" \"\"\"\n",
" \n",
" # Calculate active return by subtracting benchmark returns from portfolio returns\n",
" active_return = portfolio_returns - benchmark_returns\n",
"\n",
" # Calculate tracking error as the standard deviation of active returns\n",
" tracking_error = active_return.std()\n",
"\n",
" # Return the information ratio, which is the mean active return divided by the tracking error\n",
" return active_return.mean() / tracking_error"
]
},
{
"cell_type": "markdown",
"id": "7bd6fa3a",
"metadata": {},
"source": [
"Calculate the information ratio of the portfolio relative to the benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6960c140",
"metadata": {},
"outputs": [],
"source": [
"information_ratio(portfolio_returns, benchmark_returns)"
]
},
{
"cell_type": "markdown",
"id": "e225688c",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,305 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "920cf328",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "879db885",
"metadata": {},
"source": [
"This code downloads historical stock data, calculates returns, and fits an ARCH model to forecast volatility. It utilizes the Yahoo Finance API to obtain the adjusted closing prices of a specified stock. The ARCH model is then fitted to the calculated returns to estimate future volatility. The results are visualized and the forecasted volatility is annualized. This approach is valuable for financial modeling and risk management."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "335fbd37",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import yfinance as yf\n",
"from arch import arch_model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2332c898",
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"plt.rc(\"figure\", figsize=(16, 6))\n",
"plt.rc(\"savefig\", dpi=90)\n",
"plt.rc(\"font\", family=\"sans-serif\")\n",
"plt.rc(\"font\", size=14)"
]
},
{
"cell_type": "markdown",
"id": "2088696e",
"metadata": {},
"source": [
"Download historical stock data for Apple (AAPL) from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "38de8923",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(\"aapl\", start=\"2020-01-01\", end=\"2022-07-31\")"
]
},
{
"cell_type": "markdown",
"id": "ebf2b85c",
"metadata": {},
"source": [
"Extract adjusted closing prices and calculate daily returns in percentage terms"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff63f46b",
"metadata": {},
"outputs": [],
"source": [
"adjusted_closes = data['Adj Close']\n",
"returns = 100 * adjusted_closes.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "066381ec",
"metadata": {},
"source": [
"Initialize an ARCH model using the calculated returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "77127441",
"metadata": {},
"outputs": [],
"source": [
"model = arch_model(returns)"
]
},
{
"cell_type": "markdown",
"id": "e0fbb63b",
"metadata": {},
"source": [
"Fit the ARCH model to the returns data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8c4d2599",
"metadata": {},
"outputs": [],
"source": [
"res = model.fit()"
]
},
{
"cell_type": "markdown",
"id": "f00f8634",
"metadata": {},
"source": [
"Print a summary of the model fitting results"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "afe19f42",
"metadata": {},
"outputs": [],
"source": [
"print(res.summary())"
]
},
{
"cell_type": "markdown",
"id": "b19de7fa",
"metadata": {},
"source": [
"Plot the results of the model fitting for visualization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "820b348f",
"metadata": {},
"outputs": [],
"source": [
"fig = res.plot(\"D\")"
]
},
{
"cell_type": "markdown",
"id": "377f0e9a",
"metadata": {},
"source": [
"Forecast variance for the next period using the fitted model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "67613456",
"metadata": {},
"outputs": [],
"source": [
"forecast = res.forecast(horizon=1, reindex=False)\n",
"variance_forecast = forecast.variance.iloc[-1][0]"
]
},
{
"cell_type": "markdown",
"id": "18fd8b24",
"metadata": {},
"source": [
"Calculate the forecasted volatility and annualize it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0f457d02",
"metadata": {},
"outputs": [],
"source": [
"volatility_forecast = np.sqrt(variance_forecast)\n",
"annualized_volatility_forecast = volatility_forecast * np.sqrt(252) / 100\n",
"annualized_volatility_forecast"
]
},
{
"cell_type": "markdown",
"id": "8f619b19",
"metadata": {},
"source": [
"Calculate the historical annualized volatility standard deviation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3522f50d",
"metadata": {},
"outputs": [],
"source": [
"annualized_volatility_stdev = returns.std() * np.sqrt(252) / 100\n",
"annualized_volatility_stdev"
]
},
{
"cell_type": "markdown",
"id": "b1fa7d5d",
"metadata": {},
"source": [
"Compute the relative error between the forecasted volatility and the historical volatility standard deviation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a5e08674",
"metadata": {},
"outputs": [],
"source": [
"(annualized_volatility_forecast - annualized_volatility_stdev) / annualized_volatility_stdev"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "35246630",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "ca318b09",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "d2cc08df",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "a12d9575",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "3956f9ac",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "ac528c77",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "5449b958",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "7548750a",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,250 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e8e4c152",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "a57f8b45",
"metadata": {},
"source": [
"This code downloads historical stock data for SPY and AAPL from Yahoo Finance and calculates daily returns. It then computes the Sortino ratio, a risk-adjusted performance metric, for these returns. The Sortino ratio focuses on downside risk, providing a better measure for evaluating strategies with asymmetric risk profiles. The code also visualizes the rolling Sortino ratio and its distribution for AAPL. This is useful for performance analysis and comparison of different investment strategies."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a9c9a42",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "aac23142",
"metadata": {},
"source": [
"Download historical adjusted closing prices for SPY and AAPL from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3627e86",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download([\"SPY\", \"AAPL\"], start=\"2020-01-01\", end=\"2022-07-31\")"
]
},
{
"cell_type": "markdown",
"id": "778fee89",
"metadata": {},
"source": [
"Extract adjusted closing prices and calculate daily returns for SPY and AAPL"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "41003991",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"closes = data['Adj Close']\n",
"spy_returns = closes.SPY.pct_change().dropna()\n",
"aapl_returns = closes.AAPL.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "dbb043d7",
"metadata": {},
"source": [
"Define a function to calculate the Sortino ratio for a series of returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fefe6265",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def sortino_ratio(returns, adjustment_factor=0.0):\n",
" \"\"\"\n",
" Determines the Sortino ratio of a strategy.\n",
" \n",
" Parameters\n",
" ----------\n",
" returns : pd.Series or np.ndarray\n",
" Daily returns of the strategy, noncumulative.\n",
" adjustment_factor : int, float\n",
" Constant daily benchmark return throughout the period.\n",
"\n",
" Returns\n",
" -------\n",
" sortino_ratio : float\n",
"\n",
" Note\n",
" -----\n",
" See `<https://www.sunrisecapital.com/wp-content/uploads/2014/06/Futures_\n",
" Mag_Sortino_0213.pdf>`__ for more details.\n",
" \"\"\"\n",
"\n",
" # Adjust returns by subtracting the adjustment factor and compute average annual return\n",
" returns_risk_adj = np.asanyarray(returns - adjustment_factor)\n",
" average_annual_return = returns_risk_adj.mean() * 252\n",
"\n",
" # Compute downside deviation by considering only negative deviations\n",
" downside_diff = np.clip(returns_risk_adj, np.NINF, 0)\n",
" np.square(downside_diff, out=downside_diff)\n",
" annualized_downside_deviation = np.sqrt(downside_diff.mean()) * np.sqrt(252)\n",
"\n",
" # Calculate and return the Sortino ratio\n",
" return average_annual_return / annualized_downside_deviation"
]
},
{
"cell_type": "markdown",
"id": "334d33ea",
"metadata": {},
"source": [
"Calculate the Sortino ratio for SPY's daily returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0c281e5e",
"metadata": {},
"outputs": [],
"source": [
"sortino_ratio(spy_returns)"
]
},
{
"cell_type": "markdown",
"id": "cd306508",
"metadata": {},
"source": [
"Calculate the Sortino ratio for AAPL's daily returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a6d8b4a1",
"metadata": {},
"outputs": [],
"source": [
"sortino_ratio(aapl_returns)"
]
},
{
"cell_type": "markdown",
"id": "ea3d422f",
"metadata": {},
"source": [
"Plot the rolling 30-day Sortino ratio for AAPL's returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b86b1ba0",
"metadata": {},
"outputs": [],
"source": [
"aapl_returns.rolling(30).apply(sortino_ratio).plot()"
]
},
{
"cell_type": "markdown",
"id": "bf0f5718",
"metadata": {},
"source": [
"Plot the histogram of the rolling 30-day Sortino ratio for AAPL's returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dcf47150",
"metadata": {},
"outputs": [],
"source": [
"aapl_returns.rolling(30).apply(sortino_ratio).hist(bins=50)"
]
},
{
"cell_type": "markdown",
"id": "8eaa8c01",
"metadata": {},
"source": [
"Plot the histogram of the difference between rolling 30-day Sortino ratios of AAPL and SPY"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f3b8abb",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" aapl_returns.rolling(30).apply(sortino_ratio)\n",
" - spy_returns.rolling(30).apply(sortino_ratio)\n",
").hist(bins=50)"
]
},
{
"cell_type": "markdown",
"id": "7ffcdc8a",
"metadata": {},
"source": [
"Calculate the Sortino ratio for SPY's daily returns again"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "221fcbf5",
"metadata": {},
"outputs": [],
"source": [
"sortino_ratio(spy_returns)"
]
},
{
"cell_type": "markdown",
"id": "ac229400",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,310 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ffd5a549",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "8ebd9176",
"metadata": {},
"source": [
"This code constructs and optimizes a financial portfolio using historical stock data. It uses the Riskfolio library to estimate asset returns and covariance. The code further optimizes the portfolio for risk parity and visualizes the resulting asset allocation and risk contributions. Additionally, it demonstrates the impact of adding a constraint on expected returns. This is useful for portfolio management and risk assessment in finance."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe7c58a8",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf\n",
"import riskfolio as rp\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "f926ae4e",
"metadata": {},
"source": [
"Define a list of stock tickers to include in the portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7042f7bf",
"metadata": {},
"outputs": [],
"source": [
"assets = [\"JCI\", \"TGT\", \"CMCSA\", \"CPB\", \"MO\", \"APA\", \"MMC\", \"JPM\",\n",
" \"ZION\", \"PSA\", \"BAX\", \"BMY\", \"LUV\", \"PCAR\", \"TXT\", \"TMO\",\n",
" \"DE\", \"MSFT\", \"HPQ\", \"SEE\", \"VZ\", \"CNP\", \"NI\", \"T\", \"BA\"]"
]
},
{
"cell_type": "markdown",
"id": "79e0a66f",
"metadata": {},
"source": [
"Sort the list of tickers alphabetically"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "68e33540",
"metadata": {},
"outputs": [],
"source": [
"assets.sort()"
]
},
{
"cell_type": "markdown",
"id": "0e39a06b",
"metadata": {},
"source": [
"Download historical stock data for the specified tickers from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1020999e",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(assets, start=\"2016-01-01\", end=\"2019-12-30\")"
]
},
{
"cell_type": "markdown",
"id": "965ab385",
"metadata": {},
"source": [
"Calculate daily returns based on adjusted closing prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c6cf1fd",
"metadata": {},
"outputs": [],
"source": [
"returns = data['Adj Close'].pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "0b4b580c",
"metadata": {},
"source": [
"Create a Portfolio object using the calculated returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1f3a849b",
"metadata": {},
"outputs": [],
"source": [
"port = rp.Portfolio(returns=returns)"
]
},
{
"cell_type": "markdown",
"id": "aefeba8e",
"metadata": {},
"source": [
"Estimate expected returns and covariance matrix using historical data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d5508fd",
"metadata": {},
"outputs": [],
"source": [
"port.assets_stats(method_mu='hist', method_cov='hist', d=0.94)"
]
},
{
"cell_type": "markdown",
"id": "d01b6618",
"metadata": {},
"source": [
"Optimize the portfolio for risk parity using mean-variance optimization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e81ffa05",
"metadata": {},
"outputs": [],
"source": [
"w_rp = port.rp_optimization(\n",
" model=\"Classic\", # use historical\n",
" rm=\"MV\", # use mean-variance optimization\n",
" hist=True, # use historical scenarios\n",
" rf=0, # set risk free rate to 0\n",
" b=None # don't use constraints\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c1831b35",
"metadata": {},
"source": [
"Plot the asset allocation of the optimized portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "65904137",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_pie(w=w_rp)"
]
},
{
"cell_type": "markdown",
"id": "e25e251f",
"metadata": {},
"source": [
"Plot the risk contribution of each asset in the optimized portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "79c79c02",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_risk_con(\n",
" w_rp,\n",
" cov=port.cov,\n",
" returns=port.returns,\n",
" rm=\"MV\",\n",
" rf=0,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "2a97ece3",
"metadata": {},
"source": [
"Set a constraint for the minimum level of expected returns for the portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be3c355a",
"metadata": {},
"outputs": [],
"source": [
"port.lowerret = 0.0008"
]
},
{
"cell_type": "markdown",
"id": "6400a196",
"metadata": {},
"source": [
"Optimize the portfolio for risk parity with the added constraint on expected returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "856a87ed",
"metadata": {},
"outputs": [],
"source": [
"w_rp_c = port.rp_optimization(\n",
" model=\"Classic\", # use historical\n",
" rm=\"MV\", # use mean-variance optimization\n",
" hist=True, # use historical scenarios\n",
" rf=0, # set risk free rate to 0\n",
" b=None # don't use constraints\n",
")"
]
},
{
"cell_type": "markdown",
"id": "0aa64fb9",
"metadata": {},
"source": [
"Plot the asset allocation of the constrained optimized portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f34eb3a",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_pie(w=w_rp_c)"
]
},
{
"cell_type": "markdown",
"id": "42e39406",
"metadata": {},
"source": [
"Plot the risk contribution of each asset in the constrained optimized portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9dc06f47",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_risk_con(\n",
" w_rp_c,\n",
" cov=port.cov,\n",
" returns=port.returns,\n",
" rm=\"MV\",\n",
" rf=0,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "bcf00531",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,287 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "b99b374d",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "db4c147e",
"metadata": {},
"source": [
"This notebook fetches financial data for specified stock symbols from Yahoo Finance and stores it in a SQLite database. It provides functions to download stock data over a given date range and save the data into the database. Additionally, it can save data for the latest trading session. This is useful for maintaining a local database of historical stock prices for analysis and backtesting. The code demonstrates usage with example stock symbols and queries the stored data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d3000251",
"metadata": {},
"outputs": [],
"source": [
"from sys import argv"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "280a44ca",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import yfinance as yf\n",
"import sqlite3"
]
},
{
"cell_type": "markdown",
"id": "6223c51a",
"metadata": {},
"source": [
"Establish a connection to the SQLite database"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8b6b88ae",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"con = sqlite3.connect(\"market_data.sqlite\")"
]
},
{
"cell_type": "markdown",
"id": "a7a5eed8",
"metadata": {},
"source": [
"Fetch stock data for a given symbol and date range from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "03765086",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def get_stock_data(symbol, start, end):\n",
" \"\"\"Fetch stock data from Yahoo Finance.\n",
" \n",
" Downloads stock data for a specified symbol and date range and processes it.\n",
" \n",
" Parameters\n",
" ----------\n",
" symbol : str\n",
" The stock ticker symbol.\n",
" start : str\n",
" The start date (YYYY-MM-DD).\n",
" end : str\n",
" The end date (YYYY-MM-DD).\n",
" \n",
" Returns\n",
" -------\n",
" data : pd.DataFrame\n",
" A DataFrame containing the stock data.\n",
" \"\"\"\n",
" \n",
" # Download the stock data from Yahoo Finance\n",
" data = yf.download(symbol, start=start, end=end)\n",
" \n",
" # Reset the DataFrame index to use integer indexing\n",
" data.reset_index(inplace=True)\n",
"\n",
" # Rename the columns to match database schema\n",
" data.rename(columns={\n",
" \"Date\": \"date\",\n",
" \"Open\": \"open\",\n",
" \"Low\": \"low\",\n",
" \"Close\": \"close\",\n",
" \"Adj Close\": \"adj_close\",\n",
" \"Volume\": \"volume\"\n",
" }, inplace=True)\n",
" \n",
" # Add a column for the stock symbol\n",
" data['symbol'] = symbol\n",
" \n",
" return data"
]
},
{
"cell_type": "markdown",
"id": "1d907550",
"metadata": {},
"source": [
"Save stock data for a given symbol and date range to the SQLite database"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7117438c",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def save_data_range(symbol, start, end):\n",
" \"\"\"Save stock data to database.\n",
" \n",
" Fetches and saves stock data for a specified symbol and date range.\n",
" \n",
" Parameters\n",
" ----------\n",
" symbol : str\n",
" The stock ticker symbol.\n",
" start : str\n",
" The start date (YYYY-MM-DD).\n",
" end : str\n",
" The end date (YYYY-MM-DD).\n",
" \n",
" Returns\n",
" -------\n",
" None\n",
" \"\"\"\n",
" \n",
" # Get stock data for the specified symbol and date range\n",
" data = get_stock_data(symbol, start, end)\n",
" \n",
" # Save the data to the SQLite database\n",
" data.to_sql(\n",
" \"stock_data\", \n",
" con, \n",
" if_exists=\"append\", \n",
" index=False\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "1cd30bfc",
"metadata": {},
"source": [
"Save stock data for the latest trading session to the SQLite database"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3cb23ec7",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def save_last_trading_session(symbol):\n",
" \"\"\"Save the latest trading session data.\n",
" \n",
" Fetches and saves stock data for the latest trading session.\n",
" \n",
" Parameters\n",
" ----------\n",
" symbol : str\n",
" The stock ticker symbol.\n",
" \n",
" Returns\n",
" -------\n",
" None\n",
" \"\"\"\n",
" \n",
" # Get today's date\n",
" today = pd.Timestamp.today()\n",
" \n",
" # Get stock data for the latest trading session\n",
" data = get_stock_data(symbol, today, today)\n",
" \n",
" # Save the data to the SQLite database\n",
" data.to_sql(\n",
" \"stock_data\", \n",
" con, \n",
" if_exists=\"append\", \n",
" index=False\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "42704aa5",
"metadata": {},
"source": [
"Fetch and save data for the symbol \"TLT\" from 2022-01-01 to 2022-10-21"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d84f5d39",
"metadata": {},
"outputs": [],
"source": [
"save_data_range(\"TLT\", \"2022-01-01\", \"2022-10-21\")"
]
},
{
"cell_type": "markdown",
"id": "9bb24b46",
"metadata": {},
"source": [
"Query the SQLite database to fetch data for the symbol \"SPY\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a34f05ee",
"metadata": {},
"outputs": [],
"source": [
"df = pd.read_sql_query(\"SELECT * from stock_data where symbol='SPY'\", con)"
]
},
{
"cell_type": "markdown",
"id": "97dd546f",
"metadata": {},
"source": [
"Display the fetched data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "481992be",
"metadata": {},
"outputs": [],
"source": [
"df"
]
},
{
"cell_type": "markdown",
"id": "fcf58b0f",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,240 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "79ea368d",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "58225e34",
"metadata": {},
"source": [
"This code calculates the Omega ratio for financial returns, a performance metric that captures more information about the distribution of returns than traditional metrics like the Sharpe ratio. It uses stock price data from Yahoo Finance to compute daily returns for a specific stock (AAPL). The Omega ratio is calculated by dividing the sum of positive returns above a threshold by the absolute sum of negative returns below that threshold. This metric is useful for assessing the risk and return profile of investments, especially those with non-normal return distributions. The code also visualizes the rolling Omega ratio and basic statistical properties of the returns."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3a954243",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "a375f9fa",
"metadata": {},
"source": [
"Download the stock data for AAPL from Yahoo Finance for the specified date range"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3430b66",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(\"AAPL\", start=\"2020-01-01\", end=\"2021-12-31\")"
]
},
{
"cell_type": "markdown",
"id": "294330b3",
"metadata": {},
"source": [
"Calculate daily returns from the adjusted closing prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4133cec7",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"returns = data[\"Adj Close\"].pct_change()"
]
},
{
"cell_type": "markdown",
"id": "68e174db",
"metadata": {},
"source": [
"Calculate the Omega ratio of a strategy's returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aa288ad4",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def omega_ratio(returns, required_return=0.0):\n",
" \"\"\"Determines the Omega ratio of a strategy.\n",
" \n",
" Parameters\n",
" ----------\n",
" returns : pd.Series or np.ndarray\n",
" Daily returns of the strategy, noncumulative.\n",
" required_return : float, optional\n",
" Minimum acceptance return of the investor. Threshold over which to\n",
" consider positive vs negative returns. It will be converted to a\n",
" value appropriate for the period of the returns. E.g. An annual minimum\n",
" acceptable return of 100 will translate to a minimum acceptable\n",
" return of 0.018.\n",
"\n",
" Returns\n",
" -------\n",
" omega_ratio : float\n",
"\n",
" Note\n",
" -----\n",
" See https://en.wikipedia.org/wiki/Omega_ratio for more details.\n",
" \"\"\"\n",
"\n",
" # Convert the required return to a daily return threshold\n",
"\n",
" return_threshold = (1 + required_return) ** (1 / 252) - 1\n",
"\n",
" # Calculate the difference between returns and the return threshold\n",
"\n",
" returns_less_thresh = returns - return_threshold\n",
"\n",
" # Calculate the numerator as the sum of positive returns above the threshold\n",
"\n",
" numer = sum(returns_less_thresh[returns_less_thresh > 0.0])\n",
"\n",
" # Calculate the denominator as the absolute sum of negative returns below the threshold\n",
"\n",
" denom = -1.0 * sum(returns_less_thresh[returns_less_thresh < 0.0])\n",
"\n",
" # Return the Omega ratio if the denominator is positive; otherwise, return NaN\n",
"\n",
" if denom > 0.0:\n",
" return numer / denom\n",
" else:\n",
" return np.nan"
]
},
{
"cell_type": "markdown",
"id": "cc63495d",
"metadata": {},
"source": [
"Calculate the Omega ratio for the given returns and required return"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da67b3ba",
"metadata": {},
"outputs": [],
"source": [
"omega_ratio(returns, 0.07)"
]
},
{
"cell_type": "markdown",
"id": "9e89e93b",
"metadata": {},
"source": [
"Compute and plot the rolling 30-day Omega ratio of the returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba112f43",
"metadata": {},
"outputs": [],
"source": [
"returns.rolling(30).apply(omega_ratio).plot()"
]
},
{
"cell_type": "markdown",
"id": "33e329c8",
"metadata": {},
"source": [
"Plot a histogram of the daily returns to visualize their distribution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1bad9e37",
"metadata": {},
"outputs": [],
"source": [
"returns.hist(bins=50)"
]
},
{
"cell_type": "markdown",
"id": "cc2176f5",
"metadata": {},
"source": [
"Calculate and display the skewness of the returns distribution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "85b6eba5",
"metadata": {},
"outputs": [],
"source": [
"returns.skew()"
]
},
{
"cell_type": "markdown",
"id": "2cd3f659",
"metadata": {},
"source": [
"Calculate and display the kurtosis of the returns distribution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "025b51fc",
"metadata": {},
"outputs": [],
"source": [
"returns.kurtosis()"
]
},
{
"cell_type": "markdown",
"id": "9f4bae70",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,388 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d75c1762",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "46b15169",
"metadata": {},
"source": [
"This code retrieves option chains for a given ticker symbol using Yahoo Finance, then processes and visualizes the data. It fetches call and put options, calculates days to expiration, and filters based on implied volatility. The code further segments the options data by expiration date and strike price to plot implied volatility skew and term structure. Finally, it creates a 3D surface plot to visualize implied volatility across different strikes and expirations. This is useful for options analysis and trading strategies."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b020a9f9",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import yfinance as yf\n",
"import datetime as dt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "23169cbf",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"id": "582752e3",
"metadata": {},
"source": [
"Define a function to retrieve and process option chains for a given ticker symbol"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7883ca7d",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def option_chains(ticker):\n",
" \"\"\"Retrieve and process option chains.\n",
" \n",
" This function fetches option chains from Yahoo Finance for a given ticker symbol, \n",
" processes the data, and calculates days to expiration for each option.\n",
" \n",
" Parameters\n",
" ----------\n",
" ticker : str\n",
" The ticker symbol of the asset.\n",
" \n",
" Returns\n",
" -------\n",
" chains : pd.DataFrame\n",
" DataFrame containing processed option chains with days to expiration.\n",
" \"\"\"\n",
" \n",
" # Fetch option chains from Yahoo Finance for the given ticker\n",
" asset = yf.Ticker(ticker)\n",
" expirations = asset.options\n",
" \n",
" chains = pd.DataFrame()\n",
" \n",
" for expiration in expirations:\n",
" # Retrieve option chain for a specific expiration date\n",
" opt = asset.option_chain(expiration)\n",
" \n",
" calls = opt.calls\n",
" calls['optionType'] = \"call\"\n",
" \n",
" puts = opt.puts\n",
" puts['optionType'] = \"put\"\n",
" \n",
" # Concatenate call and put options into a single DataFrame\n",
" chain = pd.concat([calls, puts])\n",
" chain['expiration'] = pd.to_datetime(expiration) + pd.DateOffset(hours=23, minutes=59, seconds=59)\n",
" \n",
" chains = pd.concat([chains, chain])\n",
" \n",
" # Calculate days to expiration for each option\n",
" chains[\"daysToExpiration\"] = (chains.expiration - dt.datetime.today()).dt.days + 1\n",
" \n",
" return chains"
]
},
{
"cell_type": "markdown",
"id": "7548148b",
"metadata": {},
"source": [
"Retrieve the option chains for the SPY ticker symbol"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2fd4f869",
"metadata": {},
"outputs": [],
"source": [
"options = option_chains(\"SPY\")"
]
},
{
"cell_type": "markdown",
"id": "a6f20ede",
"metadata": {},
"source": [
"Filter the option chains to get only call options"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3b39a055",
"metadata": {},
"outputs": [],
"source": [
"calls = options[options[\"optionType\"] == \"call\"]"
]
},
{
"cell_type": "markdown",
"id": "21838163",
"metadata": {},
"source": [
"Select call options that expire on 2023-01-20"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2e289397",
"metadata": {},
"outputs": [],
"source": [
"calls_at_expiry = calls[calls[\"expiration\"] == \"2023-01-20 23:59:59\"]"
]
},
{
"cell_type": "markdown",
"id": "ef86ea45",
"metadata": {},
"source": [
"Filter out call options with low implied volatility"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f4fb5275",
"metadata": {},
"outputs": [],
"source": [
"filtered_calls_at_expiry = calls_at_expiry[calls_at_expiry.impliedVolatility >= 0.001]"
]
},
{
"cell_type": "markdown",
"id": "5cdc711d",
"metadata": {},
"source": [
"Plot implied volatility skew for call options expiring on 2023-01-20"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8a97578",
"metadata": {},
"outputs": [],
"source": [
"filtered_calls_at_expiry[[\"strike\", \"impliedVolatility\"]].set_index(\"strike\").plot(\n",
" title=\"Implied Volatility Skew\", figsize=(7, 4)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "96a47554",
"metadata": {},
"source": [
"Select options with a strike price of 400.0"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc10d700",
"metadata": {},
"outputs": [],
"source": [
"calls_at_strike = options[options[\"strike\"] == 400.0]"
]
},
{
"cell_type": "markdown",
"id": "e7f5080e",
"metadata": {},
"source": [
"Filter out options with low implied volatility"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "055393a8",
"metadata": {},
"outputs": [],
"source": [
"filtered_calls_at_strike = calls_at_strike[calls_at_strike.impliedVolatility >= 0.001]"
]
},
{
"cell_type": "markdown",
"id": "d06e635d",
"metadata": {},
"source": [
"Plot implied volatility term structure for options with a strike price of 400.0"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "85184a63",
"metadata": {},
"outputs": [],
"source": [
"filtered_calls_at_strike[[\"expiration\", \"impliedVolatility\"]].set_index(\"expiration\").plot(\n",
" title=\"Implied Volatility Term Structure\", figsize=(7, 4)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "439c1746",
"metadata": {},
"source": [
"Pivot the DataFrame to prepare data for a 3D surface plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f05c6eca",
"metadata": {},
"outputs": [],
"source": [
"surface = (\n",
" calls[['daysToExpiration', 'strike', 'impliedVolatility']]\n",
" .pivot_table(values='impliedVolatility', index='strike', columns='daysToExpiration')\n",
" .dropna()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "35a8ce2a",
"metadata": {},
"source": [
"Create a figure object for the 3D plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aaece560",
"metadata": {},
"outputs": [],
"source": [
"fig = plt.figure(figsize=(10, 8))"
]
},
{
"cell_type": "markdown",
"id": "e817706c",
"metadata": {},
"source": [
"Add a 3D subplot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c859d74c",
"metadata": {},
"outputs": [],
"source": [
"ax = fig.add_subplot(111, projection='3d')"
]
},
{
"cell_type": "markdown",
"id": "51cff93c",
"metadata": {},
"source": [
"Prepare coordinate matrices for the 3D plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a3cf2cc",
"metadata": {},
"outputs": [],
"source": [
"x, y, z = surface.columns.values, surface.index.values, surface.values\n",
"X, Y = np.meshgrid(x, y)"
]
},
{
"cell_type": "markdown",
"id": "f3983db1",
"metadata": {},
"source": [
"Set axis labels and title"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d2be8ca5",
"metadata": {},
"outputs": [],
"source": [
"ax.set_xlabel('Days to expiration')\n",
"ax.set_ylabel('Strike price')\n",
"ax.set_zlabel('Implied volatility')\n",
"ax.set_title('Call implied volatility surface')"
]
},
{
"cell_type": "markdown",
"id": "774bbcb1",
"metadata": {},
"source": [
"Plot the 3D surface of implied volatility"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d281abb",
"metadata": {},
"outputs": [],
"source": [
"ax.plot_surface(X, Y, z)"
]
},
{
"cell_type": "markdown",
"id": "a65a16aa",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,383 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "5f2ea2cb",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "ab6f458d",
"metadata": {},
"source": [
"This code analyzes the relationship between Tesla (TSLA) and S&P 500 (SPY) returns using linear regression. It downloads historical price data for TSLA and SPY, calculates daily returns, and plots them. A linear regression model is then used to estimate the alpha and beta of TSLA in relation to SPY. The code also constructs and evaluates a beta-hedged portfolio combining TSLA and SPY returns."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4e952311",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from statsmodels import regression\n",
"import statsmodels.api as sm\n",
"import matplotlib.pyplot as plt\n",
"import yfinance as yf"
]
},
{
"cell_type": "markdown",
"id": "60b5a085",
"metadata": {},
"source": [
"Download historical price data for TSLA and SPY from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c84671a0",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(\"TSLA, SPY\", start=\"2014-01-01\", end=\"2015-01-01\")"
]
},
{
"cell_type": "markdown",
"id": "81dc42f6",
"metadata": {},
"source": [
"Extract adjusted close prices for TSLA and SPY"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bd0ff214",
"metadata": {},
"outputs": [],
"source": [
"asset = data[\"Adj Close\"].TSLA\n",
"benchmark = data[\"Adj Close\"].SPY"
]
},
{
"cell_type": "markdown",
"id": "b77d7257",
"metadata": {},
"source": [
"Calculate daily returns for TSLA and SPY and drop any missing values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8c2ec45f",
"metadata": {},
"outputs": [],
"source": [
"asset_returns = asset.pct_change().dropna()\n",
"benchmark_returns = benchmark.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "4b54ff94",
"metadata": {},
"source": [
"Plot daily returns for TSLA and SPY"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9da3ec4c",
"metadata": {},
"outputs": [],
"source": [
"asset_returns.plot()\n",
"benchmark_returns.plot()\n",
"plt.ylabel(\"Daily Return\")\n",
"plt.legend()"
]
},
{
"cell_type": "markdown",
"id": "6864d36d",
"metadata": {},
"source": [
"Extract daily returns values for linear regression analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "23a6712a",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"X = benchmark_returns.values\n",
"Y = asset_returns.values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "04e42249",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def linreg(x, y):\n",
" \"\"\"Perform linear regression on two arrays.\n",
" \n",
" This function adds a constant to x, performs linear regression, \n",
" and returns the alpha and beta coefficients.\n",
" \n",
" Parameters\n",
" ----------\n",
" x : np.ndarray\n",
" The independent variable (e.g., benchmark returns).\n",
" y : np.ndarray\n",
" The dependent variable (e.g., asset returns).\n",
" \n",
" Returns\n",
" -------\n",
" alpha : float\n",
" The intercept of the regression line.\n",
" beta : float\n",
" The slope of the regression line.\n",
" \"\"\"\n",
"\n",
" # Add a column of 1s to fit alpha\n",
" x = sm.add_constant(x)\n",
"\n",
" # Fit the OLS regression model to the data\n",
" model = regression.linear_model.OLS(y, x).fit()\n",
"\n",
" # Remove the constant now that we're done\n",
" x = x[:, 1]\n",
"\n",
" return model.params[0], model.params[1]"
]
},
{
"cell_type": "markdown",
"id": "789ba4a8",
"metadata": {},
"source": [
"Calculate alpha and beta for TSLA relative to SPY using linear regression"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ffd2463d",
"metadata": {},
"outputs": [],
"source": [
"alpha, beta = linreg(X, Y)\n",
"print(f\"Alpha: {alpha}\")\n",
"print(f\"Beta: {beta}\")"
]
},
{
"cell_type": "markdown",
"id": "4a34f58e",
"metadata": {},
"source": [
"Generate a range of X values for plotting the regression line"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "70e5fc38",
"metadata": {},
"outputs": [],
"source": [
"X2 = np.linspace(X.min(), X.max(), 100)"
]
},
{
"cell_type": "markdown",
"id": "cac07ff2",
"metadata": {},
"source": [
"Calculate the predicted Y values (regression line) for the generated X values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "523900e1",
"metadata": {},
"outputs": [],
"source": [
"Y_hat = X2 * beta + alpha"
]
},
{
"cell_type": "markdown",
"id": "2d92649d",
"metadata": {},
"source": [
"Plot the raw data points (TSLA vs. SPY daily returns)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "79d7840d",
"metadata": {},
"outputs": [],
"source": [
"plt.scatter(X, Y, alpha=0.3)\n",
"plt.xlabel(\"SPY Daily Return\")\n",
"plt.ylabel(\"TSLA Daily Return\")"
]
},
{
"cell_type": "markdown",
"id": "90e244dc",
"metadata": {},
"source": [
"Plot the regression line on the scatter plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6ce9f245",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(X2, Y_hat, 'r', alpha=0.9);"
]
},
{
"cell_type": "markdown",
"id": "7cf8a6a8",
"metadata": {},
"source": [
"Construct a beta-hedged portfolio by combining TSLA and SPY returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ada8b88c",
"metadata": {},
"outputs": [],
"source": [
"portfolio = -1 * beta * benchmark_returns + asset_returns\n",
"portfolio.name = \"TSLA + Hedge\""
]
},
{
"cell_type": "markdown",
"id": "ad5ee872",
"metadata": {},
"source": [
"Plot the returns of the beta-hedged portfolio, TSLA, and SPY"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "163b687b",
"metadata": {},
"outputs": [],
"source": [
"portfolio.plot(alpha=0.9)\n",
"benchmark_returns.plot(alpha=0.5)\n",
"asset_returns.plot(alpha=0.5)\n",
"plt.ylabel(\"Daily Return\")\n",
"plt.legend();"
]
},
{
"cell_type": "markdown",
"id": "d8eb7cbd",
"metadata": {},
"source": [
"Print the mean and standard deviation of the beta-hedged portfolio and TSLA returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "99d31898",
"metadata": {},
"outputs": [],
"source": [
"print(\"means: \", portfolio.mean(), asset_returns.mean())\n",
"print(\"volatilities: \", portfolio.std(), asset_returns.std())"
]
},
{
"cell_type": "markdown",
"id": "cab17da9",
"metadata": {},
"source": [
"Extract portfolio values for further linear regression analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "81a44668",
"metadata": {},
"outputs": [],
"source": [
"P = portfolio.values"
]
},
{
"cell_type": "markdown",
"id": "a926759c",
"metadata": {},
"source": [
"Calculate alpha and beta for the beta-hedged portfolio using linear regression"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "383c99d8",
"metadata": {},
"outputs": [],
"source": [
"alpha, beta = linreg(X, P)\n",
"print('alpha: ' + str(alpha))\n",
"print('beta: ' + str(beta))"
]
},
{
"cell_type": "markdown",
"id": "3fdca098",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,697 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "7dc325ea",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "7fbdbb7d",
"metadata": {},
"source": [
"This code analyzes the cointegration of stock pairs using historical price data. It downloads stock prices, finds cointegrated pairs, and calculates their spread. Cointegration indicates a stable, long-term relationship between stock pairs, useful for statistical arbitrage. Visualization tools like heatmaps and rolling z-scores help to identify trading signals. The code is practical in pairs trading strategies and quantitative finance."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66c329f2",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "46e13192",
"metadata": {},
"outputs": [],
"source": [
"import statsmodels.api as sm\n",
"from statsmodels.tsa.stattools import coint\n",
"from statsmodels.regression.rolling import RollingOLS"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ace9406d",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf\n",
"import seaborn\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"id": "fef80814",
"metadata": {},
"source": [
"Define the list of stock symbols to analyze"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3656066",
"metadata": {},
"outputs": [],
"source": [
"symbol_list = ['meta', 'amzn', 'aapl', 'nflx', 'goog']"
]
},
{
"cell_type": "markdown",
"id": "aea87a6c",
"metadata": {},
"source": [
"Download historical adjusted closing prices for the specified symbols"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30fcec98",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"data = yf.download(symbol_list, start='2014-01-01', end='2015-01-01')['Adj Close']"
]
},
{
"cell_type": "markdown",
"id": "060ae221",
"metadata": {},
"source": [
"Define a function to find cointegrated pairs of stocks"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "56b50383",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def find_cointegrated_pairs(data):\n",
" \"\"\"Find cointegrated stock pairs\n",
"\n",
" This function calculates cointegration scores and p-values for stock pairs.\n",
"\n",
" Parameters\n",
" ----------\n",
" data : pd.DataFrame\n",
" DataFrame of stock prices with stocks as columns.\n",
"\n",
" Returns\n",
" -------\n",
" score_matrix : np.ndarray\n",
" Matrix of cointegration scores.\n",
" pvalue_matrix : np.ndarray\n",
" Matrix of p-values for cointegration tests.\n",
" pairs : list\n",
" List of cointegrated stock pairs.\n",
" \"\"\"\n",
" \n",
" # Initialize matrices for scores and p-values, and a list for pairs\n",
" n = data.shape[1]\n",
" score_matrix = np.zeros((n, n))\n",
" pvalue_matrix = np.ones((n, n))\n",
" keys = data.keys()\n",
" pairs = []\n",
"\n",
" # Loop over combinations of stock pairs to test for cointegration\n",
" for i in range(n):\n",
" for j in range(i + 1, n):\n",
" S1 = data[keys[i]]\n",
" S2 = data[keys[j]]\n",
" result = coint(S1, S2)\n",
" score = result[0]\n",
" pvalue = result[1]\n",
" score_matrix[i, j] = score\n",
" pvalue_matrix[i, j] = pvalue\n",
" \n",
" # Add pair to list if p-value is less than 0.05\n",
" if pvalue < 0.05:\n",
" pairs.append((keys[i], keys[j]))\n",
" \n",
" return score_matrix, pvalue_matrix, pairs"
]
},
{
"cell_type": "markdown",
"id": "c154e226",
"metadata": {},
"source": [
"Find cointegrated pairs and store scores, p-values, and pairs"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "efff3232",
"metadata": {},
"outputs": [],
"source": [
"scores, pvalues, pairs = find_cointegrated_pairs(data)"
]
},
{
"cell_type": "markdown",
"id": "44112349",
"metadata": {},
"source": [
"Visualize the p-value matrix as a heatmap"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "288ff7d1",
"metadata": {},
"outputs": [],
"source": [
"seaborn.heatmap(\n",
" pvalues, \n",
" xticklabels=symbol_list, \n",
" yticklabels=symbol_list, \n",
" cmap='RdYlGn_r', \n",
" mask=(pvalues >= 0.10)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "65dabf09",
"metadata": {},
"source": [
"Select two stocks, Amazon (AMZN) and Apple (AAPL), for further analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2c41310c",
"metadata": {},
"outputs": [],
"source": [
"S1 = data.AMZN\n",
"S2 = data.AAPL"
]
},
{
"cell_type": "markdown",
"id": "5edd06ec",
"metadata": {},
"source": [
"Perform a cointegration test on the selected pair"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "83fc4b52",
"metadata": {},
"outputs": [],
"source": [
"score, pvalue, _ = coint(S1, S2)"
]
},
{
"cell_type": "markdown",
"id": "1ad95d73",
"metadata": {},
"source": [
"Print the p-value of the cointegration test"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "240c1103",
"metadata": {},
"outputs": [],
"source": [
"pvalue"
]
},
{
"cell_type": "markdown",
"id": "51882f40",
"metadata": {},
"source": [
"Add a constant term to the Amazon stock prices for regression analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4bf6bfd7",
"metadata": {},
"outputs": [],
"source": [
"S1 = sm.add_constant(S1)"
]
},
{
"cell_type": "markdown",
"id": "fa8668d3",
"metadata": {},
"source": [
"Fit an Ordinary Least Squares (OLS) regression model using Apple as the dependent variable"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fbd88ec3",
"metadata": {},
"outputs": [],
"source": [
"results = sm.OLS(S2, S1).fit()"
]
},
{
"cell_type": "markdown",
"id": "e8d11ac1",
"metadata": {},
"source": [
"Remove the constant term from Amazon stock prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d1de3f7",
"metadata": {},
"outputs": [],
"source": [
"S1 = S1.AMZN"
]
},
{
"cell_type": "markdown",
"id": "a38f4599",
"metadata": {},
"source": [
"Extract the regression coefficient (beta) for Amazon"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "03b56755",
"metadata": {},
"outputs": [],
"source": [
"b = results.params['AMZN']"
]
},
{
"cell_type": "markdown",
"id": "0199e5b8",
"metadata": {},
"source": [
"Calculate the spread between Apple and the beta-adjusted Amazon prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "67d972a7",
"metadata": {},
"outputs": [],
"source": [
"spread = S2 - b * S1"
]
},
{
"cell_type": "markdown",
"id": "da2513be",
"metadata": {},
"source": [
"Plot the spread and its mean value"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "29b6df5f",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"spread.plot()\n",
"plt.axhline(spread.mean(), color='black')\n",
"plt.legend(['Spread'])"
]
},
{
"cell_type": "markdown",
"id": "c0cc2832",
"metadata": {},
"source": [
"Define a function to calculate the z-score of a series"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "56b5ca81",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def zscore(series):\n",
" \"\"\"Calculate z-score of a series\n",
"\n",
" This function returns the z-score for a given series.\n",
"\n",
" Parameters\n",
" ----------\n",
" series : pd.Series\n",
" A pandas Series for which to calculate z-score.\n",
"\n",
" Returns\n",
" -------\n",
" zscore : pd.Series\n",
" Z-score of the input series.\n",
" \"\"\"\n",
" \n",
" return (series - series.mean()) / np.std(series)"
]
},
{
"cell_type": "markdown",
"id": "ef4d6e5c",
"metadata": {},
"source": [
"Plot the z-score of the spread with mean and threshold lines"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e28d66d0",
"metadata": {},
"outputs": [],
"source": [
"zscore(spread).plot()\n",
"plt.axhline(zscore(spread).mean(), color='black')\n",
"plt.axhline(1.0, color='red', linestyle='--')\n",
"plt.axhline(-1.0, color='green', linestyle='--')\n",
"plt.legend(['Spread z-score', 'Mean', '+1', '-1'])"
]
},
{
"cell_type": "markdown",
"id": "daf80128",
"metadata": {},
"source": [
"Create a DataFrame with the signal and position size in the pair"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e3b8c8f",
"metadata": {},
"outputs": [],
"source": [
"trades = pd.concat([zscore(spread), S2 - b * S1], axis=1)\n",
"trades.columns = [\"signal\", \"position\"]"
]
},
{
"cell_type": "markdown",
"id": "82f2b445",
"metadata": {},
"source": [
"Add long and short positions based on z-score thresholds"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "102994fc",
"metadata": {},
"outputs": [],
"source": [
"trades[\"side\"] = 0.0\n",
"trades.loc[trades.signal <= -1, \"side\"] = 1\n",
"trades.loc[trades.signal >= 1, \"side\"] = -1"
]
},
{
"cell_type": "markdown",
"id": "20667ab3",
"metadata": {},
"source": [
"Calculate and plot cumulative returns from the trading strategy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "202ffdea",
"metadata": {},
"outputs": [],
"source": [
"returns = trades.position.pct_change() * trades.side\n",
"returns.cumsum().plot()"
]
},
{
"cell_type": "markdown",
"id": "ade9d6ff",
"metadata": {},
"source": [
"Print the trades DataFrame for inspection"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a176d604",
"metadata": {},
"outputs": [],
"source": [
"trades"
]
},
{
"cell_type": "markdown",
"id": "d7180626",
"metadata": {},
"source": [
"Calculate the spread using a rolling OLS model with a 30-day window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c7690187",
"metadata": {},
"outputs": [],
"source": [
"model = RollingOLS(endog=S1, exog=S2, window=30)\n",
"rres = model.fit()\n",
"spread = S2 - rres.params.AAPL * S1\n",
"spread.name = 'spread'"
]
},
{
"cell_type": "markdown",
"id": "9b5f37bb",
"metadata": {},
"source": [
"Calculate 1-day and 30-day moving averages of the spread"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f64b6a8c",
"metadata": {},
"outputs": [],
"source": [
"spread_mavg1 = spread.rolling(1).mean()\n",
"spread_mavg1.name = 'spread 1d mavg'\n",
"spread_mavg30 = spread.rolling(30).mean()\n",
"spread_mavg30.name = 'spread 30d mavg'"
]
},
{
"cell_type": "markdown",
"id": "34db999b",
"metadata": {},
"source": [
"Plot the 1-day and 30-day moving averages of the spread"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "847c6226",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(spread_mavg1.index, spread_mavg1.values)\n",
"plt.plot(spread_mavg30.index, spread_mavg30.values)\n",
"plt.legend(['1 Day Spread MAVG', '30 Day Spread MAVG'])\n",
"plt.ylabel('Spread')"
]
},
{
"cell_type": "markdown",
"id": "f45c9783",
"metadata": {},
"source": [
"Calculate the rolling 30-day standard deviation of the spread"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c4235ea2",
"metadata": {},
"outputs": [],
"source": [
"std_30 = spread.rolling(30).std()\n",
"std_30.name = 'std 30d'"
]
},
{
"cell_type": "markdown",
"id": "63186967",
"metadata": {},
"source": [
"Compute and plot the z-score of the spread using 30-day moving averages and standard deviation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3f5ed69",
"metadata": {},
"outputs": [],
"source": [
"zscore_30_1 = (spread_mavg1 - spread_mavg30) / std_30\n",
"zscore_30_1.name = 'z-score'\n",
"zscore_30_1.plot()\n",
"plt.axhline(0, color='black')\n",
"plt.axhline(1.0, color='red', linestyle='--')"
]
},
{
"cell_type": "markdown",
"id": "2639cbd5",
"metadata": {},
"source": [
"Plot the scaled stock prices and the rolling z-score for comparison"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d976a6a",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(S1.index, S1.values / 10)\n",
"plt.plot(S2.index, S2.values / 10)\n",
"plt.plot(zscore_30_1.index, zscore_30_1.values)\n",
"plt.legend(['S1 Price / 10', 'S2 Price / 10', 'Price Spread Rolling z-Score'])"
]
},
{
"cell_type": "markdown",
"id": "3e889d9b",
"metadata": {},
"source": [
"Update the symbol list and download new data for another analysis period"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de1483ff",
"metadata": {},
"outputs": [],
"source": [
"symbol_list = ['amzn', 'aapl']\n",
"data = yf.download(symbol_list, start='2015-01-01', end='2016-01-01')['Adj Close']"
]
},
{
"cell_type": "markdown",
"id": "64d819f3",
"metadata": {},
"source": [
"Select Amazon (AMZN) and Apple (AAPL) prices from the new data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4ae4d2ee",
"metadata": {},
"outputs": [],
"source": [
"S1 = data.AMZN\n",
"S2 = data.AAPL"
]
},
{
"cell_type": "markdown",
"id": "d48f9db4",
"metadata": {},
"source": [
"Perform a cointegration test on the new data and print the p-value"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1f80f3d8",
"metadata": {},
"outputs": [],
"source": [
"score, pvalue, _ = coint(S1, S2)\n",
"print(pvalue)"
]
},
{
"cell_type": "markdown",
"id": "c0e7a627",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,351 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "301963b1",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "667b85b6",
"metadata": {},
"source": [
"This code performs Principal Component Analysis (PCA) on a portfolio of stocks to identify principal components driving the returns. It retrieves historical stock data, calculates daily returns, and applies PCA to these returns. The explained variance and principal components are visualized, and the factor returns and exposures are computed. These statistical risk factors help in understanding how much of the portfolio's returns arise from unobservable features. This is useful for portfolio management, risk assessment, and diversification analysis."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "71d0282c",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "746b91c7",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"from sklearn.decomposition import PCA\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"id": "2863a52a",
"metadata": {},
"source": [
"Define a list of stock symbols to retrieve historical data for"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4f98bfc4",
"metadata": {},
"outputs": [],
"source": [
"symbols = [\n",
" 'IBM',\n",
" 'MSFT',\n",
" 'META',\n",
" 'INTC',\n",
" 'NEM',\n",
" 'AU',\n",
" 'AEM',\n",
" 'GFI'\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "02e922c9",
"metadata": {},
"source": [
"Download historical adjusted close prices for the defined stock symbols within the specified date range"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a563405d",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(symbols, start=\"2020-01-01\", end=\"2022-11-30\")"
]
},
{
"cell_type": "markdown",
"id": "1f52f786",
"metadata": {},
"source": [
"Calculate daily returns for the portfolio by computing percentage change and dropping NaN values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b26bb2f",
"metadata": {},
"outputs": [],
"source": [
"portfolio_returns = data['Adj Close'].pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "d6a1198c",
"metadata": {},
"source": [
"Apply Principal Component Analysis (PCA) to the portfolio returns to identify key components"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a9c1ac8b",
"metadata": {},
"outputs": [],
"source": [
"pca = PCA(n_components=3)\n",
"pca.fit(portfolio_returns)"
]
},
{
"cell_type": "markdown",
"id": "7dea06ae",
"metadata": {},
"source": [
"Retrieve the explained variance ratio and the principal components from the PCA model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ed0fced",
"metadata": {},
"outputs": [],
"source": [
"pct = pca.explained_variance_ratio_\n",
"pca_components = pca.components_"
]
},
{
"cell_type": "markdown",
"id": "91fe5bfd",
"metadata": {},
"source": [
"Calculate the cumulative explained variance for visualization and create an array for component indices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4b40fb20",
"metadata": {},
"outputs": [],
"source": [
"cum_pct = np.cumsum(pct)\n",
"x = np.arange(1, len(pct) + 1, 1)"
]
},
{
"cell_type": "markdown",
"id": "a58e88c7",
"metadata": {},
"source": [
"Plot the percentage contribution of each principal component and the cumulative contribution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "184bb55c",
"metadata": {},
"outputs": [],
"source": [
"plt.subplot(1, 2, 1)\n",
"plt.bar(x, pct * 100, align=\"center\")\n",
"plt.title('Contribution (%)')\n",
"plt.xlabel('Component')\n",
"plt.xticks(x)\n",
"plt.xlim([0, 4])\n",
"plt.ylim([0, 100])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9fbd347d",
"metadata": {},
"outputs": [],
"source": [
"plt.subplot(1, 2, 2)\n",
"plt.plot(x, cum_pct * 100, 'ro-')\n",
"plt.title('Cumulative contribution (%)')\n",
"plt.xlabel('Component')\n",
"plt.xticks(x)\n",
"plt.xlim([0, 4])\n",
"plt.ylim([0, 100])"
]
},
{
"cell_type": "markdown",
"id": "5b0a4722",
"metadata": {},
"source": [
"Construct statistical risk factors using the principal components and portfolio returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "027a88cf",
"metadata": {},
"outputs": [],
"source": [
"X = np.asarray(portfolio_returns)\n",
"factor_returns = X.dot(pca_components.T)\n",
"factor_returns = pd.DataFrame(\n",
" columns=[\"f1\", \"f2\", \"f3\"], \n",
" index=portfolio_returns.index,\n",
" data=factor_returns\n",
")"
]
},
{
"cell_type": "markdown",
"id": "3ba5fc5f",
"metadata": {},
"source": [
"Display the first few rows of the factor returns DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f7ee295",
"metadata": {},
"outputs": [],
"source": [
"factor_returns.head()"
]
},
{
"cell_type": "markdown",
"id": "ae7022ed",
"metadata": {},
"source": [
"Calculate and display the factor exposures by transposing the principal components matrix"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "69a1609b",
"metadata": {},
"outputs": [],
"source": [
"factor_exposures = pd.DataFrame(\n",
" index=[\"f1\", \"f2\", \"f3\"], \n",
" columns=portfolio_returns.columns,\n",
" data=pca_components\n",
").T"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "406e4727",
"metadata": {},
"outputs": [],
"source": [
"factor_exposures"
]
},
{
"cell_type": "markdown",
"id": "73a266c3",
"metadata": {},
"source": [
"Sort and plot the factor exposures for the first principal component (f1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ff5ca2a",
"metadata": {},
"outputs": [],
"source": [
"factor_exposures.f1.sort_values().plot.bar()"
]
},
{
"cell_type": "markdown",
"id": "65c58ba2",
"metadata": {},
"source": [
"Scatter plot to visualize factor exposures of the first two principal components (f1 and f2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6fd70496",
"metadata": {},
"outputs": [],
"source": [
"labels = factor_exposures.index\n",
"data = factor_exposures.values\n",
"plt.scatter(data[:, 0], data[:, 1])\n",
"plt.xlabel('factor exposure of PC1')\n",
"plt.ylabel('factor exposure of PC2')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "789e384f",
"metadata": {},
"outputs": [],
"source": [
"for label, x, y in zip(labels, data[:, 0], data[:, 1]):\n",
" plt.annotate(\n",
" label,\n",
" xy=(x, y), \n",
" xytext=(-20, 20),\n",
" textcoords='offset points',\n",
" arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0')\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "425b0e57",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,595 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "431b9977",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "937cf4f7",
"metadata": {},
"source": [
"This code calculates Value at Risk (VaR) and Conditional Value at Risk (CVaR) for a portfolio of stocks. It uses historical stock data to compute returns, then scales these returns to simulate a portfolio's performance. It defines functions to calculate VaR and CVaR, both of which are measures of potential losses in a portfolio. The code also generates visualizations of these risk metrics to aid in financial risk assessment. This is practical for portfolio management and risk analysis in finance."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5b1162c9",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import yfinance as yf"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3d76629",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"id": "b8b5d355",
"metadata": {},
"source": [
"Define a list of stock tickers representing the portfolio components"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9fd6d834",
"metadata": {},
"outputs": [],
"source": [
"oex = ['MMM','T','ABBV','ABT','ACN','ALL','GOOGL','GOOG','MO','AMZN','AXP','AIG','AMGN','AAPL','BAC',\n",
" 'BRK-B','BIIB','BLK','BA','BMY','CVS','COF','CAT','CVX','CSCO','C','KO','CL','CMCSA',\n",
" 'COP','DHR','DUK','DD','EMC','EMR','EXC','XOM','META','FDX','F','GD','GE','GM','GILD',\n",
" 'GS','HAL','HD','HON','INTC','IBM','JPM','JNJ','KMI','LLY','LMT','LOW','MA','MCD','MDT','MRK',\n",
" 'MET','MSFT','MS','NKE','NEE','OXY','ORCL','PYPL','PEP','PFE','PM','PG','QCOM',\n",
" 'SLB','SPG','SO','SBUX','TGT','TXN','BK','USB','UNP','UPS','UNH','VZ','V','WMT',\n",
" 'WBA','DIS','WFC']"
]
},
{
"cell_type": "markdown",
"id": "2e00c960",
"metadata": {},
"source": [
"Count the number of stocks in the portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5583926",
"metadata": {},
"outputs": [],
"source": [
"num_stocks = len(oex)"
]
},
{
"cell_type": "markdown",
"id": "37a412ad",
"metadata": {},
"source": [
"Download historical stock data for the defined period"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c794c59a",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(oex, start='2014-01-01', end='2016-04-04')"
]
},
{
"cell_type": "markdown",
"id": "13db52ed",
"metadata": {},
"source": [
"Calculate daily returns and de-mean the returns by subtracting the mean"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c61098bd",
"metadata": {},
"outputs": [],
"source": [
"returns = data['Adj Close'].pct_change()\n",
"returns = returns - returns.mean(skipna=True)"
]
},
{
"cell_type": "markdown",
"id": "ec932e56",
"metadata": {},
"source": [
"Plot the de-meaned returns for visualization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f2d5ad90",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"returns.plot(legend=None)"
]
},
{
"cell_type": "markdown",
"id": "38d7b1de",
"metadata": {},
"source": [
"Define a function to scale weights so their absolute values sum to one"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "31ffb424",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def scale(x):\n",
" return x / np.sum(np.abs(x))"
]
},
{
"cell_type": "markdown",
"id": "87766d95",
"metadata": {},
"source": [
"Generate random weights for the portfolio and scale them"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0884a511",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"weights = scale(np.random.random(num_stocks))\n",
"plt.bar(np.arange(num_stocks), weights)"
]
},
{
"cell_type": "markdown",
"id": "1f97cb07",
"metadata": {},
"source": [
"Define a function to calculate Value at Risk (VaR)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5bccf1c9",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def value_at_risk(value_invested, returns, weights, alpha=0.95, lookback_days=500):\n",
" \"\"\"Calculates Value at Risk (VaR) for a portfolio\n",
" \n",
" Parameters\n",
" ----------\n",
" value_invested : float\n",
" Total value of the portfolio\n",
" returns : pd.DataFrame\n",
" Historical returns of the portfolio components\n",
" weights : np.ndarray\n",
" Weights of each asset in the portfolio\n",
" alpha : float, optional\n",
" Confidence level for VaR, by default 0.95\n",
" lookback_days : int, optional\n",
" Number of days to look back for historical data, by default 500\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" The Value at Risk (VaR) at the specified confidence level\n",
" \"\"\"\n",
" \n",
" # Fill missing values in returns with zero and calculate portfolio returns\n",
"\n",
" returns = returns.fillna(0.0)\n",
" portfolio_returns = returns.iloc[-lookback_days:].dot(weights)\n",
"\n",
" # Calculate the VaR as the percentile of portfolio returns\n",
"\n",
" return np.percentile(portfolio_returns, 100 * (1 - alpha)) * value_invested"
]
},
{
"cell_type": "markdown",
"id": "700ce7bd",
"metadata": {},
"source": [
"Define the total value invested in the portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b087d682",
"metadata": {},
"outputs": [],
"source": [
"value_invested = 1000000"
]
},
{
"cell_type": "markdown",
"id": "926f99ac",
"metadata": {},
"source": [
"Calculate Value at Risk (VaR) using the defined function"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b0d5a7f9",
"metadata": {},
"outputs": [],
"source": [
"value_at_risk(value_invested, returns, weights, alpha=0.95, lookback_days=520)"
]
},
{
"cell_type": "markdown",
"id": "c6bc2883",
"metadata": {},
"source": [
"Define parameters for lookback days and confidence level"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5e9379f3",
"metadata": {},
"outputs": [],
"source": [
"lookback_days = 500\n",
"alpha = 0.95"
]
},
{
"cell_type": "markdown",
"id": "260bc52d",
"metadata": {},
"source": [
"Calculate portfolio returns using historical data and weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2396b2f1",
"metadata": {},
"outputs": [],
"source": [
"portfolio_returns = returns.fillna(0.0).iloc[-lookback_days:].dot(weights)"
]
},
{
"cell_type": "markdown",
"id": "79001b24",
"metadata": {},
"source": [
"Calculate VaR and express it as a return rather than absolute loss"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "64973e13",
"metadata": {},
"outputs": [],
"source": [
"portfolio_VaR = value_at_risk(value_invested, returns, weights, alpha=0.95)\n",
"portfolio_VaR_return = portfolio_VaR / value_invested"
]
},
{
"cell_type": "markdown",
"id": "43704fbe",
"metadata": {},
"source": [
"Plot histogram of portfolio returns and mark the VaR on the plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bd62e764",
"metadata": {},
"outputs": [],
"source": [
"plt.hist(portfolio_returns, bins=30)\n",
"plt.axvline(portfolio_VaR_return, color='red', linestyle='solid')\n",
"plt.legend(['VaR', 'Returns'])\n",
"plt.title('Historical VaR')\n",
"plt.xlabel('Return')\n",
"plt.ylabel('Observation Frequency')"
]
},
{
"cell_type": "markdown",
"id": "376798ce",
"metadata": {},
"source": [
"Define the number of iterations for VaR calculation and initialize an array"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c6eecfc0",
"metadata": {},
"outputs": [],
"source": [
"N = 1000\n",
"VaRs = np.zeros((N, 1))"
]
},
{
"cell_type": "markdown",
"id": "9d5cb851",
"metadata": {},
"source": [
"Iterate to calculate VaR over different lookback windows"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58bf2dfe",
"metadata": {},
"outputs": [],
"source": [
"for i in range(N):\n",
" VaRs[i] = value_at_risk(value_invested, returns, weights, lookback_days=i + 1)"
]
},
{
"cell_type": "markdown",
"id": "aadb983c",
"metadata": {},
"source": [
"Plot the VaR values over different lookback windows"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58715c8f",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"plt.plot(VaRs)\n",
"plt.xlabel('Lookback Window')\n",
"plt.ylabel('VaR')"
]
},
{
"cell_type": "markdown",
"id": "9d78afc3",
"metadata": {},
"source": [
"Define a function to calculate Conditional Value at Risk (CVaR)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c9debae5",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def cvar(value_invested, returns, weights, alpha=0.95, lookback_days=500):\n",
" \"\"\"Calculates Conditional Value at Risk (CVaR) for a portfolio\n",
" \n",
" Parameters\n",
" ----------\n",
" value_invested : float\n",
" Total value of the portfolio\n",
" returns : pd.DataFrame\n",
" Historical returns of the portfolio components\n",
" weights : np.ndarray\n",
" Weights of each asset in the portfolio\n",
" alpha : float, optional\n",
" Confidence level for CVaR, by default 0.95\n",
" lookback_days : int, optional\n",
" Number of days to look back for historical data, by default 500\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" The Conditional Value at Risk (CVaR) at the specified confidence level\n",
" \"\"\"\n",
" \n",
" # Calculate VaR and portfolio returns for the specified lookback period\n",
"\n",
" var = value_at_risk(value_invested, returns, weights, alpha, lookback_days=lookback_days)\n",
" \n",
" returns = returns.fillna(0.0)\n",
" portfolio_returns = returns.iloc[-lookback_days:].dot(weights)\n",
" var_pct_loss = var / value_invested\n",
" \n",
" # Calculate the mean of returns below the VaR threshold\n",
"\n",
" return np.nanmean(portfolio_returns[portfolio_returns < var_pct_loss]) * value_invested"
]
},
{
"cell_type": "markdown",
"id": "3362b55d",
"metadata": {},
"source": [
"Calculate CVaR using the defined function"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8273f2f",
"metadata": {},
"outputs": [],
"source": [
"cvar(value_invested, returns, weights, lookback_days=500)"
]
},
{
"cell_type": "markdown",
"id": "64439069",
"metadata": {},
"source": [
"Calculate VaR again for consistency"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff33b141",
"metadata": {},
"outputs": [],
"source": [
"value_at_risk(value_invested, returns, weights, lookback_days=500)"
]
},
{
"cell_type": "markdown",
"id": "db4a0e67",
"metadata": {},
"source": [
"Calculate portfolio returns using historical data and weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2dc1b37c",
"metadata": {},
"outputs": [],
"source": [
"lookback_days = 500"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a2a13483",
"metadata": {},
"outputs": [],
"source": [
"portfolio_returns = returns.fillna(0.0).iloc[-lookback_days:].dot(weights)"
]
},
{
"cell_type": "markdown",
"id": "0ac89dd2",
"metadata": {},
"source": [
"Calculate VaR and CVaR and express them as returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36b35c9e",
"metadata": {},
"outputs": [],
"source": [
"portfolio_VaR = value_at_risk(value_invested, returns, weights)\n",
"portfolio_VaR_return = portfolio_VaR / value_invested"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8efa2126",
"metadata": {},
"outputs": [],
"source": [
"portfolio_CVaR = cvar(value_invested, returns, weights)\n",
"portfolio_CVaR_return = portfolio_CVaR / value_invested"
]
},
{
"cell_type": "markdown",
"id": "40bead66",
"metadata": {},
"source": [
"Plot histogram of portfolio returns, marking VaR and CVaR on the plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b4bc480d",
"metadata": {},
"outputs": [],
"source": [
"plt.hist(portfolio_returns[portfolio_returns > portfolio_VaR_return], bins=20)\n",
"plt.hist(portfolio_returns[portfolio_returns < portfolio_VaR_return], bins=10)\n",
"plt.axvline(portfolio_VaR_return, color='red', linestyle='solid')\n",
"plt.axvline(portfolio_CVaR_return, color='red', linestyle='dashed')\n",
"plt.legend(['VaR', 'CVaR', 'Returns', 'Returns < VaR'])\n",
"plt.title('Historical VaR and CVaR')\n",
"plt.xlabel('Return')\n",
"plt.ylabel('Observation Frequency')"
]
},
{
"cell_type": "markdown",
"id": "4b6c6fbc",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,290 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8bea070e",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "59b6b6e4",
"metadata": {},
"source": [
"This code performs a multifactor analysis on monthly stock returns, applying the Fama-French three-factor model for financial analysis. It fetches historical factor data, calculates active returns of selected stocks, and estimates their sensitivities to the Fama-French factors. The code also performs rolling regression to analyze the stability of factor exposures over time. Lastly, it calculates and prints the marginal contributions to risk from each factor."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "84290a88",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ec2de24",
"metadata": {},
"outputs": [],
"source": [
"import pandas_datareader as pdr\n",
"import yfinance as yf"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06d39571",
"metadata": {},
"outputs": [],
"source": [
"import statsmodels.api as sm\n",
"from statsmodels import regression\n",
"from statsmodels.regression.rolling import RollingOLS"
]
},
{
"cell_type": "markdown",
"id": "bc2a7be0",
"metadata": {},
"source": [
"Fetch Fama-French factors data starting from 2000-01-01 and select the SMB and HML factors"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5aff0b52",
"metadata": {},
"outputs": [],
"source": [
"factors = pdr.get_data_famafrench(\n",
" 'F-F_Research_Data_Factors',\n",
" start='2000-01-01'\n",
")[0][1:]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "65938e3e",
"metadata": {},
"outputs": [],
"source": [
"SMB = factors.SMB\n",
"HML = factors.HML"
]
},
{
"cell_type": "markdown",
"id": "8528b4ce",
"metadata": {},
"source": [
"Download monthly adjusted close prices for specified stocks starting from 2000-01-01"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8518efcf",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(\n",
" ['SPY', 'MSFT', 'AAPL', 'INTC'], \n",
" start=\"2000-01-01\", \n",
" interval=\"1mo\"\n",
")['Adj Close']"
]
},
{
"cell_type": "markdown",
"id": "439951e5",
"metadata": {},
"source": [
"Calculate the monthly returns and convert them to period-based returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9b300366",
"metadata": {},
"outputs": [],
"source": [
"monthly_returns = data.pct_change().to_period(\"M\")"
]
},
{
"cell_type": "markdown",
"id": "246d309b",
"metadata": {},
"source": [
"Extract the benchmark returns (SPY) and calculate active returns against the benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f71342e4",
"metadata": {},
"outputs": [],
"source": [
"bench = monthly_returns.pop(\"SPY\")\n",
"R = monthly_returns.mean(axis=1)\n",
"active = R - bench"
]
},
{
"cell_type": "markdown",
"id": "ff54a6cb",
"metadata": {},
"source": [
"Create a DataFrame with active returns and Fama-French factors SMB and HML"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8ed7a12",
"metadata": {},
"outputs": [],
"source": [
"df = pd.DataFrame({\n",
" 'R': active,\n",
" 'F1': SMB,\n",
" 'F2': HML,\n",
"}).dropna()"
]
},
{
"cell_type": "markdown",
"id": "3201423a",
"metadata": {},
"source": [
"Perform Ordinary Least Squares (OLS) regression to estimate sensitivities to the factors"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d5e1b15c",
"metadata": {},
"outputs": [],
"source": [
"b1, b2 = regression.linear_model.OLS(\n",
" df.R, \n",
" df[['F1', 'F2']]\n",
").fit().params"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f621be1f",
"metadata": {},
"outputs": [],
"source": [
"print(f'Sensitivities of active returns to factors:\\nSMB: {b1}\\nHML: {b2}')"
]
},
{
"cell_type": "markdown",
"id": "2eab25c3",
"metadata": {},
"source": [
"Perform rolling OLS regression to estimate how factor sensitivities change over time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c2489be4",
"metadata": {},
"outputs": [],
"source": [
"exog_vars = [\"SMB\", \"HML\"]\n",
"exog = sm.add_constant(factors[exog_vars])\n",
"rols = RollingOLS(df.R, exog, window=12)\n",
"rres = rols.fit()\n",
"fig = rres.plot_recursive_coefficient(variables=exog_vars)"
]
},
{
"cell_type": "markdown",
"id": "f4a1b059",
"metadata": {},
"source": [
"Calculate covariance between factors and marginal contributions to active risk (MCAR) for each factor"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ad0355a8",
"metadata": {},
"outputs": [],
"source": [
"F1 = df.F1\n",
"F2 = df.F2"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b0126e7",
"metadata": {},
"outputs": [],
"source": [
"cov = np.cov(F1, F2)\n",
"ar_squared = (active.std())**2\n",
"mcar1 = (b1 * (b2 * cov[0,1] + b1 * cov[0,0])) / ar_squared\n",
"mcar2 = (b2 * (b1 * cov[0,1] + b2 * cov[1,1])) / ar_squared\n",
"print(f'SMB risk contribution: {mcar1}')\n",
"print(f'HML risk contribution: {mcar2}')\n",
"print(f'Unexplained risk contribution: {1 - (mcar1 + mcar2)}')"
]
},
{
"cell_type": "markdown",
"id": "96df69b9",
"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
}

View File

@ -0,0 +1,188 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "120db895",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "98c6ae8b",
"metadata": {},
"source": [
"This code uses the Kalman Filter to smooth financial time series data and compare it to a simple moving average. It loads historical stock prices for a specified date range and computes the Kalman Filter estimate of the average price. It then plots the Kalman Filter estimate alongside the actual prices and a 30-day moving average. This is useful for financial analysis and modeling to reduce noise and detect underlying trends in stock prices."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d864e60",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7c52f92",
"metadata": {},
"outputs": [],
"source": [
"from pykalman import KalmanFilter\n",
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "5fa2307c",
"metadata": {},
"source": [
"Load historical stock prices for LMT between specified start and end dates"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "216b4c1a",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.stocks.load(\"LMT\", start_date=\"2013-01-01\", end_date=\"2015-01-01\")\n",
"prices = data[\"Adj Close\"]"
]
},
{
"cell_type": "markdown",
"id": "f8486b4c",
"metadata": {},
"source": [
"Initialize the Kalman Filter with specified parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a262938f",
"metadata": {},
"outputs": [],
"source": [
"kf = KalmanFilter(\n",
" transition_matrices = [1],\n",
" observation_matrices = [1],\n",
" initial_state_mean = 0,\n",
" initial_state_covariance = 1,\n",
" observation_covariance=1,\n",
" transition_covariance=0.01\n",
")"
]
},
{
"cell_type": "markdown",
"id": "84830040",
"metadata": {},
"source": [
"Apply Kalman Filter to the price data to estimate the average. The filter smooths the data to reduce noise and detect trends. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "733d4019",
"metadata": {},
"outputs": [],
"source": [
"state_means, _ = kf.filter(prices.values)\n",
"state_means = pd.Series(state_means.flatten(), index=prices.index)"
]
},
{
"cell_type": "markdown",
"id": "b3bc4dde",
"metadata": {},
"source": [
"Compute a 30-day rolling mean of the price data for comparison"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b96677e2",
"metadata": {},
"outputs": [],
"source": [
"mean30 = prices.rolling(window=30).mean()"
]
},
{
"cell_type": "markdown",
"id": "7cf2307d",
"metadata": {},
"source": [
"Plot the Kalman Filter estimate, actual prices, and 30-day moving average to visualize data smoothing and trend detection"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "42c88d04",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(state_means)\n",
"plt.plot(prices)\n",
"plt.plot(mean30)\n",
"plt.title('Kalman filter estimate of average')\n",
"plt.legend(['Kalman', 'Price', '30-day MA'])\n",
"plt.xlabel('Day')\n",
"plt.ylabel('Price')"
]
},
{
"cell_type": "markdown",
"id": "726482c3",
"metadata": {},
"source": [
"Plot the last 200 days of Kalman Filter estimate, actual prices, and 30-day moving average for a more detailed view"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "71660a32",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(state_means[-200:])\n",
"plt.plot(prices[-200:])\n",
"plt.plot(mean30[-200:])\n",
"plt.title('Kalman filter estimate of average')\n",
"plt.legend(['Kalman', 'Price', '30-day MA'])\n",
"plt.xlabel('Day')\n",
"plt.ylabel('Price')"
]
},
{
"cell_type": "markdown",
"id": "fa717712",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,220 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "cf1415df",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "b9fd5ee6",
"metadata": {},
"source": [
"This notebook analyzes the Hurst exponent of the S&P 500 index to measure market trends and randomness. It loads historical S&P 500 data using the OpenBB SDK and calculates the Hurst exponent for various time lags. The Hurst exponent helps in understanding the nature of time series, whether it is mean-reverting, trending, or a random walk. This information is valuable for financial analysts and quant traders for making informed decisions. Additionally, it plots rolling volatility to observe changes in market volatility over time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d89701d0",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c475ecc1",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "883862a7",
"metadata": {},
"source": [
"Load historical S&P 500 data from 2000 to 2019 using the OpenBB SDK and select the adjusted close prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "00be2ba2",
"metadata": {},
"outputs": [],
"source": [
"df = openbb.stocks.load(\"^GSPC\", start_date=\"2000-01-01\", end_date=\"2019-12-31\")[\"Adj Close\"]"
]
},
{
"cell_type": "markdown",
"id": "8dd5f150",
"metadata": {},
"source": [
"Plot the S&P 500 adjusted close prices to visualize the historical data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab7ba936",
"metadata": {},
"outputs": [],
"source": [
"df.plot(title=\"S&P 500\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9a9e6e7d",
"metadata": {},
"outputs": [],
"source": [
"def get_hurst_exponent(ts, max_lag=20):\n",
" \"\"\"Calculate the Hurst exponent of a time series\n",
" \n",
" Parameters\n",
" ----------\n",
" ts : np.ndarray\n",
" Time series data\n",
" max_lag : int, optional\n",
" Maximum lag to consider, by default 20\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" Estimated Hurst exponent\n",
" \n",
" Notes\n",
" -----\n",
" The Hurst exponent is used to determine the \n",
" long-term memory of time series data.\n",
" \"\"\"\n",
"\n",
" # Define the range of lags to be used in the calculation\n",
" lags = range(2, max_lag)\n",
"\n",
" # Calculate the standard deviation of differences for each lag\n",
" tau = [np.std(np.subtract(ts[lag:], ts[:-lag])) for lag in lags]\n",
"\n",
" # Perform a linear fit to estimate the Hurst exponent\n",
" return np.polyfit(np.log(lags), np.log(tau), 1)[0]"
]
},
{
"cell_type": "markdown",
"id": "fc132efc",
"metadata": {},
"source": [
"Calculate and print the Hurst exponent for various lags using the full dataset"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7a1872d6",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"for lag in [20, 100, 250, 500, 1000]:\n",
" hurst_exp = get_hurst_exponent(df.values, lag)\n",
" print(f\"{lag} lags: {hurst_exp:.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "d51dc23f",
"metadata": {},
"source": [
"Select a shorter series from 2005 to 2007 and calculate the Hurst exponent for various lags"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3cd6e2df",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"shorter_series = df.loc[\"2005\":\"2007\"].values\n",
"for lag in [20, 100, 250, 500]:\n",
" hurst_exp = get_hurst_exponent(shorter_series, lag)\n",
" print(f\"{lag} lags: {hurst_exp:.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "0a623fcf",
"metadata": {},
"source": [
"Calculate rolling volatility using a 30-day window and plot the results to observe changes over time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "100403dc",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"rv = df.rolling(30).apply(np.std)\n",
"rv.plot()"
]
},
{
"cell_type": "markdown",
"id": "a021555b",
"metadata": {},
"source": [
"Calculate and print the Hurst exponent for various lags using the rolling volatility data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2175669f",
"metadata": {},
"outputs": [],
"source": [
"for lag in [20, 100, 250, 500, 1000]:\n",
" hurst_exp = get_hurst_exponent(rv.dropna().values, lag)\n",
" print(f\"{lag} lags: {hurst_exp:.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "dfb39650",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,194 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f265ef79",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "d7770571",
"metadata": {},
"source": [
"This code retrieves and analyzes unemployment data, focusing on trends and seasonality. It uses the OpenBB SDK to fetch unemployment data from 2010 to 2019. The data is then processed to calculate rolling statistics and visualized. Seasonal decomposition and STL decomposition are applied to understand the seasonal and trend components. Additionally, the Hodrick-Prescott filter is used to separate the cyclical and trend components of the data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "60656fb6",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1436ce1d",
"metadata": {},
"outputs": [],
"source": [
"from statsmodels.tsa.seasonal import seasonal_decompose, STL\n",
"from statsmodels.tsa.filters.hp_filter import hpfilter\n",
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "566d605d",
"metadata": {},
"source": [
"Retrieve unemployment data from OpenBB SDK for the period starting 2010"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "df30c6a4",
"metadata": {},
"outputs": [],
"source": [
"df = openbb.economy.unemp(2010)"
]
},
{
"cell_type": "markdown",
"id": "e7a42626",
"metadata": {},
"source": [
"Set the index to the 'date' column, filter up to 2019-12-31, and sort by date"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cbcf68cf",
"metadata": {},
"outputs": [],
"source": [
"df = df.set_index(\"date\")[:\"2019-12-31\"].sort_index()"
]
},
{
"cell_type": "markdown",
"id": "9b4b91cd",
"metadata": {},
"source": [
"Calculate rolling mean and standard deviation with a 12-month window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e28f9215",
"metadata": {},
"outputs": [],
"source": [
"df[\"rolling_mean\"] = df[\"unemp\"].rolling(window=12).mean()\n",
"df[\"rolling_std\"] = df[\"unemp\"].rolling(window=12).std()"
]
},
{
"cell_type": "markdown",
"id": "e5863458",
"metadata": {},
"source": [
"Plot the unemployment rate with rolling mean and standard deviation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c10e8025",
"metadata": {},
"outputs": [],
"source": [
"df.plot(title=\"Unemployment rate\")"
]
},
{
"cell_type": "markdown",
"id": "17630ce4",
"metadata": {},
"source": [
"Perform seasonal decomposition of the unemployment data using an additive model and plot results"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "52f48f1d",
"metadata": {},
"outputs": [],
"source": [
"decomposition_results = seasonal_decompose(\n",
" df[\"unemp\"], \n",
" model=\"additive\"\n",
").plot()"
]
},
{
"cell_type": "markdown",
"id": "47f1d516",
"metadata": {},
"source": [
"Apply STL decomposition to the unemployment data and plot the results"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a75ed137",
"metadata": {},
"outputs": [],
"source": [
"stl_decomposition = STL(df[[\"unemp\"]]).fit()\n",
"stl_decomposition.plot().suptitle(\"STL Decomposition\");"
]
},
{
"cell_type": "markdown",
"id": "b61552be",
"metadata": {},
"source": [
"Apply Hodrick-Prescott filter to decompose the unemployment data into cycle and trend components and plot results"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36c7a50e",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"hp_df = df[[\"unemp\"]].copy()\n",
"hp_df[\"cycle\"], hp_df[\"trend\"] = hpfilter(hp_df[\"unemp\"], 129600)\n",
"hp_df.plot(subplots=True, title=\"Hodrick-Prescott filter\");"
]
},
{
"cell_type": "markdown",
"id": "c508c532",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,339 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "04574bc2",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "8c027594",
"metadata": {},
"source": [
"This notebook demonstrates backtesting a trading strategy using Backtrader and OpenBB SDK for data acquisition. It outlines creating a trading strategy and running backtests to simulate performance over historical data. The code includes setting up a backtest, downloading data, defining strategy logic, and assessing results with performance metrics. This is useful for evaluating trading strategies' robustness and optimizing them for better risk-adjusted returns."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3bee1431",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"import datetime as dt\n",
"import pandas as pd\n",
"from openbb_terminal.sdk import openbb\n",
"import quantstats as qs\n",
"import backtrader as bt"
]
},
{
"cell_type": "markdown",
"id": "c0e61059",
"metadata": {},
"source": [
"Define a function to fetch stock data from OpenBB SDK and convert it for Backtrader use"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "132f6e66",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def openbb_data_to_bt_data(symbol, start_date, end_date):\n",
" \"\"\"Fetch and convert stock data for Backtrader\n",
" \n",
" Parameters\n",
" ----------\n",
" symbol : str\n",
" Stock symbol to fetch\n",
" start_date : str\n",
" Start date for data in 'YYYY-MM-DD' format\n",
" end_date : str\n",
" End date for data in 'YYYY-MM-DD' format\n",
" \n",
" Returns\n",
" -------\n",
" bt.feeds.YahooFinanceCSVData\n",
" Formatted data for Backtrader\n",
" \"\"\"\n",
" \n",
" # Fetch stock data from OpenBB SDK\n",
" df = openbb.stocks.load(symbol, start_date=start_date, end_date=end_date)\n",
"\n",
" # Save data to a CSV file\n",
" fn = f\"{symbol.lower()}.csv\"\n",
" df.to_csv(fn)\n",
" \n",
" # Return data formatted for Backtrader\n",
" return bt.feeds.YahooFinanceCSVData(\n",
" dataname=fn,\n",
" fromdate=dt.datetime.strptime(start_date, '%Y-%m-%d'),\n",
" todate=dt.datetime.strptime(end_date, '%Y-%m-%d')\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "6c3c0564",
"metadata": {},
"source": [
"Determine the last day of a given month"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cc17ebfc",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def last_day_of_month(any_day):\n",
" \"\"\"Calculate the last day of the month\n",
" \n",
" Parameters\n",
" ----------\n",
" any_day : datetime.date\n",
" Any date within the target month\n",
" \n",
" Returns\n",
" -------\n",
" int\n",
" Last day of the month\n",
" \"\"\"\n",
" \n",
" # Move to the next month and then back to the last day of the current month\n",
" next_month = any_day.replace(day=28) + dt.timedelta(days=4)\n",
" return (next_month - dt.timedelta(days=next_month.day)).day"
]
},
{
"cell_type": "markdown",
"id": "64b04705",
"metadata": {},
"source": [
"Define a trading strategy to execute monthly flows"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0729cd4a",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"class MonthlyFlows(bt.Strategy):\n",
" \"\"\"Strategy to trade based on monthly flows\n",
" \n",
" Parameters\n",
" ----------\n",
" end_of_month : int, optional\n",
" Day of the month to start buying (default is 23)\n",
" start_of_month : int, optional\n",
" Day of the month to start selling (default is 7)\n",
" \n",
" Attributes\n",
" ----------\n",
" order : bt.Order or None\n",
" Current order in the market\n",
" dataclose : bt.LineSeries\n",
" Closing prices of the data feed\n",
" \"\"\"\n",
" \n",
" params = (\n",
" (\"end_of_month\", 23),\n",
" (\"start_of_month\", 7),\n",
" )\n",
" \n",
" def __init__(self):\n",
" self.order = None\n",
" self.dataclose = self.datas[0].close\n",
" \n",
" def notify_order(self, order):\n",
" \"\"\"Handle order notifications\"\"\"\n",
" \n",
" # No more orders\n",
" self.order = None \n",
" \n",
" def next(self):\n",
" \"\"\"Execute strategy logic for each step in the backtest\"\"\"\n",
" \n",
" # Get today's date, day of month, and last day of current month\n",
" dt_ = self.datas[0].datetime.date(0)\n",
" dom = dt_.day\n",
" ldm = last_day_of_month(dt_)\n",
" \n",
" # If an order is pending, exit\n",
" if self.order:\n",
" return\n",
" \n",
" # Check if we are in the market\n",
" if not self.position:\n",
" \n",
" # We're in the first week of the month, sell\n",
" if dom <= self.params.start_of_month:\n",
" self.order = self.order_target_percent(target=-1)\n",
" print(f\"Created SELL of {self.order.size} at {self.data_close[0]} on day {dom}\")\n",
" \n",
" # We're in the last week of the month, buy\n",
" if dom >= self.params.end_of_month:\n",
" self.order = self.order_target_percent(target=1)\n",
" print(f\"Created BUY of {self.order.size} {self.data_close[0]} on day {dom}\")\n",
" \n",
" # We are not in the market\n",
" else:\n",
" \n",
" # If we're long\n",
" if self.position.size > 0:\n",
" if not self.params.end_of_month <= dom <= ldm:\n",
" print(f\"Created CLOSE of {self.position.size} at {self.data_close[0]} on day {dom}\")\n",
" self.order = self.order_target_percent(target=0.0)\n",
" \n",
" # If we're short\n",
" if self.position.size < 0:\n",
" if not 1 <= dom <= self.params.start_of_month:\n",
" print(f\"Created CLOSE of {self.position.size} at {self.data_close[0]} on day {dom}\")\n",
" self.order = self.order_target_percent(target=0.0)"
]
},
{
"cell_type": "markdown",
"id": "e8fbbc83",
"metadata": {},
"source": [
"Run the strategy using Backtrader"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b47c7a71",
"metadata": {},
"outputs": [],
"source": [
"data = openbb_data_to_bt_data(\"TLT\", start_date=\"2002-01-01\", end_date=\"2022-06-30\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d53cbfee",
"metadata": {},
"outputs": [],
"source": [
"cerebro = bt.Cerebro(stdstats=False)\n",
"cerebro.adddata(data)\n",
"cerebro.broker.setcash(1000.0)\n",
"cerebro.addstrategy(MonthlyFlows)\n",
"cerebro.addobserver(bt.observers.Value)\n",
"cerebro.addanalyzer(bt.analyzers.Returns, _name=\"returns\")\n",
"cerebro.addanalyzer(bt.analyzers.TimeReturn, _name=\"time_return\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "94ea3665",
"metadata": {},
"outputs": [],
"source": [
"backtest_result = cerebro.run()"
]
},
{
"cell_type": "markdown",
"id": "257cc57c",
"metadata": {},
"source": [
"Convert backtest results into a pandas DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27a1ec64",
"metadata": {},
"outputs": [],
"source": [
"returns_dict = backtest_result[0].analyzers.time_return.get_analysis()\n",
"returns_df = (\n",
" pd.DataFrame(\n",
" list(returns_dict.items()),\n",
" columns=[\"date\", \"return\"]\n",
" )\n",
" .set_index(\"date\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "7b6e5887",
"metadata": {},
"source": [
"Fetch benchmark data for comparison"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "86c540be",
"metadata": {},
"outputs": [],
"source": [
"bench = openbb.stocks.load(\"TLT\", start_date=\"2002-01-01\", end_date=\"2022-06-30\")[\"Adj Close\"]"
]
},
{
"cell_type": "markdown",
"id": "3c39d605",
"metadata": {},
"source": [
"Assess results using QuantStats metrics"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2dec6aa2",
"metadata": {},
"outputs": [],
"source": [
"qs.reports.metrics(\n",
" returns_df,\n",
" benchmark=bench,\n",
" mode=\"full\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "5b590090",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,539 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c599b573",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "88633890",
"metadata": {},
"source": [
"This code calculates various methods of historical volatility from stock price data. It downloads historical prices for Apple Inc. (AAPL) and computes returns. The code then defines functions to compute volatility using different models: Parkinson, Garman-Klass, Hodges-Tompkins, Rogers-Satchell, Yang-Zhang, and standard deviation. Each model is applied to the data, and the results are plotted for visualization. This is useful for risk management, option pricing, and financial analysis."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d1d67415",
"metadata": {},
"outputs": [],
"source": [
"import math"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a2a29969",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import yfinance as yf"
]
},
{
"cell_type": "markdown",
"id": "8da6c98e",
"metadata": {},
"source": [
"Download historical stock data for Apple Inc. (AAPL) from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4f2be4ee",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(\"AAPL\", start=\"2017-01-01\", end=\"2022-06-30\")"
]
},
{
"cell_type": "markdown",
"id": "7b8097d1",
"metadata": {},
"source": [
"Display the downloaded stock data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9c28f735",
"metadata": {},
"outputs": [],
"source": [
"data"
]
},
{
"cell_type": "markdown",
"id": "3677bc76",
"metadata": {},
"source": [
"Calculate daily returns from adjusted closing prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0713eb09",
"metadata": {},
"outputs": [],
"source": [
"returns = (data[\"Adj Close\"] / data[\"Adj Close\"].shift(1)) - 1"
]
},
{
"cell_type": "markdown",
"id": "03de5a56",
"metadata": {},
"source": [
"Plot the adjusted closing prices over time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "615a1ff9",
"metadata": {},
"outputs": [],
"source": [
"data[\"Adj Close\"].plot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "297e9737",
"metadata": {},
"outputs": [],
"source": [
"def parkinson(price_data, window=30, trading_periods=252, clean=True):\n",
" \"\"\"Calculate volatility using the Parkinson model\n",
"\n",
" Parameters\n",
" ----------\n",
" price_data : DataFrame\n",
" Historical stock price data\n",
" window : int, optional\n",
" Rolling window size in days (default is 30)\n",
" trading_periods : int, optional\n",
" Number of trading periods per year (default is 252)\n",
" clean : bool, optional\n",
" Whether to drop NaN values in the result (default is True)\n",
"\n",
" Returns\n",
" -------\n",
" Series\n",
" Parkinson volatility\n",
" \"\"\"\n",
"\n",
" # Calculate the Parkinson volatility estimate using high and low prices\n",
" rs = (1.0 / (4.0 * math.log(2.0))) * (\n",
" (price_data[\"High\"] / price_data[\"Low\"]).apply(np.log)\n",
" ) ** 2.0\n",
"\n",
" # Define a function to apply the rolling mean and scale by trading periods\n",
" def f(v):\n",
" return (trading_periods * v.mean()) ** 0.5\n",
"\n",
" # Apply the rolling window calculation to the Parkinson estimate\n",
" result = rs.rolling(window=window, center=False).apply(func=f)\n",
"\n",
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
" if clean:\n",
" return result.dropna()\n",
" else:\n",
" return result"
]
},
{
"cell_type": "markdown",
"id": "60f29474",
"metadata": {},
"source": [
"Plot Parkinson volatility over time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "31a319a3",
"metadata": {},
"outputs": [],
"source": [
"parkinson(data).plot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4ac1610e",
"metadata": {},
"outputs": [],
"source": [
"def garman_klass(price_data, window=30, trading_periods=252, clean=True):\n",
" \"\"\"Calculate volatility using the Garman-Klass model\n",
"\n",
" Parameters\n",
" ----------\n",
" price_data : DataFrame\n",
" Historical stock price data\n",
" window : int, optional\n",
" Rolling window size in days (default is 30)\n",
" trading_periods : int, optional\n",
" Number of trading periods per year (default is 252)\n",
" clean : bool, optional\n",
" Whether to drop NaN values in the result (default is True)\n",
"\n",
" Returns\n",
" -------\n",
" Series\n",
" Garman-Klass volatility\n",
" \"\"\"\n",
"\n",
" # Calculate log returns of high/low and close/open prices\n",
" log_hl = (price_data[\"High\"] / price_data[\"Low\"]).apply(np.log)\n",
" log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n",
"\n",
" # Compute the Garman-Klass volatility estimate\n",
" rs = 0.5 * log_hl**2 - (2 * math.log(2) - 1) * log_co**2\n",
"\n",
" # Define a function to apply the rolling mean and scale by trading periods\n",
" def f(v):\n",
" return (trading_periods * v.mean()) ** 0.5\n",
"\n",
" # Apply the rolling window calculation to the Garman-Klass estimate\n",
" result = rs.rolling(window=window, center=False).apply(func=f)\n",
"\n",
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
" if clean:\n",
" return result.dropna()\n",
" else:\n",
" return result"
]
},
{
"cell_type": "markdown",
"id": "01168fcc",
"metadata": {},
"source": [
"Plot Garman-Klass volatility over time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "88a8d0d0",
"metadata": {},
"outputs": [],
"source": [
"garman_klass(data).plot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2011bdea",
"metadata": {},
"outputs": [],
"source": [
"def hodges_tompkins(price_data, window=30, trading_periods=252, clean=True):\n",
" \"\"\"Calculate volatility using the Hodges-Tompkins model\n",
"\n",
" Parameters\n",
" ----------\n",
" price_data : DataFrame\n",
" Historical stock price data\n",
" window : int, optional\n",
" Rolling window size in days (default is 30)\n",
" trading_periods : int, optional\n",
" Number of trading periods per year (default is 252)\n",
" clean : bool, optional\n",
" Whether to drop NaN values in the result (default is True)\n",
"\n",
" Returns\n",
" -------\n",
" Series\n",
" Hodges-Tompkins volatility\n",
" \"\"\"\n",
"\n",
" # Calculate log returns of closing prices\n",
" log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
"\n",
" # Compute the rolling standard deviation and scale by trading periods\n",
" vol = log_return.rolling(window=window, center=False).std() * math.sqrt(\n",
" trading_periods\n",
" )\n",
"\n",
" # Calculate adjustment factors based on window size and sample size\n",
" h = window\n",
" n = (log_return.count() - h) + 1\n",
"\n",
" adj_factor = 1.0 / (1.0 - (h / n) + ((h**2 - 1) / (3 * n**2)))\n",
"\n",
" # Apply the adjustment factor to the volatility estimate\n",
" result = vol * adj_factor\n",
"\n",
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
" if clean:\n",
" return result.dropna()\n",
" else:\n",
" return result"
]
},
{
"cell_type": "markdown",
"id": "c4349cdc",
"metadata": {},
"source": [
"Plot Hodges-Tompkins volatility over time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b4824347",
"metadata": {},
"outputs": [],
"source": [
"hodges_tompkins(data).plot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5c6305c6",
"metadata": {},
"outputs": [],
"source": [
"def rogers_satchell(price_data, window=30, trading_periods=252, clean=True):\n",
" \"\"\"Calculate volatility using the Rogers-Satchell model\n",
"\n",
" Parameters\n",
" ----------\n",
" price_data : DataFrame\n",
" Historical stock price data\n",
" window : int, optional\n",
" Rolling window size in days (default is 30)\n",
" trading_periods : int, optional\n",
" Number of trading periods per year (default is 252)\n",
" clean : bool, optional\n",
" Whether to drop NaN values in the result (default is True)\n",
"\n",
" Returns\n",
" -------\n",
" Series\n",
" Rogers-Satchell volatility\n",
" \"\"\"\n",
"\n",
" # Calculate log returns of high/open, low/open, and close/open prices\n",
" log_ho = (price_data[\"High\"] / price_data[\"Open\"]).apply(np.log)\n",
" log_lo = (price_data[\"Low\"] / price_data[\"Open\"]).apply(np.log)\n",
" log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n",
"\n",
" # Compute the Rogers-Satchell volatility estimate\n",
" rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)\n",
"\n",
" # Define a function to apply the rolling mean and scale by trading periods\n",
" def f(v):\n",
" return (trading_periods * v.mean()) ** 0.5\n",
"\n",
" # Apply the rolling window calculation to the Rogers-Satchell estimate\n",
" result = rs.rolling(window=window, center=False).apply(func=f)\n",
"\n",
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
" if clean:\n",
" return result.dropna()\n",
" else:\n",
" return result"
]
},
{
"cell_type": "markdown",
"id": "bea6c84e",
"metadata": {},
"source": [
"Plot Rogers-Satchell volatility over time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2887f9a0",
"metadata": {},
"outputs": [],
"source": [
"rogers_satchell(data).plot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06167183",
"metadata": {},
"outputs": [],
"source": [
"def yang_zhang(price_data, window=30, trading_periods=252, clean=True):\n",
" \"\"\"Calculate volatility using the Yang-Zhang model\n",
"\n",
" Parameters\n",
" ----------\n",
" price_data : DataFrame\n",
" Historical stock price data\n",
" window : int, optional\n",
" Rolling window size in days (default is 30)\n",
" trading_periods : int, optional\n",
" Number of trading periods per year (default is 252)\n",
" clean : bool, optional\n",
" Whether to drop NaN values in the result (default is True)\n",
"\n",
" Returns\n",
" -------\n",
" Series\n",
" Yang-Zhang volatility\n",
" \"\"\"\n",
"\n",
" # Calculate log returns of high/open, low/open, close/open, open/close, and close/close prices\n",
" log_ho = (price_data[\"High\"] / price_data[\"Open\"]).apply(np.log)\n",
" log_lo = (price_data[\"Low\"] / price_data[\"Open\"]).apply(np.log)\n",
" log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n",
"\n",
" log_oc = (price_data[\"Open\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
" log_oc_sq = log_oc**2\n",
"\n",
" log_cc = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
" log_cc_sq = log_cc**2\n",
"\n",
" # Compute the Rogers-Satchell volatility estimate\n",
" rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)\n",
"\n",
" # Compute close-to-close and open-to-close volatilities\n",
" close_vol = log_cc_sq.rolling(window=window, center=False).sum() * (\n",
" 1.0 / (window - 1.0)\n",
" )\n",
" open_vol = log_oc_sq.rolling(window=window, center=False).sum() * (\n",
" 1.0 / (window - 1.0)\n",
" )\n",
" window_rs = rs.rolling(window=window, center=False).sum() * (1.0 / (window - 1.0))\n",
"\n",
" # Calculate the weighting factor 'k'\n",
" k = 0.34 / (1.34 + (window + 1) / (window - 1))\n",
"\n",
" # Compute the final Yang-Zhang volatility estimate\n",
" result = (open_vol + k * close_vol + (1 - k) * window_rs).apply(\n",
" np.sqrt\n",
" ) * math.sqrt(trading_periods)\n",
"\n",
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
" if clean:\n",
" return result.dropna()\n",
" else:\n",
" return result"
]
},
{
"cell_type": "markdown",
"id": "71ed8302",
"metadata": {},
"source": [
"Plot Yang-Zhang volatility over time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "40717ed4",
"metadata": {},
"outputs": [],
"source": [
"yang_zhang(data).plot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e64bbc96",
"metadata": {},
"outputs": [],
"source": [
"def standard_deviation(price_data, window=30, trading_periods=252, clean=True):\n",
" \"\"\"Calculate volatility using standard deviation of log returns\n",
"\n",
" Parameters\n",
" ----------\n",
" price_data : DataFrame\n",
" Historical stock price data\n",
" window : int, optional\n",
" Rolling window size in days (default is 30)\n",
" trading_periods : int, optional\n",
" Number of trading periods per year (default is 252)\n",
" clean : bool, optional\n",
" Whether to drop NaN values in the result (default is True)\n",
"\n",
" Returns\n",
" -------\n",
" Series\n",
" Standard deviation volatility\n",
" \"\"\"\n",
"\n",
" # Calculate log returns of closing prices\n",
" log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
"\n",
" # Compute the rolling standard deviation and scale by trading periods\n",
" result = log_return.rolling(window=window, center=False).std() * math.sqrt(\n",
" trading_periods\n",
" )\n",
"\n",
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
" if clean:\n",
" return result.dropna()\n",
" else:\n",
" return result"
]
},
{
"cell_type": "markdown",
"id": "86c10b8e",
"metadata": {},
"source": [
"Plot standard deviation volatility over time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45c9f814",
"metadata": {},
"outputs": [],
"source": [
"standard_deviation(data).plot()"
]
},
{
"cell_type": "markdown",
"id": "243a9283",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,361 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8a2b2530",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "70c08f76",
"metadata": {},
"source": [
"This code retrieves U.S. Treasury bond yield data using the OpenBB SDK and visualizes the yield curve over time. It sets up the necessary imports, initializes plotting parameters, and configures the OpenBB SDK with a FRED API key. The script extracts treasury yield data for various maturities and indicates whether the yield curve is inverted. An animation is created to dynamically display the yield curve changes over time, highlighting inversions in red."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f47685af",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.animation as animation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "876b4eac",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "77931a95",
"metadata": {},
"source": [
"Define font properties for the plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21676002",
"metadata": {},
"outputs": [],
"source": [
"font = {\n",
" \"family\": \"normal\",\n",
" \"weight\": \"normal\",\n",
" \"size\": 12\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8a309f7",
"metadata": {},
"outputs": [],
"source": [
"plt.rc('font', **font)"
]
},
{
"cell_type": "markdown",
"id": "24fa7787",
"metadata": {},
"source": [
"Configure OpenBB SDK with FRED API key"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3632ee19",
"metadata": {},
"outputs": [],
"source": [
"openbb.keys.fred(\n",
" key=\"3d20c1fcbb26ea21b9f78fafbbdce900\",\n",
" persist=True,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "8bad0715",
"metadata": {},
"source": [
"Define treasury bond maturities to be retrieved"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab0acba1",
"metadata": {},
"outputs": [],
"source": [
"maturities = ['3m', '6m', '1y', '2y', '3y', '5y', '7y', '10y', '30y']"
]
},
{
"cell_type": "markdown",
"id": "717bade2",
"metadata": {},
"source": [
"Retrieve treasury bond yield data from OpenBB SDK for specified maturities and time range"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "55e6010a",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.economy.treasury(\n",
" instruments=[\"nominal\"],\n",
" maturities=maturities,\n",
" start_date=\"1985-01-01\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "4edab174",
"metadata": {},
"source": [
"Rename columns to match maturities list"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "96be72bd",
"metadata": {},
"outputs": [],
"source": [
"data.columns = maturities"
]
},
{
"cell_type": "markdown",
"id": "ad0285d8",
"metadata": {},
"source": [
"Add a column to indicate if yield curve is inverted (30y yield less than 3m yield)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b852c95a",
"metadata": {},
"outputs": [],
"source": [
"data[\"inverted\"] = data[\"30y\"] < data[\"3m\"]"
]
},
{
"cell_type": "markdown",
"id": "3ee1c2ee",
"metadata": {},
"source": [
"Initialize plot figure and axis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a134e05",
"metadata": {},
"outputs": [],
"source": [
"fig = plt.figure()\n",
"ax = fig.add_subplot(1, 1, 1)\n",
"line, = ax.plot([], [])"
]
},
{
"cell_type": "markdown",
"id": "2c115cca",
"metadata": {},
"source": [
"Set x and y-axis limits for the plot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c552c60f",
"metadata": {},
"outputs": [],
"source": [
"ax.set_xlim(0, 7)\n",
"ax.set_ylim(0, 20)"
]
},
{
"cell_type": "markdown",
"id": "6014ed6b",
"metadata": {},
"source": [
"Define tick locations and labels for both axes"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24cb514e",
"metadata": {},
"outputs": [],
"source": [
"ax.set_xticks(range(8))\n",
"ax.set_yticks([2, 4, 6, 8, 10, 12, 14, 16, 18])\n",
"ax.set_xticklabels([\"1m\",\"3m\",\"6m\",\"1y\",\"5y\",\"10y\",\"20y\",\"30y\"])\n",
"ax.set_yticklabels([2, 4, 6, 8, 10, 12, 14, 16, 18])"
]
},
{
"cell_type": "markdown",
"id": "9c3581ec",
"metadata": {},
"source": [
"Force y-axis labels to appear on the left side"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af5728b1",
"metadata": {},
"outputs": [],
"source": [
"ax.yaxis.set_label_position(\"left\")\n",
"ax.yaxis.tick_left()"
]
},
{
"cell_type": "markdown",
"id": "e34a8998",
"metadata": {},
"source": [
"Add labels for both axes"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e470b81",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"plt.ylabel(\"Yield (%)\")\n",
"plt.xlabel(\"Time to maturty\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "25541eb8",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def init_func():\n",
" \"\"\"Initialize plot with empty data and title\"\"\"\n",
" line.set_data([], [])\n",
" plt.title(\"U.S. Treasury Bond Yield Curve\")\n",
" return line"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3826d2c",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def animate(i):\n",
" \"\"\"Update plot data for each frame in the animation\n",
" \n",
" Parameters\n",
" ----------\n",
" i : int\n",
" Current frame index\n",
" \n",
" Returns\n",
" -------\n",
" line : Line2D object\n",
" \"\"\"\n",
" x = range(0, len(maturities))\n",
" y = data[maturities].iloc[i]\n",
" dt_ = data.index[i].strftime(\"%Y-%m-%d\")\n",
" \n",
" # Change line color based on yield curve inversion\n",
" if data.inverted.iloc[i]:\n",
" line.set_color(\"r\")\n",
" else:\n",
" line.set_color(\"y\")\n",
" \n",
" line.set_data(x, y)\n",
" \n",
" plt.title(f\"U.S. Treasury Bond Yield Curve ({dt_})\")\n",
" return line,"
]
},
{
"cell_type": "markdown",
"id": "144c195f",
"metadata": {},
"source": [
"Create animation for the yield curve using the animate function"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ccd9ff8",
"metadata": {},
"outputs": [],
"source": [
"ani = animation.FuncAnimation(\n",
" fig, animate, init_func=init_func, frames=len(data.index), interval=5, blit=True\n",
")"
]
},
{
"cell_type": "markdown",
"id": "305bb8bf",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,242 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fa749e17",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "58737b4a",
"metadata": {},
"source": [
"This code simulates stock returns using Geometric Brownian Motion (GBM) and calculates the premium for a barrier option. It defines a function to simulate GBM paths based on initial stock price, drift, volatility, and other parameters. The code then generates multiple simulated price paths, checks if the maximum value of each path exceeds a barrier, and calculates the option payoff accordingly. Lastly, it discounts the payoffs to present value and computes the average premium. This is useful for pricing exotic financial derivatives and analyzing risk."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ecc8c484",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "748351bc",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def simulate_gbm(s_0, mu, sigma, T, N, n_sims=10**3, random_seed=42):\n",
" \"\"\"\n",
" Function used for simulating stock returns using Geometric Brownian Motion.\n",
"\n",
" Parameters\n",
" ------------\n",
" s_0 : float\n",
" Initial stock price\n",
" mu : float\n",
" Drift coefficient\n",
" sigma : float\n",
" Diffusion coefficient\n",
" T : float\n",
" Length of the forecast horizon, same unit as dt\n",
" N : int\n",
" Number of time increments in the forecast horizon\n",
" n_sims : int\n",
" Number of simulation paths\n",
" random_seed : int\n",
" Random seed for reproducibility\n",
"\n",
" Returns\n",
" -----------\n",
" S_t : np.ndarray\n",
" Matrix (size: n_sims x (T+1)) containing the simulation results.\n",
" Rows represent sample paths, while columns point of time.\n",
" \"\"\"\n",
"\n",
" # Set random seed for reproducibility\n",
" np.random.seed(random_seed)\n",
"\n",
" # Calculate time increment\n",
" dt = T / N\n",
"\n",
" # Generate normally distributed random values for the Wiener process\n",
" dW = np.random.normal(scale=np.sqrt(dt), size=(n_sims, N + 1))\n",
"\n",
" # Simulate the evolution of the process using GBM formula\n",
" S_t = s_0 * np.exp(np.cumsum((mu - 0.5 * sigma**2) * dt + sigma * dW, axis=1))\n",
" S_t[:, 0] = s_0\n",
"\n",
" return S_t"
]
},
{
"cell_type": "markdown",
"id": "e67fab55",
"metadata": {},
"source": [
"Define initial stock price, drift, volatility, time horizon, and number of increments"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "96268bc8",
"metadata": {},
"outputs": [],
"source": [
"S_0 = 55\n",
"r = 0.06\n",
"sigma = 0.2\n",
"T = 1\n",
"N = 252"
]
},
{
"cell_type": "markdown",
"id": "be23773e",
"metadata": {},
"source": [
"Define barrier level and strike price for the option"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30090621",
"metadata": {},
"outputs": [],
"source": [
"BARRIER = 65\n",
"K = 60"
]
},
{
"cell_type": "markdown",
"id": "126163f8",
"metadata": {},
"source": [
"Generate GBM simulations for the given parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "227001bf",
"metadata": {},
"outputs": [],
"source": [
"gbm_sims = simulate_gbm(s_0=S_0, mu=r, sigma=sigma, T=T, N=N)"
]
},
{
"cell_type": "markdown",
"id": "6c5abfb3",
"metadata": {},
"source": [
"Plot the simulated price paths and the barrier level"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b718dafd",
"metadata": {},
"outputs": [],
"source": [
"plt.axhline(y=BARRIER, color='r', linestyle='-')\n",
"plt.xlim(0, N)\n",
"plt.plot(gbm_sims.T, linewidth=0.25)"
]
},
{
"cell_type": "markdown",
"id": "58aed041",
"metadata": {},
"source": [
"Calculate the maximum value per path to determine if the barrier was breached"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7fc85776",
"metadata": {},
"outputs": [],
"source": [
"max_value_per_path = np.max(gbm_sims, axis=1)"
]
},
{
"cell_type": "markdown",
"id": "589b129d",
"metadata": {},
"source": [
"Calculate the payoff of the barrier option based on the barrier breach condition"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "685b4103",
"metadata": {},
"outputs": [],
"source": [
"payoff = np.where(\n",
" max_value_per_path > BARRIER, \n",
" np.maximum(0, gbm_sims[:, -1] - K), \n",
" 0\n",
")"
]
},
{
"cell_type": "markdown",
"id": "74527d4a",
"metadata": {},
"source": [
"Calculate the discount factor and the average premium for the option"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0c654f62",
"metadata": {},
"outputs": [],
"source": [
"discount_factor = np.exp(-r * T)\n",
"premium = discount_factor * np.mean(payoff)\n",
"premium"
]
},
{
"cell_type": "markdown",
"id": "51efd6b6",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,275 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "abfc4950",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "8a8caa4f",
"metadata": {},
"source": [
"This code calculates the Kelly fraction, the optimal leverage for maximizing long-term growth, using historical stock returns. It fetches annual returns for the S&P 500 index, computes rolling statistics, and applies a numerical integration method to optimize the leverage. The code also visualizes the cumulative compounded returns using the Kelly strategy. This is useful in practice for portfolio management, risk management, and enhancing investment performance through optimized leverage."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bee14bab",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cf2ef711",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from scipy.optimize import minimize_scalar\n",
"from scipy.integrate import quad\n",
"from scipy.stats import norm"
]
},
{
"cell_type": "markdown",
"id": "174c0343",
"metadata": {},
"source": [
"Fetch annual returns for the S&P 500 index since 1950"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8cebb20d",
"metadata": {},
"outputs": [],
"source": [
"annual_returns = (\n",
" openbb.economy.index([\"^GSPC\"], start_date=\"1950-01-01\", column=\"Close\")\n",
" .resample(\"A\")\n",
" .last()\n",
" .pct_change()\n",
" .dropna()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "283545ce",
"metadata": {},
"source": [
"Compute rolling mean and standard deviation over a 25-year window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5798723",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"return_params = annual_returns[\"^GSPC\"].rolling(25).agg([\"mean\", \"std\"]).dropna()"
]
},
{
"cell_type": "markdown",
"id": "eca2ef30",
"metadata": {},
"source": [
"Define a function to calculate the negative value of the expected log return"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8dd7072f",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def norm_integral(f, mean, std):\n",
" \"\"\"Calculates the negative expected log return\n",
" \n",
" Parameters\n",
" ----------\n",
" f : float\n",
" Leverage factor\n",
" mean : float\n",
" Mean return\n",
" std : float\n",
" Standard deviation of returns\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" Negative expected log return\n",
" \"\"\"\n",
" \n",
" val, er = quad(\n",
" lambda s: np.log(1 + f * s) * norm.pdf(s, mean, std),\n",
" mean - 3 * std,\n",
" mean + 3 * std,\n",
" )\n",
" return -val"
]
},
{
"cell_type": "markdown",
"id": "8df71ad1",
"metadata": {},
"source": [
"Define a function to optimize the Kelly fraction using the minimize_scalar method"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d3d8894",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def get_kelly(data):\n",
" \"\"\"Optimizes the Kelly fraction\n",
" \n",
" Parameters\n",
" ----------\n",
" data : pd.Series\n",
" Contains mean and standard deviation of returns\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" Optimal Kelly fraction\n",
" \"\"\"\n",
" \n",
" solution = minimize_scalar(\n",
" norm_integral, args=(data[\"mean\"], data[\"std\"]), bounds=[0, 2], method=\"bounded\"\n",
" )\n",
" return solution.x"
]
},
{
"cell_type": "markdown",
"id": "03519c41",
"metadata": {},
"source": [
"Calculate the Kelly fraction for each rolling window and add it to the annual returns DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff591e28",
"metadata": {},
"outputs": [],
"source": [
"annual_returns['f'] = return_params.apply(get_kelly, axis=1)"
]
},
{
"cell_type": "markdown",
"id": "bce1e53b",
"metadata": {},
"source": [
"Visualize the cumulative compounded returns using the Kelly strategy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "681f13ca",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" annual_returns[[\"^GSPC\"]]\n",
" .assign(kelly=annual_returns[\"^GSPC\"].mul(annual_returns.f.shift()))\n",
" .dropna()\n",
" .loc[\"1900\":]\n",
" .add(1)\n",
" .cumprod()\n",
" .sub(1)\n",
" .plot(lw=2)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "80eacb96",
"metadata": {},
"source": [
"Pick an arbitrary point for mean and standard deviation to calculate optimal Kelly fraction"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "126a1da8",
"metadata": {},
"outputs": [],
"source": [
"m = .058\n",
"s = .216"
]
},
{
"cell_type": "markdown",
"id": "8d61618b",
"metadata": {},
"source": [
"Optimize the Kelly fraction for the given mean and standard deviation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "60235902",
"metadata": {},
"outputs": [],
"source": [
"sol = minimize_scalar(norm_integral, args=(m, s), bounds=[0.0, 2.0], method=\"bounded\")\n",
"print(\"Optimal Kelly fraction: {:.4f}\".format(sol.x))"
]
},
{
"cell_type": "markdown",
"id": "687a9a94",
"metadata": {},
"source": [
"This formula can result in Kelly fractions higher than 1. In this case, it is theoretically advantageous to use leverage to purchase additional securities on margin."
]
},
{
"cell_type": "markdown",
"id": "3ce91624",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,419 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "aa29771a",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "1ed22fa1",
"metadata": {},
"source": [
"This code implements a trading algorithm using Zipline, simulating trades on Apple Inc. (AAPL) stock based on moving average crossovers. It initializes the trading environment, defines trading logic, and records relevant data. The algorithm is backtested over a specified time period, and performance metrics are analyzed and visualized using Pyfolio. This is useful for developing and evaluating trading strategies in a systematic manner."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5155ca1e",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import pandas_datareader.data as web"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d1b4290f",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aef80e58",
"metadata": {},
"outputs": [],
"source": [
"from zipline import run_algorithm\n",
"from zipline.api import order_target, record, symbol\n",
"from zipline.finance import commission, slippage"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eec4cef3",
"metadata": {},
"outputs": [],
"source": [
"import pyfolio as pf"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1fa431a",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"import warnings\n",
"warnings.filterwarnings('ignore')"
]
},
{
"cell_type": "markdown",
"id": "19f0166c",
"metadata": {},
"source": [
"Load the Zipline extension for Jupyter notebooks"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d952e31",
"metadata": {},
"outputs": [],
"source": [
"%load_ext zipline"
]
},
{
"cell_type": "markdown",
"id": "86ddb827",
"metadata": {},
"source": [
"Ingest historical data from Quandl for Zipline to use"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6416593a",
"metadata": {},
"outputs": [],
"source": [
"! zipline ingest -b quandl"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c578c2fc",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def initialize(context):\n",
" \"\"\"Initialize the trading algorithm\n",
"\n",
" Sets up initial parameters and trading environment.\n",
"\n",
" Parameters\n",
" ----------\n",
" context : zipline.assets.context\n",
" The algorithm's context, including variables and state.\n",
" \"\"\"\n",
"\n",
" context.i = 0\n",
" context.asset = symbol(\"AAPL\")\n",
"\n",
" context.set_commission(commission.PerShare(cost=0.01))\n",
" context.set_slippage(slippage.FixedSlippage(spread=0.01))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ad255427",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def handle_data(context, data):\n",
" \"\"\"Handle the trading logic\n",
"\n",
" Executes trading logic based on moving averages.\n",
"\n",
" Parameters\n",
" ----------\n",
" context : zipline.assets.context\n",
" The algorithm's context, including variables and state.\n",
" data : zipline.protocol.BarData\n",
" Contains current and historical market data.\n",
" \"\"\"\n",
"\n",
" context.i += 1\n",
" if context.i < 50:\n",
" return\n",
"\n",
" short_mavg = data.history(\n",
" context.asset, \n",
" \"price\", \n",
" bar_count=14,\n",
" frequency=\"1d\"\n",
" ).mean()\n",
" \n",
" long_mavg = data.history(\n",
" context.asset,\n",
" \"price\",\n",
" bar_count=50,\n",
" frequency=\"1d\"\n",
" ).mean()\n",
"\n",
" if short_mavg > long_mavg:\n",
" order_target(context.asset, 100)\n",
" elif short_mavg < long_mavg:\n",
" order_target(context.asset, 0)\n",
"\n",
" record(\n",
" AAPL=data.current(context.asset, \"price\"),\n",
" short_mavg=short_mavg,\n",
" long_mavg=long_mavg,\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c995758d",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def analyze(context, perf):\n",
" \"\"\"Analyze the performance of the algorithm\n",
"\n",
" Visualizes the portfolio and stock performance.\n",
"\n",
" Parameters\n",
" ----------\n",
" context : zipline.assets.context\n",
" The algorithm's context, including variables and state.\n",
" perf : pandas.DataFrame\n",
" A dataframe containing the performance metrics.\n",
" \"\"\"\n",
"\n",
" fig = plt.figure()\n",
" ax1 = fig.add_subplot(211)\n",
" perf.portfolio_value.plot(ax=ax1)\n",
" ax1.set_ylabel(\"portfolio value in $\")\n",
"\n",
" ax2 = fig.add_subplot(212)\n",
" perf[\"AAPL\"].plot(ax=ax2)\n",
" perf[[\"short_mavg\", \"long_mavg\"]].plot(ax=ax2)\n",
"\n",
" perf_trans = perf.loc[\n",
" [t != [] for t in perf.transactions]\n",
" ]\n",
" buys = perf_trans.loc[\n",
" [t[0][\"amount\"] > 0 for t in perf_trans.transactions]\n",
" ]\n",
" sells = perf_trans.loc[\n",
" [t[0][\"amount\"] < 0 for t in perf_trans.transactions]\n",
" ]\n",
" ax2.plot(\n",
" buys.index,\n",
" perf.short_mavg.loc[buys.index],\n",
" \"^\",\n",
" markersize=10,\n",
" color=\"m\"\n",
" )\n",
" ax2.plot(\n",
" sells.index,\n",
" perf.short_mavg.loc[sells.index],\n",
" \"v\",\n",
" markersize=10,\n",
" color=\"k\"\n",
" )\n",
" ax2.set_ylabel(\"price in $\")\n",
" plt.legend(loc=0)\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"id": "e4bce6eb",
"metadata": {},
"source": [
"Define the start and end dates for the backtesting period"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "606aa860",
"metadata": {},
"outputs": [],
"source": [
"start = pd.Timestamp('2000')\n",
"end = pd.Timestamp('2018')"
]
},
{
"cell_type": "markdown",
"id": "87ff84e5",
"metadata": {},
"source": [
"Fetch the benchmark returns for the S&P 500 index from the Federal Reserve Economic Data (FRED)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc68568d",
"metadata": {},
"outputs": [],
"source": [
"sp500 = web.DataReader('SP500', 'fred', start, end).SP500\n",
"benchmark_returns = sp500.pct_change()"
]
},
{
"cell_type": "markdown",
"id": "3340b872",
"metadata": {},
"source": [
"Run the algorithm with the specified parameters and historical data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f81482a1",
"metadata": {},
"outputs": [],
"source": [
"perf = run_algorithm(\n",
" start=start,\n",
" end=end,\n",
" initialize=initialize,\n",
" handle_data=handle_data,\n",
" analyze=analyze,\n",
" capital_base=100000,\n",
" benchmark_returns=benchmark_returns,\n",
" bundle=\"quandl\",\n",
" data_frequency=\"daily\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "8f8420c1",
"metadata": {},
"source": [
"Display the performance metrics of the algorithm"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2c8fdd55",
"metadata": {},
"outputs": [],
"source": [
"perf.info()"
]
},
{
"cell_type": "markdown",
"id": "01c3ebb3",
"metadata": {},
"source": [
"Extract returns, positions, and transactions from the performance DataFrame for analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2f2c6986",
"metadata": {},
"outputs": [],
"source": [
"returns, positions, transactions = \\\n",
" pf.utils.extract_rets_pos_txn_from_zipline(perf)"
]
},
{
"cell_type": "markdown",
"id": "c658ddfc",
"metadata": {},
"source": [
"Create a full tear sheet using Pyfolio to analyze the trading strategy's performance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a92b98b",
"metadata": {},
"outputs": [],
"source": [
"pf.create_full_tear_sheet(\n",
" returns,\n",
" positions=positions,\n",
" transactions=transactions,\n",
" live_start_date=\"2016-01-01\",\n",
" round_trips=True,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5ad9c36f",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "633aa53b",
"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
}

View File

@ -0,0 +1,292 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e4414ec2",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "c4a34016",
"metadata": {},
"source": [
"This notebook demonstrates the use of technical indicators for stock analysis using OpenBB Terminal and TA-Lib. It loads historical stock prices for Apple (AAPL) and calculates Bollinger Bands (BBANDS), Relative Strength Index (RSI), and Moving Average Convergence Divergence (MACD). The data is then visualized to provide insights into stock price trends and potential trading signals. This analysis is useful for investors and traders to make informed decisions based on technical analysis."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f4f99d73",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
"from talib import RSI, BBANDS, MACD"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "687cacb8",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb\n",
"from openbb_terminal.sdk import TerminalStyle\n",
"theme = TerminalStyle(\"light\", \"light\", \"light\")"
]
},
{
"cell_type": "markdown",
"id": "55b3ddb4",
"metadata": {},
"source": [
"Load historical stock data for Apple (AAPL) and rename 'Adj Close' to 'close'"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7a4c3ed2",
"metadata": {},
"outputs": [],
"source": [
"data = (\n",
" openbb\n",
" .stocks\n",
" .load(\"AAPL\", start_date=\"2020-01-01\")\n",
" .rename(columns={\"Adj Close\": \"close\"})\n",
")"
]
},
{
"cell_type": "markdown",
"id": "81eaf1bb",
"metadata": {},
"source": [
"Calculate Bollinger Bands (BBANDS) for the close prices with a 21-day period"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "628c013c",
"metadata": {},
"outputs": [],
"source": [
"up, mid, low = BBANDS(\n",
" data.close, \n",
" timeperiod=21, \n",
" nbdevup=2, \n",
" nbdevdn=2, \n",
" matype=0\n",
")"
]
},
{
"cell_type": "markdown",
"id": "71e64502",
"metadata": {},
"source": [
"Calculate Relative Strength Index (RSI) for the close prices with a 14-day period"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "efb493b3",
"metadata": {},
"outputs": [],
"source": [
"rsi = RSI(data.close, timeperiod=14)"
]
},
{
"cell_type": "markdown",
"id": "410dd7f1",
"metadata": {},
"source": [
"Calculate Moving Average Convergence Divergence (MACD) for the close prices with standard parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f9857261",
"metadata": {},
"outputs": [],
"source": [
"macd, macdsignal, macdhist = MACD(\n",
" data.close, \n",
" fastperiod=12, \n",
" slowperiod=26, \n",
" signalperiod=9\n",
")"
]
},
{
"cell_type": "markdown",
"id": "12793fb1",
"metadata": {},
"source": [
"Create a DataFrame for the MACD components"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66d2556b",
"metadata": {},
"outputs": [],
"source": [
"macd = pd.DataFrame(\n",
" {\n",
" \"MACD\": macd,\n",
" \"MACD Signal\": macdsignal,\n",
" \"MACD History\": macdhist,\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"id": "9f55ee6a",
"metadata": {},
"source": [
"Create a DataFrame for the stock data and technical indicators"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "500d00d0",
"metadata": {},
"outputs": [],
"source": [
"data = pd.DataFrame(\n",
" {\n",
" \"AAPL\": data.close,\n",
" \"BB Up\": up,\n",
" \"BB Mid\": mid,\n",
" \"BB down\": low,\n",
" \"RSI\": rsi,\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"id": "fdb7f597",
"metadata": {},
"source": [
"Plot the Bollinger Bands, RSI, and MACD indicators in subplots"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "adfa1169",
"metadata": {},
"outputs": [],
"source": [
"fig, axes = plt.subplots(\n",
" nrows=3,\n",
" figsize=(15, 10),\n",
" sharex=True\n",
")"
]
},
{
"cell_type": "markdown",
"id": "01d1b071",
"metadata": {},
"source": [
"Plot Bollinger Bands and close prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6de6ec6d",
"metadata": {},
"outputs": [],
"source": [
"data.drop([\"RSI\"], axis=1).plot(\n",
" ax=axes[0],\n",
" lw=1,\n",
" title=\"BBANDS\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "97855af1",
"metadata": {},
"source": [
"Plot RSI and add horizontal lines at 70 and 30 to indicate overbought and oversold levels"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17fc3747",
"metadata": {},
"outputs": [],
"source": [
"data[\"RSI\"].plot(\n",
" ax=axes[1],\n",
" lw=1,\n",
" title=\"RSI\"\n",
")\n",
"axes[1].axhline(70, lw=1, ls=\"--\", c=\"k\")\n",
"axes[1].axhline(30, lw=1, ls=\"--\", c=\"k\")"
]
},
{
"cell_type": "markdown",
"id": "5153654b",
"metadata": {},
"source": [
"Plot MACD components"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8ae01fba",
"metadata": {},
"outputs": [],
"source": [
"macd.plot(\n",
" ax=axes[2],\n",
" lw=1,\n",
" title=\"MACD\",\n",
" rot=0\n",
")\n",
"axes[2].set_xlabel(\"\")\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "ab373866",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,232 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0c0b824d",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "14722e46",
"metadata": {},
"source": [
"This notebook calculates the Treynor ratio for a given stock and its benchmark index over time. It fetches historical price data for the specified stock and benchmark, computes daily returns, and calculates rolling beta values. The Treynor ratio is then derived using these beta values. This ratio helps in assessing the risk-adjusted performance of the asset relative to the benchmark, aiding in investment decision-making."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "64fbd380",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb\n",
"from openbb_terminal.sdk import TerminalStyle"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f263a055",
"metadata": {},
"outputs": [],
"source": [
"theme = TerminalStyle(\"light\", \"light\", \"light\")"
]
},
{
"cell_type": "markdown",
"id": "6bd464bd",
"metadata": {},
"source": [
"Load historical stock data for JPM and SPY over the specified date range"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4adcbef5",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.stocks.load(\n",
" \"JPM, SPY\",\n",
" start_date=\"2014-01-01\",\n",
" end_date=\"2022-12-31\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "be35b95f",
"metadata": {},
"source": [
"Extract adjusted closing prices for JPM and SPY from the loaded data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "84cbd927",
"metadata": {},
"outputs": [],
"source": [
"asset = data[\"Adj Close\"].JPM\n",
"benchmark = data[\"Adj Close\"].SPY"
]
},
{
"cell_type": "markdown",
"id": "7325eefa",
"metadata": {},
"source": [
"Calculate daily returns for JPM and SPY, dropping any missing values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "201dc273",
"metadata": {},
"outputs": [],
"source": [
"asset_returns = asset.pct_change().dropna()\n",
"benchmark_returns = benchmark.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "c5b1530c",
"metadata": {},
"source": [
"Calculate the rolling variance of benchmark returns over a 30-day window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eb1cbc10",
"metadata": {},
"outputs": [],
"source": [
"bm_var = benchmark_returns.rolling(\n",
" window=30\n",
").var()"
]
},
{
"cell_type": "markdown",
"id": "af91c7ce",
"metadata": {},
"source": [
"Calculate the rolling covariance between asset returns and benchmark returns over a 30-day window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ebcee8bb",
"metadata": {},
"outputs": [],
"source": [
"bm_cov = benchmark_returns.rolling(\n",
" window=30\n",
").cov(asset_returns)"
]
},
{
"cell_type": "markdown",
"id": "44a33731",
"metadata": {},
"source": [
"Compute the rolling beta values by dividing the rolling covariance by the rolling variance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d100612",
"metadata": {},
"outputs": [],
"source": [
"beta = bm_cov / bm_var"
]
},
{
"cell_type": "markdown",
"id": "fb8f6eee",
"metadata": {},
"source": [
"Calculate the Treynor ratio by adjusting the returns of the asset against its beta"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "40d560cd",
"metadata": {},
"outputs": [],
"source": [
"treynor = (\n",
" asset_returns - benchmark_returns\n",
") / beta"
]
},
{
"cell_type": "markdown",
"id": "7e050c57",
"metadata": {},
"source": [
"Display the calculated Treynor ratio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a927dab4",
"metadata": {},
"outputs": [],
"source": [
"treynor"
]
},
{
"cell_type": "markdown",
"id": "cfc7731a",
"metadata": {},
"source": [
"Plot the Treynor ratio over time to visualize its trend"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "863ccecf",
"metadata": {},
"outputs": [],
"source": [
"treynor.plot()"
]
},
{
"cell_type": "markdown",
"id": "6f0482e8",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,616 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f9127175",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "235dd464",
"metadata": {},
"source": [
"This code uses the OpenBB Terminal SDK to load historical forex data and apply signal processing techniques to detect trading signals. It computes the Hilbert Transform to derive the dominant cycle phase and period, applies a Butterworth bandpass filter, and calculates amplitude and exponential moving average of amplitude. With these transformations, it identifies buy and sell positions based on signal thresholds and plots the results. This approach is useful for analyzing market cycles and testing trading strategies."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d9c52ae1",
"metadata": {},
"outputs": [],
"source": [
"from math import pi"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d48ef61",
"metadata": {},
"outputs": [],
"source": [
"from scipy.signal import butter, filtfilt\n",
"import numpy as np\n",
"import talib"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b79d1ca",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d5fe3441",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "a5cf8458",
"metadata": {},
"source": [
"Load historical forex data for the EUR/USD pair between the specified dates"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "668e2456",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.forex.load(\n",
" from_symbol=\"EUR\", \n",
" to_symbol=\"USD\", \n",
" start_date=\"2016-01-01\", \n",
" end_date=\"2021-12-31\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "0e6f3093",
"metadata": {},
"source": [
"Load and preprocess forex data, converting adjusted close prices to a DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "31dc063d",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.forex.load(\n",
" from_symbol=\"EUR\", \n",
" to_symbol=\"USD\", \n",
" start_date=\"2016-01-01\", \n",
" end_date=\"2021-12-31\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "01ddec47",
"metadata": {},
"outputs": [],
"source": [
"prices = (\n",
" data[\"Adj Close\"]\n",
" .to_frame()\n",
" .rename(\n",
" columns={\n",
" \"Adj Close\": \"close\"\n",
" }\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"id": "06940c12",
"metadata": {},
"source": [
"Calculate log returns from the closing prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15166b77",
"metadata": {},
"outputs": [],
"source": [
"prices[\"log_return\"] = (\n",
" prices.close\n",
" .apply(np.log)\n",
" .diff(1)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "9dea8d31",
"metadata": {},
"source": [
"Compute the Hilbert Transform - Dominant Cycle Phase"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dc8300e1",
"metadata": {},
"outputs": [],
"source": [
"prices[\"phase\"] = talib.HT_DCPHASE(prices.close)"
]
},
{
"cell_type": "markdown",
"id": "59eb8282",
"metadata": {},
"source": [
"Convert the phase into a sinusoidal signal"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef233ac9",
"metadata": {},
"outputs": [],
"source": [
"prices[\"signal\"] = np.sin(prices.phase + pi / 4)"
]
},
{
"cell_type": "markdown",
"id": "408eef48",
"metadata": {},
"source": [
"Compute the Hilbert Transform - Dominant Cycle Period"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a4a8a74",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"prices[\"period\"] = talib.HT_DCPERIOD(prices.close)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0c0544a6",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def butter_bandpass(data, period, delta=0.5, fs=5):\n",
" \"\"\"Applies Butterworth bandpass filter to data\n",
" \n",
" Parameters\n",
" ----------\n",
" data : np.ndarray\n",
" The data to be filtered\n",
" period : float\n",
" Dominant cycle period\n",
" delta : float, optional\n",
" Delta value to adjust cutoff frequencies, by default 0.5\n",
" fs : int, optional\n",
" Sampling frequency, by default 5\n",
" \n",
" Returns\n",
" -------\n",
" np.ndarray\n",
" Filtered data\n",
" \n",
" Notes\n",
" -----\n",
" This function applies a bandpass filter to the data using \n",
" the specified period and delta to determine cutoff frequencies.\n",
" \"\"\"\n",
" \n",
" nyq = 0.5 * fs\n",
"\n",
" # Low cutoff frequency\n",
" low = 1.0 / (period * (1 + delta))\n",
" low /= nyq\n",
"\n",
" # High cutoff frequency\n",
" high = 1.0 / (period * (1 - delta))\n",
" high /= nyq\n",
"\n",
" b, a = butter(2, [low, high], btype=\"band\")\n",
"\n",
" return filtfilt(b, a, data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af3a5919",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def roll_apply(e):\n",
" \"\"\"Applies Butterworth bandpass filter to rolling window data\n",
" \n",
" Parameters\n",
" ----------\n",
" e : pd.Series\n",
" Rolling window of data\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" Filtered value for the last data point in the window\n",
" \n",
" Notes\n",
" -----\n",
" This function takes a rolling window of data, applies the \n",
" Butterworth bandpass filter, and returns the filtered value \n",
" for the last data point in the window.\n",
" \"\"\"\n",
" \n",
" close = prices.close.loc[e.index]\n",
" period = prices.period.loc[e.index][-1]\n",
" out = butter_bandpass(close, period)\n",
" return out[-1]"
]
},
{
"cell_type": "markdown",
"id": "f52ba8f3",
"metadata": {},
"source": [
"Apply rolling window to compute filtered signal using Butterworth bandpass filter"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b36efc4a",
"metadata": {},
"outputs": [],
"source": [
"prices[\"filtered\"] = (\n",
" prices.dropna()\n",
" .rolling(window=30)\n",
" .apply(lambda series: roll_apply(series), raw=False)\n",
" .iloc[:, 0]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "afb2c00d",
"metadata": {},
"source": [
"Calculate amplitude from the filtered signal over a rolling window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ee4a444",
"metadata": {},
"outputs": [],
"source": [
"prices[\"amplitude\"] = (\n",
" prices.\n",
" filtered\n",
" .rolling(window=30)\n",
" .apply(\n",
" lambda series: series.max() - series.min()\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c1a6a39b",
"metadata": {},
"source": [
"Compute exponential moving average of amplitude"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1503d9a8",
"metadata": {},
"outputs": [],
"source": [
"prices[\"ema_amplitude\"] = (\n",
" talib\n",
" .EMA(\n",
" prices.amplitude,\n",
" timeperiod=30\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"id": "64229e8f",
"metadata": {},
"source": [
"Define signal and amplitude thresholds for position determination"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c639f06a",
"metadata": {},
"outputs": [],
"source": [
"signal_thresh = 0.75\n",
"amp_thresh = 0.004 # 40 pips"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f6a7f8a0",
"metadata": {},
"outputs": [],
"source": [
"prices[\"position\"] = 0"
]
},
{
"cell_type": "markdown",
"id": "f799c701",
"metadata": {},
"source": [
"Determine short positions based on signal and amplitude thresholds"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "753b389e",
"metadata": {},
"outputs": [],
"source": [
"prices.loc[\n",
" (prices.signal >= signal_thresh) & \n",
" (prices.amplitude > amp_thresh), \"position\"\n",
"] = -1"
]
},
{
"cell_type": "markdown",
"id": "3fd33d2e",
"metadata": {},
"source": [
"Determine long positions based on signal and amplitude thresholds"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3df30906",
"metadata": {},
"outputs": [],
"source": [
"prices.loc[\n",
" (prices.signal <= -signal_thresh) & \n",
" (prices.amplitude > amp_thresh), \"position\"\n",
"] = 1"
]
},
{
"cell_type": "markdown",
"id": "d1756024",
"metadata": {},
"source": [
"Plot the amplitude, signal, and position time series"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "91667313",
"metadata": {},
"outputs": [],
"source": [
"fig, axes = plt.subplots(\n",
" nrows=3,\n",
" figsize=(15, 10),\n",
" sharex=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "014c83b4",
"metadata": {},
"outputs": [],
"source": [
"prices.ema_amplitude.plot(\n",
" ax=axes[0],\n",
" title=\"amp\"\n",
")\n",
"axes[0].axhline(\n",
" amp_thresh,\n",
" lw=1,\n",
" c=\"r\"\n",
")\n",
"prices.signal.plot(\n",
" ax=axes[1],\n",
" title=\"signal\"\n",
")\n",
"axes[1].axhline(\n",
" signal_thresh,\n",
" c=\"r\"\n",
")\n",
"axes[1].axhline(\n",
" -signal_thresh,\n",
" c=\"r\"\n",
")\n",
"prices.position.plot(\n",
" ax=axes[2],\n",
" title=\"position\"\n",
")\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "cb3226c7",
"metadata": {},
"source": [
"Calculate strategy returns and cumulative returns based on positions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e5d72bf",
"metadata": {},
"outputs": [],
"source": [
"prices[\"strategy_return\"] = prices.position.shift(1) * prices.log_return\n",
"prices[\"strategy_cum_return\"] = prices.strategy_return.cumsum().apply(np.exp)\n",
"prices[\"bh_rtn_cum\"] = prices[\"log_return\"].cumsum().apply(np.exp)"
]
},
{
"cell_type": "markdown",
"id": "23ca420d",
"metadata": {},
"source": [
"Plot cumulative returns of the buy-and-hold strategy and the trading strategy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3b32ee59",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" prices[[\"bh_rtn_cum\", \"strategy_cum_return\"]]\n",
" .plot(title=\"Cumulative returns\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "660eb7bd",
"metadata": {},
"source": [
"Create a copy of the prices DataFrame for further analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7b745dfb",
"metadata": {},
"outputs": [],
"source": [
"df = prices.copy()"
]
},
{
"cell_type": "markdown",
"id": "df18bb2f",
"metadata": {},
"source": [
"Identify local minima and maxima in the signal for visualization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "340c66b2",
"metadata": {},
"outputs": [],
"source": [
"n = 5 # number of points to be checked before and after"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "92aa121d",
"metadata": {},
"outputs": [],
"source": [
"df[\"min\"] = df.iloc[argrelextrema(df.signal.values, np.less_equal, order=n)[0]][\"signal\"]\n",
"df[\"max\"] = df.iloc[argrelextrema(df.signal.values, np.greater_equal, order=n)[0]][\n",
" \"signal\"\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "e7e63625",
"metadata": {},
"source": [
"Plot the identified local minima and maxima along with the signal"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a921b68f",
"metadata": {},
"outputs": [],
"source": [
"plt.scatter(df.index, df[\"min\"], c=\"r\")\n",
"plt.scatter(df.index, df[\"max\"], c=\"g\")\n",
"plt.plot(df.index, df[\"signal\"])\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "831233d1",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,190 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9d21a51c",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "658dd5ee",
"metadata": {},
"source": [
"This code implements a binomial model to price American options, considering early exercise features. Using parameters like spot price, strike price, risk-free rate, volatility, time to expiry, and the number of steps, the model constructs a price tree. It then calculates the option value at each node, considering both holding and exercising the option. This approach is practical for valuing American-style options, which can be exercised at any time before expiration."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8791fb6f",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "9af5deeb",
"metadata": {},
"source": [
"Define a function to price American options using the binomial tree model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f00b850d",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def american_option_pricer(spot, strike, rate, vol, expiry, steps, option_type):\n",
" \"\"\"Price an American option using binomial model\n",
" \n",
" Parameters\n",
" ----------\n",
" spot : float\n",
" Current spot price of the underlying asset\n",
" strike : float\n",
" Strike price of the option\n",
" rate : float\n",
" Risk-free interest rate\n",
" vol : float\n",
" Volatility of the underlying asset\n",
" expiry : float\n",
" Time to expiry in years\n",
" steps : int\n",
" Number of steps in the binomial tree\n",
" option_type : str\n",
" Type of the option ('call' or 'put')\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" Estimated price of the American option\n",
" \n",
" Notes\n",
" -----\n",
" This function constructs a binomial tree to \n",
" estimate the price of an American option. It \n",
" accounts for early exercise features by comparing \n",
" the value of holding versus exercising the option \n",
" at each node.\n",
" \"\"\"\n",
"\n",
" # Calculate the time interval and the up and down factors\n",
"\n",
" dt = expiry / steps\n",
" u = np.exp(vol * np.sqrt(dt))\n",
" d = 1 / u\n",
"\n",
" # Calculate the risk-neutral probability\n",
"\n",
" p = (np.exp(rate * dt) - d) / (u - d)\n",
"\n",
" # Create the binomial price tree\n",
"\n",
" price_tree = np.zeros((steps + 1, steps + 1))\n",
" for i in range(steps + 1):\n",
" price_tree[i, -1] = spot * (u ** (steps - i)) * (d**i)\n",
"\n",
" # Calculate the option value at each node\n",
"\n",
" option_tree = np.zeros_like(price_tree)\n",
" if option_type.lower() == \"call\":\n",
" option_tree[:, -1] = np.maximum(price_tree[:, -1] - strike, 0)\n",
" elif option_type.lower() == \"put\":\n",
" option_tree[:, -1] = np.maximum(strike - price_tree[:, -1], 0)\n",
" else:\n",
" raise ValueError(\"Option type must be either 'call' or 'put'.\")\n",
"\n",
" # Traverse the tree backward to find the option price today\n",
"\n",
" for t in range(steps - 1, -1, -1):\n",
" for i in range(t + 1):\n",
" exercise = 0\n",
" if option_type.lower() == \"call\":\n",
" exercise = price_tree[i, t] - strike\n",
" elif option_type.lower() == \"put\":\n",
" exercise = strike - price_tree[i, t]\n",
"\n",
" hold = np.exp(-rate * dt) * (\n",
" p * option_tree[i, t + 1] + (1 - p) * option_tree[i + 1, t + 1]\n",
" )\n",
" option_tree[i, t] = np.maximum(exercise, hold)\n",
"\n",
" return option_tree[0, 0]"
]
},
{
"cell_type": "markdown",
"id": "cba2ebed",
"metadata": {},
"source": [
"Estimate the price of an American Call option using the defined binomial model parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e615d29c",
"metadata": {},
"outputs": [],
"source": [
"option_price = american_option_pricer(\n",
" spot=55.0,\n",
" strike=50.0,\n",
" rate=0.05,\n",
" vol=0.3,\n",
" expiry=1.0,\n",
" steps=100,\n",
" option_type=\"Call\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a87537d8",
"metadata": {},
"source": [
"Print the estimated price of the American Call option"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5118d3b2",
"metadata": {},
"outputs": [],
"source": [
"print(\n",
" f\"The estimated price of the American Call option is: {option_price:.2f}\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "16e10849",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,307 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "639e1d89",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "0b7f6187",
"metadata": {},
"source": [
"This code downloads historical stock data for TLT, computes log returns, and analyzes mean returns by calendar day of the month. It visualizes these mean returns to identify any calendar effects. The code also simulates a simple trading strategy of buying near month-end and selling at month-start. It evaluates the strategy's performance by aggregating and plotting returns over time. This analysis helps in identifying potential trading opportunities based on calendar effects."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7817a7b",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import yfinance as yf"
]
},
{
"cell_type": "markdown",
"id": "58832881",
"metadata": {},
"source": [
"Download historical stock data for TLT from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8181d627",
"metadata": {},
"outputs": [],
"source": [
"tlt = yf.download(\"TLT\", start=\"2002-01-01\", end=\"2022-06-30\")"
]
},
{
"cell_type": "markdown",
"id": "19030c97",
"metadata": {},
"source": [
"Compute log returns for the adjusted closing prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c4a980db",
"metadata": {},
"outputs": [],
"source": [
"tlt[\"log_return\"] = np.log(tlt[\"Adj Close\"] / tlt[\"Adj Close\"].shift(1))"
]
},
{
"cell_type": "markdown",
"id": "3b1939d4",
"metadata": {},
"source": [
"Add a column indicating the day of the month for each data entry"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "09f90951",
"metadata": {},
"outputs": [],
"source": [
"tlt[\"day_of_month\"] = tlt.index.day"
]
},
{
"cell_type": "markdown",
"id": "2984311e",
"metadata": {},
"source": [
"Add a column indicating the year for each data entry"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "75dabddb",
"metadata": {},
"outputs": [],
"source": [
"tlt[\"year\"] = tlt.index.year"
]
},
{
"cell_type": "markdown",
"id": "8c32a1d9",
"metadata": {},
"source": [
"Group the data by the day of the month and compute the mean log returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "603cb273",
"metadata": {},
"outputs": [],
"source": [
"grouped_by_day = tlt.groupby(\"day_of_month\").log_return.mean()"
]
},
{
"cell_type": "markdown",
"id": "1b4f8529",
"metadata": {},
"source": [
"Plot the mean log returns by calendar day of the month"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "91560197",
"metadata": {},
"outputs": [],
"source": [
"grouped_by_day.plot.bar(title=\"Mean Log Returns by Calendar Day of Month\")"
]
},
{
"cell_type": "markdown",
"id": "140667ff",
"metadata": {},
"source": [
"Initialize a simple trading strategy of buying before month-end and selling at month-start"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b18f9df",
"metadata": {},
"outputs": [],
"source": [
"# Set initial returns for the first week of the month to zero\n",
"tlt[\"first_week_returns\"] = 0.0"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af93c085",
"metadata": {},
"outputs": [],
"source": [
"# Assign log returns to the first week of the month\n",
"tlt.loc[tlt.day_of_month <= 7, \"first_week_returns\"] = tlt[tlt.day_of_month <= 7].log_return"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f309aad",
"metadata": {},
"outputs": [],
"source": [
"# Set initial returns for the last week of the month to zero\n",
"tlt[\"last_week_returns\"] = 0.0"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2059ffd3",
"metadata": {},
"outputs": [],
"source": [
"# Assign log returns to the last week of the month\n",
"tlt.loc[tlt.day_of_month >= 23, \"last_week_returns\"] = tlt[tlt.day_of_month >= 23].log_return"
]
},
{
"cell_type": "markdown",
"id": "62b0088e",
"metadata": {},
"source": [
"Compute the difference between last week returns and first week returns to simulate the strategy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba54fbac",
"metadata": {},
"outputs": [],
"source": [
"tlt[\"last_week_less_first_week\"] = tlt.last_week_returns - tlt.first_week_returns"
]
},
{
"cell_type": "markdown",
"id": "faa8c041",
"metadata": {},
"source": [
"Group the data by year and plot the mean returns of the strategy for each year"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18bd50b3",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" tlt.groupby(\"year\")\n",
" .last_week_less_first_week.mean()\n",
" .plot.bar(title=\"Mean Log Strategy Returns by Year\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "5e426d67",
"metadata": {},
"source": [
"Group the data by year, compute cumulative sum of returns, and plot it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4deb5f87",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" tlt.groupby(\"year\")\n",
" .last_week_less_first_week.sum()\n",
" .cumsum()\n",
" .plot(title=\"Cumulative Sum of Returns By Year\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "6d5019f4",
"metadata": {},
"source": [
"Compute and plot the cumulative sum of returns by day"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fea7dc3f",
"metadata": {},
"outputs": [],
"source": [
"tlt.last_week_less_first_week.cumsum().plot(title=\"Cumulative Sum of Returns By Day\")"
]
},
{
"cell_type": "markdown",
"id": "6dc28f61",
"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
}

View File

@ -0,0 +1,209 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "eabb2e65",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "44c499db",
"metadata": {},
"source": [
"This code streams real-time option quotes using the ThetaData API to calculate and print straddle prices for a specific option contract. It defines a callback function to handle incoming quote messages and update the bid, ask, and mid prices of the straddle. The main function initializes the ThetaClient, connects to the stream, and requests quote updates for both call and put options. This setup is useful for traders and analysts monitoring option prices and straddle strategies in real-time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "db51b442",
"metadata": {},
"outputs": [],
"source": [
"import datetime as dt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "54c5fb62",
"metadata": {},
"outputs": [],
"source": [
"import thetadata.client\n",
"from thetadata import (\n",
" Quote,\n",
" StreamMsg,\n",
" ThetaClient,\n",
" OptionRight,\n",
" StreamMsgType\n",
")\n",
"from thetadata import StreamResponseType as ResponseType"
]
},
{
"cell_type": "markdown",
"id": "68ddfbd0",
"metadata": {},
"source": [
"Initialize last call and put quotes and price variable"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14880c60",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"last_call_quote = Quote()\n",
"last_put_quote = Quote()\n",
"price = 0"
]
},
{
"cell_type": "markdown",
"id": "9b319868",
"metadata": {},
"source": [
"Callback function to handle incoming stream messages and update straddle prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "65f8ab69",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def callback_straddle(msg):\n",
" \"\"\"Handles incoming stream messages to update straddle prices.\n",
" \n",
" Parameters\n",
" ----------\n",
" msg : StreamMsg\n",
" Incoming stream message containing quote data.\n",
" \n",
" Returns\n",
" -------\n",
" None\n",
" \"\"\"\n",
"\n",
" if (msg.type != StreamMsgType.QUOTE):\n",
" return\n",
"\n",
" if msg.contract.isCall:\n",
" last_call_quote.copy_from(msg.quote)\n",
" else:\n",
" last_put_quote.copy_from(msg.quote)\n",
"\n",
" straddle_bid = round(last_call_quote.bid_price + last_put_quote.bid_price, 2)\n",
" straddle_ask = round(last_call_quote.ask_price + last_put_quote.ask_price, 2)\n",
" straddle_mid = round((straddle_bid + straddle_ask) / 2, 2)\n",
" \n",
" time_stamp = thetadata.client.ms_to_time(\n",
" msg.quote.ms_of_day\n",
" )\n",
"\n",
" if price != straddle_mid:\n",
" print(\n",
" f\"time: {time_stamp} bid: {straddle_bid} mid: {straddle_mid} ask: {straddle_ask}\"\n",
" )\n",
" price = straddle_mid"
]
},
{
"cell_type": "markdown",
"id": "09157b67",
"metadata": {},
"source": [
"Main function to set up streaming for straddle prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d963b0f",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def streaming_straddle():\n",
" \"\"\"Streams option quotes to calculate straddle prices in real-time.\n",
" \n",
" Returns\n",
" -------\n",
" None\n",
" \"\"\"\n",
" \n",
" client = ThetaClient(\n",
" username=\"strimp101@gmail.com\",\n",
" passwd=\"kdk_fzu6pyb0UZA-yuz\"\n",
" )\n",
"\n",
" client.connect_stream(\n",
" callback_straddle\n",
" )\n",
" \n",
" req_id_call = client.req_quote_stream_opt(\n",
" \"SPY\", dt.date(2024, 3, 28), 475, OptionRight.CALL\n",
" ) # Request quote updates\n",
" \n",
" req_id_put = client.req_quote_stream_opt(\n",
" \"SPY\", dt.date(2024, 3, 28), 475, OptionRight.PUT\n",
" )\n",
"\n",
" if (\n",
" client.verify(req_id_call) != ResponseType.SUBSCRIBED\n",
" or client.verify(req_id_put) != ResponseType.SUBSCRIBED\n",
" ):\n",
" raise Exception(\n",
" \"Unable to stream contract. A standard/PRO subscription required.\"\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "f08a4bc5",
"metadata": {},
"source": [
"Call the main function to start streaming"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1bfbff7c",
"metadata": {},
"outputs": [],
"source": [
"streaming_straddle()"
]
},
{
"cell_type": "markdown",
"id": "74a5b687",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,655 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d2770d21",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "18f4d52f",
"metadata": {},
"source": [
"This code performs a walk-forward analysis using moving averages (MA) to optimize trading strategies. It splits historical stock prices into training and testing periods, runs simulations to find optimal MA parameters, and then tests the strategy on out-of-sample data. The code also evaluates the strategy's performance using the Sharpe ratio and compares it to a simple buy-and-hold strategy. Additionally, statistical tests are conducted to determine if the optimized strategy significantly outperforms the buy-and-hold approach. The results are visualized for further analysis."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b64f33c4",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import vectorbt as vbt\n",
"from datetime import datetime, timedelta\n",
"import scipy.stats as stats"
]
},
{
"cell_type": "markdown",
"id": "435ada84",
"metadata": {},
"source": [
"Create a date range index for the Series"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a98a4bb",
"metadata": {},
"outputs": [],
"source": [
"index = [datetime(2020, 1, 1) + timedelta(days=i) for i in range(10)]"
]
},
{
"cell_type": "markdown",
"id": "841c3cbf",
"metadata": {},
"source": [
"Create a pandas Series with the date index and sequential integer values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e00fdf4a",
"metadata": {},
"outputs": [],
"source": [
"sr = pd.Series(np.arange(len(index)), index=index)"
]
},
{
"cell_type": "markdown",
"id": "0f9ca485",
"metadata": {},
"source": [
"Perform a rolling split on the Series and plot the in-sample and out-sample periods"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c99f8c2d",
"metadata": {},
"outputs": [],
"source": [
"sr.vbt.rolling_split(\n",
" window_len=5, \n",
" set_lens=(1,), \n",
" left_to_right=False, \n",
" plot=True, \n",
" trace_names=['in_sample', 'out_sample']\n",
")"
]
},
{
"cell_type": "markdown",
"id": "6e767588",
"metadata": {},
"source": [
"Create a range of window sizes for moving averages to iterate through"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a1d2df1",
"metadata": {},
"outputs": [],
"source": [
"windows = np.arange(10, 50)"
]
},
{
"cell_type": "markdown",
"id": "1c454f62",
"metadata": {},
"source": [
"Download historical closing prices for Apple (AAPL) stock"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0f6d7a7",
"metadata": {},
"outputs": [],
"source": [
"price = vbt.YFData.download('AAPL').get('Close')"
]
},
{
"cell_type": "markdown",
"id": "4a49a8bb",
"metadata": {},
"source": [
"Perform a rolling split on the price data for walk-forward analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3966351e",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"(in_price, in_indexes), (out_price, out_indexes) = price.vbt.rolling_split(\n",
" n=30, \n",
" window_len=365 * 2,\n",
" set_lens=(180,),\n",
" left_to_right=False,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "971778fd",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def simulate_holding(price, **kwargs):\n",
" \"\"\"Returns Sharpe ratio for holding strategy\n",
" \n",
" Parameters\n",
" ----------\n",
" price : pd.Series\n",
" Historical price data\n",
" kwargs : dict\n",
" Additional arguments for the Portfolio function\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" Sharpe ratio of the holding strategy\n",
" \"\"\"\n",
" \n",
" # Run a backtest for holding the asset and return the Sharpe ratio\n",
" pf = vbt.Portfolio.from_holding(price, **kwargs)\n",
" return pf.sharpe_ratio()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5fad1f2c",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def simulate_all_params(price, windows, **kwargs):\n",
" \"\"\"Returns Sharpe ratio for all parameter combinations\n",
" \n",
" Parameters\n",
" ----------\n",
" price : pd.Series\n",
" Historical price data\n",
" windows : iterable\n",
" Range of window sizes for moving averages\n",
" kwargs : dict\n",
" Additional arguments for the Portfolio function\n",
" \n",
" Returns\n",
" -------\n",
" pd.Series\n",
" Sharpe ratios for all parameter combinations\n",
" \"\"\"\n",
" \n",
" # Run combinations of moving averages for all window sizes\n",
" fast_ma, slow_ma = vbt.MA.run_combs(\n",
" price, windows, r=2, short_names=[\"fast\", \"slow\"]\n",
" )\n",
" \n",
" # Generate entry signals when fast MA crosses above slow MA\n",
" entries = fast_ma.ma_crossed_above(slow_ma)\n",
" \n",
" # Generate exit signals when fast MA crosses below slow MA\n",
" exits = fast_ma.ma_crossed_below(slow_ma)\n",
" \n",
" # Run the backtest and return the Sharpe ratio\n",
" pf = vbt.Portfolio.from_signals(price, entries, exits, **kwargs)\n",
" return pf.sharpe_ratio()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "020c7750",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def get_best_index(performance, higher_better=True):\n",
" \"\"\"Returns the best performing index\n",
" \n",
" Parameters\n",
" ----------\n",
" performance : pd.Series\n",
" Performance metrics for each split\n",
" higher_better : bool, optional\n",
" Whether higher values are better, by default True\n",
" \n",
" Returns\n",
" -------\n",
" pd.Index\n",
" Index of the best performing parameters\n",
" \"\"\"\n",
" \n",
" if higher_better:\n",
" return performance[performance.groupby('split_idx').idxmax()].index\n",
" return performance[performance.groupby('split_idx').idxmin()].index"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7e944870",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def get_best_params(best_index, level_name):\n",
" \"\"\"Returns the best parameters\n",
" \n",
" Parameters\n",
" ----------\n",
" best_index : pd.Index\n",
" Index of the best performing parameters\n",
" level_name : str\n",
" Name of the level to extract values from\n",
" \n",
" Returns\n",
" -------\n",
" np.ndarray\n",
" Best parameter values\n",
" \"\"\"\n",
" \n",
" return best_index.get_level_values(level_name).to_numpy()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2c0a7d4e",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def simulate_best_params(price, best_fast_windows, best_slow_windows, **kwargs):\n",
" \"\"\"Returns Sharpe ratio for best parameters\n",
" \n",
" Parameters\n",
" ----------\n",
" price : pd.Series\n",
" Historical price data\n",
" best_fast_windows : np.ndarray\n",
" Best fast moving average windows\n",
" best_slow_windows : np.ndarray\n",
" Best slow moving average windows\n",
" kwargs : dict\n",
" Additional arguments for the Portfolio function\n",
" \n",
" Returns\n",
" -------\n",
" pd.Series\n",
" Sharpe ratios for the best parameters\n",
" \"\"\"\n",
" \n",
" # Run the moving average indicators with the best parameters\n",
" fast_ma = vbt.MA.run(price, window=best_fast_windows, per_column=True)\n",
" slow_ma = vbt.MA.run(price, window=best_slow_windows, per_column=True)\n",
" \n",
" # Generate entry signals when fast MA crosses above slow MA\n",
" entries = fast_ma.ma_crossed_above(slow_ma)\n",
" \n",
" # Generate exit signals when fast MA crosses below slow MA\n",
" exits = fast_ma.ma_crossed_below(slow_ma)\n",
" \n",
" # Run the backtest and return the Sharpe ratio\n",
" pf = vbt.Portfolio.from_signals(price, entries, exits, **kwargs)\n",
" return pf.sharpe_ratio()"
]
},
{
"cell_type": "markdown",
"id": "8700077f",
"metadata": {},
"source": [
"Get the Sharpe ratio of the strategy across all MA windows for in-sample data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1a716951",
"metadata": {},
"outputs": [],
"source": [
"in_sharpe = simulate_all_params(\n",
" in_price, \n",
" windows, \n",
" direction=\"both\", \n",
" freq=\"d\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d87dbf9c",
"metadata": {},
"source": [
"Find the best performing parameter index for in-sample data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c423f29",
"metadata": {},
"outputs": [],
"source": [
"in_best_index = get_best_index(in_sharpe)"
]
},
{
"cell_type": "markdown",
"id": "1331309d",
"metadata": {},
"source": [
"Extract the best fast and slow moving average window values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "603dcbf3",
"metadata": {},
"outputs": [],
"source": [
"in_best_fast_windows = get_best_params(\n",
" in_best_index,\n",
" 'fast_window'\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4f078e7d",
"metadata": {},
"outputs": [],
"source": [
"in_best_slow_windows = get_best_params(\n",
" in_best_index,\n",
" 'slow_window'\n",
")"
]
},
{
"cell_type": "markdown",
"id": "2f629c91",
"metadata": {},
"source": [
"Pair the best fast and slow moving average windows"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a2c767ec",
"metadata": {},
"outputs": [],
"source": [
"in_best_window_pairs = np.array(\n",
" list(\n",
" zip(\n",
" in_best_fast_windows, \n",
" in_best_slow_windows\n",
" )\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"id": "9ff01a15",
"metadata": {},
"source": [
"Use best parameters from in-sample ranges and simulate them for out-sample ranges"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5f1762ea",
"metadata": {},
"outputs": [],
"source": [
"out_test_sharpe = simulate_best_params(\n",
" out_price, \n",
" in_best_fast_windows, \n",
" in_best_slow_windows, \n",
" direction=\"both\", \n",
" freq=\"d\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "24130e6f",
"metadata": {},
"source": [
"Extract the best in-sample Sharpe ratios"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9038e60e",
"metadata": {},
"outputs": [],
"source": [
"in_sample_best = in_sharpe[in_best_index].values"
]
},
{
"cell_type": "markdown",
"id": "a9323f92",
"metadata": {},
"source": [
"Extract the out-sample Sharpe ratios"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "92069cbb",
"metadata": {},
"outputs": [],
"source": [
"out_sample_test = out_test_sharpe.values"
]
},
{
"cell_type": "markdown",
"id": "b699af77",
"metadata": {},
"source": [
"Perform a t-test to compare in-sample and out-sample performance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "75e3aa28",
"metadata": {},
"outputs": [],
"source": [
"t, p = stats.ttest_ind(\n",
" a=out_sample_test,\n",
" b=in_sample_best,\n",
" alternative=\"greater\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a7fba885",
"metadata": {},
"source": [
"Check if the p-value is greater than 0.05 to determine statistical significance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "98184273",
"metadata": {},
"outputs": [],
"source": [
"p > 0.05"
]
},
{
"cell_type": "markdown",
"id": "430c76ed",
"metadata": {},
"source": [
"Print the t-statistic and p-value"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9c7f0dcd",
"metadata": {},
"outputs": [],
"source": [
"t, p"
]
},
{
"cell_type": "markdown",
"id": "b93cfef7",
"metadata": {},
"source": [
"Plot the out-sample performance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9ef9f240",
"metadata": {},
"outputs": [],
"source": [
"out_sample.plot()"
]
},
{
"cell_type": "markdown",
"id": "5cc318cf",
"metadata": {},
"source": [
"Create a DataFrame to store cross-validation results"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e014c89",
"metadata": {},
"outputs": [],
"source": [
"cv_results_df = pd.DataFrame({\n",
" 'in_sample_median': in_sharpe.groupby('split_idx').median().values,\n",
" 'out_sample_median': out_sharpe.groupby('split_idx').median().values,\n",
"})"
]
},
{
"cell_type": "markdown",
"id": "5fd42853",
"metadata": {},
"source": [
"Plot the cross-validation results"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "227948a3",
"metadata": {},
"outputs": [],
"source": [
"color_schema = vbt.settings['plotting']['color_schema']"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f297af27",
"metadata": {},
"outputs": [],
"source": [
"cv_results_df.vbt.plot(\n",
" trace_kwargs=[\n",
" dict(line_color=color_schema['blue']),\n",
" dict(line_color=color_schema['blue'], line_dash='dash'),\n",
" dict(line_color=color_schema['orange']),\n",
" dict(line_color=color_schema['orange'], line_dash='dash'),\n",
" ]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "fd55424d",
"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
}

View File

@ -0,0 +1,332 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "7a0be8d0",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "9159b91e",
"metadata": {},
"source": [
"This code uses the OpenBB Terminal and Riskfolio libraries to create and optimize a financial portfolio based on historical stock data. It first screens for stocks hitting new highs, filters them, and retrieves their historical prices. It calculates the returns, uses them to create a portfolio, and applies mean-variance optimization to determine the optimal asset allocation. The results are then visualized and detailed with the number of shares to purchase based on a specified investment amount."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef4428e3",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb, TerminalStyle\n",
"theme = TerminalStyle(\"light\", \"light\", \"light\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "08c0afb0",
"metadata": {},
"outputs": [],
"source": [
"import riskfolio as rp"
]
},
{
"cell_type": "markdown",
"id": "4f3bdc68",
"metadata": {},
"source": [
"Screen for stocks hitting new highs using OpenBB Terminal."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "106cb79a",
"metadata": {},
"outputs": [],
"source": [
"new_highs = openbb.stocks.screener.screener_data(\"new_high\")"
]
},
{
"cell_type": "markdown",
"id": "6a71c50c",
"metadata": {},
"source": [
"Filter stocks with a price above $15 and located in the USA."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d2f32e31",
"metadata": {},
"outputs": [],
"source": [
"port_data = new_highs[\n",
" (new_highs.Price > 15) & (new_highs.Country == \"USA\")\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "0109063b",
"metadata": {},
"source": [
"Retrieve the list of ticker symbols from the filtered data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aea54a4d",
"metadata": {},
"outputs": [],
"source": [
"tickers = port_data.Ticker.tolist()"
]
},
{
"cell_type": "markdown",
"id": "2f647c98",
"metadata": {},
"source": [
"Fetch historical price data for the selected tickers from 2016-01-01 to 2022-12-31."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5adbca4a",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.economy.index(\n",
" tickers, \n",
" start_date=\"2016-01-01\", \n",
" end_date=\"2022-12-31\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "76163388",
"metadata": {},
"source": [
"Calculate the daily percentage returns of the stock prices and drop columns with any NaN values."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "785d23f5",
"metadata": {},
"outputs": [],
"source": [
"returns = data.pct_change()[1:]\n",
"returns.dropna(how=\"any\", axis=1, inplace=True)"
]
},
{
"cell_type": "markdown",
"id": "35afdcfe",
"metadata": {},
"source": [
"Create a portfolio object using the calculated returns."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c92245e0",
"metadata": {},
"outputs": [],
"source": [
"port = rp.Portfolio(returns=returns)"
]
},
{
"cell_type": "markdown",
"id": "c06747d1",
"metadata": {},
"source": [
"Compute asset statistics such as historical mean and covariance."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ecfd866",
"metadata": {},
"outputs": [],
"source": [
"port.assets_stats(method_mu='hist', method_cov='hist', d=0.94)"
]
},
{
"cell_type": "markdown",
"id": "b7b7529f",
"metadata": {},
"source": [
"Set the lower return constraint for the optimization problem."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "808168b2",
"metadata": {},
"outputs": [],
"source": [
"port.lowerret = 0.0008"
]
},
{
"cell_type": "markdown",
"id": "a7fed1c3",
"metadata": {},
"source": [
"Perform mean-variance optimization using historical data and no risk-free rate."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "85a8ec29",
"metadata": {},
"outputs": [],
"source": [
"w_rp_c = port.rp_optimization(\n",
" model=\"Classic\", # use historical\n",
" rm=\"MV\", # use mean-variance optimization\n",
" hist=True, # use historical scenarios\n",
" rf=0, # set risk-free rate to 0\n",
" b=None # don't use constraints\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e7df27bc",
"metadata": {},
"source": [
"Plot the optimized portfolio allocation as a pie chart."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "22d9a70c",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_pie(w=w_rp_c)"
]
},
{
"cell_type": "markdown",
"id": "78e3c04c",
"metadata": {},
"source": [
"Plot the risk contributions of each asset in the portfolio."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3d2307b",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_risk_con(\n",
" w_rp_c,\n",
" cov=port.cov,\n",
" returns=port.returns,\n",
" rm=\"MV\",\n",
" rf=0,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e077bdaf",
"metadata": {},
"source": [
"Calculate the investment amount and number of shares to purchase for each asset."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e23ce3c7",
"metadata": {},
"outputs": [],
"source": [
"port_val = 10_000\n",
"w_rp_c[\"invest_amt\"] = w_rp_c * port_val\n",
"w_rp_c[\"last_price\"] = data.iloc[-1]\n",
"w_rp_c[\"shares\"] = (w_rp_c.invest_amt / w_rp_c.last_price).astype(int)"
]
},
{
"cell_type": "markdown",
"id": "086649c5",
"metadata": {},
"source": [
"Display the optimized portfolio with investment amounts and number of shares."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0ce0af32",
"metadata": {},
"outputs": [],
"source": [
"w_rp_c"
]
},
{
"cell_type": "markdown",
"id": "4c5e18cb",
"metadata": {},
"source": [
"Sort the portfolio by asset weights in ascending order."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "707e8c1f",
"metadata": {},
"outputs": [],
"source": [
"w_rp_c.sort_values(by=\"weights\")"
]
},
{
"cell_type": "markdown",
"id": "ed9a0034",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,478 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "abad0a97",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "b1e037dc",
"metadata": {},
"source": [
"This code implements a trading algorithm using the Zipline library in Python. It defines a custom momentum factor and a pipeline to rank stocks based on their momentum. The algorithm schedules rebalancing every week and executes trades based on the ranked stocks. The performance of the algorithm is then simulated over a specified time period. This is useful for backtesting trading strategies and analyzing their performance."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19b48d3d",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12999480",
"metadata": {},
"outputs": [],
"source": [
"from zipline import run_algorithm\n",
"from zipline.pipeline import Pipeline, CustomFactor\n",
"from zipline.pipeline.data import USEquityPricing\n",
"from zipline.api import (\n",
" attach_pipeline,\n",
" calendars,\n",
" pipeline_output,\n",
" date_rules,\n",
" time_rules,\n",
" set_commission,\n",
" set_slippage,\n",
" record,\n",
" order_target_percent,\n",
" get_open_orders,\n",
" schedule_function\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d81d298",
"metadata": {},
"outputs": [],
"source": [
"import re"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "60cdbb78",
"metadata": {},
"outputs": [],
"source": [
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "b7c0cdd7",
"metadata": {},
"source": [
"Define the number of stocks to long and short"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a32b948a",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"N_LONGS = N_SHORTS = 10"
]
},
{
"cell_type": "markdown",
"id": "40820869",
"metadata": {},
"source": [
"Define a custom factor for momentum calculation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e85c8b94",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"class Momentum(CustomFactor):\n",
" \"\"\"Calculate momentum as the ratio of the last to first price.\n",
"\n",
" This class calculates momentum by dividing the last closing price\n",
" by the first closing price over a window.\n",
"\n",
" Attributes\n",
" ----------\n",
" inputs : list\n",
" Data inputs for the factor, here the closing prices.\n",
" window_length : int\n",
" Length of the window to compute momentum.\n",
" \"\"\"\n",
"\n",
" inputs = [USEquityPricing.close]\n",
"\n",
" def compute(self, today, assets, out, close):\n",
" \"\"\"Compute the momentum factor.\n",
"\n",
" Parameters\n",
" ----------\n",
" today : datetime\n",
" Current date.\n",
" assets : iterable\n",
" List of asset identifiers.\n",
" out : ndarray\n",
" Output array to store computed momentum values.\n",
" close : ndarray\n",
" Array of closing prices over the window length.\n",
" \"\"\"\n",
" out[:] = close[-1] / close[0]"
]
},
{
"cell_type": "markdown",
"id": "75e25588",
"metadata": {},
"source": [
"Construct a pipeline to filter and rank stocks based on momentum"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e8c181d4",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def make_pipeline():\n",
" \"\"\"Create a pipeline for ranking stocks based on momentum.\n",
"\n",
" This function defines a pipeline that filters stocks with positive\n",
" momentum and ranks them.\n",
"\n",
" Returns\n",
" -------\n",
" Pipeline\n",
" A Zipline Pipeline object.\n",
" \"\"\"\n",
"\n",
" twenty_day_momentum = Momentum(window_length=20)\n",
" thirty_day_momentum = Momentum(window_length=30)\n",
"\n",
" positive_momentum = (\n",
" (twenty_day_momentum > 1) & \n",
" (thirty_day_momentum > 1)\n",
" )\n",
"\n",
" return Pipeline(\n",
" columns={\n",
" 'longs': thirty_day_momentum.top(N_LONGS),\n",
" 'shorts': thirty_day_momentum.top(N_SHORTS),\n",
" 'ranking': twenty_day_momentum.rank(ascending=False)\n",
" },\n",
" screen=positive_momentum\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "a0aa83e7",
"metadata": {},
"source": [
"Function called before the trading day starts to fetch pipeline output"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18c012bc",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def before_trading_start(context, data):\n",
" \"\"\"Fetch pipeline output before trading starts.\n",
"\n",
" This function is called before the trading day begins to fetch\n",
" the output of the pipeline.\n",
"\n",
" Parameters\n",
" ----------\n",
" context : object\n",
" An object to store global variables.\n",
" data : object\n",
" An object to fetch data.\n",
" \"\"\"\n",
" context.factor_data = pipeline_output(\"factor_pipeline\")"
]
},
{
"cell_type": "markdown",
"id": "2294da67",
"metadata": {},
"source": [
"Initialize the algorithm by attaching the pipeline and scheduling rebalancing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1d3b17c6",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def initialize(context):\n",
" \"\"\"Initialize the trading algorithm.\n",
"\n",
" This function sets up the pipeline, schedules the rebalancing\n",
" function, and initializes other settings.\n",
"\n",
" Parameters\n",
" ----------\n",
" context : object\n",
" An object to store global variables.\n",
" \"\"\"\n",
" attach_pipeline(make_pipeline(), \"factor_pipeline\")\n",
" schedule_function(\n",
" rebalance,\n",
" date_rules.week_start(),\n",
" time_rules.market_open(),\n",
" calendar=calendars.US_EQUITIES,\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "2a56d8fe",
"metadata": {},
"source": [
"Function to rebalance the portfolio based on pipeline output"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "96a3e1d5",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def rebalance(context, data):\n",
" \"\"\"Rebalance the portfolio based on pipeline output.\n",
"\n",
" This function rebalances the portfolio by executing trades\n",
" according to the ranked stocks from the pipeline.\n",
"\n",
" Parameters\n",
" ----------\n",
" context : object\n",
" An object to store global variables.\n",
" data : object\n",
" An object to fetch data.\n",
" \"\"\"\n",
" factor_data = context.factor_data\n",
" record(factor_data=factor_data.ranking)\n",
"\n",
" assets = factor_data.index\n",
" record(prices=data.current(assets, 'price'))\n",
"\n",
" longs = assets[factor_data.longs]\n",
" shorts = assets[factor_data.shorts]\n",
" divest = set(context.portfolio.positions.keys()) - set(longs.union(shorts))\n",
"\n",
" exec_trades(data, assets=divest, target_percent=0)\n",
" exec_trades(data, assets=longs, target_percent=1 / N_LONGS)\n",
" exec_trades(data, assets=shorts, target_percent=-1 / N_SHORTS)"
]
},
{
"cell_type": "markdown",
"id": "8191036c",
"metadata": {},
"source": [
"Function to execute trades for given assets and target percentages"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c071a6b",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def exec_trades(data, assets, target_percent):\n",
" \"\"\"Execute trades for given assets and target percentages.\n",
"\n",
" This function loops through each asset and executes trades\n",
" if the asset is tradeable and has no open orders.\n",
"\n",
" Parameters\n",
" ----------\n",
" data : object\n",
" An object to fetch data.\n",
" assets : iterable\n",
" List of asset identifiers.\n",
" target_percent : float\n",
" Target portfolio weight for each asset.\n",
" \"\"\"\n",
" for asset in assets:\n",
" if data.can_trade(asset) and not get_open_orders(asset):\n",
" order_target_percent(asset, target_percent)"
]
},
{
"cell_type": "markdown",
"id": "3078e517",
"metadata": {},
"source": [
"Define the start and end dates for the backtest simulation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "545a10c5",
"metadata": {},
"outputs": [],
"source": [
"start = pd.Timestamp('2016')\n",
"end = pd.Timestamp('2018')"
]
},
{
"cell_type": "markdown",
"id": "89fb2b32",
"metadata": {},
"source": [
"Run the algorithm with the defined parameters and fetch performance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8486b932",
"metadata": {},
"outputs": [],
"source": [
"perf = run_algorithm(\n",
" start=start,\n",
" end=end,\n",
" initialize=initialize,\n",
" before_trading_start=before_trading_start,\n",
" capital_base=100_000,\n",
" bundle=\"quandl\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d8cb0a55",
"metadata": {},
"source": [
"Output the final portfolio value after the simulation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "76abaa0e",
"metadata": {},
"outputs": [],
"source": [
"perf.portfolio_value"
]
},
{
"cell_type": "markdown",
"id": "e0c61cb6",
"metadata": {},
"source": [
"Construct a DataFrame with symbols as columns and dates as rows"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "982e3994",
"metadata": {},
"outputs": [],
"source": [
"prices = pd.concat([df.to_frame(d) for d, df in perf.prices.dropna().items()], axis=1).T"
]
},
{
"cell_type": "markdown",
"id": "077d7376",
"metadata": {},
"source": [
"Convert column names to strings"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3941c22c",
"metadata": {},
"outputs": [],
"source": [
"prices.columns = [col.symbol for col in prices.columns]"
]
},
{
"cell_type": "markdown",
"id": "0c7c8b43",
"metadata": {},
"source": [
"Normalize Timestamp to midnight, preserving TZ information"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "108487c4",
"metadata": {},
"outputs": [],
"source": [
"prices.index = prices.index.normalize()"
]
},
{
"cell_type": "markdown",
"id": "31255068",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,729 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "af91626b",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "42f5827b",
"metadata": {},
"source": [
"This notebook implements a momentum-based trading strategy using Zipline and performs performance analysis with Alphalens. It defines custom factors to measure stock momentum and uses these factors to build a trading pipeline. The strategy is then backtested over a specified period, and results are analyzed using information coefficients and other metrics. This is useful for quantitative trading, backtesting, and performance evaluation of trading strategies."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45bbe3ff",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"from scipy import stats\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f537421d",
"metadata": {},
"outputs": [],
"source": [
"from zipline import run_algorithm\n",
"from zipline.pipeline import Pipeline, CustomFactor\n",
"from zipline.pipeline.data import USEquityPricing\n",
"from zipline.api import (\n",
" attach_pipeline,\n",
" calendars,\n",
" pipeline_output,\n",
" date_rules,\n",
" time_rules,\n",
" set_commission,\n",
" set_slippage,\n",
" record,\n",
" order_target_percent,\n",
" get_open_orders,\n",
" schedule_function\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8182c403",
"metadata": {},
"outputs": [],
"source": [
"from alphalens.utils import (\n",
" get_clean_factor_and_forward_returns,\n",
" get_forward_returns_columns\n",
")\n",
"from alphalens.plotting import plot_ic_ts\n",
"from alphalens.performance import factor_information_coefficient, mean_information_coefficient"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "16f879d5",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "9aa346e9",
"metadata": {},
"source": [
"Define the number of long and short positions to hold in the portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5a639c8",
"metadata": {},
"outputs": [],
"source": [
"N_LONGS = N_SHORTS = 10"
]
},
{
"cell_type": "markdown",
"id": "cd497509",
"metadata": {},
"source": [
"Define a custom factor class to compute momentum"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f9d77ed6",
"metadata": {},
"outputs": [],
"source": [
"class Momentum(CustomFactor):\n",
" \"\"\"Computes momentum factor for assets.\"\"\"\n",
" \n",
" inputs = [USEquityPricing.close]\n",
"\n",
" def compute(self, today, assets, out, close):\n",
" \"\"\"Computes momentum as the ratio of latest to earliest closing price over the window.\"\"\"\n",
" out[:] = close[-1] / close[0]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17b1ae3f",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "977f61af",
"metadata": {},
"source": [
"Define a pipeline to fetch and filter stocks based on momentum"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea9970cd",
"metadata": {},
"outputs": [],
"source": [
"def make_pipeline():\n",
" \"\"\"Creates a pipeline for fetching and filtering stocks based on momentum.\"\"\"\n",
" \n",
" twenty_day_momentum = Momentum(window_length=20)\n",
" thirty_day_momentum = Momentum(window_length=30)\n",
"\n",
" positive_momentum = (\n",
" (twenty_day_momentum > 1) & \n",
" (thirty_day_momentum > 1)\n",
" )\n",
"\n",
" return Pipeline(\n",
" columns={\n",
" 'longs': thirty_day_momentum.top(N_LONGS),\n",
" 'shorts': thirty_day_momentum.top(N_SHORTS),\n",
" 'ranking': thirty_day_momentum.rank(ascending=False)\n",
" },\n",
" screen=positive_momentum\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1c8c73f8",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "1494ac97",
"metadata": {},
"source": [
"Define a function that runs before the trading day starts"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ac225d0c",
"metadata": {},
"outputs": [],
"source": [
"def before_trading_start(context, data):\n",
" \"\"\"Fetches factor data before trading starts.\"\"\"\n",
" \n",
" context.factor_data = pipeline_output(\"factor_pipeline\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c7b1854b",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "f1bd1bb6",
"metadata": {},
"source": [
"Define the initialize function to set up the algorithm"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cf08b200",
"metadata": {},
"outputs": [],
"source": [
"def initialize(context):\n",
" \"\"\"Initializes the trading algorithm and schedules rebalancing.\"\"\"\n",
" \n",
" attach_pipeline(make_pipeline(), \"factor_pipeline\")\n",
" schedule_function(\n",
" rebalance,\n",
" date_rules.week_start(),\n",
" time_rules.market_open(),\n",
" calendar=calendars.US_EQUITIES,\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cda91bd5",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "a1eebf33",
"metadata": {},
"source": [
"Define the rebalancing function to execute trades based on factor data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd4c8964",
"metadata": {},
"outputs": [],
"source": [
"def rebalance(context, data):\n",
" \"\"\"Rebalances the portfolio based on factor data.\"\"\"\n",
" \n",
" factor_data = context.factor_data\n",
" record(factor_data=factor_data.ranking)\n",
"\n",
" assets = factor_data.index\n",
" record(prices=data.current(assets, 'price'))\n",
"\n",
" longs = assets[factor_data.longs]\n",
" shorts = assets[factor_data.shorts]\n",
" divest = set(context.portfolio.positions.keys()) - set(longs.union(shorts))\n",
"\n",
" exec_trades(data, assets=divest, target_percent=0)\n",
" exec_trades(data, assets=longs, target_percent=1 / N_LONGS)\n",
" exec_trades(data, assets=shorts, target_percent=-1 / N_SHORTS)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "25e1653e",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "ae353e46",
"metadata": {},
"source": [
"Define a function to execute trades for given assets and target percentages"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d55bfdf",
"metadata": {},
"outputs": [],
"source": [
"def exec_trades(data, assets, target_percent):\n",
" \"\"\"Executes trades for given assets to achieve target portfolio weights.\"\"\"\n",
" \n",
" for asset in assets:\n",
" if data.can_trade(asset) and not get_open_orders(asset):\n",
" order_target_percent(asset, target_percent)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b8b6b5a",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "17866c5a",
"metadata": {},
"source": [
"Define the backtesting period"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cffe0933",
"metadata": {},
"outputs": [],
"source": [
"start = pd.Timestamp('2015')\n",
"end = pd.Timestamp('2018')"
]
},
{
"cell_type": "markdown",
"id": "9cc5a44e",
"metadata": {},
"source": [
"Run the backtest using the specified start and end dates"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9bdcefdf",
"metadata": {},
"outputs": [],
"source": [
"perf = run_algorithm(\n",
" start=start,\n",
" end=end,\n",
" initialize=initialize,\n",
" before_trading_start=before_trading_start,\n",
" capital_base=100_000,\n",
" bundle=\"quandl\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "1b744283",
"metadata": {},
"source": [
"Get the final portfolio value"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe5fc4f4",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"perf.portfolio_value"
]
},
{
"cell_type": "markdown",
"id": "76841808",
"metadata": {},
"source": [
"Construct a DataFrame with stock prices and corresponding dates"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d155f3a5",
"metadata": {},
"outputs": [],
"source": [
"prices = pd.concat(\n",
" [df.to_frame(d) for d, df in perf.prices.dropna().items()], \n",
" axis=1\n",
").T"
]
},
{
"cell_type": "markdown",
"id": "1b843ad7",
"metadata": {},
"source": [
"Convert column names to stock symbols"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d2c08533",
"metadata": {},
"outputs": [],
"source": [
"prices.columns = [col.symbol for col in prices.columns]"
]
},
{
"cell_type": "markdown",
"id": "80f6ea3a",
"metadata": {},
"source": [
"Normalize the index to midnight, preserving timezone information"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f0e039f8",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"prices.index = prices.index.normalize()"
]
},
{
"cell_type": "markdown",
"id": "024c97d3",
"metadata": {},
"source": [
"Construct a DataFrame with factor ranks and corresponding dates"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eca244f6",
"metadata": {},
"outputs": [],
"source": [
"factor_data = pd.concat(\n",
" [df.to_frame(d) for d, df in perf.factor_data.dropna().items()],\n",
" axis=1\n",
").T"
]
},
{
"cell_type": "markdown",
"id": "37bcda09",
"metadata": {},
"source": [
"Convert column names to stock symbols"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e14e9b5",
"metadata": {},
"outputs": [],
"source": [
"factor_data.columns = [col.symbol for col in factor_data.columns]"
]
},
{
"cell_type": "markdown",
"id": "e0827b1d",
"metadata": {},
"source": [
"Normalize the index to midnight, preserving timezone information"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "50c1200d",
"metadata": {},
"outputs": [],
"source": [
"factor_data.index = factor_data.index.normalize()"
]
},
{
"cell_type": "markdown",
"id": "6ecbce84",
"metadata": {},
"source": [
"Create a MultiIndex with date and asset"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "79888180",
"metadata": {},
"outputs": [],
"source": [
"factor_data = factor_data.stack()"
]
},
{
"cell_type": "markdown",
"id": "675d5dc9",
"metadata": {},
"source": [
"Rename the MultiIndex levels"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2050795f",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"factor_data.index.names = ['date', 'asset']"
]
},
{
"cell_type": "markdown",
"id": "d6a5240f",
"metadata": {},
"source": [
"Compile forward returns, factor ranks, and factor quantiles"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe1db4bd",
"metadata": {},
"outputs": [],
"source": [
"alphalens_data = get_clean_factor_and_forward_returns(\n",
" factor=factor_data, prices=prices, periods=(5, 10, 21, 63), quantiles=5\n",
")"
]
},
{
"cell_type": "markdown",
"id": "cda0df84",
"metadata": {},
"source": [
"Display the compiled Alphalens data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "70e9a889",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"alphalens_data"
]
},
{
"cell_type": "markdown",
"id": "38144b65",
"metadata": {},
"source": [
"Generate the information coefficient for each holding period on each date"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3af97fa7",
"metadata": {
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"ic = factor_information_coefficient(alphalens_data)"
]
},
{
"cell_type": "markdown",
"id": "b256162d",
"metadata": {},
"source": [
"Calculate the mean information coefficient over all periods"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "254c0617",
"metadata": {},
"outputs": [],
"source": [
"ii = mean_information_coefficient(alphalens_data)"
]
},
{
"cell_type": "markdown",
"id": "3007dad5",
"metadata": {},
"source": [
"Plot the mean information coefficient"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ba48590",
"metadata": {},
"outputs": [],
"source": [
"ii.plot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "56d5a42a",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "f12e37c8",
"metadata": {},
"source": [
"Plot the information coefficient for the 5-day holding period"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "56d4b894",
"metadata": {},
"outputs": [],
"source": [
"plot_ic_ts(ic[[\"5D\"]])\n",
"plt.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "b24451da",
"metadata": {},
"source": [
"Calculate mean information coefficient per holding period per year"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dda93f62",
"metadata": {},
"outputs": [],
"source": [
"ic_by_year = ic.resample('A').mean()\n",
"ic_by_year.index = ic_by_year.index.year"
]
},
{
"cell_type": "markdown",
"id": "a2167b6a",
"metadata": {},
"source": [
"Plot the mean information coefficient by year"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1ea9e9bb",
"metadata": {},
"outputs": [],
"source": [
"ic_by_year.plot.bar(figsize=(14, 6))\n",
"plt.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "2003869d",
"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
}

View File

@ -0,0 +1,293 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "daa349e7",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "47f62676",
"metadata": {},
"source": [
"This code fetches historical futures data for specific indices, calculates their percentage changes, and creates two different portfolios. It then computes the annualized return and Calmar ratio for each portfolio. The annualized return provides a measure of the portfolio's performance over time. The Calmar ratio assesses the risk-adjusted return by comparing the annualized return to the maximum drawdown. This analysis helps in understanding and comparing the performance and risk of different investment portfolios."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4d4b6bf1",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b74585c6",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "f5bb635c",
"metadata": {},
"source": [
"Fetch historical futures data for specified indices from OpenBB"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a9449129",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.futures.historical(\n",
" [\"ES\", \"YM\", \"NQ\"], \n",
" start_date=\"2020-01-01\", \n",
" end_date=\"2022-12-31\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "b15bfced",
"metadata": {},
"source": [
"Calculate percentage changes of adjusted closing prices and drop NaN values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "388d9c35",
"metadata": {},
"outputs": [],
"source": [
"futs = data['Adj Close'].pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "0fb9dbb0",
"metadata": {},
"source": [
"Create first portfolio with specified weights for each index"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5e04783d",
"metadata": {},
"outputs": [],
"source": [
"port_1 = futs.ES * 0.60 + futs.YM * 0.10 + futs.NQ * 0.10"
]
},
{
"cell_type": "markdown",
"id": "edbeec72",
"metadata": {},
"source": [
"Create second portfolio with different weights for each index"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d1166c30",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"port_2 = futs.ES * 0.90 + futs.YM * 0.15 + futs.NQ * 0.15"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d94715de",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def ann_return(returns):\n",
" \"\"\"Calculate annualized return of a portfolio\n",
" \n",
" Parameters\n",
" ----------\n",
" returns : pd.Series\n",
" Daily returns of the portfolio\n",
" \n",
" Returns\n",
" -------\n",
" ann_return : float\n",
" Annualized return of the portfolio\n",
" \n",
" Notes\n",
" -----\n",
" This method computes the annualized return \n",
" using the geometric mean of daily returns.\n",
" \"\"\"\n",
" \n",
" # Compute the ending value of the investment\n",
" ending_value = (returns + 1).prod()\n",
" \n",
" # Calculate the number of years based on daily returns\n",
" num_years = len(returns) / 252\n",
" \n",
" # Calculate annualized return\n",
" ann_return = ending_value ** (1/num_years) - 1\n",
" \n",
" return ann_return"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bfa077cb",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def calmar_ratio(returns):\n",
" \"\"\"Calculate Calmar ratio of a portfolio\n",
" \n",
" Parameters\n",
" ----------\n",
" returns : pd.Series\n",
" Daily returns of the portfolio\n",
" \n",
" Returns\n",
" -------\n",
" calmar_ratio : float\n",
" Calmar ratio of the portfolio\n",
" \n",
" Notes\n",
" -----\n",
" This method computes the Calmar ratio by \n",
" dividing the annualized return by the \n",
" absolute value of maximum drawdown.\n",
" \"\"\"\n",
" \n",
" # Compute cumulative returns\n",
" cumulative_returns = (returns + 1).cumprod() * 100\n",
" \n",
" # Compute maximum drawdown\n",
" max_return = np.fmax.accumulate(cumulative_returns)\n",
" max_dd = ((cumulative_returns - max_return) / max_return).min()\n",
" \n",
" # Calculate annualized return\n",
" ann_ret = ann_return(returns)\n",
" \n",
" return ann_ret / abs(max_dd)"
]
},
{
"cell_type": "markdown",
"id": "be187227",
"metadata": {},
"source": [
"Calculate annualized return for the first portfolio and print it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a34d0895",
"metadata": {},
"outputs": [],
"source": [
"ret = ann_return(port_1)\n",
"print(ret)"
]
},
{
"cell_type": "markdown",
"id": "e1cb6f61",
"metadata": {},
"source": [
"Calculate Calmar ratio for the first portfolio and print it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a4b583b",
"metadata": {},
"outputs": [],
"source": [
"p1 = calmar_ratio(port_1)\n",
"print(p1)"
]
},
{
"cell_type": "markdown",
"id": "07af7202",
"metadata": {},
"source": [
"Calculate annualized return for the second portfolio and print it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dae931cc",
"metadata": {},
"outputs": [],
"source": [
"ret = ann_return(port_2)\n",
"print(ret)"
]
},
{
"cell_type": "markdown",
"id": "33e5bf30",
"metadata": {},
"source": [
"Calculate Calmar ratio for the second portfolio and print it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21ca7fd9",
"metadata": {},
"outputs": [],
"source": [
"p2 = calmar_ratio(port_2)\n",
"print(p2)"
]
},
{
"cell_type": "markdown",
"id": "0850471f",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,216 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c60b36d4",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "b0709d1d",
"metadata": {},
"source": [
"This code performs k-means clustering on the Dow Jones Industrial Average (DJIA) stock data from 2020 to 2022. It extracts historical stock prices, calculates returns and volatility, and then clusters the stocks based on these metrics. The 'Elbow Method' is used to determine the optimal number of clusters. Finally, it visualizes the clusters with a scatter plot, annotating each stock with its cluster label. This is useful for identifying patterns or groupings in stock performance metrics."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "09e34b5c",
"metadata": {},
"outputs": [],
"source": [
"from math import sqrt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d73dcf13",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"from sklearn.cluster import KMeans\n",
"import matplotlib.pyplot as plt\n",
"plt.rc(\"font\", size=10)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da61cf6b",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "2c671ff3",
"metadata": {},
"source": [
"Fetch the list of Dow Jones Industrial Average (DJIA) component symbols from Wikipedia"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b0dcdce9",
"metadata": {},
"outputs": [],
"source": [
"dji = (\n",
" pd.read_html('https://en.wikipedia.org/wiki/Dow_Jones_Industrial_Average')[1]\n",
")\n",
"symbols = dji.Symbol.tolist()"
]
},
{
"cell_type": "markdown",
"id": "7140d32a",
"metadata": {},
"source": [
"Download historical stock price data for the DJIA components using OpenBB SDK"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "82f64558",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.stocks.ca.hist(\n",
" symbols, \n",
" start_date=\"2020-01-01\",\n",
" end_date=\"2022-12-31\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "08436f25",
"metadata": {},
"source": [
"Calculate annualized returns and volatility for each stock in the dataset"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3306d91",
"metadata": {},
"outputs": [],
"source": [
"moments = (\n",
" data\n",
" .pct_change()\n",
" .describe()\n",
" .T[[\"mean\", \"std\"]]\n",
" .rename(columns={\"mean\": \"returns\", \"std\": \"vol\"})\n",
") * [252, sqrt(252)]"
]
},
{
"cell_type": "markdown",
"id": "6b3b12c5",
"metadata": {},
"source": [
"Compute sum of squared errors (SSE) for k-means clustering with different cluster counts to use the Elbow Method for optimal k determination. SSE helps identify the point where adding more clusters doesn't significantly improve the model."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da6607e8",
"metadata": {},
"outputs": [],
"source": [
"sse = []\n",
"for k in range(2, 15):\n",
" kmeans = KMeans(n_clusters=k, n_init=10)\n",
" kmeans.fit(moments)\n",
" sse.append(kmeans.inertia_)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4853d749",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(range(2, 15), sse)\n",
"plt.title(\"Elbow Curve\");"
]
},
{
"cell_type": "markdown",
"id": "8d4b74d2",
"metadata": {},
"source": [
"Perform k-means clustering with 5 clusters on the calculated returns and volatility metrics. Visualize the clusters in a scatter plot and annotate each stock with its cluster label for easy identification."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cab4e3bd",
"metadata": {},
"outputs": [],
"source": [
"kmeans = KMeans(n_clusters=5, n_init=10).fit(moments)\n",
"plt.scatter(\n",
" moments.returns, \n",
" moments.vol, \n",
" c=kmeans.labels_, \n",
" cmap=\"rainbow\",\n",
");"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9e0d3176",
"metadata": {},
"outputs": [],
"source": [
"plt.title(\"Dow Jones stocks by return and volatility (K=5)\")\n",
"for i in range(len(moments.index)):\n",
" txt = f\"{moments.index[i]} ({kmeans.labels_[i]})\"\n",
" xy = tuple(moments.iloc[i, :] + [0, 0.01])\n",
" plt.annotate(txt, xy)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "48ae8500",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "5b962026",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,432 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "50bb2bb6",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "cafa6d34",
"metadata": {},
"source": [
"This code retrieves historical stock data and options chains for specific symbols, storing the data in an SQL database. It then sets up a language model with tools for querying the database. Finally, it executes natural language prompts to extract specific options data based on given criteria. This workflow can be used in practice for financial analysis and investment decision-making."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f508fa0f",
"metadata": {},
"outputs": [],
"source": [
"import os"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7fac27e7",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5b5b1cca",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb\n",
"from sqlalchemy import create_engine"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "59d04f12",
"metadata": {},
"outputs": [],
"source": [
"from langchain import OpenAI\n",
"from langchain.sql_database import SQLDatabase\n",
"from langchain.chains import SQLDatabaseChain\n",
"from langchain.agents import Tool, load_tools, initialize_agent"
]
},
{
"cell_type": "markdown",
"id": "7a706992",
"metadata": {},
"source": [
"Set the OpenAI API key environment variable."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d60df547",
"metadata": {},
"outputs": [],
"source": [
"os.environ[\"OPENAI_API_KEY\"] = \"\"\""
]
},
{
"cell_type": "markdown",
"id": "ce2dfe25",
"metadata": {},
"source": [
"Create an in-memory SQLite database engine."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "62f3c19e",
"metadata": {},
"outputs": [],
"source": [
"engine = create_engine(\"sqlite:///:memory:\")"
]
},
{
"cell_type": "markdown",
"id": "7c2fe82a",
"metadata": {},
"source": [
"Define a list of stock symbols for data retrieval."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "57b98c1e",
"metadata": {},
"outputs": [],
"source": [
"symbols = [\"META\", \"AMZN\", \"AAPL\", \"NFLX\", \"GOOG\"]"
]
},
{
"cell_type": "markdown",
"id": "3c3647ca",
"metadata": {},
"source": [
"Retrieve historical stock prices for the defined symbols."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b3fa247",
"metadata": {},
"outputs": [],
"source": [
"prices = openbb.stocks.ca.hist(symbols)"
]
},
{
"cell_type": "markdown",
"id": "5e1f7169",
"metadata": {},
"source": [
"Retrieve options chains for each symbol and append them to a list."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3db7bdc5",
"metadata": {},
"outputs": [],
"source": [
"chains = []\n",
"for symbol in symbols:\n",
" chain = openbb.stocks.options.chains(symbol)\n",
" chain[\"symbol\"] = symbol\n",
" chain[\"underlying_last\"] = prices.iloc[-1][symbol]\n",
" chains.append(chain)"
]
},
{
"cell_type": "markdown",
"id": "d3062f59",
"metadata": {},
"source": [
"Concatenate all options chains into a single DataFrame."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "91ef4bc4",
"metadata": {},
"outputs": [],
"source": [
"options_chains = pd.concat(chains)"
]
},
{
"cell_type": "markdown",
"id": "23917cbc",
"metadata": {},
"source": [
"Save the concatenated options chains to the SQLite database."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "669171a8",
"metadata": {},
"outputs": [],
"source": [
"options_chains.to_sql(\"options\", con=engine, index=False)"
]
},
{
"cell_type": "markdown",
"id": "8e855553",
"metadata": {},
"source": [
"Initialize the OpenAI language model with a specified temperature setting."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0c6a31ed",
"metadata": {},
"outputs": [],
"source": [
"llm = OpenAI(temperature=0)"
]
},
{
"cell_type": "markdown",
"id": "f1df619e",
"metadata": {},
"source": [
"Initialize the SQLDatabase object with the created engine."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cc6269ec",
"metadata": {},
"outputs": [],
"source": [
"db = SQLDatabase(engine)"
]
},
{
"cell_type": "markdown",
"id": "832f8af5",
"metadata": {},
"source": [
"Create a SQLDatabaseChain instance for querying the database using the language model."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "54bb5675",
"metadata": {},
"outputs": [],
"source": [
"sql_chain = SQLDatabaseChain.from_llm(\n",
" llm=llm, \n",
" db=db, \n",
" verbose=True\n",
")"
]
},
{
"cell_type": "markdown",
"id": "22df2870",
"metadata": {},
"source": [
"Define a Tool for querying options data using the SQL chain."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a3cbec3f",
"metadata": {},
"outputs": [],
"source": [
"sql_tool = Tool(\n",
" name=\"Options DB\",\n",
" func=sql_chain.run,\n",
" description=\"Query options data.\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "0193d046",
"metadata": {},
"source": [
"Load LLM math tools and append the SQL tool to the list of tools."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "165c93bb",
"metadata": {},
"outputs": [],
"source": [
"tools = load_tools([\"llm-math\"], llm=llm)\n",
"tools.append(sql_tool)"
]
},
{
"cell_type": "markdown",
"id": "26946096",
"metadata": {},
"source": [
"Initialize a zero-shot agent with the defined tools and language model."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "03879406",
"metadata": {},
"outputs": [],
"source": [
"zero_shot_agent = initialize_agent(\n",
" agent=\"zero-shot-react-description\",\n",
" tools=tools,\n",
" llm=llm,\n",
" verbose=True,\n",
" max_iterations=5,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a4ce372a",
"metadata": {},
"source": [
"Define a prompt to query the last prices of specific META call options."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3c3d4e77",
"metadata": {},
"outputs": [],
"source": [
"prompt = \"\"\"\n",
"What are the last prices of 5 META call options with expiration \n",
"date greater than 60 days away and strike price within 5% of \n",
"the underlying price?\n",
"\n",
"Create a list of the options and include the expiration date,\n",
"strike price, and last price. Use that list to create a table\n",
"using the following template:\n",
"\n",
"Expiration Strike Price\n",
"------------------------------\n",
"expiration_date strike last\n",
"...\n",
"expiration_date strike last\n",
"\n",
"If there are no results, print 'no results.'\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"id": "f76f33ef",
"metadata": {},
"source": [
"Execute the prompt using the zero-shot agent."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "00dfcef9",
"metadata": {},
"outputs": [],
"source": [
"zero_shot_agent.run(prompt)"
]
},
{
"cell_type": "markdown",
"id": "eaba876c",
"metadata": {},
"source": [
"Define a prompt to query specific options from each symbol with given criteria."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3b39756",
"metadata": {},
"outputs": [],
"source": [
"prompt = \"\"\"\n",
"What are the 5 options from each symbol with \n",
"expiration date between 40 and 60 days away, a strike \n",
"price within 5% of the underlying price, open interest \n",
"greater than 100, and the difference between the ask and \n",
"the bid less than 0.05?\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"id": "0467548f",
"metadata": {},
"source": [
"Execute the second prompt using the zero-shot agent."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24f7ff4b",
"metadata": {},
"outputs": [],
"source": [
"zero_shot_agent.run(prompt)"
]
},
{
"cell_type": "markdown",
"id": "f2d1c335",
"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"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,252 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "06df379f",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "c008466a",
"metadata": {},
"source": [
"This code downloads historical stock data for JPMorgan Chase & Co. (JPM) from Yahoo Finance, covering the year 2020. It calculates realized volatility over various rolling windows (30, 60, 90, 120 days) and analyzes the maximum, minimum, top quantile, median, and bottom quantile of these volatilities. The results are visualized using Matplotlib to show the volatility distribution over different windows. This is useful for understanding the stock's risk profile over different time frames."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "373db843",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"import yfinance as yf\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"id": "93871d13",
"metadata": {},
"source": [
"Download historical stock data for JPM from Yahoo Finance for the year 2020"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f9af1afc",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(\"JPM\", start=\"2020-01-01\", end=\"2020-12-31\")"
]
},
{
"cell_type": "markdown",
"id": "67429a4d",
"metadata": {},
"source": [
"Define rolling windows and quantiles to analyze"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4139b06",
"metadata": {},
"outputs": [],
"source": [
"windows = [30, 60, 90, 120]\n",
"quantiles = [0.25, 0.75]"
]
},
{
"cell_type": "markdown",
"id": "2b183370",
"metadata": {},
"source": [
"Initialize lists to store maximum, minimum, quantiles, median, and realized volatilities"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3dbec930",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"max_ = []\n",
"min_ = []\n",
"top_q = []\n",
"median = []\n",
"bottom_q = []\n",
"realized = []"
]
},
{
"cell_type": "markdown",
"id": "3c72c004",
"metadata": {},
"source": [
"Calculate realized volatility using rolling windows"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f7c79c2",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def realized_vol(price_data, window=30):\n",
" \"\"\"\n",
" Calculates realized volatility over a rolling window\n",
" \n",
" Parameters\n",
" ----------\n",
" price_data : pd.DataFrame\n",
" DataFrame containing stock price data\n",
" window : int\n",
" Rolling window size in days\n",
" \n",
" Returns\n",
" -------\n",
" realized_vol : pd.Series\n",
" Series containing realized volatility values\n",
" \"\"\"\n",
" \n",
" # Compute log returns from closing prices\n",
" log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
" \n",
" # Compute rolling standard deviation of log returns and annualize it\n",
" return log_return.rolling(window=window, center=False).std() * math.sqrt(252)"
]
},
{
"cell_type": "markdown",
"id": "0d965c51",
"metadata": {},
"source": [
"Loop over each window to calculate and store volatility metrics"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ede0e0e3",
"metadata": {},
"outputs": [],
"source": [
"for window in windows:\n",
"\n",
" # Calculate realized volatility for the current window\n",
" estimator = realized_vol(window=window, price_data=data)\n",
"\n",
" # Append the maximum realized volatility to the list\n",
" max_.append(estimator.max())\n",
"\n",
" # Append the top quantile realized volatility to the list\n",
" top_q.append(estimator.quantile(quantiles[1]))\n",
"\n",
" # Append the median realized volatility to the list\n",
" median.append(estimator.median())\n",
"\n",
" # Append the bottom quantile realized volatility to the list\n",
" bottom_q.append(estimator.quantile(quantiles[0]))\n",
"\n",
" # Append the minimum realized volatility to the list\n",
" min_.append(estimator.min())\n",
"\n",
" # Append the last realized volatility to the list\n",
" realized.append(estimator[-1])"
]
},
{
"cell_type": "markdown",
"id": "ab0bf7e1",
"metadata": {},
"source": [
"Plot the realized volatility metrics for different rolling windows"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "73b648d5",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(windows, max_, \"-o\", linewidth=1, label=\"Max\")\n",
"plt.plot(windows, top_q, \"-o\", linewidth=1, label=f\"{quantiles[1] * 100:.0f} Prctl\")\n",
"plt.plot(windows, median, \"-o\", linewidth=1, label=\"Median\")\n",
"plt.plot(windows, bottom_q, \"-o\", linewidth=1, label=f\"{quantiles[0] * 100:.0f} Prctl\")\n",
"plt.plot(windows, min_, \"-o\", linewidth=1, label=\"Min\")"
]
},
{
"cell_type": "markdown",
"id": "9aa0f64d",
"metadata": {},
"source": [
"Plot the realized volatility calculated from the latest window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba1919d4",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(windows, realized, \"ro-.\", linewidth=1, label=\"Realized\")\n",
"plt.xticks(windows)\n",
"plt.legend(loc=\"upper center\", bbox_to_anchor=(0.5, -0.1), ncol=3)"
]
},
{
"cell_type": "markdown",
"id": "6a4dfc4b",
"metadata": {},
"source": [
"Plot the closing prices of the stock data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b959f4c8",
"metadata": {},
"outputs": [],
"source": [
"data.Close.plot()"
]
},
{
"cell_type": "markdown",
"id": "011cc534",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,287 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "b7d86319",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "59c0ce89",
"metadata": {},
"source": [
"This code acquires stock price data, calculates financial indicators, and analyzes their relationships with future returns. It uses OpenBB SDK to fetch stock data, filters it, and computes the Average True Range (ATR) as a volatility measure. The code then calculates historical returns over multiple time lags and sets up target variables for future returns. Finally, it visualizes the relationship between ATR and future returns and computes their correlation. This workflow is useful for quantitative analysis in financial markets."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ddc92576",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"from openbb_terminal.sdk import openbb\n",
"from talib import ATR\n",
"from scipy.stats import spearmanr\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns"
]
},
{
"cell_type": "markdown",
"id": "47f42114",
"metadata": {},
"source": [
"Acquire stock data using OpenBB SDK and filter based on country and price criteria"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "73be12b2",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.stocks.screener.screener_data(preset_loaded=\"most_volatile\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1407bbe9",
"metadata": {},
"outputs": [],
"source": [
"universe = data[(data.Country == \"USA\") & (data.Price > 5)]"
]
},
{
"cell_type": "markdown",
"id": "36d938a6",
"metadata": {},
"source": [
"Fetch historical price data for each ticker and store in a list of DataFrames"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eb90090d",
"metadata": {},
"outputs": [],
"source": [
"stocks = []\n",
"for ticker in universe.Ticker.tolist():\n",
" df = openbb.stocks.load(ticker, start_date=\"2010-01-01\", verbose=False).drop(\"Close\", axis=1)\n",
" df[\"ticker\"] = ticker\n",
" stocks.append(df)"
]
},
{
"cell_type": "markdown",
"id": "97e1c7aa",
"metadata": {},
"source": [
"Concatenate all DataFrames into a single DataFrame and rename columns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "95b04c04",
"metadata": {},
"outputs": [],
"source": [
"prices = pd.concat(stocks)\n",
"prices.columns = [\"open\", \"high\", \"low\", \"close\", \"volume\", \"ticker\"]"
]
},
{
"cell_type": "markdown",
"id": "badcbca5",
"metadata": {},
"source": [
"Filter out stocks with insufficient data and remove duplicate entries"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bcb8ae26",
"metadata": {},
"outputs": [],
"source": [
"nobs = prices.groupby(\"ticker\").size()\n",
"mask = nobs[nobs > 2 * 12 * 21].index\n",
"prices = prices[prices.ticker.isin(mask)]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "469ec0d5",
"metadata": {},
"outputs": [],
"source": [
"prices = prices.set_index(\"ticker\", append=True).reorder_levels([\"ticker\", \"date\"]).drop_duplicates()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e31308f2",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"prices.drop_duplicates()"
]
},
{
"cell_type": "markdown",
"id": "2db6025a",
"metadata": {},
"source": [
"Calculate Average True Range (ATR) for each stock and standardize it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "55211855",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def atr(data):\n",
" \"\"\"Calculate and standardize ATR.\n",
" \n",
" Parameters\n",
" ----------\n",
" data : DataFrame\n",
" Data containing high, low, and close prices.\n",
" \n",
" Returns\n",
" -------\n",
" DataFrame\n",
" Standardized ATR values.\n",
" \"\"\"\n",
" df = ATR(data.high, data.low, data.close, timeperiod=14)\n",
" return df.sub(df.mean()).div(df.std())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f03ebd4",
"metadata": {},
"outputs": [],
"source": [
"prices[\"atr\"] = prices.groupby('ticker', group_keys=False).apply(atr)"
]
},
{
"cell_type": "markdown",
"id": "55ed7582",
"metadata": {},
"source": [
"Calculate historical returns over different time lags and add them to the DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d96a26d",
"metadata": {},
"outputs": [],
"source": [
"lags = [1, 5, 10, 21, 42, 63]\n",
"for lag in lags:\n",
" prices[f\"return_{lag}d\"] = prices.groupby(level=\"ticker\").close.pct_change(lag)"
]
},
{
"cell_type": "markdown",
"id": "7d161381",
"metadata": {},
"source": [
"Set up target variables for future returns by shifting historical returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "112b42a8",
"metadata": {},
"outputs": [],
"source": [
"for t in [1, 5, 10, 21]:\n",
" prices[f\"target_{t}d\"] = prices.groupby(level=\"ticker\")[f\"return_{t}d\"].shift(-t)"
]
},
{
"cell_type": "markdown",
"id": "e97c0708",
"metadata": {},
"source": [
"Visualize the relationship between ATR and future 1-day returns using Seaborn"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e7740b40",
"metadata": {},
"outputs": [],
"source": [
"target = \"target_1d\"\n",
"metric = \"atr\"\n",
"j = sns.jointplot(x=metric, y=target, data=prices)\n",
"plt.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "3578ee83",
"metadata": {},
"source": [
"Calculate and print the Spearman correlation between ATR and future 1-day returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18751214",
"metadata": {},
"outputs": [],
"source": [
"df = prices[[metric, target]].dropna()\n",
"r, p = spearmanr(df[metric], df[target])\n",
"print(f\"{r:,.2%} ({p:.2%})\")"
]
},
{
"cell_type": "markdown",
"id": "69e275a0",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,283 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0f1af07e",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "da69000c",
"metadata": {},
"source": [
"This code uses the OpenBB Terminal SDK to analyze option chains for the SPY ETF. It fetches option chains and expiration dates from the Yahoo Finance source, and identifies the at-the-money (ATM) strike price for SPY options. It then plots the implied volatility (IV) term structure for ATM call options and the IV skew for call options with a specific expiration date. This is useful for visualizing how implied volatility varies across different strike prices and expiration dates, aiding in options trading strategies."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "efc8d575",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f875e54b",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"plt.rc(\"font\", size=10)"
]
},
{
"cell_type": "markdown",
"id": "7d7114f0",
"metadata": {},
"source": [
"Fetch option chains for SPY from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7b96161",
"metadata": {},
"outputs": [],
"source": [
"chains = openbb.stocks.options.chains(\n",
" symbol=\"SPY\", \n",
" source=\"YahooFinance\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "3d05e985",
"metadata": {},
"source": [
"Fetch expiration dates for SPY options"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fb360b93",
"metadata": {},
"outputs": [],
"source": [
"expirations = openbb.stocks.options.expirations(\"SPY\")"
]
},
{
"cell_type": "markdown",
"id": "318a8b30",
"metadata": {},
"source": [
"Retrieve the last adjusted closing price for SPY"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1b027826",
"metadata": {},
"outputs": [],
"source": [
"last = (\n",
" openbb\n",
" .stocks\n",
" .load(\"SPY\")\n",
" .iloc[-1][\"Adj Close\"]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cdf8e3c2",
"metadata": {},
"outputs": [],
"source": [
"last"
]
},
{
"cell_type": "markdown",
"id": "d17254b4",
"metadata": {},
"source": [
"Identify the index of the strike price closest to the last adjusted closing price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a0063ef3",
"metadata": {},
"outputs": [],
"source": [
"idx = (\n",
" (chains.strike - last)\n",
" .abs()\n",
" .sort_values()\n",
" .index[0]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "6ef7cbf1",
"metadata": {},
"source": [
"Retrieve the at-the-money (ATM) strike price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7432cf1b",
"metadata": {},
"outputs": [],
"source": [
"atm_strike = (\n",
" chains\n",
" .iloc[idx]\n",
" .strike\n",
")"
]
},
{
"cell_type": "markdown",
"id": "00624270",
"metadata": {},
"source": [
"Filter the option chains to get only the ATM call options"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9fbe0cd8",
"metadata": {},
"outputs": [],
"source": [
"calls = (\n",
" chains[\n",
" (chains.strike == atm_strike) \n",
" & (chains.optionType == \"call\")\n",
" ]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "17ad09bb",
"metadata": {},
"source": [
"Plot the implied volatility term structure for ATM call options"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a776685a",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" calls\n",
" .set_index(\"expiration\")\n",
" .impliedVolatility.plot(title=\"IV term structure for ATM call options\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "742dc7cf",
"metadata": {},
"source": [
"Filter the option chains to get call options expiring on a specific date"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea22fc4c",
"metadata": {},
"outputs": [],
"source": [
"calls = (\n",
" chains[\n",
" (chains.expiration == expirations[4]) \n",
" & (chains.optionType == \"call\")\n",
" ]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "40ba8471",
"metadata": {},
"source": [
"Plot the implied volatility skew for call options with the specified expiration date"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f2219e7a",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" calls\n",
" .set_index(\"strike\")\n",
" .impliedVolatility\n",
" .plot(title=f\"IV term structure for call options expiring {expirations[1]}\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "7c4f7488",
"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
}

View File

@ -0,0 +1,325 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4d2d5f0f",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "5e2cdebe",
"metadata": {},
"source": [
"This code implements a momentum-based trading strategy using Zipline, a backtesting library for Python. It calculates the Simple Moving Average (SMA) of selected ETFs over a 10-month period. The strategy rebalances the portfolio at the start of each month, investing in ETFs that are above their SMA. It also sets realistic commission and slippage values. The final performance of the strategy is evaluated using Pyfolio for detailed analysis."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4454d728",
"metadata": {},
"outputs": [],
"source": [
"import warnings"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "abcc9c9f",
"metadata": {},
"outputs": [],
"source": [
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a51de9e",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import pandas_datareader.data as web"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "238b106c",
"metadata": {},
"outputs": [],
"source": [
"from zipline import run_algorithm\n",
"from zipline.api import (\n",
" attach_pipeline,\n",
" date_rules,\n",
" order_target_percent,\n",
" pipeline_output,\n",
" record,\n",
" schedule_function,\n",
" symbol,\n",
" time_rules,\n",
" get_open_orders,\n",
")\n",
"from zipline.finance import commission, slippage\n",
"from zipline.pipeline import Pipeline\n",
"from zipline.pipeline.factors import SimpleMovingAverage\n",
"from zipline.pipeline.data import USEquityPricing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1a0027e4",
"metadata": {},
"outputs": [],
"source": [
"import pyfolio as pf"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "33f4ed29",
"metadata": {},
"outputs": [],
"source": [
"def initialize(context):\n",
" \"\"\"\n",
" Initialization function for setting up the algorithm.\n",
"\n",
" Defines the ETFs to be traded, sets the SMA period, and schedules the rebalance function.\n",
"\n",
" Parameters\n",
" ----------\n",
" context : object\n",
" A context object holding the state of the algorithm.\n",
"\n",
" Returns\n",
" -------\n",
" None\n",
" \"\"\"\n",
"\n",
" context.symbols = [\n",
" symbol(\"SPY\"),\n",
" symbol(\"EFA\"),\n",
" symbol(\"IEF\"),\n",
" symbol(\"VNQ\"),\n",
" symbol(\"GSG\"),\n",
" ]\n",
"\n",
" # Create an empty dictionary to store Simple Moving Average values for each ETF\n",
"\n",
" context.sma = {}\n",
"\n",
" # Define the SMA period as 10 months (approximately 21 trading days per month)\n",
"\n",
" context.period = 10 * 21\n",
"\n",
" # Calculate the SMA for each ETF over the defined period\n",
"\n",
" for asset in context.symbols: \n",
" context.sma[asset] = SimpleMovingAverage(\n",
" inputs=[USEquityPricing.close],\n",
" window_length=context.period\n",
" )\n",
" \n",
" # Schedule the rebalance function to run at the start of each month, one minute after market opens\n",
"\n",
" schedule_function(\n",
" func=rebalance,\n",
" date_rule=date_rules.month_start(),\n",
" time_rule=time_rules.market_open(minutes=1),\n",
" )\n",
"\n",
" # Set commission and slippage to realistic values\n",
"\n",
" context.set_commission(\n",
" commission.PerShare(cost=0.01, min_trade_cost=1.00)\n",
" )\n",
" context.set_slippage(slippage.VolumeShareSlippage())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc9a0b79",
"metadata": {},
"outputs": [],
"source": [
"def rebalance(context, data):\n",
" \"\"\"\n",
" Rebalance function to be run at the start of each month.\n",
"\n",
" Adjusts the portfolio by closing positions not in the 'longs' list and setting target percentages for 'longs'.\n",
"\n",
" Parameters\n",
" ----------\n",
" context : object\n",
" A context object holding the state of the algorithm.\n",
" data : object\n",
" A data object providing market data.\n",
"\n",
" Returns\n",
" -------\n",
" None\n",
" \"\"\"\n",
"\n",
" longs = [\n",
" asset\n",
" for asset in context.symbols\n",
" if data.current(asset, \"price\") > context.sma[asset].mean()\n",
" ]\n",
"\n",
" # Close positions for assets not in 'longs'\n",
"\n",
" for asset in context.portfolio.positions:\n",
" if asset not in longs and data.can_trade(asset):\n",
" order_target_percent(asset, 0)\n",
"\n",
" # Set target portfolio percentage for each asset in 'longs'\n",
"\n",
" for asset in longs:\n",
" if data.can_trade(asset):\n",
" order_target_percent(asset, 1.0 / len(longs))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a857b85",
"metadata": {},
"outputs": [],
"source": [
"start = pd.Timestamp(\"2010\")\n",
"end = pd.Timestamp(\"2023-06-30\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5cce708",
"metadata": {},
"outputs": [],
"source": [
"sp500 = web.DataReader('SP500', 'fred', start, end).SP500\n",
"benchmark_returns = sp500.pct_change()"
]
},
{
"cell_type": "markdown",
"id": "e9fd980d",
"metadata": {},
"source": [
"Run the algorithm with the specified parameters and capture performance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff897820",
"metadata": {},
"outputs": [],
"source": [
"perf = run_algorithm(\n",
" start=start,\n",
" end=end,\n",
" initialize=initialize,\n",
" capital_base=100000,\n",
" bundle='quandl-eod'\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "564a63a8",
"metadata": {},
"outputs": [],
"source": [
"perf"
]
},
{
"cell_type": "markdown",
"id": "5b1b0608",
"metadata": {},
"source": [
"Extract returns, positions, and transactions from the performance DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9f8e96d8",
"metadata": {},
"outputs": [],
"source": [
"returns, positions, transactions = \\\n",
" pf.utils.extract_rets_pos_txn_from_zipline(perf)"
]
},
{
"cell_type": "markdown",
"id": "ff8dfb4f",
"metadata": {},
"source": [
"Create a full tear sheet to analyze the performance of the strategy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06a56fb8",
"metadata": {},
"outputs": [],
"source": [
"pf.create_full_tear_sheet(\n",
" returns,\n",
" positions=positions,\n",
" transactions=transactions,\n",
" round_trips=True,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "3be746eb",
"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
}

View File

@ -0,0 +1,473 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "122e94bb",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "aed964b7",
"metadata": {},
"source": [
"This code retrieves S&P 500 company tickers and fetches historical index data for those companies using the OpenBB SDK. It converts the data between Pandas and Polars dataframes and performs various operations like writing to CSV, reading from CSV, selecting, filtering, and grouping data. The code also measures the performance of these operations using Pandas and Polars. This is useful for comparing the efficiency of data manipulation operations between the two libraries in practice."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7fb65f4c",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import polars as pl"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5ec4ddfd",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "f3646b59",
"metadata": {},
"source": [
"Retrieve S&P 500 tickers from Wikipedia and create a list of symbols"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b9a984b2",
"metadata": {},
"outputs": [],
"source": [
"table = pd.read_html(\"http://en.wikipedia.org/wiki/List_of_S%26P_500_companies\")[0]\n",
"tickers = table.Symbol.tolist()"
]
},
{
"cell_type": "markdown",
"id": "d90b333a",
"metadata": {},
"source": [
"Fetch historical index data for the retrieved tickers using OpenBB SDK"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f46d2ccb",
"metadata": {},
"outputs": [],
"source": [
"df_pandas = openbb.economy.index(tickers, start_date=\"1990-01-01\")"
]
},
{
"cell_type": "markdown",
"id": "ab6544ab",
"metadata": {},
"source": [
"Save the fetched data to a CSV file using Pandas"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "133d2e71",
"metadata": {},
"outputs": [],
"source": [
"df_pandas.to_csv(\"data.csv\")"
]
},
{
"cell_type": "markdown",
"id": "354713f3",
"metadata": {},
"source": [
"Convert the Pandas dataframe to a Polars dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e24a6b45",
"metadata": {},
"outputs": [],
"source": [
"df_polars = pl.from_pandas(df_pandas)"
]
},
{
"cell_type": "markdown",
"id": "dffdb2ef",
"metadata": {},
"source": [
"Measure the time taken to write the Pandas dataframe to a CSV file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7d3c7eac",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_pandas.to_csv(\"data.csv\", index=False)"
]
},
{
"cell_type": "markdown",
"id": "21c90011",
"metadata": {},
"source": [
"Measure the time taken to write the Polars dataframe to a CSV file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1b18f6cb",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_polars.write_csv(\"data.csv\")"
]
},
{
"cell_type": "markdown",
"id": "21fcfb58",
"metadata": {},
"source": [
"Measure the time taken to read the CSV file into a Pandas dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef305fac",
"metadata": {},
"outputs": [],
"source": [
"%timeit pd.read_csv(\"data.csv\")"
]
},
{
"cell_type": "markdown",
"id": "e146436a",
"metadata": {},
"source": [
"Measure the time taken to read the CSV file into a Polars dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e2a5ab4",
"metadata": {},
"outputs": [],
"source": [
"%timeit pl.scan_csv(\"data.csv\")"
]
},
{
"cell_type": "markdown",
"id": "bf112c8b",
"metadata": {},
"source": [
"Select the first 100 tickers from the list"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1c37ceba",
"metadata": {},
"outputs": [],
"source": [
"selected = tickers[:100]"
]
},
{
"cell_type": "markdown",
"id": "07db719c",
"metadata": {},
"source": [
"Measure the time taken to select columns in the Pandas dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be6affd1",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_pandas[selected]"
]
},
{
"cell_type": "markdown",
"id": "1952cfdc",
"metadata": {},
"source": [
"Measure the time taken to select columns in the Polars dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "787fe7d3",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_polars.select(pl.col(selected))"
]
},
{
"cell_type": "markdown",
"id": "a6ff1cdc",
"metadata": {},
"source": [
"Measure the time taken to filter rows in the Pandas dataframe where 'GE' > 100"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bfcbf64d",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_pandas[df_pandas[\"GE\"] > 100]"
]
},
{
"cell_type": "markdown",
"id": "e7ac6d8d",
"metadata": {},
"source": [
"Measure the time taken to filter rows in the Polars dataframe where 'GE' > 100"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9ed35dc2",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_polars.filter(pl.col(\"GE\") > 100)"
]
},
{
"cell_type": "markdown",
"id": "8f26a363",
"metadata": {},
"source": [
"Measure the time taken to group by 'GE' and calculate the mean in the Pandas dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "43a2fe0d",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_pandas.groupby(\"GE\").mean()"
]
},
{
"cell_type": "markdown",
"id": "41a927a8",
"metadata": {},
"source": [
"Measure the time taken to group by 'GE' and calculate the mean in the Polars dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af594a24",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_polars.groupby(\"GE\").mean()"
]
},
{
"cell_type": "markdown",
"id": "ba86d47f",
"metadata": {},
"source": [
"Measure the time taken to create a new column 'GE_Return' with percentage change in Pandas"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "067e1c7c",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_pandas.assign(GE_Return=df_pandas[\"GE\"].pct_change())"
]
},
{
"cell_type": "markdown",
"id": "7f218df1",
"metadata": {},
"source": [
"Measure the time taken to create a new column 'GE_return' with percentage change in Polars"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa653184",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_polars.with_columns((pl.col(\"GE\").pct_change()).alias(\"GE_return\"))"
]
},
{
"cell_type": "markdown",
"id": "b8037123",
"metadata": {},
"source": [
"Measure the time taken to fill missing values in the 'GE' column with 0 in Pandas"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9715f643",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_pandas[\"GE\"].fillna(0)"
]
},
{
"cell_type": "markdown",
"id": "e7495700",
"metadata": {},
"source": [
"Measure the time taken to fill missing values in the 'GE' column with 0 in Polars"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7113f4df",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_polars.with_columns(pl.col(\"GE\").fill_null(0))"
]
},
{
"cell_type": "markdown",
"id": "5ff4e917",
"metadata": {},
"source": [
"Measure the time taken to sort the dataframe by the 'GE' column in Pandas"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8df68642",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_pandas.sort_values(\"GE\")"
]
},
{
"cell_type": "markdown",
"id": "1c98f0ef",
"metadata": {},
"source": [
"Measure the time taken to sort the dataframe by the 'GE' column in Polars"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "69b396c5",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_polars.sort(\"GE\")"
]
},
{
"cell_type": "markdown",
"id": "40cea3be",
"metadata": {},
"source": [
"Measure the time taken to calculate the rolling mean for 'GE' with a window of 20 in Pandas"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d59086c",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_pandas.GE.rolling(window=20).mean()"
]
},
{
"cell_type": "markdown",
"id": "9c533081",
"metadata": {},
"source": [
"Measure the time taken to calculate the rolling mean for 'GE' with a window of 20 in Polars"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4fcf1c5e",
"metadata": {},
"outputs": [],
"source": [
"%timeit df_polars.with_columns(pl.col(\"GE\").rolling_mean(20))"
]
},
{
"cell_type": "markdown",
"id": "e9a81f98",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,208 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "63831470",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "535538d4",
"metadata": {},
"source": [
"This code utilizes the Black-Litterman model to incorporate subjective views into the portfolio optimization process. It fetches price data for selected assets, constructs a covariance matrix, and sets up absolute views on asset returns. The Black-Litterman model is then used to compute new expected returns, followed by the construction of an efficient frontier. This approach helps in creating a more informed and optimized portfolio by blending market equilibrium with investor views."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30dc6f4f",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c1d4af5",
"metadata": {},
"outputs": [],
"source": [
"from pypfopt.black_litterman import BlackLittermanModel\n",
"from pypfopt.efficient_frontier import EfficientFrontier\n",
"from pypfopt import risk_models, plotting"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "293a45be",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb\n",
"import seaborn as sns\n",
"sns.set_theme()"
]
},
{
"cell_type": "markdown",
"id": "60664575",
"metadata": {},
"source": [
"Fetch price data for selected assets from the OpenBB terminal"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "02c6c09c",
"metadata": {},
"outputs": [],
"source": [
"prices = openbb.economy.index([\"AAPL\", \"BBY\", \"BAC\", \"SBUX\", \"T\"])"
]
},
{
"cell_type": "markdown",
"id": "9081783b",
"metadata": {},
"source": [
"Define absolute views on the expected returns for specific assets"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d9cb1ca",
"metadata": {},
"outputs": [],
"source": [
"viewdict = {\n",
" \"AAPL\": 0.20, \n",
" \"BBY\": 0.30,\n",
" \"BAC\": 0.10,\n",
" \"SBUX\": 0.2,\n",
" \"T\": 0.15\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "a3a6f94a",
"metadata": {},
"source": [
"Construct the sample covariance matrix using historical price data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d0e2e01",
"metadata": {},
"outputs": [],
"source": [
"cov_matrix = risk_models.sample_cov(prices)"
]
},
{
"cell_type": "markdown",
"id": "500bebeb",
"metadata": {},
"source": [
"Initialize the Black-Litterman model with equal weight priors and absolute views"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6db6f3b2",
"metadata": {},
"outputs": [],
"source": [
"bl = BlackLittermanModel(\n",
" cov_matrix, \n",
" absolute_views=viewdict,\n",
" pi=\"equal\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "b3ac9e80",
"metadata": {},
"source": [
"Compute the implied expected returns using the Black-Litterman model and initialize Efficient Frontier"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "024947c8",
"metadata": {},
"outputs": [],
"source": [
"rets = bl.bl_returns()\n",
"ef = EfficientFrontier(rets, cov_matrix)"
]
},
{
"cell_type": "markdown",
"id": "644d3dec",
"metadata": {},
"source": [
"Plot the efficient frontier showing the possible portfolios"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "86ea1335",
"metadata": {},
"outputs": [],
"source": [
"plotting.plot_efficient_frontier(ef, show_tickers=True)"
]
},
{
"cell_type": "markdown",
"id": "aba68864",
"metadata": {},
"source": [
"Calculate and display the optimal weights for the portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef1c2d68",
"metadata": {},
"outputs": [],
"source": [
"bl.bl_weights()"
]
},
{
"cell_type": "markdown",
"id": "8bbae896",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,201 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8addf00f",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "e5202c9f",
"metadata": {},
"source": [
"This code calculates the downside deviation of stock returns for Apple Inc. (AAPL).\n",
"It imports necessary libraries and loads historical adjusted closing prices.\n",
"The downside deviation is computed to measure risk by focusing on negative returns.\n",
"This metric is useful for investors aiming to quantify the volatility of negative returns.\n",
"The downside deviation is annualized for practical financial analysis."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eba3a353",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cf45199f",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "fee9bac4",
"metadata": {},
"source": [
"Load historical adjusted closing prices for Apple Inc. (AAPL)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0ccc7c3f",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.stocks.load(\"AAPL\")"
]
},
{
"cell_type": "markdown",
"id": "98ac41af",
"metadata": {},
"source": [
"Calculate daily percentage change in adjusted closing prices to obtain returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f39cd038",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"returns = data[\"Adj Close\"].pct_change()"
]
},
{
"cell_type": "markdown",
"id": "7e8c9c93",
"metadata": {},
"source": [
"Calculate the downside deviation of the returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "beb978c6",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def downside_deviation(returns):\n",
" \"\"\"Calculate downside deviation of returns\n",
" \n",
" Parameters\n",
" ----------\n",
" returns : np.ndarray\n",
" Array of daily percentage returns\n",
" \n",
" Returns\n",
" -------\n",
" downside_deviation : float\n",
" Annualized downside deviation of returns\n",
" \n",
" Notes\n",
" -----\n",
" This function calculates the downside deviation, \n",
" which measures the volatility of negative returns. \n",
" It annualizes the deviation for practical financial analysis.\n",
" \"\"\"\n",
"\n",
" # Initialize an empty array to store downside deviation values\n",
"\n",
" out = np.empty(returns.shape[1:])\n",
"\n",
" # Clip returns at zero to focus on negative returns\n",
"\n",
" downside_diff = np.clip(returns, np.NINF, 0)\n",
"\n",
" # Square the clipped values to calculate the squared deviations\n",
"\n",
" np.square(downside_diff, out=downside_diff)\n",
"\n",
" # Calculate the mean of squared deviations ignoring NaNs\n",
"\n",
" np.nanmean(downside_diff, axis=0, out=out)\n",
"\n",
" # Take the square root of the mean squared deviations\n",
"\n",
" np.sqrt(out, out=out)\n",
"\n",
" # Annualize the downside deviation by multiplying by the square root of 252\n",
"\n",
" np.multiply(out, np.sqrt(252), out=out)\n",
"\n",
" # Return the annualized downside deviation as a single value\n",
"\n",
" return out.item()"
]
},
{
"cell_type": "markdown",
"id": "28a2ce36",
"metadata": {},
"source": [
"Calculate and output the downside deviation of the returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "470ebf6d",
"metadata": {},
"outputs": [],
"source": [
"downside_deviation(returns)"
]
},
{
"cell_type": "markdown",
"id": "f26cc8cd",
"metadata": {},
"source": [
"Calculate the annualized standard deviation of returns for comparison"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fb6d47b4",
"metadata": {},
"outputs": [],
"source": [
"np.sqrt(np.square(returns).mean()) * np.sqrt(252)"
]
},
{
"cell_type": "markdown",
"id": "fa64ff0c",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,214 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "7b39d6ef",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "09b6a2a7",
"metadata": {},
"source": [
"This code fetches historical stock prices for AAPL, WMT, and SPY, calculates daily returns, and constructs a portfolio. It defines functions to compute the annual return and upside capture ratio of the portfolio compared to a benchmark. The upside capture ratio measures the portfolio's performance relative to the benchmark when the benchmark is up. This is useful in practice for portfolio performance analysis and risk management."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7589769e",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "d7af040b",
"metadata": {},
"source": [
"Fetch historical stock prices for AAPL, WMT, and SPY starting from 2020-01-01"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "01ac1e2a",
"metadata": {},
"outputs": [],
"source": [
"prices = openbb.economy.index(\n",
" [\"AAPL\", \"WMT\", \"SPY\"], \n",
" start_date=\"2020-01-01\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "6cf5e1ea",
"metadata": {},
"source": [
"Calculate daily returns by taking the percentage change and dropping any NaN values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fdd82c4a",
"metadata": {},
"outputs": [],
"source": [
"returns = prices.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "859f62ce",
"metadata": {},
"source": [
"Construct a portfolio's returns by summing the returns of AAPL and WMT, and selecting SPY and portfolio columns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1138bf9a",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"data = returns.assign(\n",
" port=returns[[\"AAPL\", \"WMT\"]].sum(axis=1)\n",
")[[\"SPY\", \"port\"]]"
]
},
{
"cell_type": "markdown",
"id": "a8b57c5b",
"metadata": {},
"source": [
"Compute annualized return for a series of returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4341da4",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def annual_return(returns):\n",
" \"\"\"Compute annualized return.\n",
" \n",
" This function calculates the annualized return of a series of returns.\n",
" \n",
" Parameters\n",
" ----------\n",
" returns : pd.Series\n",
" Series of daily returns.\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" Annualized return.\n",
" \"\"\"\n",
" \n",
" # Calculate the number of years by dividing total days by 252 (trading days in a year)\n",
" num_years = len(returns) / 252\n",
" \n",
" # Calculate the annualized return using the geometric average\n",
" return (returns + 1).prod() ** (1 / num_years) - 1"
]
},
{
"cell_type": "markdown",
"id": "567d5ef4",
"metadata": {},
"source": [
"Compute upside capture ratio comparing portfolio returns to benchmark returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1dea9509",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def upside_capture(port_returns, bench_returns):\n",
" \"\"\"Compute upside capture ratio.\n",
" \n",
" This function calculates the upside capture ratio of portfolio returns \n",
" compared to benchmark returns.\n",
" \n",
" Parameters\n",
" ----------\n",
" port_returns : pd.Series\n",
" Series of portfolio returns.\n",
" bench_returns : pd.Series\n",
" Series of benchmark returns.\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" Upside capture ratio.\n",
" \"\"\"\n",
" \n",
" # Filter portfolio and benchmark returns where benchmark returns are positive\n",
" mask = bench_returns > 0\n",
" port_returns = port_returns[mask]\n",
" bench_returns = bench_returns[mask]\n",
" \n",
" # Calculate the upside capture ratio by dividing annualized portfolio return by annualized benchmark return\n",
" return (\n",
" annual_return(port_returns) \n",
" / annual_return(bench_returns)\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "600cd3ac",
"metadata": {},
"source": [
"Calculate the upside capture ratio for the constructed portfolio compared to SPY"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "47239c60",
"metadata": {},
"outputs": [],
"source": [
"upside_capture(data.port, data.SPY)"
]
},
{
"cell_type": "markdown",
"id": "4d6184fc",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,214 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e61b6551",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "8cebd5cb",
"metadata": {},
"source": [
"This code retrieves stock prices, calculates returns, and evaluates portfolio performance against a benchmark. It fetches historical prices for specified stocks and computes their percentage changes to derive returns. The code then calculates portfolio returns based on equal weighting and compares them with a benchmark index. Finally, it computes the batting average to assess the portfolio's performance in up and down markets. This is useful for portfolio managers and financial analysts to evaluate investment strategies."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f484f7f8",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "e3fe4de2",
"metadata": {},
"source": [
"Fetch historical prices for specified stocks using the OpenBB SDK"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e6394bcf",
"metadata": {},
"outputs": [],
"source": [
"prices = openbb.economy.index([\n",
" \"META\", \n",
" \"AAPL\", \n",
" \"AMZN\", \n",
" \"NFLX\", \n",
" \"GOOG\", \n",
" \"QQQ\"\n",
"])"
]
},
{
"cell_type": "markdown",
"id": "9006ddd4",
"metadata": {},
"source": [
"Calculate percentage returns from the historical prices and remove any rows with NaN values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "97f84964",
"metadata": {},
"outputs": [],
"source": [
"returns = prices.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "4aa143ad",
"metadata": {},
"source": [
"Extract the benchmark returns (QQQ) from the returns DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e639344f",
"metadata": {},
"outputs": [],
"source": [
"bench_returns = returns.pop(\"QQQ\")"
]
},
{
"cell_type": "markdown",
"id": "664669a3",
"metadata": {},
"source": [
"Calculate portfolio returns by equally weighting the individual stock returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1f427ac8",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"port_returns = (returns * 0.2).sum(axis=1)"
]
},
{
"cell_type": "markdown",
"id": "24341092",
"metadata": {},
"source": [
"Define the function to calculate the batting average of portfolio returns against the benchmark"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d15cb24f",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def batting_average(port_returns, bench_returns):\n",
" \"\"\"Calculate batting average of portfolio returns.\n",
" \n",
" This function computes the batting average of portfolio \n",
" returns compared to a benchmark, indicating performance \n",
" in up and down markets.\n",
" \n",
" Parameters\n",
" ----------\n",
" port_returns : pd.Series\n",
" Returns of the portfolio.\n",
" bench_returns : pd.Series\n",
" Returns of the benchmark index.\n",
" \n",
" Returns\n",
" -------\n",
" pd.Series\n",
" Batting average, up market, and down market metrics.\n",
" \"\"\"\n",
" \n",
" # Initialize a dictionary to store the results\n",
" results = dict(\n",
" {\n",
" \"batting average\": np.nan,\n",
" \"up market\": np.nan,\n",
" \"down market\": np.nan,\n",
" }\n",
" )\n",
" \n",
" # Calculate active returns by subtracting benchmark returns from portfolio returns\n",
" active_returns = port_returns - bench_returns\n",
"\n",
" # Determine boolean arrays for batting average, up market, and down market conditions\n",
" ba = active_returns > 0\n",
" up = active_returns[bench_returns >= 0.0] > 0\n",
" down = active_returns[bench_returns < 0.0] > 0\n",
"\n",
" # Compute the mean values for the batting average, up market, and down market\n",
" if len(ba) > 0:\n",
" results[\"batting average\"] = ba.mean()\n",
" if len(up) > 0:\n",
" results[\"up market\"] = up.mean()\n",
" if len(down) > 0:\n",
" results[\"down market\"] = down.mean()\n",
"\n",
" # Return a Series with the computed results\n",
" return pd.Series(results, index=results.keys())"
]
},
{
"cell_type": "markdown",
"id": "046b9295",
"metadata": {},
"source": [
"Calculate and display the batting average metrics for the portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "82e94611",
"metadata": {},
"outputs": [],
"source": [
"batting_average(port_returns, bench_returns)"
]
},
{
"cell_type": "markdown",
"id": "e210bcf1",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,330 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f0b34223",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "faa221b8",
"metadata": {},
"source": [
"This code retrieves stock and options data for the ETF \"SPY\" and futures data for the symbol \"ES\". It uses the OpenBB Terminal SDK to load the data, including historical prices and options chains. The retrieved data is then stored in HDF5 files for easy access and persistence. This is useful for financial analysis and modeling, enabling efficient data storage and manipulation. The code concludes by reading the stored data and printing it for verification."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1e4509d9",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d2e2e11",
"metadata": {},
"outputs": [],
"source": [
"STOCKS_DATA_STORE = \"stocks.h5\"\n",
"FUTURES_DATA_STORE = \"futures.h5\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2fe2335b",
"metadata": {},
"outputs": [],
"source": [
"ticker = \"SPY\"\n",
"root = \"ES\""
]
},
{
"cell_type": "markdown",
"id": "24c85387",
"metadata": {},
"source": [
"Retrieve stock price data for the ticker \"SPY\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b650ad85",
"metadata": {},
"outputs": [],
"source": [
"spy_equity = openbb.stocks.load(ticker)"
]
},
{
"cell_type": "markdown",
"id": "8e1952ce",
"metadata": {},
"source": [
"Retrieve options expiration dates for the ticker \"SPY\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f1867fa",
"metadata": {},
"outputs": [],
"source": [
"spy_expirations = openbb.stocks.options.expirations(ticker)"
]
},
{
"cell_type": "markdown",
"id": "473c96d9",
"metadata": {},
"source": [
"Retrieve historical options data for a specific expiration date"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "88dd5dff",
"metadata": {},
"outputs": [],
"source": [
"spy_historic = openbb.stocks.options.hist(\n",
" ticker, \n",
" spy_expirations[1], \n",
" 440\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a15b5e11",
"metadata": {},
"source": [
"Retrieve options chains for the ticker \"SPY\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "22932258",
"metadata": {},
"outputs": [],
"source": [
"spy_chains = openbb.stocks.options.chains(ticker)"
]
},
{
"cell_type": "markdown",
"id": "2b653927",
"metadata": {},
"source": [
"Store the retrieved stock and options data in an HDF5 file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "87844c25",
"metadata": {},
"outputs": [],
"source": [
"with pd.HDFStore(STOCKS_DATA_STORE) as store:\n",
" store.put(\"equities/spy/stock_prices\", spy_equity)\n",
" store.put(\"equities/spy/options_prices\", spy_historic)\n",
" store.put(\"equities/spy/chains\", spy_chains)"
]
},
{
"cell_type": "markdown",
"id": "f19a14bb",
"metadata": {},
"source": [
"Retrieve and store futures data for the symbol \"ES\" for specific expiration dates"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a3596058",
"metadata": {},
"outputs": [],
"source": [
"with pd.HDFStore(FUTURES_DATA_STORE) as store:\n",
" for i in range(23, 31):\n",
" expiry = f\"20{i}-12\"\n",
" df = openbb.futures.historical(\n",
" symbols=[root],\n",
" expiry=expiry,\n",
" start_date=\"2020-01-01\",\n",
" end_date=\"2022-12-31\"\n",
" )\n",
" df.rename(\n",
" columns={\n",
" \"Adj Close\": expiry\n",
" },\n",
" inplace=True\n",
" )\n",
" prices = df[expiry]\n",
" store.put(f'futures/{root}/{expiry}', prices)"
]
},
{
"cell_type": "markdown",
"id": "e2d26e16",
"metadata": {},
"source": [
"Load stored stock prices, options prices, and options chains from the HDF5 file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1f6f38e8",
"metadata": {},
"outputs": [],
"source": [
"with pd.HDFStore(STOCKS_DATA_STORE) as store:\n",
" spy_prices = store[\"equities/spy/stock_prices\"]\n",
" spy_options = store[\"equities/spy/options_prices\"]\n",
" spy_chains = store[\"equities/spy/chains\"]"
]
},
{
"cell_type": "markdown",
"id": "eaef333c",
"metadata": {},
"source": [
"Load stored futures prices for a specific expiration date from the HDF5 file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "718f2b5e",
"metadata": {},
"outputs": [],
"source": [
"with pd.HDFStore(FUTURES_DATA_STORE) as store:\n",
" es_prices = store[f\"futures/{root}/2023-12\"]"
]
},
{
"cell_type": "markdown",
"id": "6e8de69d",
"metadata": {},
"source": [
"Print the loaded stock prices for verification"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be69f51a",
"metadata": {},
"outputs": [],
"source": [
"print(spy_prices)"
]
},
{
"cell_type": "markdown",
"id": "738952d1",
"metadata": {},
"source": [
"Print the loaded options prices for verification"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a6ce0462",
"metadata": {},
"outputs": [],
"source": [
"print(spy_options)"
]
},
{
"cell_type": "markdown",
"id": "36b43858",
"metadata": {},
"source": [
"Print the loaded options prices for verification"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d23ab3b5",
"metadata": {},
"outputs": [],
"source": [
"print(spy_options)"
]
},
{
"cell_type": "markdown",
"id": "5b9da3de",
"metadata": {},
"source": [
"Print the loaded futures prices for verification"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3afda92",
"metadata": {},
"outputs": [],
"source": [
"print(es_prices)"
]
},
{
"cell_type": "markdown",
"id": "3c427529",
"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
}

View File

@ -0,0 +1,336 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0914f29a",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "febfac35",
"metadata": {},
"source": [
"This code calculates the Value at Risk (VaR) for a portfolio of stocks. It downloads historical stock data, computes daily returns, and calculates the portfolio's mean return and standard deviation. The code uses these metrics to compute the potential maximum loss (VaR) at a specified confidence interval. Additionally, it plots the VaR over a 30-day period, providing a visual representation of the risk. This is useful for risk management and financial planning."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b182fa6a",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"from scipy.stats import norm\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10edf2bd",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf"
]
},
{
"cell_type": "markdown",
"id": "f4ba67b6",
"metadata": {},
"source": [
"Define the portfolio of stocks and their respective weights. The weights must sum to 1."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c1351a91",
"metadata": {},
"outputs": [],
"source": [
"tickers = [\"AAPL\", \"FB\", \"C\", \"DIS\"]\n",
"weights = np.array([0.25, 0.3, 0.15, 0.3])"
]
},
{
"cell_type": "markdown",
"id": "8f730b65",
"metadata": {},
"source": [
"Define the initial investment amount and confidence interval for VaR calculation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a2aadea",
"metadata": {},
"outputs": [],
"source": [
"initial_investment = 1_000\n",
"confidence = 0.05"
]
},
{
"cell_type": "markdown",
"id": "0c784214",
"metadata": {},
"source": [
"Download historical stock data for the specified tickers and date range. Filter to close prices only."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5e93c03c",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(tickers, start=\"2018-01-01\", end=\"2021-12-31\")\n",
"data = data.Close"
]
},
{
"cell_type": "markdown",
"id": "4ea34632",
"metadata": {},
"source": [
"Calculate the daily returns for each stock in the portfolio."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "61270dc0",
"metadata": {},
"outputs": [],
"source": [
"returns = data.pct_change()"
]
},
{
"cell_type": "markdown",
"id": "66e7b41d",
"metadata": {},
"source": [
"Compute the covariance matrix of the stock returns to understand the variance and correlation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "684cfbf6",
"metadata": {},
"outputs": [],
"source": [
"cov_matrix = returns.cov()"
]
},
{
"cell_type": "markdown",
"id": "604a1970",
"metadata": {},
"source": [
"Compute the mean daily returns for each stock in the portfolio."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7fc27a88",
"metadata": {},
"outputs": [],
"source": [
"mean_returns = returns.mean()"
]
},
{
"cell_type": "markdown",
"id": "c07ff0af",
"metadata": {},
"source": [
"Calculate the portfolio's expected mean return using the weighted average of individual stock returns."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff9bbcb1",
"metadata": {},
"outputs": [],
"source": [
"port_mean = mean_returns.dot(weights)"
]
},
{
"cell_type": "markdown",
"id": "a10bde64",
"metadata": {},
"source": [
"Calculate the portfolio's standard deviation to measure the overall risk."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "55c75e3a",
"metadata": {},
"outputs": [],
"source": [
"port_stdev = np.sqrt(weights.T.dot(cov_matrix).dot(weights))"
]
},
{
"cell_type": "markdown",
"id": "756c6dc7",
"metadata": {},
"source": [
"Compute the mean investment return based on the initial investment and expected portfolio return."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "81e66c7b",
"metadata": {},
"outputs": [],
"source": [
"mean_investment = (1 + port_mean) * initial_investment"
]
},
{
"cell_type": "markdown",
"id": "6e08e709",
"metadata": {},
"source": [
"Calculate the standard deviation of the investment returns to assess the volatility in dollar terms."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a411b301",
"metadata": {},
"outputs": [],
"source": [
"investment_stdev = initial_investment * port_stdev"
]
},
{
"cell_type": "markdown",
"id": "809d85fd",
"metadata": {},
"source": [
"Calculate the percent point function (PPF) for the given confidence interval."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6bc37ef2",
"metadata": {},
"outputs": [],
"source": [
"percent_point = norm.ppf(confidence, mean_investment, investment_stdev)"
]
},
{
"cell_type": "markdown",
"id": "09805c37",
"metadata": {},
"source": [
"Calculate the Value at Risk (VaR) at the specified confidence interval."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2c79e0c0",
"metadata": {},
"outputs": [],
"source": [
"value_at_risk = initial_investment - percent_point"
]
},
{
"cell_type": "markdown",
"id": "7e537935",
"metadata": {},
"source": [
"Display the calculated VaR for the portfolio."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58065b04",
"metadata": {},
"outputs": [],
"source": [
"f\"Portfolio VaR: {value_at_risk}\""
]
},
{
"cell_type": "markdown",
"id": "95a6e6d7",
"metadata": {},
"source": [
"Calculate the VaR over a 30-day period by scaling with the square root of time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7e6e2859",
"metadata": {},
"outputs": [],
"source": [
"value_at_risks = value_at_risk * np.sqrt(range(1, 31))"
]
},
{
"cell_type": "markdown",
"id": "5147f76c",
"metadata": {},
"source": [
"Plot the VaR over a 30-day period to visualize potential maximum loss over time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de3d7ef3",
"metadata": {},
"outputs": [],
"source": [
"plt.xlabel(\"Day\")\n",
"plt.ylabel(\"Max loss\")\n",
"plt.title(\"Portfolio VaR\")\n",
"plt.plot(value_at_risks, \"r\")"
]
},
{
"cell_type": "markdown",
"id": "04e96fbc",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,557 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ba62c4ae",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "7d26bf77",
"metadata": {},
"source": [
"This notebook performs momentum-based stock trading simulation using vectorbt. It downloads historical stock prices for selected symbols, calculates momentum, and filters the top stocks based on their momentum. The code then applies various exit strategies like Stop Loss (SL), Trailing Stop (TS), and Take Profit (TP) to the selected stocks. Finally, it creates a portfolio with the selected stocks and exit strategies and visualizes the total returns using histograms and boxplots."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6e804ab6",
"metadata": {},
"outputs": [],
"source": [
"import pytz\n",
"from datetime import datetime, timedelta"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9fd91c63",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "90a345fa",
"metadata": {},
"outputs": [],
"source": [
"import vectorbt as vbt"
]
},
{
"cell_type": "markdown",
"id": "a1890d17",
"metadata": {},
"source": [
"Define stock symbols to be analyzed"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f7229afd",
"metadata": {},
"outputs": [],
"source": [
"symbols = [\n",
" \"META\",\n",
" \"AMZN\",\n",
" \"AAPL\",\n",
" \"NFLX\",\n",
" \"GOOG\",\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "c8e0df97",
"metadata": {},
"source": [
"Define the start and end dates for historical data download"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff36497f",
"metadata": {},
"outputs": [],
"source": [
"start_date = datetime(2018, 1, 1, tzinfo=pytz.utc)\n",
"end_date = datetime(2021, 1, 1, tzinfo=pytz.utc)"
]
},
{
"cell_type": "markdown",
"id": "ab953913",
"metadata": {},
"source": [
"Define various simulation parameters including number of stocks to trade, window length, seed, window count, exit types, and stop values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0417b3ed",
"metadata": {},
"outputs": [],
"source": [
"traded_count = 3\n",
"window_len = timedelta(days=12 * 21)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fbb72d7f",
"metadata": {},
"outputs": [],
"source": [
"seed = 42\n",
"window_count = 400\n",
"exit_types = [\"SL\", \"TS\", \"TP\"]\n",
"stops = np.arange(0.01, 1 + 0.01, 0.01)"
]
},
{
"cell_type": "markdown",
"id": "42525ce7",
"metadata": {},
"source": [
"Download historical stock data using vectorbt's YFData module and concatenate the data into a single DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a4b6d14",
"metadata": {},
"outputs": [],
"source": [
"yfdata = vbt.YFData.download(symbols, start=start_date, end=end_date)\n",
"ohlcv = yfdata.concat()"
]
},
{
"cell_type": "markdown",
"id": "c7f07cd7",
"metadata": {},
"source": [
"Split the OHLCV data into windows defined by the window length and count"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b5ee17e3",
"metadata": {},
"outputs": [],
"source": [
"split_ohlcv = {}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "54c69e07",
"metadata": {},
"outputs": [],
"source": [
"for k, v in ohlcv.items():\n",
" split_df, split_indexes = v.vbt.range_split(\n",
" range_len=window_len.days, n=window_count\n",
" )\n",
" split_ohlcv[k] = split_df\n",
"ohlcv = split_ohlcv"
]
},
{
"cell_type": "markdown",
"id": "cae16ab7",
"metadata": {},
"source": [
"Calculate the momentum as the mean percentage change of the closing prices, then select the top stocks based on momentum"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e8762ce5",
"metadata": {},
"outputs": [],
"source": [
"momentum = ohlcv[\"Close\"].pct_change().mean()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2e62896e",
"metadata": {},
"outputs": [],
"source": [
"sorted_momentum = (\n",
" momentum\n",
" .groupby(\n",
" \"split_idx\", \n",
" group_keys=False, \n",
" sort=False\n",
" )\n",
" .apply(\n",
" pd.Series.sort_values\n",
" )\n",
" .groupby(\"split_idx\")\n",
" .head(traded_count)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "83807d67",
"metadata": {},
"source": [
"Select the OHLCV data for the stocks with the highest momentum"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a07939d3",
"metadata": {},
"outputs": [],
"source": [
"selected_open = ohlcv[\"Open\"][sorted_momentum.index]\n",
"selected_high = ohlcv[\"High\"][sorted_momentum.index]\n",
"selected_low = ohlcv[\"Low\"][sorted_momentum.index]\n",
"selected_close = ohlcv[\"Close\"][sorted_momentum.index]"
]
},
{
"cell_type": "markdown",
"id": "64fb1e8f",
"metadata": {},
"source": [
"Initialize entry signals to be true on the first day of each window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9fa6acb9",
"metadata": {},
"outputs": [],
"source": [
"entries = pd.DataFrame.vbt.signals.empty_like(selected_open)\n",
"entries.iloc[0, :] = True"
]
},
{
"cell_type": "markdown",
"id": "6dd7d916",
"metadata": {},
"source": [
"Define stop loss exits using vectorbt's OHLCSTX module"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24bf8d0b",
"metadata": {},
"outputs": [],
"source": [
"sl_exits = vbt.OHLCSTX.run(\n",
" entries,\n",
" selected_open,\n",
" selected_high,\n",
" selected_low,\n",
" selected_close,\n",
" sl_stop=list(stops),\n",
" stop_type=None,\n",
" stop_price=None,\n",
").exits"
]
},
{
"cell_type": "markdown",
"id": "4ec6eab3",
"metadata": {},
"source": [
"Define trailing stop exits using vectorbt's OHLCSTX module"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b038fae0",
"metadata": {},
"outputs": [],
"source": [
"ts_exits = vbt.OHLCSTX.run(\n",
" entries,\n",
" selected_open,\n",
" selected_high,\n",
" selected_low,\n",
" selected_close,\n",
" sl_stop=list(stops),\n",
" sl_trail=True,\n",
" stop_type=None,\n",
" stop_price=None,\n",
").exits"
]
},
{
"cell_type": "markdown",
"id": "ad721230",
"metadata": {},
"source": [
"Define take profit exits using vectorbt's OHLCSTX module"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8ff261d7",
"metadata": {},
"outputs": [],
"source": [
"tp_exits = vbt.OHLCSTX.run(\n",
" entries,\n",
" selected_open,\n",
" selected_high,\n",
" selected_low,\n",
" selected_close,\n",
" tp_stop=list(stops),\n",
" stop_type=None,\n",
" stop_price=None,\n",
").exits"
]
},
{
"cell_type": "markdown",
"id": "c2a0bd88",
"metadata": {},
"source": [
"Rename and drop levels for the different exit types to standardize the DataFrame structure"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5674fff",
"metadata": {},
"outputs": [],
"source": [
"sl_exits.vbt.rename_levels({\"ohlcstx_sl_stop\": \"stop_value\"}, inplace=True)\n",
"ts_exits.vbt.rename_levels({\"ohlcstx_sl_stop\": \"stop_value\"}, inplace=True)\n",
"tp_exits.vbt.rename_levels({\"ohlcstx_tp_stop\": \"stop_value\"}, inplace=True)\n",
"ts_exits.vbt.drop_levels(\"ohlcstx_sl_trail\", inplace=True)"
]
},
{
"cell_type": "markdown",
"id": "e4ecca94",
"metadata": {},
"source": [
"Ensure the last day in the window is always an exit signal for all exit types"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "89d7940d",
"metadata": {},
"outputs": [],
"source": [
"sl_exits.iloc[-1, :] = True\n",
"ts_exits.iloc[-1, :] = True\n",
"tp_exits.iloc[-1, :] = True"
]
},
{
"cell_type": "markdown",
"id": "3ec14fef",
"metadata": {},
"source": [
"Convert exits into first exit signals based on entries, allowing gaps"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "09e6acb1",
"metadata": {},
"outputs": [],
"source": [
"sl_exits = sl_exits.vbt.signals.first(reset_by=entries, allow_gaps=True)\n",
"ts_exits = ts_exits.vbt.signals.first(reset_by=entries, allow_gaps=True)\n",
"tp_exits = tp_exits.vbt.signals.first(reset_by=entries, allow_gaps=True)"
]
},
{
"cell_type": "markdown",
"id": "0570cee9",
"metadata": {},
"source": [
"Concatenate all exit signals into a single DataFrame for further analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e06ca90c",
"metadata": {},
"outputs": [],
"source": [
"exits = pd.DataFrame.vbt.concat(\n",
" sl_exits,\n",
" ts_exits,\n",
" tp_exits,\n",
" keys=pd.Index(exit_types, name=\"exit_type\"),\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a246e7b2",
"metadata": {},
"source": [
"Create a portfolio using the selected close prices, entries, and exits"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "657130c5",
"metadata": {},
"outputs": [],
"source": [
"portfolio = vbt.Portfolio.from_signals(selected_close, entries, exits)"
]
},
{
"cell_type": "markdown",
"id": "4e7d40f2",
"metadata": {},
"source": [
"Calculate the total return of the portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "76791b7d",
"metadata": {},
"outputs": [],
"source": [
"total_return = portfolio.total_return()"
]
},
{
"cell_type": "markdown",
"id": "9b92b0fe",
"metadata": {},
"source": [
"Unstack the total returns by exit type for visualization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f9c0ffc",
"metadata": {},
"outputs": [],
"source": [
"total_return_by_type = total_return.unstack(level=\"exit_type\")[exit_types]"
]
},
{
"cell_type": "markdown",
"id": "5a1fd9bc",
"metadata": {},
"source": [
"Plot histograms of the total returns for each exit type"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb0943a9",
"metadata": {},
"outputs": [],
"source": [
"total_return_by_type[exit_types].vbt.histplot(\n",
" xaxis_title=\"Total return\",\n",
" xaxis_tickformat=\"%\",\n",
" yaxis_title=\"Count\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "85d76d8c",
"metadata": {},
"source": [
"Plot boxplots of the total returns for each exit type"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f05fdd98",
"metadata": {},
"outputs": [],
"source": [
"total_return_by_type.vbt.boxplot(\n",
" yaxis_title='Total return',\n",
" yaxis_tickformat='%'\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c88d4f8a",
"metadata": {},
"source": [
"Provide descriptive statistics for the total returns by exit type"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ca8a1a68",
"metadata": {},
"outputs": [],
"source": [
"total_return_by_type.describe(percentiles=[])"
]
},
{
"cell_type": "markdown",
"id": "da178273",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,534 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8ca207cc",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "246d6494",
"metadata": {},
"source": [
"This notebook demonstrates the use of VectorBT PRO for backtesting and analyzing quantitative trading strategies focusing on chart patterns. It provides tools for data acquisition, signal generation, portfolio optimization, and strategy simulation. The notebook includes a pattern detector optimized with Numba to scan data for specified patterns. We use hourly price data from TradingView to detect and backtest various bullish and bearish patterns. Practical applications include testing trading strategies, understanding historical market behavior, and optimizing algorithmic trading setups."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c991ec37",
"metadata": {},
"outputs": [],
"source": [
"import vectorbtpro as vbt\n",
"import pandas as pd\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e6dd037f",
"metadata": {},
"outputs": [],
"source": [
"vbt.settings.set_theme(\"dark\")"
]
},
{
"cell_type": "markdown",
"id": "de1c5df4",
"metadata": {},
"source": [
"Define the symbols for which we will pull data from TradingView."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "763d7811",
"metadata": {},
"outputs": [],
"source": [
"symbols = [\n",
" \"NASDAQ:META\",\n",
" \"NASDAQ:AMZN\",\n",
" \"NASDAQ:AAPL\",\n",
" \"NASDAQ:NFLX\",\n",
" \"NASDAQ:GOOG\",\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "0e383eb4",
"metadata": {},
"source": [
"Pull hourly price data for the defined symbols from TradingView."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a427c9d",
"metadata": {},
"outputs": [],
"source": [
"data = vbt.TVData.pull(symbols, timeframe=\"hourly\")"
]
},
{
"cell_type": "markdown",
"id": "47cb95de",
"metadata": {},
"source": [
"Define the date range for the analysis."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0cdb0559",
"metadata": {},
"outputs": [],
"source": [
"start_date = \"2020\"\n",
"end_date = None"
]
},
{
"cell_type": "markdown",
"id": "613d08bf",
"metadata": {},
"source": [
"Slice the data to include only the specified date range."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d9dd962",
"metadata": {},
"outputs": [],
"source": [
"data = data.xloc[start_date:end_date]"
]
},
{
"cell_type": "markdown",
"id": "cc590aba",
"metadata": {},
"source": [
"Display statistics of the data to ensure it spans the correct date period and is free of NaN values."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b713a7e",
"metadata": {},
"outputs": [],
"source": [
"print(data.stats())"
]
},
{
"cell_type": "markdown",
"id": "55636e91",
"metadata": {},
"source": [
"Calculate the HLC3 (High-Low-Close average) as the chosen feature for pattern detection."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "32b66020",
"metadata": {},
"outputs": [],
"source": [
"price = data.hlc3"
]
},
{
"cell_type": "markdown",
"id": "0ff519a4",
"metadata": {},
"source": [
"Define bullish and bearish chart patterns as numerical sequences."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58bd8102",
"metadata": {},
"outputs": [],
"source": [
"bullish_patterns = {\n",
" \"double_bottom\": [5, 1, 3, 1, 5],\n",
" \"exp_triangle\": [3, 4, 2, 5, 1, 6],\n",
" \"asc_triangle\": [1, 5, 2, 5, 3, 6],\n",
" \"symm_triangle\": [1, 6, 2, 5, 3, 6],\n",
" \"pennant\": [6, 1, 5, 2, 4, 3, 6]\n",
"}\n",
"bearish_patterns = {\n",
" \"head_and_shoulders\": [1, 4, 2, 6, 2, 4, 1],\n",
" \"double_top\": [1, 5, 3, 5, 1],\n",
" \"desc_triangle\": [6, 2, 5, 2, 4, 1],\n",
" \"symm_triangle\": [6, 1, 5, 2, 4, 1],\n",
" \"pennant\": [1, 6, 2, 5, 3, 4, 1]\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "01fb8f75",
"metadata": {},
"source": [
"Plot the numerical sequence of the \"double_bottom\" pattern for visual confirmation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "62a45611",
"metadata": {},
"outputs": [],
"source": [
"pd.Series(bullish_patterns[\"double_bottom\"]).vbt.plot().show_png()"
]
},
{
"cell_type": "markdown",
"id": "1fa10c11",
"metadata": {},
"source": [
"Define the minimum and maximum window lengths for pattern detection."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "015b440c",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"min_window = 24\n",
"max_window = 24 * 30"
]
},
{
"cell_type": "markdown",
"id": "3da2640b",
"metadata": {},
"source": [
"Define a function to detect patterns in the price data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "93f4bb63",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def detect_patterns(patterns):\n",
" \"\"\"Detect patterns in price data.\n",
"\n",
" This function searches for specified patterns in the price data within defined windows.\n",
"\n",
" Parameters\n",
" ----------\n",
" patterns : dict\n",
" Dictionary of patterns to detect.\n",
"\n",
" Returns\n",
" -------\n",
" vbt.PatternRanges\n",
" Detected pattern ranges.\n",
" \"\"\"\n",
" \n",
" return vbt.PatternRanges.from_pattern_search(\n",
" price,\n",
" open=data.open,\n",
" high=data.high,\n",
" low=data.low,\n",
" close=data.close,\n",
" pattern=patterns,\n",
" window=min_window,\n",
" max_window=max_window,\n",
" execute_kwargs=dict(\n",
" engine=\"threadpool\",\n",
" chunk_len=\"auto\",\n",
" show_progress=True\n",
" )\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "042b70e5",
"metadata": {},
"source": [
"Detect bullish and bearish patterns in the price data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "61345014",
"metadata": {},
"outputs": [],
"source": [
"bullish_matches = detect_patterns(\n",
" vbt.Param(\n",
" bullish_patterns,\n",
" name=\"bullish_pattern\"\n",
" )\n",
")\n",
"bearish_matches = detect_patterns(\n",
" vbt.Param(\n",
" bearish_patterns,\n",
" name=\"bearish_pattern\"\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a551859b",
"metadata": {},
"source": [
"Print the number of matches for each bullish pattern and dataset."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "38713797",
"metadata": {},
"outputs": [],
"source": [
"print(bullish_matches.count())"
]
},
{
"cell_type": "markdown",
"id": "c204331e",
"metadata": {},
"source": [
"Plot the pattern and dataset with the most matches."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7dc6ae58",
"metadata": {},
"outputs": [],
"source": [
"vbt.settings.plotting.auto_rangebreaks = True"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a31237e",
"metadata": {},
"outputs": [],
"source": [
"display_column = bullish_matches.count().idxmax()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72eaec81",
"metadata": {},
"outputs": [],
"source": [
"bullish_matches.plot(column=display_column, fit_ranges=True).show_png()"
]
},
{
"cell_type": "markdown",
"id": "b7f111fc",
"metadata": {},
"source": [
"Zoom in on a specific match within the dataset."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d0ab460",
"metadata": {},
"outputs": [],
"source": [
"display_match = 3"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3080ce4f",
"metadata": {},
"outputs": [],
"source": [
"bullish_matches.plot(\n",
" column=display_column,\n",
" fit_ranges=display_match\n",
").show_png()"
]
},
{
"cell_type": "markdown",
"id": "459d9343",
"metadata": {},
"source": [
"Convert detected patterns into entry and exit signals for backtesting."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34771aa0",
"metadata": {},
"outputs": [],
"source": [
"entries = bullish_matches.last_pd_mask\n",
"exits = bearish_matches.last_pd_mask"
]
},
{
"cell_type": "markdown",
"id": "89be1882",
"metadata": {},
"source": [
"Generate a Cartesian product of bullish and bearish patterns for systematic testing."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b51429ba",
"metadata": {},
"outputs": [],
"source": [
"entries, exits = entries.vbt.x(exits)"
]
},
{
"cell_type": "markdown",
"id": "b24cb4af",
"metadata": {},
"source": [
"Print the columns representing individual backtests."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a80e5a62",
"metadata": {},
"outputs": [],
"source": [
"print(entries.columns)"
]
},
{
"cell_type": "markdown",
"id": "9f39d343",
"metadata": {},
"source": [
"Simulate a portfolio using the generated signals."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d278d770",
"metadata": {},
"outputs": [],
"source": [
"pf = vbt.Portfolio.from_signals(data, entries, exits)"
]
},
{
"cell_type": "markdown",
"id": "3a7c5f04",
"metadata": {},
"source": [
"Calculate and print the mean total return for each combination of bullish and bearish patterns."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "50369ac9",
"metadata": {},
"outputs": [],
"source": [
"mean_total_return = pf.total_return.groupby(\n",
" [\"bullish_pattern\", \"bearish_pattern\"]\n",
").mean()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "02089a5a",
"metadata": {},
"outputs": [],
"source": [
"print(mean_total_return)"
]
},
{
"cell_type": "markdown",
"id": "294cf7dc",
"metadata": {},
"source": [
"Visualize the mean total return as a heatmap."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea171583",
"metadata": {},
"outputs": [],
"source": [
"mean_total_return.vbt.heatmap(\n",
" x_level=\"bearish_pattern\",\n",
" y_level=\"bullish_pattern\"\n",
").show_png()"
]
},
{
"cell_type": "markdown",
"id": "9350f03e",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,217 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "6aa29ed3",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "dbe3c872",
"metadata": {},
"source": [
"This code performs sentiment analysis on news articles related to Microsoft (MSFT) using a predefined language model. It imports necessary libraries, loads environment variables, and sets up a language chain model with a prompt template. The code then fetches news articles, applies the sentiment analysis model to each news description, and appends the sentiment results. Finally, it displays the news descriptions along with their corresponding sentiments."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a7beec45",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dcdaade8",
"metadata": {},
"outputs": [],
"source": [
"from langchain.chains import LLMChain\n",
"from langchain.prompts import PromptTemplate\n",
"from langchain.chat_models import ChatOpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "86d35212",
"metadata": {},
"outputs": [],
"source": [
"from dotenv import load_dotenv"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb878e28",
"metadata": {},
"outputs": [],
"source": [
"load_dotenv()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c6d9e010",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cee1e48e",
"metadata": {},
"outputs": [],
"source": [
"pd.set_option(\"max_colwidth\", None)"
]
},
{
"cell_type": "markdown",
"id": "ecdeff99",
"metadata": {},
"source": [
"Initialize the language model with GPT-4 and set temperature for response variability"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cc0e296a",
"metadata": {},
"outputs": [],
"source": [
"llm = ChatOpenAI(model=\"gpt-4\", temperature=0)"
]
},
{
"cell_type": "markdown",
"id": "929eb3e1",
"metadata": {},
"source": [
"Define the sentiment analysis prompt template"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34670050",
"metadata": {},
"outputs": [],
"source": [
"prompt = \"\"\"\n",
"Is the predominant sentiment in the following statement positive, negative, or neutral?\n",
"---------\n",
"Statement: {statement}\n",
"---------\n",
"Respond with one word in lowercase: positive, negative, or neutral.\n",
"Sentiment:\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"id": "a1067c85",
"metadata": {},
"source": [
"Create a sentiment analysis chain using the language model and prompt template"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "540facbd",
"metadata": {},
"outputs": [],
"source": [
"chain = LLMChain.from_string(\n",
" llm=llm,\n",
" template=prompt\n",
")"
]
},
{
"cell_type": "markdown",
"id": "905ccc67",
"metadata": {},
"source": [
"Fetch news articles related to Microsoft (MSFT) using the OpenBB SDK"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a85fdbbe",
"metadata": {},
"outputs": [],
"source": [
"msft = openbb.news(term=\"msft\")"
]
},
{
"cell_type": "markdown",
"id": "0e72a0f5",
"metadata": {},
"source": [
"Apply the sentiment analysis chain to each news description and add the sentiment results to a new column"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7c08925",
"metadata": {},
"outputs": [],
"source": [
"msft[\"Sentiment\"] = msft.Description.apply(chain.run)"
]
},
{
"cell_type": "markdown",
"id": "ae31394d",
"metadata": {},
"source": [
"Display the news descriptions along with their corresponding sentiment results"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "259bb33e",
"metadata": {},
"outputs": [],
"source": [
"msft[[\"Description\", \"Sentiment\"]]"
]
},
{
"cell_type": "markdown",
"id": "29c68af1",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,232 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "344122e6",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "fedd6a2c",
"metadata": {},
"source": [
"This code performs clustering on Nasdaq-100 stock returns and volatilities to identify distinct groups using the K-Medoids algorithm. It fetches historical stock data, calculates annualized returns and volatilities, and visualizes the clustering results. The Elbow method is used to determine the optimal number of clusters. This approach is useful for financial analysis, portfolio management, and identifying patterns in stock performance."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a3b6f094",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"from sklearn_extra.cluster import KMedoids\n",
"import matplotlib.pyplot as plt\n",
"from openbb_terminal.sdk import openbb"
]
},
{
"cell_type": "markdown",
"id": "67dda957",
"metadata": {},
"source": [
"Configure default plot style and parameters for visualizations"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a254a022",
"metadata": {},
"outputs": [],
"source": [
"plt.style.use(\"default\")\n",
"plt.rcParams[\"figure.figsize\"] = [5.5, 4.0]\n",
"plt.rcParams[\"figure.dpi\"] = 140\n",
"plt.rcParams[\"lines.linewidth\"] = 0.75\n",
"plt.rcParams[\"font.size\"] = 8"
]
},
{
"cell_type": "markdown",
"id": "144440ca",
"metadata": {},
"source": [
"Fetch Nasdaq-100 tickers from Wikipedia and retrieve historical stock data from the OpenBB Terminal SDK"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9bfb4664",
"metadata": {},
"outputs": [],
"source": [
"nq = pd.read_html(\"https://en.wikipedia.org/wiki/Nasdaq-100\")[4]\n",
"symbols = nq.Ticker.tolist()\n",
"data = openbb.stocks.ca.hist(\n",
" symbols, \n",
" start_date=\"2020-01-01\", \n",
" end_date=\"2022-12-31\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "835e4118",
"metadata": {},
"source": [
"Calculate annualized returns and volatilities from the historical stock data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "293200a9",
"metadata": {},
"outputs": [],
"source": [
"moments = (\n",
" data\n",
" .pct_change()\n",
" .describe()\n",
" .T[[\"mean\", \"std\"]]\n",
" .rename(columns={\"mean\": \"returns\", \"std\": \"vol\"})\n",
") * [252, np.sqrt(252)]"
]
},
{
"cell_type": "markdown",
"id": "896c573b",
"metadata": {},
"source": [
"Calculate the sum of squared errors (SSE) for different numbers of clusters to determine the optimal number using the Elbow method"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4728d89",
"metadata": {},
"outputs": [],
"source": [
"sse = []\n",
"for k in range(2, 15):\n",
" km = KMedoids(n_clusters=k).fit(moments)\n",
" sse.append(km.inertia_)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe9f1536",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(range(2, 15), sse)\n",
"plt.title(\"Elbow Curve\")"
]
},
{
"cell_type": "markdown",
"id": "69b91a09",
"metadata": {},
"source": [
"Fit the K-Medoids algorithm with the optimal number of clusters (in this case, 5) and obtain cluster labels"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "918cbfa6",
"metadata": {},
"outputs": [],
"source": [
"km = KMedoids(n_clusters=5).fit(moments)\n",
"labels = km.labels_\n",
"unique_labels = set(labels)\n",
"colors = [\n",
" plt.cm.Spectral(each) for each in np.linspace(0, 1, len(unique_labels))\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8687502",
"metadata": {},
"outputs": [],
"source": [
"labels"
]
},
{
"cell_type": "markdown",
"id": "1cffebb3",
"metadata": {},
"source": [
"Visualize the clustering results by plotting annualized returns and volatilities, color-coded by cluster"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7f465b31",
"metadata": {},
"outputs": [],
"source": [
"for k, col in zip(unique_labels, colors):\n",
" class_member_mask = labels == k\n",
"\n",
" xy = moments[class_member_mask]\n",
" plt.plot(\n",
" xy.iloc[:, 0],\n",
" xy.iloc[:, 1],\n",
" \"o\",\n",
" markerfacecolor=tuple(col),\n",
" markeredgecolor=\"k\",\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "97140a52",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(\n",
" km.cluster_centers_[:, 0],\n",
" km.cluster_centers_[:, 1],\n",
" \"o\",\n",
" markerfacecolor=\"cyan\",\n",
" markeredgecolor=\"k\",\n",
")\n",
"plt.xlabel(\"Return\")\n",
"plt.ylabel(\"Ann. Vol.\")"
]
},
{
"cell_type": "markdown",
"id": "ec75d2f7",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,390 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "7aa4ae69",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "e643b553",
"metadata": {},
"source": [
"This notebook calibrates the SABR (Stochastic Alpha, Beta, Rho) model to market data, specifically for the SPY options expiring in January 2026. It fetches option chains, extracts call and put options, and calculates the mid-prices. The SABR model is used to fit implied volatilities and generate a volatility smile. Finally, the code uses the Black model to calculate theoretical option prices and compares them to market prices. This is useful in practice for financial modeling and option pricing."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e822978d",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from openbb_terminal.sdk import openbb\n",
"from pysabr import Hagan2002LognormalSABR\n",
"from pysabr import hagan_2002_lognormal_sabr as sabr\n",
"from pysabr.black import lognormal_call"
]
},
{
"cell_type": "markdown",
"id": "5030ab09",
"metadata": {},
"source": [
"Set plot style and parameters for visualizations"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17060c09",
"metadata": {},
"outputs": [],
"source": [
"plt.style.use(\"default\")\n",
"plt.rcParams[\"figure.figsize\"] = [5.5, 4.0]\n",
"plt.rcParams[\"figure.dpi\"] = 140\n",
"plt.rcParams[\"lines.linewidth\"] = 0.75\n",
"plt.rcParams[\"font.size\"] = 8"
]
},
{
"cell_type": "markdown",
"id": "c0a074da",
"metadata": {},
"source": [
"Define the symbol and expiration date for the SPY options"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e2d1fdb5",
"metadata": {},
"outputs": [],
"source": [
"symbol = \"SPY\"\n",
"expiration = \"2026-01-16\""
]
},
{
"cell_type": "markdown",
"id": "bf1880a3",
"metadata": {},
"source": [
"Fetch the option chains for the given symbol from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cccd411c",
"metadata": {},
"outputs": [],
"source": [
"spy = openbb.stocks.options.chains(symbol, source=\"YahooFinance\")"
]
},
{
"cell_type": "markdown",
"id": "c28a3ce1",
"metadata": {},
"source": [
"Extract call options and calculate their mid-prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de2ec85c",
"metadata": {},
"outputs": [],
"source": [
"calls = spy[spy.optionType == \"call\"]\n",
"jan_2026_c = calls[calls.expiration == expiration].set_index(\"strike\")\n",
"jan_2026_c[\"mid\"] = (jan_2026_c.ask + jan_2026_c.ask) / 2"
]
},
{
"cell_type": "markdown",
"id": "9b365dea",
"metadata": {},
"source": [
"Extract put options and calculate their mid-prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "184af719",
"metadata": {},
"outputs": [],
"source": [
"puts = spy[spy.optionType == \"put\"]\n",
"jan_2026_p = puts[puts.expiration == expiration].set_index(\"strike\")\n",
"jan_2026_p[\"mid\"] = (jan_2026_p.ask + jan_2026_p.ask) / 2"
]
},
{
"cell_type": "markdown",
"id": "b1cfcfad",
"metadata": {},
"source": [
"Extract strikes and implied volatilities for the call options"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d804f968",
"metadata": {},
"outputs": [],
"source": [
"strikes = jan_2026_c.index\n",
"vols = jan_2026_c.impliedVolatility * 100"
]
},
{
"cell_type": "markdown",
"id": "b23b23f7",
"metadata": {},
"source": [
"Find the forward price using put-call parity and calculate the time to expiration"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0fdc6eeb",
"metadata": {},
"outputs": [],
"source": [
"f = (\n",
" (jan_2026_c.mid - jan_2026_p.mid)\n",
" .dropna()\n",
" .abs()\n",
" .sort_values()\n",
" .index[0]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e29bc873",
"metadata": {},
"source": [
"Calculate the time fraction until expiration in years"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3fbcafa0",
"metadata": {},
"outputs": [],
"source": [
"t = (pd.Timestamp(expiration) - pd.Timestamp.now()).days / 365"
]
},
{
"cell_type": "markdown",
"id": "a6c823e1",
"metadata": {},
"source": [
"Set the beta parameter for the SABR model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3358162e",
"metadata": {},
"outputs": [],
"source": [
"beta = 0.5"
]
},
{
"cell_type": "markdown",
"id": "a0546bd3",
"metadata": {},
"source": [
"Initialize the SABR model with the forward price, time to expiration, and beta"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "618ffe6d",
"metadata": {},
"outputs": [],
"source": [
"sabr_lognormal = Hagan2002LognormalSABR(\n",
" f=f,\n",
" t=t,\n",
" beta=beta\n",
")"
]
},
{
"cell_type": "markdown",
"id": "8cc847c8",
"metadata": {},
"source": [
"Fit the SABR model to the market implied volatilities"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "81e7bee9",
"metadata": {},
"outputs": [],
"source": [
"alpha, rho, volvol = sabr_lognormal.fit(strikes, vols)\n",
"print(alpha, rho, volvol)"
]
},
{
"cell_type": "markdown",
"id": "5c744107",
"metadata": {},
"source": [
"Calculate calibrated volatilities using the SABR model for each strike price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4bc7072b",
"metadata": {},
"outputs": [],
"source": [
"calibrated_vols = [\n",
" sabr.lognormal_vol(strike, f, t, alpha, beta, rho, volvol) * 100\n",
" for strike in strikes\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "0df4deb9",
"metadata": {},
"source": [
"Plot the volatility smile generated by the SABR model and compare it to market implied volatilities"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7f74ef42",
"metadata": {},
"outputs": [],
"source": [
"plt.plot(\n",
" strikes, \n",
" calibrated_vols\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27e406d8",
"metadata": {},
"outputs": [],
"source": [
"plt.xlabel(\"Strike\")\n",
"plt.ylabel(\"Volatility\")\n",
"plt.title(\"Volatility Smile\")\n",
"plt.plot(strikes, vols)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "b6b09053",
"metadata": {},
"source": [
"Calculate theoretical option prices using the Black model for each strike price and calibrated volatility"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17ee92ea",
"metadata": {},
"outputs": [],
"source": [
"black_values = []\n",
"for strike, calibrated_vol in zip(strikes.tolist(), calibrated_vols):\n",
" black_value = lognormal_call(\n",
" strike, \n",
" f, \n",
" t, \n",
" calibrated_vol / 100, \n",
" 0.05, \n",
" cp=\"call\"\n",
" )\n",
" black_values.append(black_value)"
]
},
{
"cell_type": "markdown",
"id": "07f10dc9",
"metadata": {},
"source": [
"Create a DataFrame to compare Black model values with market mid-prices and plot the differences"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66d3bf47",
"metadata": {},
"outputs": [],
"source": [
"option_values = pd.DataFrame(\n",
" {\n",
" \"black\": black_values,\n",
" \"market\": jan_2026_c.mid\n",
" },\n",
" index=strikes\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3682905",
"metadata": {},
"outputs": [],
"source": [
"(option_values.black - option_values.market).plot.bar()"
]
},
{
"cell_type": "markdown",
"id": "b7da06c5",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,310 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "7711dedc",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "6ebec8a8",
"metadata": {},
"source": [
"This code utilizes the VectorBT PRO library to detect historical price patterns and project them into the future. It identifies segments of price history similar to the latest price trend and extrapolates these patterns for statistical analysis. The code retrieves price data, finds similar historical patterns, and generates projections to visualize potential future price movements. Additionally, it creates an animated GIF to demonstrate how these projections evolve over time. This is useful for market analysis, forecasting, and real-time decision-making."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ebd6203",
"metadata": {},
"outputs": [],
"source": [
"import vectorbtpro as vbt\n",
"vbt.settings.set_theme(\"dark\")"
]
},
{
"cell_type": "markdown",
"id": "7385ee7b",
"metadata": {},
"source": [
"Configure the settings to set global defaults for Plotly figures."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a41fa11",
"metadata": {},
"outputs": [],
"source": [
"# vbt.settings.plotting.layout[\"width\"] = 900\n",
"# vbt.settings.plotting.layout[\"height\"] = 450\n",
"# vbt.settings.plotting.layout[\"images\"] = [dict(\n",
"# source=\"https://vectorbt.pro/assets/logo/logo.svg\",\n",
"# xref=\"paper\", yref=\"paper\",\n",
"# x=0, y=0.95,\n",
"# sizex=0.15, sizey=0.15,\n",
"# xanchor=\"center\", yanchor=\"bottom\"\n",
"# )]"
]
},
{
"cell_type": "markdown",
"id": "f606be4c",
"metadata": {},
"source": [
"Define variables for the analysis including symbol, timeframe, and other parameters."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e29f7952",
"metadata": {},
"outputs": [],
"source": [
"SYMBOL = \"BTC-USD\"\n",
"TIMEFRAME = \"1 hour\"\n",
"START = \"one year ago\"\n",
"\n",
"LAST_N_BARS = 66\n",
"PRED_N_BARS = 12\n",
"\n",
"GIF_FNAME = \"projections.gif\"\n",
"GIF_N_BARS = 72\n",
"GIF_FPS = 4\n",
"GIF_PAD = 0.01"
]
},
{
"cell_type": "markdown",
"id": "d56a4058",
"metadata": {},
"source": [
"Retrieve price data from Yahoo Finance based on the defined parameters."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f1e009c2",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"data = vbt.YFData.pull(SYMBOL, timeframe=TIMEFRAME, start=START)"
]
},
{
"cell_type": "markdown",
"id": "8185c650",
"metadata": {},
"source": [
"Analyze the most recent price trend and identify similar historical price segments."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f21739ae",
"metadata": {},
"outputs": [],
"source": [
"def find_patterns(data):\n",
" \"\"\"Identify historical price segments similar to the latest trend.\n",
" \n",
" This function finds price patterns in historical data that match the most recent price trend.\n",
" \n",
" Parameters\n",
" ----------\n",
" data : vbt.YFData\n",
" The data containing historical price information.\n",
" \n",
" Returns\n",
" -------\n",
" pattern_ranges : vbt.PatternRanges\n",
" The identified historical price segments similar to the latest trend.\n",
" \"\"\"\n",
" price = data.hlc3\n",
" pattern = price.values[-LAST_N_BARS:]\n",
" pattern_ranges = price.vbt.find_pattern(\n",
" pattern=pattern,\n",
" rescale_mode=\"rebase\",\n",
" overlap_mode=\"allow\",\n",
" wrapper_kwargs=dict(freq=TIMEFRAME)\n",
" )\n",
" pattern_ranges = pattern_ranges.status_closed\n",
" return pattern_ranges\n",
"\n",
"pattern_ranges = find_patterns(data)\n",
"print(pattern_ranges.count())\n"
]
},
{
"cell_type": "markdown",
"id": "4d02c58a",
"metadata": {},
"source": [
"Identify and plot projections from the identified price segments."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5486c907",
"metadata": {},
"outputs": [],
"source": [
"def plot_projections(data, pattern_ranges, **kwargs):\n",
" \"\"\"Plot projections of historical price segments.\n",
" \n",
" This function extracts and plots the price data following each identified segment.\n",
" \n",
" Parameters\n",
" ----------\n",
" data : vbt.YFData\n",
" The data containing historical price information.\n",
" pattern_ranges : vbt.PatternRanges\n",
" The identified historical price segments.\n",
" \n",
" Returns\n",
" -------\n",
" plot : vbt.Figure\n",
" The plot of projections.\n",
" \"\"\"\n",
" projection_ranges = pattern_ranges.with_delta(\n",
" PRED_N_BARS,\n",
" open=data.open,\n",
" high=data.high,\n",
" low=data.low,\n",
" close=data.close,\n",
" )\n",
" projection_ranges = projection_ranges.status_closed\n",
" return projection_ranges.plot_projections(\n",
" plot_past_period=LAST_N_BARS, \n",
" **kwargs,\n",
" )\n",
"\n",
"plot_projections(data, pattern_ranges, plot_bands=False).show_png()\n"
]
},
{
"cell_type": "markdown",
"id": "440dd86c",
"metadata": {},
"source": [
"Display confidence bands for a visually compelling and statistically robust forecast."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b477c463",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"plot_projections(data, pattern_ranges, plot_bands=True).show_png()"
]
},
{
"cell_type": "markdown",
"id": "97e0ee8d",
"metadata": {},
"source": [
"Generate a GIF animation that iterates through a specified range of bars."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "850758bd",
"metadata": {},
"outputs": [],
"source": [
"def plot_frame(frame_index, **kwargs):\n",
" \"\"\"Plot a single frame for the GIF animation.\n",
" \n",
" This function applies the pattern recognition procedure to each bar within the specified range.\n",
" \n",
" Parameters\n",
" ----------\n",
" frame_index : pd.Index\n",
" Index of the data frame.\n",
" \n",
" Returns\n",
" -------\n",
" plot : vbt.Figure or None\n",
" The plot of projections or None if conditions are not met.\n",
" \"\"\"\n",
" sub_data = data.loc[:frame_index[-1]]\n",
" pattern_ranges = find_patterns(sub_data)\n",
" if pattern_ranges.count() < 3:\n",
" return None\n",
" return plot_projections(sub_data, pattern_ranges, **kwargs)\n",
"\n",
"vbt.save_animation(\n",
" GIF_FNAME,\n",
" data.index[-GIF_N_BARS:],\n",
" plot_frame,\n",
" plot_projections=False,\n",
" delta=1,\n",
" fps=GIF_FPS,\n",
" writer_kwargs=dict(loop=0),\n",
" yaxis_range=[\n",
" data.low.iloc[-GIF_N_BARS:].min() * (1 - GIF_PAD), \n",
" data.high.iloc[-GIF_N_BARS:].max() * (1 + GIF_PAD)\n",
" ],\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e4c94a20",
"metadata": {},
"source": [
"Display the generated GIF animation to visualize the projections over time."
]
},
{
"cell_type": "markdown",
"id": "33d0cac8",
"metadata": {},
"source": [
"![projections.gif](projections.gif)"
]
},
{
"cell_type": "markdown",
"id": "dec39a10",
"metadata": {},
"source": [
"Confidence bands describe past performance but are not guarantees of future results."
]
},
{
"cell_type": "markdown",
"id": "014acdeb",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,272 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9bcf2ee3",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "a5b4c5df",
"metadata": {},
"source": [
"This code retrieves historical stock price data for Netflix from Yahoo Finance and performs statistical analysis. It calculates the rolling z-score of the closing prices over a 30-day window, allowing for the detection of significant deviations from the mean. The z-score is then plotted and its distribution visualized using a histogram. Additionally, it computes the minimum percentage change in closing prices over a 30-day rolling window and visualizes it. This is useful for identifying extreme price movements and understanding the stock's volatility."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "50d5e330",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf"
]
},
{
"cell_type": "markdown",
"id": "393360c8",
"metadata": {},
"source": [
"Download historical stock price data for Netflix from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b89ccf32",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"data = yf.download(\"NFLX\", start=\"2020-01-01\", end=\"2022-06-30\")"
]
},
{
"cell_type": "markdown",
"id": "87c13794",
"metadata": {},
"source": [
"Define a function to calculate the z-score for a given chunk of data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b6152e4",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def z_score(chunk):\n",
" \"\"\"Calculate z-score for a given chunk.\n",
" \n",
" Parameters\n",
" ----------\n",
" chunk : pd.Series\n",
" A series of stock prices or values.\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" The z-score of the last value in the chunk.\n",
" \n",
" Notes\n",
" -----\n",
" This method computes the z-score, which is the number \n",
" of standard deviations a value is from the mean.\n",
" \"\"\"\n",
" return (chunk[-1] - chunk.mean()) / chunk.std()"
]
},
{
"cell_type": "markdown",
"id": "d92d5b53",
"metadata": {},
"source": [
"Calculate the rolling z-score of the closing prices over a 30-day window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9c3b8292",
"metadata": {},
"outputs": [],
"source": [
"rolled = data.Close.rolling(window=30).apply(z_score)"
]
},
{
"cell_type": "markdown",
"id": "f0c0ce32",
"metadata": {},
"source": [
"Plot the rolling z-score to visualize deviations from the mean"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5c0f3295",
"metadata": {},
"outputs": [],
"source": [
"rolled.plot()"
]
},
{
"cell_type": "markdown",
"id": "5fc5a361",
"metadata": {},
"source": [
"Plot a histogram of the rolling z-score to understand its distribution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "979cf4f9",
"metadata": {},
"outputs": [],
"source": [
"rolled.hist(bins=20)"
]
},
{
"cell_type": "markdown",
"id": "1acedb50",
"metadata": {},
"source": [
"Find the minimum z-score value to identify significant deviations"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "127b0f43",
"metadata": {},
"outputs": [],
"source": [
"rolled.min()"
]
},
{
"cell_type": "markdown",
"id": "f13fc8f4",
"metadata": {},
"source": [
"Calculate the percentage change from the closing price on 20 April 2022"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "502e5d91",
"metadata": {},
"outputs": [],
"source": [
"(226.19 - 348.61) / 348.61"
]
},
{
"cell_type": "markdown",
"id": "da9e48db",
"metadata": {},
"source": [
"Calculate the minimum percentage change in closing prices over a 30-day rolling window"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5c98be53",
"metadata": {},
"outputs": [],
"source": [
"min_pct_change = (\n",
" data\n",
" .Close\n",
" .pct_change()\n",
" .rolling(window=30)\n",
" .min()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "47be23a9",
"metadata": {},
"source": [
"Plot the minimum percentage change to visualize extreme price movements"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1fabbf2",
"metadata": {},
"outputs": [],
"source": [
"min_pct_change.plot()"
]
},
{
"cell_type": "markdown",
"id": "3c0c5951",
"metadata": {},
"source": [
"Plot a histogram of the minimum percentage change to understand its distribution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a1697d6b",
"metadata": {},
"outputs": [],
"source": [
"min_pct_change.hist(bins=20)"
]
},
{
"cell_type": "markdown",
"id": "d97d6472",
"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
}

View File

@ -0,0 +1,251 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "2a7b4695",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "4dde6243",
"metadata": {},
"source": [
"This code processes historical stock data for financial analysis and option strategy backtesting. It reads multiple CSV files containing end-of-day data, concatenates them, and saves the result in a single file. The script then loads this combined data and uses the Optopsy library to analyze option chains. It evaluates various option strategies such as short calls, long straddles, and short strangles, providing a practical tool for financial professionals to assess historical performance and strategy efficacy."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d5d8246",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import glob\n",
"import pandas as pd\n",
"import optopsy as op"
]
},
{
"cell_type": "markdown",
"id": "9337e673",
"metadata": {},
"source": [
"Collect all CSV files in the \"rut-eod\" directory"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "25707d5a",
"metadata": {},
"outputs": [],
"source": [
"files = glob.glob(os.path.join(\"rut-eod\", \"*.csv\"))"
]
},
{
"cell_type": "markdown",
"id": "da83ac55",
"metadata": {},
"source": [
"Initialize an empty list for storing dataframes"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "20abd76b",
"metadata": {},
"outputs": [],
"source": [
"dfs = []\n",
"for fl in files:\n",
" # Read each CSV file and append its dataframe to the list\n",
" df = pd.read_csv(fl)\n",
" dfs.append(df)"
]
},
{
"cell_type": "markdown",
"id": "106998bc",
"metadata": {},
"source": [
"Concatenate all dataframes and save the combined data to a new CSV file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "47998816",
"metadata": {},
"outputs": [],
"source": [
"pd.concat(dfs).to_csv(\"rut_historic.csv\", index=False)"
]
},
{
"cell_type": "markdown",
"id": "3d36b3a7",
"metadata": {},
"source": [
"Load the combined historical data from the new CSV file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e504c82f",
"metadata": {},
"outputs": [],
"source": [
"rut = pd.read_csv(\"rut_historic.csv\")"
]
},
{
"cell_type": "markdown",
"id": "0d6640b5",
"metadata": {},
"source": [
"Print the most recent date in the dataset"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "67b0527c",
"metadata": {},
"outputs": [],
"source": [
"rut.date.max()"
]
},
{
"cell_type": "markdown",
"id": "4c9823ce",
"metadata": {},
"source": [
"Load the option chains from the combined historical data using specific column indices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8240913f",
"metadata": {},
"outputs": [],
"source": [
"rut_chains = op.csv_data(\n",
" \"rut_historic.csv\",\n",
" underlying_symbol=1,\n",
" underlying_price=4,\n",
" option_type=8,\n",
" expiration=6,\n",
" quote_date=0,\n",
" strike=7,\n",
" bid=14,\n",
" ask=15\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e21cd76d",
"metadata": {},
"source": [
"Display the first few rows of the option chains dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5850a16f",
"metadata": {},
"outputs": [],
"source": [
"rut_chains.head()"
]
},
{
"cell_type": "markdown",
"id": "219a8e53",
"metadata": {},
"source": [
"Evaluate and round the results of the short calls strategy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f7e6cd7",
"metadata": {},
"outputs": [],
"source": [
"op.short_calls(rut_chains).round(2)"
]
},
{
"cell_type": "markdown",
"id": "ad327b31",
"metadata": {},
"source": [
"Evaluate and round the results of the long straddles strategy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "57f48082",
"metadata": {},
"outputs": [],
"source": [
"op.long_straddles(rut_chains).round(2)"
]
},
{
"cell_type": "markdown",
"id": "1d4573d2",
"metadata": {},
"source": [
"Evaluate and round the results of the short strangles strategy with specified parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bb9d8953",
"metadata": {},
"outputs": [],
"source": [
"op.short_strangles(\n",
" rut_chains, \n",
" dte_interval=60, \n",
" max_entry_dte=70, \n",
" exit_dte=10,\n",
" otm_pct_interval=0.01,\n",
" max_otm_pct=0.10\n",
").round(2)"
]
},
{
"cell_type": "markdown",
"id": "5baa0691",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,408 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e2c3f24b",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "35d6d902",
"metadata": {},
"source": [
"This code processes and analyzes equity options data, extracting volatility curves for specific options chains. It reads options data from CSV files, stores them in an Arctic database, and then queries the database to retrieve options chains for analysis. The code includes functions to read the options data, filter it based on specific criteria, and query it for expiration dates. Finally, it plots the implied volatility curves for the options, visualizing how volatility changes with different strikes and expiration dates."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c11513ab",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import glob\n",
"import matplotlib.pyplot as plt\n",
"import datetime as dt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "92e29c6f",
"metadata": {},
"outputs": [],
"source": [
"import arcticdb as adb\n",
"import pandas as pd"
]
},
{
"cell_type": "markdown",
"id": "ae881285",
"metadata": {},
"source": [
"Initialize an Arctic database connection and get the \"options\" library"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8669ee9a",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"arctic = adb.Arctic(\"lmdb://equity_options\")\n",
"lib = arctic.get_library(\"options\", create_if_missing=True)"
]
},
{
"cell_type": "markdown",
"id": "23e2dfbe",
"metadata": {},
"source": [
"Define a function to read options chains from CSV files"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c99d8bf0",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def read_chains(fl):\n",
" \"\"\"Reads options chains from a CSV file\n",
" \n",
" Parameters\n",
" ----------\n",
" fl : str\n",
" Path to the CSV file\n",
" \n",
" Returns\n",
" -------\n",
" df : pd.DataFrame\n",
" Dataframe containing the options chains\n",
" \"\"\"\n",
" \n",
" # Read the CSV file, set the \"date\" column as the index, and convert to datetime\n",
" df = (\n",
" pd\n",
" .read_csv(fl)\n",
" .set_index(\"date\")\n",
" )\n",
" df.index = pd.to_datetime(df.index)\n",
" return df"
]
},
{
"cell_type": "markdown",
"id": "691e962c",
"metadata": {},
"source": [
"Read all CSV files from the \"rut-eod\" directory and store their data in the Arctic database"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12d9662e",
"metadata": {},
"outputs": [],
"source": [
"files = glob.glob(os.path.join(\"rut-eod\", \"*.csv\"))\n",
"for fl in files:\n",
" chains = read_chains(fl)\n",
" chains.option_expiration = pd.to_datetime(chains.option_expiration)\n",
" underlyings = chains.symbol.unique()\n",
" for underlying in underlyings:\n",
" df = chains[chains.symbol == underlying]\n",
" adb_sym = f\"options/{underlying}\"\n",
" adb_fcn = lib.update if lib.has_symbol(adb_sym) else lib.write\n",
" adb_fcn(adb_sym, df)"
]
},
{
"cell_type": "markdown",
"id": "7921ad3e",
"metadata": {},
"source": [
"Read data for the \"RUT\" underlying from the Arctic database"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d66b0041",
"metadata": {},
"outputs": [],
"source": [
"rut = lib.read(\"options/RUT\").data"
]
},
{
"cell_type": "markdown",
"id": "e836326c",
"metadata": {},
"source": [
"Print the minimum and maximum dates from the \"RUT\" data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9868b99f",
"metadata": {},
"outputs": [],
"source": [
"rut.index.min(), rut.index.max()"
]
},
{
"cell_type": "markdown",
"id": "c6bbb827",
"metadata": {},
"source": [
"Print information about the \"RUT\" dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "83ec5802",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"rut.info()"
]
},
{
"cell_type": "markdown",
"id": "1f7d0264",
"metadata": {},
"source": [
"Define a function to read the volatility curve for a specific options chain"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18f49a7c",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def read_vol_curve(as_of_date, underlying, expiry, delta_low, delta_high):\n",
" \"\"\"Reads the volatility curve for specific options\n",
" \n",
" Parameters\n",
" ----------\n",
" as_of_date : pd.Timestamp\n",
" The date for which to read the data\n",
" underlying : str\n",
" The underlying asset symbol\n",
" expiry : pd.Timestamp\n",
" The expiration date of the options\n",
" delta_low : float\n",
" The lower bound of the delta range\n",
" delta_high : float\n",
" The upper bound of the delta range\n",
" \n",
" Returns\n",
" -------\n",
" df : pd.DataFrame\n",
" Dataframe containing the volatility curve\n",
" \"\"\"\n",
" \n",
" # Build a query to filter options data based on expiration date and delta range\n",
" q = adb.QueryBuilder()\n",
" filter = (\n",
" (q[\"option_expiration\"] == expiry) & \n",
" (\n",
" \t(\n",
" \t\t(q[\"delta\"] >= delta_low) & (q[\"delta\"] <= delta_high)\n",
" \t) | (\n",
" \t\t(q[\"delta\"] >= -delta_high) & (q[\"delta\"] <= -delta_low)\n",
" \t)\n",
" )\n",
" )\n",
" q = (\n",
" q[filter]\n",
" .groupby(\"strike\")\n",
" .agg({\"iv\": \"mean\"})\n",
" )\n",
" \n",
" # Read the filtered data from the Arctic database\n",
" return lib.read(\n",
" f\"options/{underlying}\", \n",
" date_range=(as_of_date, as_of_date),\n",
" query_builder=q\n",
" ).data"
]
},
{
"cell_type": "markdown",
"id": "86dbe228",
"metadata": {},
"source": [
"Define a function to query expiration dates for options"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d68eb55",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def query_expirations(as_of_date, underlying, dte=30):\n",
" \"\"\"Queries expiration dates for options\n",
" \n",
" Parameters\n",
" ----------\n",
" as_of_date : pd.Timestamp\n",
" The date for which to query the data\n",
" underlying : str\n",
" The underlying asset symbol\n",
" dte : int, optional\n",
" Days to expiration threshold, by default 30\n",
" \n",
" Returns\n",
" -------\n",
" expirations : pd.Index\n",
" Index containing the expiration dates\n",
" \"\"\"\n",
" \n",
" # Build a query to filter options data based on expiration date threshold\n",
" q = adb.QueryBuilder()\n",
" filter = (q.option_expiration > as_of_date + dt.timedelta(days=dte))\n",
" q = q[filter].groupby(\"option_expiration\").agg({\"volume\": \"sum\"})\n",
" \n",
" # Read the filtered data from the Arctic database and sort by expiration date\n",
" return (\n",
" lib\n",
" .read(\n",
" f\"options/{underlying}\", \n",
" date_range=(as_of_date, as_of_date), \n",
" query_builder=q\n",
" )\n",
" .data\n",
" .sort_index()\n",
" .index\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "a344b771",
"metadata": {},
"source": [
"Define input parameters for querying and plotting the volatility curves"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8c667fe",
"metadata": {},
"outputs": [],
"source": [
"as_of_date = pd.Timestamp(\"2013-06-03\")\n",
"expiry = pd.Timestamp(\"2013-06-22\")\n",
"underlying = \"RUT\"\n",
"dte = 30\n",
"delta_low = 0.05\n",
"delta_high = 0.50"
]
},
{
"cell_type": "markdown",
"id": "421cce89",
"metadata": {},
"source": [
"Query expiration dates for the given underlying asset and date"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f138d4b9",
"metadata": {},
"outputs": [],
"source": [
"expiries = query_expirations(as_of_date, underlying, dte)"
]
},
{
"cell_type": "markdown",
"id": "37f2012c",
"metadata": {},
"source": [
"Plot the implied volatility curves for the retrieved expiration dates"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8152b931",
"metadata": {},
"outputs": [],
"source": [
"_, ax = plt.subplots(1, 1)\n",
"cmap = plt.get_cmap(\"rainbow\", len(expiries))\n",
"format_kw = {\"linewidth\": 0.5, \"alpha\": 0.85}\n",
"for i, expiry in enumerate(expiries):\n",
" curve = read_vol_curve(\n",
" as_of_date, \n",
" underlying, \n",
" expiry, \n",
" delta_low, \n",
" delta_high\n",
" )\n",
" (\n",
" curve\n",
" .sort_index()\n",
" .plot(\n",
" ax=ax, \n",
" y=\"iv\", \n",
" label=expiry.strftime(\"%Y-%m-%d\"),\n",
" grid=True,\n",
" color=cmap(i),\n",
" **format_kw\n",
" )\n",
" )\n",
"ax.set_ylabel(\"implied volatility\")\n",
"ax.legend(loc=\"upper right\", framealpha=0.7)"
]
},
{
"cell_type": "markdown",
"id": "f120b062",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,438 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "2725ec7a",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "3289d4fc",
"metadata": {},
"source": [
"This notebook simulates portfolio returns using Monte Carlo methods to estimate Value at Risk (VaR) and Conditional Value at Risk (CVaR). It begins by importing historical price data for various sectors, calculating daily returns, and determining portfolio statistics. The notebook then simulates portfolio returns over a specified number of days and simulations, using these to calculate VaR and CVaR. Finally, it visualizes the simulated portfolio paths and highlights the calculated risk metrics. This is useful for risk management and financial forecasting."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fd3b1d48",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"from openbb import obb"
]
},
{
"cell_type": "markdown",
"id": "67699707",
"metadata": {},
"source": [
"Define a list of sector symbols to analyze"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8215e18",
"metadata": {},
"outputs": [],
"source": [
"sectors = [\n",
" \"XLE\", \n",
" \"XLF\", \n",
" \"XLU\", \n",
" \"XLI\", \n",
" \"GDX\", \n",
" \"XLK\", \n",
" \"XLV\", \n",
" \"XLY\", \n",
" \"XLP\", \n",
" \"XLB\", \n",
" \"XOP\", \n",
" \"IYR\", \n",
" \"XHB\", \n",
" \"ITB\", \n",
" \"VNQ\", \n",
" \"GDXJ\", \n",
" \"IYE\", \n",
" \"OIH\", \n",
" \"XME\", \n",
" \"XRT\", \n",
" \"SMH\", \n",
" \"IBB\", \n",
" \"KBE\", \n",
" \"KRE\", \n",
" \"XTL\", \n",
"]"
]
},
{
"cell_type": "markdown",
"id": "2a89086a",
"metadata": {},
"source": [
"Fetch historical price data for the defined sectors from 2022-01-01 using yfinance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5a20900",
"metadata": {},
"outputs": [],
"source": [
"data = obb.equity.price.historical(\n",
" sectors, \n",
" start_date=\"2022-01-01\", \n",
" provider=\"yfinance\"\n",
").to_df()"
]
},
{
"cell_type": "markdown",
"id": "d0e9d9a4",
"metadata": {},
"source": [
"Calculate daily returns for each sector"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d25ec2bb",
"metadata": {},
"outputs": [],
"source": [
"data[\"returns\"] = data.groupby(\"symbol\").close.pct_change()"
]
},
{
"cell_type": "markdown",
"id": "c5c12c22",
"metadata": {},
"source": [
"Calculate mean daily returns and equal weights for each sector"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7f49a0b",
"metadata": {},
"outputs": [],
"source": [
"portfolio_stats = data.groupby(\"symbol\").agg(\n",
" daily_returns=(\"returns\", \"mean\"),\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1380c16e",
"metadata": {},
"outputs": [],
"source": [
"portfolio_stats[\"weights\"] = 1 / len(sectors)"
]
},
{
"cell_type": "markdown",
"id": "57a36ec7",
"metadata": {},
"source": [
"Compute the covariance matrix of the sector returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f4776161",
"metadata": {},
"outputs": [],
"source": [
"covariance_matrix = (\n",
" data\n",
" .pivot(\n",
" columns=\"symbol\", \n",
" values=\"returns\"\n",
" )\n",
" .dropna()\n",
" .cov()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "7031e0e7",
"metadata": {},
"source": [
"Set up simulation parameters including number of simulations, days, and initial capital"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "67b604e6",
"metadata": {},
"outputs": [],
"source": [
"simulations = 1000\n",
"days = len(data.index.unique())\n",
"initial_capital = 100_000"
]
},
{
"cell_type": "markdown",
"id": "7d5622f3",
"metadata": {},
"source": [
"Initialize an array to store portfolio values for each simulation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "25958ba0",
"metadata": {},
"outputs": [],
"source": [
"portfolio = np.zeros((days, simulations))"
]
},
{
"cell_type": "markdown",
"id": "e0ba9b8c",
"metadata": {},
"source": [
"Create an array filled with historical daily returns for each sector"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f93e3f3",
"metadata": {},
"outputs": [],
"source": [
"historical_returns = np.full(\n",
" shape=(days, len(sectors)), \n",
" fill_value=portfolio_stats.daily_returns\n",
")"
]
},
{
"cell_type": "markdown",
"id": "8c400218",
"metadata": {},
"source": [
"Perform Cholesky decomposition on the covariance matrix to generate correlated random variables"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b2bdee40",
"metadata": {},
"outputs": [],
"source": [
"L = np.linalg.cholesky(covariance_matrix)"
]
},
{
"cell_type": "markdown",
"id": "6cfae2a0",
"metadata": {},
"source": [
"Run simulations to generate daily returns and simulate portfolio paths"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8da4b25d",
"metadata": {},
"outputs": [],
"source": [
"for i in range(0, simulations):\n",
" Z = np.random.normal(size=(days, len(sectors)))\n",
" daily_returns = historical_returns + np.dot(L, Z.T).T\n",
" portfolio[:, i] = (\n",
" np.cumprod(np.dot(daily_returns, portfolio_stats.weights) + 1) * initial_capital\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "698571d9",
"metadata": {},
"source": [
"Convert simulated portfolio values into a DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d3035e1",
"metadata": {},
"outputs": [],
"source": [
"simulated_portfolio = pd.DataFrame(portfolio)"
]
},
{
"cell_type": "markdown",
"id": "f8b0d0d6",
"metadata": {},
"source": [
"Set alpha level for VaR and CVaR calculations"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a709e383",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"alpha = 5"
]
},
{
"cell_type": "markdown",
"id": "bcba57a6",
"metadata": {},
"source": [
"Define function to calculate Monte Carlo VaR"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "473ac39d",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def montecarlo_var(alpha):\n",
" \"\"\"Calculate Monte Carlo Value at Risk (VaR).\n",
" \n",
" Parameters\n",
" ----------\n",
" alpha : float\n",
" The confidence level for the VaR calculation.\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" The VaR value at the given confidence level.\n",
" \"\"\"\n",
" \n",
" # Calculate the percentile of the simulated portfolio values at alpha level\n",
" sim_val = simulated_portfolio.iloc[-1, :]\n",
" return np.percentile(sim_val, alpha)"
]
},
{
"cell_type": "markdown",
"id": "4641a8a4",
"metadata": {},
"source": [
"Define function to calculate Conditional VaR"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c084b860",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def conditional_var(alpha):\n",
" \"\"\"Calculate Conditional Value at Risk (CVaR).\n",
" \n",
" Parameters\n",
" ----------\n",
" alpha : float\n",
" The confidence level for the CVaR calculation.\n",
" \n",
" Returns\n",
" -------\n",
" float\n",
" The CVaR value at the given confidence level.\n",
" \"\"\"\n",
" \n",
" # Calculate the mean of the simulated portfolio values below the VaR threshold\n",
" sim_val = simulated_portfolio.iloc[-1, :]\n",
" return sim_val[sim_val <= montecarlo_var(alpha)].mean()"
]
},
{
"cell_type": "markdown",
"id": "84dad355",
"metadata": {},
"source": [
"Calculate Monte Carlo VaR and Conditional VaR"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "967cf6ea",
"metadata": {},
"outputs": [],
"source": [
"mc_var = montecarlo_var(alpha)\n",
"cond_var = conditional_var(alpha)"
]
},
{
"cell_type": "markdown",
"id": "611e4416",
"metadata": {},
"source": [
"Plot the simulated portfolio paths and highlight VaR and CVaR"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a0503bb6",
"metadata": {},
"outputs": [],
"source": [
"ax = simulated_portfolio.plot(lw=0.25, legend=False)\n",
"ax.axhline(mc_var, lw=0.5, c=\"r\")\n",
"ax.axhline(cond_var, lw=0.5, c=\"g\")"
]
},
{
"cell_type": "markdown",
"id": "f876166a",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,403 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "11bddb7a",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "1f83085a",
"metadata": {},
"source": [
"This notebook analyzes treasury rates by performing eigenvalue decomposition on their covariance matrix. It retrieves treasury rates, calculates their covariance, and uses eigenvalue decomposition to find principal components. The notebook verifies the orthogonality of eigenvectors and extracts the first four principal components. Then, it generates random shocks to simulate their effects on the rates. This is useful for understanding the impact of various factors on interest rates."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1ca244f6",
"metadata": {},
"outputs": [],
"source": [
"from openbb import obb\n",
"import numpy as np\n",
"import pandas as pd"
]
},
{
"cell_type": "markdown",
"id": "db8b1a50",
"metadata": {},
"source": [
"Retrieve treasury rates from the OpenBB API, convert to DataFrame, drop NaNs, and convert to decimal format."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a622e811",
"metadata": {},
"outputs": [],
"source": [
"rates = (\n",
" obb\n",
" .fixedincome\n",
" .government\n",
" .treasury_rates(\n",
" start_date=\"2020-01-01\"\n",
" )\n",
" .to_df()\n",
" .dropna()\n",
" .div(100)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "6a02b411",
"metadata": {},
"source": [
"Display the rates DataFrame."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ac1345f8",
"metadata": {},
"outputs": [],
"source": [
"rates"
]
},
{
"cell_type": "markdown",
"id": "b2c5e6c3",
"metadata": {},
"source": [
"Calculate the covariance matrix of the treasury rates."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9bebd6a3",
"metadata": {},
"outputs": [],
"source": [
"C = rates.cov()"
]
},
{
"cell_type": "markdown",
"id": "c299a5a1",
"metadata": {},
"source": [
"Perform eigenvalue decomposition on the covariance matrix to obtain eigenvalues and eigenvectors."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1040b6d8",
"metadata": {},
"outputs": [],
"source": [
"eigenvalues, eigenvectors = np.linalg.eig(C)\n",
"lambda_sqrt = np.sqrt(eigenvalues)\n",
"eigv_decom = np.diag(lambda_sqrt)"
]
},
{
"cell_type": "markdown",
"id": "86b1df03",
"metadata": {},
"source": [
"Verify that the eigenvectors are orthogonal by checking if their dot product is the identity matrix."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a754189",
"metadata": {},
"outputs": [],
"source": [
"np.allclose(\n",
" eigenvectors.T @ eigenvectors,\n",
" np.eye(eigenvectors.shape[0])\n",
")"
]
},
{
"cell_type": "markdown",
"id": "1ea06127",
"metadata": {},
"source": [
"Extract the first four principal components and create a DataFrame with meaningful labels."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "066f5d95",
"metadata": {},
"outputs": [],
"source": [
"B = eigv_decom @ eigenvectors.T\n",
"B = pd.DataFrame(\n",
" data=B[:4] * 100,\n",
" index=[\"wiggle\", \"flex\", \"twist\", \"shift\"],\n",
" columns=rates.columns\n",
")"
]
},
{
"cell_type": "markdown",
"id": "4e80c45e",
"metadata": {},
"source": [
"Display the principal components DataFrame."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5eb70388",
"metadata": {},
"outputs": [],
"source": [
"B"
]
},
{
"cell_type": "markdown",
"id": "da0728d1",
"metadata": {},
"source": [
"Generate random shocks from a normal distribution to simulate changes in the rates."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7aa00db4",
"metadata": {},
"outputs": [],
"source": [
"random_shocks = np.random.normal(0, 1, size=(4,))\n",
"random_shocks @ B"
]
},
{
"cell_type": "markdown",
"id": "a9fe0b61",
"metadata": {},
"source": [
"Define standard deviation and correlation matrices for another set of computations."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9213c16a",
"metadata": {},
"outputs": [],
"source": [
"S = np.array([0.051, 0.052, 0.061, 0.054])\n",
"R = np.array([[1, 0.61, 0.42, 0.31],\n",
" [0.61, 1, 0.83, 0.67],\n",
" [0.42, 0.83, 1, 0.88],\n",
" [0.31, 0.67, 0.88, 1]])"
]
},
{
"cell_type": "markdown",
"id": "3cd0ede5",
"metadata": {},
"source": [
"Calculate the covariance matrix using the standard deviation and correlation matrices."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "44be0dc5",
"metadata": {},
"outputs": [],
"source": [
"C = np.diag(S) @ R @ np.diag(S)"
]
},
{
"cell_type": "markdown",
"id": "b1883d4d",
"metadata": {},
"source": [
"Display the calculated covariance matrix."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1df01307",
"metadata": {},
"outputs": [],
"source": [
"C"
]
},
{
"cell_type": "markdown",
"id": "38875832",
"metadata": {},
"source": [
"Perform eigenvalue decomposition on the new covariance matrix to obtain eigenvalues and eigenvectors."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea71473f",
"metadata": {},
"outputs": [],
"source": [
"eigenvalues, eigenvectors = np.linalg.eig(C)\n",
"lambda_sqrt = np.sqrt(eigenvalues)\n",
"eigv_decom = np.diag(lambda_sqrt)"
]
},
{
"cell_type": "markdown",
"id": "5b9416f2",
"metadata": {},
"source": [
"Display the eigenvectors obtained from the decomposition."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1669a19",
"metadata": {},
"outputs": [],
"source": [
"eigenvectors"
]
},
{
"cell_type": "markdown",
"id": "9116f4e2",
"metadata": {},
"source": [
"Verify that the new eigenvectors are orthogonal by checking if their dot product is the identity matrix."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7cfc7d8a",
"metadata": {},
"outputs": [],
"source": [
"np.allclose(eigenvectors.T @ eigenvectors, np.eye(eigenvectors.shape[0]))"
]
},
{
"cell_type": "markdown",
"id": "db63d5c8",
"metadata": {},
"source": [
"Extract the principal components for the new covariance matrix."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "16ee2ad7",
"metadata": {},
"outputs": [],
"source": [
"B = eigv_decom @ eigenvectors.T"
]
},
{
"cell_type": "markdown",
"id": "ed135142",
"metadata": {},
"source": [
"Display the new principal components."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b42ac440",
"metadata": {},
"outputs": [],
"source": [
"B"
]
},
{
"cell_type": "markdown",
"id": "0afd516c",
"metadata": {},
"source": [
"Simulate changes in rates using random shocks and basis point adjustments."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "845e8d19",
"metadata": {},
"outputs": [],
"source": [
"m = 2\n",
"random_shocks = np.random.normal(0, 1, size=(4,))\n",
"random_shocks[2:] *= m # Apply magnitude to z3 and z4\n",
"delta_r = random_shocks @ (100 * B) # Basis point shocks for each rate point"
]
},
{
"cell_type": "markdown",
"id": "0dc9bd1c",
"metadata": {},
"source": [
"Display the simulated rate changes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aede691d",
"metadata": {},
"outputs": [],
"source": [
"delta_r"
]
},
{
"cell_type": "markdown",
"id": "47b0dc11",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,216 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0b8df441",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "f07fab33",
"metadata": {},
"source": [
"This code downloads historical stock data for SPY from Yahoo Finance, calculates daily returns, and analyzes drawdowns. It defines functions to compute drawdowns and maximum drawdowns, which are essential for risk management. The drawdown function calculates the percentage decline from a peak in the cumulative return series. The max_drawdown function identifies the largest drawdown over the period. These metrics are visualized to evaluate the stock's risk."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef1f1af7",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "e5a113e5",
"metadata": {},
"source": [
"Download historical stock data for SPY from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a63d13bb",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(\"SPY\", start=\"2020-01-01\", end=\"2022-07-31\")"
]
},
{
"cell_type": "markdown",
"id": "8adb54ef",
"metadata": {},
"source": [
"Calculate daily returns from the adjusted closing prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "649d8b88",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"returns = data[\"Adj Close\"].pct_change()"
]
},
{
"cell_type": "markdown",
"id": "4adb0254",
"metadata": {},
"source": [
"Define a function to determine the drawdown from daily returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d2c6022",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def drawdown(returns):\n",
" \"\"\"Determines the drawdown\n",
" \n",
" Parameters\n",
" ----------\n",
" returns : pd.Series\n",
" Daily returns of an asset, noncumulative\n",
" \n",
" Returns\n",
" -------\n",
" drawdown : pd.Series\n",
" \n",
" Notes\n",
" -----\n",
" This function calculates the percentage decline \n",
" from the peak value of cumulative returns.\n",
" \"\"\"\n",
"\n",
" # Replace the first NaN value with 0.0 for accurate calculations\n",
"\n",
" returns.fillna(0.0, inplace=True)\n",
"\n",
" # Create a series of cumulative returns over time\n",
"\n",
" cumulative = (returns + 1).cumprod()\n",
"\n",
" # Calculate the running maximum value of cumulative returns\n",
"\n",
" running_max = np.maximum.accumulate(cumulative)\n",
"\n",
" # Compute the drawdown as the percentage decline from the running maximum\n",
"\n",
" return (cumulative - running_max) / running_max"
]
},
{
"cell_type": "markdown",
"id": "1261aa32",
"metadata": {},
"source": [
"Plot the drawdown over time as an area chart"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72cffa58",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"drawdown(returns).plot(kind=\"area\", color=\"salmon\", alpha=0.5)"
]
},
{
"cell_type": "markdown",
"id": "a4aa9724",
"metadata": {},
"source": [
"Define a function to determine the maximum drawdown from daily returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "136eda22",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def max_drawdown(returns):\n",
" \"\"\"Determines the maximum drawdown\n",
" \n",
" Parameters\n",
" ----------\n",
" returns : pd.Series\n",
" Daily returns of an asset, noncumulative\n",
" \n",
" Returns\n",
" -------\n",
" max_drawdown : float\n",
" \n",
" Notes\n",
" -----\n",
" This function identifies the largest \n",
" drawdown over the specified period.\n",
" \"\"\"\n",
"\n",
" # Calculate the drawdown and return the minimum value as the max drawdown\n",
"\n",
" return np.min(drawdown(returns))"
]
},
{
"cell_type": "markdown",
"id": "8e5f427b",
"metadata": {},
"source": [
"Calculate the rolling maximum drawdown over a 30-day window and plot it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9a79e6e8",
"metadata": {},
"outputs": [],
"source": [
"returns.rolling(30).apply(max_drawdown).plot(kind=\"area\", color=\"salmon\", alpha=0.5)"
]
},
{
"cell_type": "markdown",
"id": "c46cc0e5",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,283 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ab3c3685",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "1873e801",
"metadata": {},
"source": [
"This code downloads historical price data for the TLT ETF and generates trading signals based on calendar rules. It sets up short and long entry and exit signals, such as entering a short position on the first day of each month and exiting five days later. The code also defines a function to simulate a trading strategy and calculate performance metrics, specifically the Sharpe ratio. Finally, it runs this strategy on shuffled data to generate a distribution of Sharpe ratios for comparison. This approach aids in evaluating the robustness and statistical significance of the trading strategy."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11c2ac72",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import vectorbt as vbt"
]
},
{
"cell_type": "markdown",
"id": "baa26e68",
"metadata": {},
"source": [
"Download historical price data for TLT ETF from Yahoo Finance and extract the closing prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ecf3f8f9",
"metadata": {},
"outputs": [],
"source": [
"tlt = vbt.YFData.download(\n",
" \"TLT\", \n",
" start=\"2004-01-01\"\n",
").get(\"Close\").to_frame()\n",
"close = tlt.Close"
]
},
{
"cell_type": "markdown",
"id": "0192959f",
"metadata": {},
"source": [
"Set up empty dataframes to hold trading signals for short and long positions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5aa36b7",
"metadata": {},
"outputs": [],
"source": [
"short_entries = pd.DataFrame.vbt.signals.empty_like(close)\n",
"short_exits = pd.DataFrame.vbt.signals.empty_like(close)\n",
"long_entries = pd.DataFrame.vbt.signals.empty_like(close)\n",
"long_exits = pd.DataFrame.vbt.signals.empty_like(close)"
]
},
{
"cell_type": "markdown",
"id": "b5404f63",
"metadata": {},
"source": [
"Generate short entry signals on the first day of each new month"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a37dc3c9",
"metadata": {},
"outputs": [],
"source": [
"short_entry_mask = ~tlt.index.tz_convert(None).to_period(\"M\").duplicated()\n",
"short_entries.iloc[short_entry_mask] = True"
]
},
{
"cell_type": "markdown",
"id": "79e3c619",
"metadata": {},
"source": [
"Generate short exit signals five days after short entry"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6fb2d80b",
"metadata": {},
"outputs": [],
"source": [
"short_exit_mask = short_entries.shift(5).fillna(False)\n",
"short_exits.iloc[short_exit_mask] = True"
]
},
{
"cell_type": "markdown",
"id": "d014794e",
"metadata": {},
"source": [
"Generate long entry signals seven days before the end of each month"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ebf6e5e1",
"metadata": {},
"outputs": [],
"source": [
"long_entry_mask = short_entries.shift(-7).fillna(False)\n",
"long_entries.iloc[long_entry_mask] = True"
]
},
{
"cell_type": "markdown",
"id": "16a2dce9",
"metadata": {},
"source": [
"Generate long exit signals one day before the end of each month"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "22160cf9",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"long_exit_mask = short_entries.shift(-1).fillna(False)\n",
"long_exits.iloc[long_exit_mask] = True"
]
},
{
"cell_type": "markdown",
"id": "52de0bee",
"metadata": {},
"source": [
"Define a function to simulate the trading strategy and calculate performance metrics"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2df090a8",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def run_sim(close):\n",
" \"\"\"Simulate trading strategy\n",
"\n",
" This function simulates a trading strategy based on given entry and exit signals.\n",
"\n",
" Parameters\n",
" ----------\n",
" close : pd.Series\n",
" The closing prices of the asset.\n",
"\n",
" Returns\n",
" -------\n",
" pf : vbt.Portfolio\n",
" The simulated portfolio.\n",
" \"\"\"\n",
" \n",
" return vbt.Portfolio.from_signals(\n",
" close=close,\n",
" entries=long_entries,\n",
" exits=long_exits,\n",
" short_entries=short_entries,\n",
" short_exits=short_exits,\n",
" freq=\"1d\"\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "7afb4498",
"metadata": {},
"source": [
"Run the simulation and calculate the Sharpe ratio for the trading strategy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "241a38f9",
"metadata": {},
"outputs": [],
"source": [
"pf = run_sim(close)\n",
"sr = pf.sharpe_ratio()\n",
"pf.stats()"
]
},
{
"cell_type": "markdown",
"id": "ff347e51",
"metadata": {},
"source": [
"Run the simulation on shuffled data 1000 times to generate a distribution of Sharpe ratios"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7761864b",
"metadata": {},
"outputs": [],
"source": [
"sharpes = []\n",
"for i in range(1000):\n",
" shuffled_close = close.sample(frac=1)\n",
" shuffled_close.index = close.index\n",
" shuffled_sharpe = run_sim(shuffled_close).sharpe_ratio()\n",
" sharpes.append(shuffled_sharpe)"
]
},
{
"cell_type": "markdown",
"id": "1e7613a8",
"metadata": {},
"source": [
"Plot the distribution of simulated Sharpe ratios and mark the original strategy's Sharpe ratio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff9c8df9",
"metadata": {},
"outputs": [],
"source": [
"pd.DataFrame(\n",
" sharpes, \n",
" columns=[\"Simulated Sharpe Ratios\"]\n",
").hist(\n",
" bins=np.histogram_bin_edges(sharpes, bins=\"fd\")\n",
")\n",
"plt.axvline(sr, linestyle=\"dashed\", linewidth=1)\n",
"min_ylim, max_ylim = plt.ylim()\n",
"plt.text(sr * -0.01, max_ylim * 0.9, f\"Strat Sharpe: {sr:.1f}\")"
]
},
{
"cell_type": "markdown",
"id": "8c8c6d66",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,323 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "968808e1",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "28fcc5f0",
"metadata": {},
"source": [
"This code extracts financial data from the SEC's EDGAR database, specifically targeting Apple's filings. It downloads and processes quarterly and annual financial statement datasets, converts them to a more efficient format (parquet), and constructs a comprehensive dataset of Apple's financials. The code then calculates key financial metrics such as P/E ratios from earnings per share (EPS) and stock price data. Additionally, it prepares the dataset for further analysis or visualization. This is useful for financial analysis, investment research, and academic purposes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef94fd0f",
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"from io import BytesIO\n",
"from zipfile import ZipFile, BadZipFile\n",
"from pathlib import Path\n",
"from tqdm import tqdm\n",
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6383b976",
"metadata": {},
"outputs": [],
"source": [
"from openbb import obb"
]
},
{
"cell_type": "markdown",
"id": "ffc1ec08",
"metadata": {},
"source": [
"Set the base URL and file path for SEC data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bb09a939",
"metadata": {},
"outputs": [],
"source": [
"SEC_URL = \"https://www.sec.gov/\"\n",
"FSN_PATH = \"files/dera/data/financial-statement-and-notes-data-sets/\"\n",
"DATA_PATH = Path(\"edgar\")\n",
"user_agent = \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36\""
]
},
{
"cell_type": "markdown",
"id": "c76c3c09",
"metadata": {},
"source": [
"Generate a list of filing periods (quarters) to download"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aebfd944",
"metadata": {},
"outputs": [],
"source": [
"filing_periods = [\n",
" (d.year, d.quarter) for d in pd.date_range(\"2015\", \"2015-12-31\", freq=\"QE\")\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "5e6a00ad",
"metadata": {},
"source": [
"Loop through each filing period to download and extract data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1c2bde09",
"metadata": {},
"outputs": [],
"source": [
"for yr, qtr in tqdm(filing_periods):\n",
" path = DATA_PATH / f\"{yr}_{qtr}\" / \"source\"\n",
" if not path.exists():\n",
" path.mkdir(parents=True)\n",
" filing = f\"{yr}q{qtr}_notes.zip\"\n",
" url = f\"{SEC_URL}{FSN_PATH}{filing}\"\n",
" response = requests.get(url, headers={\"User-Agent\": user_agent}).content\n",
" with ZipFile(BytesIO(response)) as zip_file:\n",
" for file in zip_file.namelist():\n",
" local_file = path / file\n",
" if local_file.exists():\n",
" continue\n",
" with local_file.open(\"wb\") as output:\n",
" for line in zip_file.open(file).readlines():\n",
" output.write(line)"
]
},
{
"cell_type": "markdown",
"id": "c658bd59",
"metadata": {},
"source": [
"Convert downloaded TSV files to parquet format for efficiency"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e7c2850f",
"metadata": {},
"outputs": [],
"source": [
"for f in tqdm(sorted(list(DATA_PATH.glob(\"**/*.tsv\")))):\n",
" parquet_path = f.parent.parent / \"parquet\"\n",
" if not parquet_path.exists():\n",
" parquet_path.mkdir(parents=True)\n",
" file_name = f.stem + \".parquet\"\n",
" if not (parquet_path / file_name).exists():\n",
" df = pd.read_csv(\n",
" f, sep=\"\\t\", encoding=\"latin1\", low_memory=False, on_bad_lines=\"skip\"\n",
" )\n",
" df.to_parquet(parquet_path / file_name)\n",
" f.unlink()"
]
},
{
"cell_type": "markdown",
"id": "1d8efa7c",
"metadata": {},
"source": [
"Filter the subset of data related to Apple Inc. for further analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9e10dd7b",
"metadata": {},
"outputs": [],
"source": [
"sub = pd.read_parquet(DATA_PATH / '2015_3' / 'parquet' / 'sub.parquet')\n",
"name = \"APPLE INC\"\n",
"cik = sub[sub.name == name].T.dropna().squeeze().cik"
]
},
{
"cell_type": "markdown",
"id": "fc2b687f",
"metadata": {},
"source": [
"Aggregate Apple's filings into a single DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef3debc0",
"metadata": {},
"outputs": [],
"source": [
"aapl_subs = pd.DataFrame()\n",
"for sub in DATA_PATH.glob(\"**/sub.parquet\"):\n",
" sub = pd.read_parquet(sub)\n",
" aapl_sub = sub[\n",
" (sub.cik.astype(int) == cik) & (sub.form.isin([\"10-Q\", \"10-K\"]))\n",
" ]\n",
" aapl_subs = pd.concat([aapl_subs, aapl_sub])"
]
},
{
"cell_type": "markdown",
"id": "81ce9915",
"metadata": {},
"source": [
"Extract numerical data from the filings and convert to parquet format"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "70b07ffb",
"metadata": {},
"outputs": [],
"source": [
"aapl_nums = pd.DataFrame()\n",
"for num in DATA_PATH.glob(\"**/num.parquet\"):\n",
" num = pd.read_parquet(num).drop(\"dimh\", axis=1)\n",
" aapl_num = num[num.adsh.isin(aapl_subs.adsh)]\n",
" aapl_nums = pd.concat([aapl_nums, aapl_num])\n",
"aapl_nums.ddate = pd.to_datetime(aapl_nums.ddate, format=\"%Y%m%d\")\n",
"aapl_nums.to_parquet(DATA_PATH / \"aapl_nums.parquet\")"
]
},
{
"cell_type": "markdown",
"id": "b3b458e0",
"metadata": {},
"source": [
"Filter EPS data and adjust for stock splits"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "88af52cd",
"metadata": {},
"outputs": [],
"source": [
"eps = aapl_nums[\n",
" (aapl_nums.tag == \"EarningsPerShareDiluted\") & (aapl_nums.qtrs == 1)\n",
"].drop(\"tag\", axis=1)\n",
"eps = eps.groupby(\"adsh\").apply(\n",
" lambda x: x.nlargest(n=1, columns=[\"ddate\"]), include_groups=False\n",
")\n",
"eps = eps[[\"ddate\", \"value\"]].set_index(\"ddate\").squeeze().sort_index()\n",
"ax = eps.plot.bar()\n",
"ax.set_xticklabels(eps.index.to_period(\"Q\"))"
]
},
{
"cell_type": "markdown",
"id": "69b0411a",
"metadata": {},
"source": [
"Retrieve historical stock price data and calculate P/E ratio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "08f476fa",
"metadata": {},
"outputs": [],
"source": [
"aapl = (\n",
" obb.equity.price.historical(\n",
" \"AAPL\", start_date=\"2014-12-31\", end_date=eps.index.max(), provider=\"yfinance\"\n",
" )\n",
" .to_df()\n",
" .resample(\"D\")\n",
" .last()\n",
" .loc[\"2014\":\"2015\"]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "969044d1",
"metadata": {},
"outputs": [],
"source": [
"pe = aapl.close.to_frame(\"price\").join(eps.to_frame(\"eps\")).ffill().dropna()\n",
"pe[\"pe_ratio\"] = pe.price.div(pe.eps)\n",
"ax = pe.plot(subplots=True, figsize=(16, 8), legend=False, lw=0.5)\n",
"ax[0].set_title(\"Adj Close\")\n",
"ax[1].set_title(\"Diluted EPS\")\n",
"ax[2].set_title(\"Trailing P/E\")"
]
},
{
"cell_type": "markdown",
"id": "cf2e1d3d",
"metadata": {},
"source": [
"Define fields of interest for further financial analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7071c22c",
"metadata": {},
"outputs": [],
"source": [
"fields = [\n",
" \"EarningsPerShareDiluted\",\n",
" \"PaymentsOfDividendsCommonStock\",\n",
" \"WeightedAverageNumberOfDilutedSharesOutstanding\",\n",
" \"OperatingIncomeLoss\",\n",
" \"NetIncomeLoss\",\n",
" \"GrossProfit\",\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "cadcb87b",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,243 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e1e4398f",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "54e702d1",
"metadata": {},
"source": [
"This code performs hierarchical risk parity (HRP) portfolio optimization using historical price data of selected assets. It fetches historical price data, calculates returns, and constructs a hierarchical tree using the Pearson correlation. The code then optimizes the portfolio using HRP methodology and visualizes the portfolio allocation and risk contributions. This is useful for constructing diversified portfolios that minimize risk through hierarchical clustering."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "81a9b353",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import riskfolio as rp\n",
"from openbb import obb"
]
},
{
"cell_type": "markdown",
"id": "987769bc",
"metadata": {},
"source": [
"Define a list of asset symbols for which historical price data will be retrieved"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b6b98345",
"metadata": {},
"outputs": [],
"source": [
"assets = [\n",
" \"XLE\", \"XLF\", \"XLU\", \"XLI\", \"GDX\", \n",
" \"XLK\", \"XLV\", \"XLY\", \"XLP\", \"XLB\", \n",
" \"XOP\", \"IYR\", \"XHB\", \"ITB\", \"VNQ\", \n",
" \"GDXJ\", \"IYE\", \"OIH\", \"XME\", \"XRT\", \n",
" \"SMH\", \"IBB\", \"KBE\", \"KRE\", \"XTL\", \n",
"]"
]
},
{
"cell_type": "markdown",
"id": "08cacea7",
"metadata": {},
"source": [
"Fetch historical price data for the specified assets and pivot the data into a DataFrame"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72e8db71",
"metadata": {},
"outputs": [],
"source": [
"data = (\n",
" obb\n",
" .equity\n",
" .price\n",
" .historical(assets, provider=\"yfinance\")\n",
" .to_df()\n",
" .pivot(columns=\"symbol\", values=\"close\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d6b073b0",
"metadata": {},
"source": [
"Calculate percentage returns from the historical price data and drop any missing values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3564caeb",
"metadata": {},
"outputs": [],
"source": [
"returns = data.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "f9df6bf1",
"metadata": {},
"source": [
"Plot a dendrogram to visualize hierarchical clustering of asset returns using Pearson correlation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a6d85ac",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_dendrogram(\n",
" returns=returns,\n",
" codependence=\"pearson\",\n",
" linkage=\"single\",\n",
" k=None,\n",
" max_k=10,\n",
" leaf_order=True,\n",
" ax=None,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "aa1da0d9",
"metadata": {},
"source": [
"Create an instance of HCPortfolio with the calculated returns for portfolio optimization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "254f5052",
"metadata": {},
"outputs": [],
"source": [
"port = rp.HCPortfolio(returns=returns)"
]
},
{
"cell_type": "markdown",
"id": "59421937",
"metadata": {},
"source": [
"Optimize the portfolio using Hierarchical Risk Parity (HRP) with specified parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "44556ae2",
"metadata": {},
"outputs": [],
"source": [
"w = port.optimization(\n",
" model=\"HRP\",\n",
" codependence=\"pearson\",\n",
" rm=\"MV\",\n",
" rf=0.05,\n",
" linkage=\"single\",\n",
" max_k=10,\n",
" leaf_order=True,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e94bd8c4",
"metadata": {},
"source": [
"Plot a pie chart to visualize the portfolio allocation resulting from the HRP optimization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "54d6c889",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_pie(\n",
" w=w,\n",
" title=\"HRP Naive Risk Parity\",\n",
" others=0.05,\n",
" nrow=25,\n",
" cmap=\"tab20\",\n",
" height=8,\n",
" width=10,\n",
" ax=None,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "bc3e0460",
"metadata": {},
"source": [
"Plot the risk contributions of each asset in the optimized portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8b566981",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_risk_con(\n",
" w=w,\n",
" cov=returns.cov(),\n",
" returns=returns,\n",
" rm=\"MV\",\n",
" rf=0,\n",
" alpha=0.05,\n",
" color=\"tab:blue\",\n",
" height=6,\n",
" width=10,\n",
" t_factor=252,\n",
" ax=None,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "2fd1f927",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,263 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e6959247",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "3cd2f51a",
"metadata": {},
"source": [
"This notebook demonstrates how to backtest a multi-timeframe (MTF) trading strategy using VectorBT PRO. The code involves fetching asset price data, calculating technical indicators across multiple timeframes, and setting up a unified trading portfolio. It addresses common pitfalls in MTF analysis, such as look-ahead bias and information loss, by resampling data effectively. The final output includes visualizations of the cumulative returns and trade signals for each timeframe, aiding in financial modeling and strategy optimization."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "debf173e",
"metadata": {},
"outputs": [],
"source": [
"from vectorbtpro import *"
]
},
{
"cell_type": "markdown",
"id": "db7d0fe5",
"metadata": {},
"source": [
"Configure our graphs to be dark and gap-free."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9e8cc500",
"metadata": {},
"outputs": [],
"source": [
"vbt.settings.set_theme(\"dark\")\n",
"vbt.settings.plotting.auto_rangebreaks = True"
]
},
{
"cell_type": "markdown",
"id": "ae287211",
"metadata": {},
"source": [
"Fetch hourly price data for TSLA from 2023 to 2024."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9443b439",
"metadata": {},
"outputs": [],
"source": [
"data = vbt.YFData.pull(\"TSLA\", start=\"2023\", end=\"2024\", timeframe=\"hourly\")"
]
},
{
"cell_type": "markdown",
"id": "c8a28ff6",
"metadata": {},
"source": [
"Calculate the fast and slow SMA indicators across different timeframes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1c64d052",
"metadata": {},
"outputs": [],
"source": [
"fast_sma = data.run(\n",
" \"talib:sma\", \n",
" timeframe=[\"1h\", \"4h\", \"1d\"], \n",
" timeperiod=vbt.Default(20),\n",
" skipna=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ce377e0d",
"metadata": {},
"outputs": [],
"source": [
"slow_sma = data.run(\n",
" \"talib:sma\", \n",
" timeframe=[\"1h\", \"4h\", \"1d\"], \n",
" timeperiod=vbt.Default(50),\n",
" skipna=True\n",
")"
]
},
{
"cell_type": "markdown",
"id": "bf5600f1",
"metadata": {},
"source": [
"Plot the fast SMA indicators to observe their behavior across timeframes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9fd489d4",
"metadata": {},
"outputs": [],
"source": [
"fast_sma.real.vbt.plot().show_png()"
]
},
{
"cell_type": "markdown",
"id": "12c9d08d",
"metadata": {},
"source": [
"Set up a trading portfolio based on SMA crossover signals and allocate capital."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d4ae2ac",
"metadata": {},
"outputs": [],
"source": [
"pf = vbt.PF.from_signals(\n",
" data, \n",
" long_entries=fast_sma.real_crossed_above(slow_sma), \n",
" short_entries=fast_sma.real_crossed_below(slow_sma), \n",
" size=[[0.05, 0.1, 0.2]],\n",
" size_type=\"valuepercent\",\n",
" init_cash=10_000,\n",
" group_by=[\"pf\", \"pf\", \"pf\"],\n",
" cash_sharing=True,\n",
" tsl_stop=0.2\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e36ba1b5",
"metadata": {},
"source": [
"Plot cumulative returns for each timeframe and the entire portfolio."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0427faea",
"metadata": {},
"outputs": [],
"source": [
"fig = (\n",
" pf\n",
" .get_cumulative_returns()\n",
" .vbt\n",
" .plot(trace_kwargs=dict(line_color=\"gray\", line_dash=\"dot\"))\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d01990a8",
"metadata": {},
"outputs": [],
"source": [
"fig = pf.get_cumulative_returns(group_by=False).vbt.plot(fig=fig)\n",
"fig.show_png()"
]
},
{
"cell_type": "markdown",
"id": "1952ee00",
"metadata": {},
"source": [
"Plot indicators and trade signals for the daily timeframe."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "021534ea",
"metadata": {},
"outputs": [],
"source": [
"fig = fast_sma.real.vbt.plot(column=\"1d\", trace_kwargs=dict(name=\"Fast\", line_color=\"limegreen\"))\n",
"fig = slow_sma.real.vbt.plot(column=\"1d\", trace_kwargs=dict(name=\"Slow\", line_color=\"orangered\"), fig=fig)\n",
"fig = pf.plot_trade_signals(column=\"1d\", fig=fig)\n",
"fig.show_png()"
]
},
{
"cell_type": "markdown",
"id": "5dedb6a8",
"metadata": {},
"source": [
"Combine indicators from different timeframes and identify the best performing pairs."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f73b207b",
"metadata": {},
"outputs": [],
"source": [
"fast_sma_real = fast_sma.real.vbt.rename_levels({\"sma_timeframe\": \"fast_sma_timeframe\"})\n",
"slow_sma_real = slow_sma.real.vbt.rename_levels({\"sma_timeframe\": \"slow_sma_timeframe\"})\n",
"fast_sma_real, slow_sma_real = fast_sma_real.vbt.x(slow_sma_real)\n",
"long_entries = fast_sma_real.vbt.crossed_above(slow_sma_real)\n",
"short_entries = fast_sma_real.vbt.crossed_below(slow_sma_real)\n",
"pf = vbt.PF.from_signals(data, long_entries=long_entries, short_entries=short_entries)\n",
"pf.trades.expectancy.sort_values(ascending=False)"
]
},
{
"cell_type": "markdown",
"id": "3d5c13f3",
"metadata": {},
"source": [
"Timeframe is another strategy parameter to tweak for novel insights and cross-validation."
]
},
{
"cell_type": "markdown",
"id": "20613667",
"metadata": {},
"source": [
"Timeframes offer a unique dimension for strategy optimization and should be cross-validated like other parameters."
]
},
{
"cell_type": "markdown",
"id": "7d7aa76b",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,287 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "cc262707",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "e7482284",
"metadata": {},
"source": [
"This code retrieves financial data and calculates the cost and implications of an options straddle strategy for a given stock. It fetches the earnings calendar, stock price, and options chain for a specific symbol. It then identifies the at-the-money (ATM) call and put options expiring shortly after the earnings report. The cost of the straddle and the implied daily price movement are calculated and printed. This is useful for traders and investors to understand potential price movements around earnings announcements."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5c23a683",
"metadata": {},
"outputs": [],
"source": [
"from datetime import datetime, timedelta\n",
"import pandas as pd\n",
"from openbb import obb"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b5b896a9",
"metadata": {},
"outputs": [],
"source": [
"obb.user.preferences.output_type = \"dataframe\"\n",
"obb.user.credentials.nasdaq_api_key = \"PLACE_HOLDER\""
]
},
{
"cell_type": "markdown",
"id": "5f4b030b",
"metadata": {},
"source": [
"Retrieve earnings calendar data from OpenBB for the next 14 days from today"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a37bb2a5",
"metadata": {},
"outputs": [],
"source": [
"earnings_calendar = obb.equity.calendar.earnings(\n",
" start_date=(datetime.now() + timedelta(days=1)).date(),\n",
" end_date=(datetime.now() + timedelta(days=14)).date(),\n",
" provider=\"nasdaq\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "1b97c34f",
"metadata": {},
"source": [
"Define the symbol for Costco (COST) and fetch the latest stock price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "759c836d",
"metadata": {},
"outputs": [],
"source": [
"symbol = \"COST\"\n",
"last_price = (\n",
" obb\n",
" .equity\n",
" .price\n",
" .quote(symbol, provider=\"yfinance\")\n",
" .T\n",
" .loc[\"last_price\", 0]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "128df5e6",
"metadata": {},
"source": [
"Retrieve options chains for the specified symbol from OpenBB"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "28c03abc",
"metadata": {},
"outputs": [],
"source": [
"options = obb.derivatives.options.chains(symbol, provider=\"cboe\")"
]
},
{
"cell_type": "markdown",
"id": "db27ab8f",
"metadata": {},
"source": [
"Filter options chain for contracts expiring on the specified date (shortly after earnings)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74cf187a",
"metadata": {},
"outputs": [],
"source": [
"expiration = datetime(2024, 3, 8).date()\n",
"chain = options.query(\"`expiration` == @expiration\")"
]
},
{
"cell_type": "markdown",
"id": "41460a1e",
"metadata": {},
"source": [
"Extract strike prices and identify the ATM strike for calls and puts"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5ed6ce2b",
"metadata": {},
"outputs": [],
"source": [
"strikes = chain.strike.to_frame()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d20e86f5",
"metadata": {},
"outputs": [],
"source": [
"call_strike = (\n",
" strikes\n",
" .loc[strikes.query(\"`strike` > @last_price\").idxmin()][\"strike\"]\n",
" .iloc[0]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d2f6265e",
"metadata": {},
"outputs": [],
"source": [
"atm_call = chain.query(\"`strike` == @call_strike and `option_type` == 'call'\")\n",
"atm_put = chain.query(\"`strike` == @call_strike and `option_type` == 'put'\")"
]
},
{
"cell_type": "markdown",
"id": "56a532be",
"metadata": {},
"source": [
"Concatenate ATM call and put options and calculate the total ask price for the straddle"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b5732e3c",
"metadata": {},
"outputs": [],
"source": [
"atm = pd.concat([atm_call, atm_put])\n",
"straddle_price = round(atm.ask.sum(), 2)"
]
},
{
"cell_type": "markdown",
"id": "986e2637",
"metadata": {},
"source": [
"Calculate the implied daily price movement based on the straddle price and time to expiration"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff7156bc",
"metadata": {},
"outputs": [],
"source": [
"days = (atm.expiration.iloc[0] - datetime.now().date()).days + 1\n",
"implied_move = ((1 + straddle_price / last_price) ** (1 / days) - 1) * 100"
]
},
{
"cell_type": "markdown",
"id": "3fc92d6c",
"metadata": {},
"source": [
"Calculate the upper and lower breakeven prices for the straddle"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d711b56",
"metadata": {},
"outputs": [],
"source": [
"upper_price = round((last_price * (1 + (straddle_price / last_price))), 2)\n",
"lower_price = round((last_price * (1 - (straddle_price / last_price))), 2)"
]
},
{
"cell_type": "markdown",
"id": "0e54c3cc",
"metadata": {},
"source": [
"Print the calculated straddle cost, its percentage of the share price, breakeven prices, and implied daily move"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "59a33449",
"metadata": {},
"outputs": [],
"source": [
"print(\n",
" f\"Cost of Straddle: ${straddle_price}\"\n",
" f\"\\nCost as a % of Share Price: {round((straddle_price / last_price) * 100, 4)}%\"\n",
" f\"\\nUpper Breakeven Price: ${upper_price}\"\n",
" f\"\\nLower Breakeven Price: ${lower_price}\\n\"\n",
" f\"\\nImplied Daily Move: {round(implied_move, 4)}%\\n\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "78bbd8f9",
"metadata": {},
"source": [
"Output the number of days until the specified options expiration date"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe3d43ba",
"metadata": {},
"outputs": [],
"source": [
"days"
]
},
{
"cell_type": "markdown",
"id": "b250a148",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,359 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4e198165",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "d0b4a98b",
"metadata": {},
"source": [
"This notebook demonstrates the use of portfolio optimization techniques to analyze and compare different investment strategies. It fetches historical price data for various sector ETFs, preprocesses it to obtain returns, and splits the data into training and testing sets. Three portfolio models—Maximum Diversification, Equal Weighted, and Random Weighted—are trained and their diversification metrics are compared. Finally, it visualizes portfolio compositions and cumulative returns to assess performance."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15ec700b",
"metadata": {},
"outputs": [],
"source": [
"from plotly.io import show\n",
"from sklearn.model_selection import train_test_split\n",
"from skfolio import Population\n",
"from skfolio.optimization import (\n",
" EqualWeighted, \n",
" MaximumDiversification,\n",
" Random\n",
")\n",
"from skfolio.preprocessing import prices_to_returns\n",
"from openbb import obb"
]
},
{
"cell_type": "markdown",
"id": "7e1ac742",
"metadata": {},
"source": [
"List of sector ETFs to fetch historical price data for analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "64eb21b9",
"metadata": {},
"outputs": [],
"source": [
"sectors = [\n",
" \"XLE\", \n",
" \"XLF\", \n",
" \"XLU\", \n",
" \"XLI\", \n",
" \"GDX\", \n",
" \"XLK\", \n",
" \"XLV\", \n",
" \"XLY\", \n",
" \"XLP\", \n",
" \"XLB\", \n",
" \"XOP\", \n",
" \"IYR\", \n",
" \"XHB\", \n",
" \"ITB\", \n",
" \"VNQ\", \n",
" \"GDXJ\", \n",
" \"IYE\", \n",
" \"OIH\", \n",
" \"XME\", \n",
" \"XRT\", \n",
" \"SMH\", \n",
" \"IBB\", \n",
" \"KBE\", \n",
" \"KRE\", \n",
" \"XTL\", \n",
"]"
]
},
{
"cell_type": "markdown",
"id": "e615f85f",
"metadata": {},
"source": [
"Fetch historical price data for the specified sector ETFs"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f858885",
"metadata": {},
"outputs": [],
"source": [
"df = obb.equity.price.historical(\n",
" sectors, \n",
" start_date=\"2010-01-01\", \n",
" provider=\"yfinance\"\n",
").to_df()"
]
},
{
"cell_type": "markdown",
"id": "40e2be93",
"metadata": {},
"source": [
"Pivot the fetched data to get closing prices for each ETF and drop missing values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f05a1194",
"metadata": {},
"outputs": [],
"source": [
"pivoted = df.pivot(\n",
" columns=\"symbol\", \n",
" values=\"close\"\n",
").dropna()"
]
},
{
"cell_type": "markdown",
"id": "214068d8",
"metadata": {},
"source": [
"Convert the closing prices to returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0c996bc",
"metadata": {},
"outputs": [],
"source": [
"X = prices_to_returns(pivoted)"
]
},
{
"cell_type": "markdown",
"id": "dd117308",
"metadata": {},
"source": [
"Split the return data into training and testing sets without shuffling"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74da06e0",
"metadata": {},
"outputs": [],
"source": [
"X_train, X_test = train_test_split(\n",
" X, \n",
" test_size=0.33, \n",
" shuffle=False\n",
")"
]
},
{
"cell_type": "markdown",
"id": "9af78247",
"metadata": {},
"source": [
"Initialize and fit the Maximum Diversification model on the training data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f31ffe88",
"metadata": {},
"outputs": [],
"source": [
"model = MaximumDiversification()\n",
"model.fit(X_train)\n",
"ptf_model_train = model.predict(X_train)"
]
},
{
"cell_type": "markdown",
"id": "3e7a6590",
"metadata": {},
"source": [
"Initialize and fit the Equal Weighted model on the training data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2f3ce359",
"metadata": {},
"outputs": [],
"source": [
"bench = EqualWeighted()\n",
"bench.fit(X_train)\n",
"ptf_bench_train = bench.predict(X_train)"
]
},
{
"cell_type": "markdown",
"id": "b960ba03",
"metadata": {},
"source": [
"Initialize and fit the Random Weighted model on the training data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "042f0091",
"metadata": {},
"outputs": [],
"source": [
"random = Random()\n",
"random.fit(X_train)\n",
"ptf_random_train = random.predict(X_train)"
]
},
{
"cell_type": "markdown",
"id": "68e585cc",
"metadata": {},
"source": [
"Print the diversification metrics for each trained model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe29bf6e",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Maximum Diversification model: {ptf_model_train.diversification:0.2f}\")\n",
"print(f\"Equal Weighted model: {ptf_bench_train.diversification:0.2f}\")\n",
"print(f\"Random Weighted model: {ptf_random_train.diversification:0.2f}\")"
]
},
{
"cell_type": "markdown",
"id": "223e8f31",
"metadata": {},
"source": [
"Predict the portfolio composition for the testing data using the trained models"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a6517f7",
"metadata": {},
"outputs": [],
"source": [
"ptf_model_test = model.predict(X_test)\n",
"ptf_bench_test = bench.predict(X_test)\n",
"ptf_random_test = random.predict(X_test)"
]
},
{
"cell_type": "markdown",
"id": "3ebeb407",
"metadata": {},
"source": [
"Create a Population object with the predicted portfolios for comparison"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9452d1ec",
"metadata": {},
"outputs": [],
"source": [
"population = Population([\n",
" ptf_model_test, \n",
" ptf_bench_test, \n",
" ptf_random_test\n",
"])"
]
},
{
"cell_type": "markdown",
"id": "7bf2774e",
"metadata": {},
"source": [
"Plot the composition of the portfolios in the population"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06d89e23",
"metadata": {},
"outputs": [],
"source": [
"population.plot_composition()"
]
},
{
"cell_type": "markdown",
"id": "49cb1480",
"metadata": {},
"source": [
"Plot the cumulative returns of the portfolios in the population"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c01afb98",
"metadata": {},
"outputs": [],
"source": [
"population.plot_cumulative_returns()"
]
},
{
"cell_type": "markdown",
"id": "5a0b21c1",
"metadata": {},
"source": [
"Display a summary of statistics for the portfolios in the population"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d3f4ed05",
"metadata": {},
"outputs": [],
"source": [
"population.summary()"
]
},
{
"cell_type": "markdown",
"id": "3b9d2638",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,340 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "42505bbc",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "880f1027",
"metadata": {},
"source": [
"This code calculates the theta (time decay) of an American call option on Apple Inc. (AAPL) stock using QuantLib and OpenBB libraries. It fetches option chains and historical prices, determines the closest strike price, and computes volatility. The code then sets up the market data, constructs a Black-Scholes-Merton process, and uses a binomial pricing engine to price the option. Finally, it computes and returns the theta of the call option, which measures the sensitivity of the option's price to the passage of time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7d1894f",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import QuantLib as ql\n",
"from openbb import obb\n",
"import warnings"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d7816ee",
"metadata": {},
"outputs": [],
"source": [
"warnings.filterwarnings(\"ignore\")\n",
"obb.user.preferences.output_type = \"dataframe\""
]
},
{
"cell_type": "markdown",
"id": "eb909667",
"metadata": {},
"source": [
"Define the stock symbol and fetch option chains and historical prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f10f430b",
"metadata": {},
"outputs": [],
"source": [
"symbol = \"AAPL\"\n",
"chains = obb.derivatives.options.chains(symbol=symbol)\n",
"prices = obb.equity.price.historical(symbol=symbol, provider=\"yfinance\")"
]
},
{
"cell_type": "markdown",
"id": "5c8fec35",
"metadata": {},
"source": [
"Select an expiration date and fetch associated strike prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd6d5806",
"metadata": {},
"outputs": [],
"source": [
"expiration = chains.expiration.unique()[5]\n",
"strikes = chains.query(\"`expiration` == @expiration\").strike.to_frame()"
]
},
{
"cell_type": "markdown",
"id": "3647823f",
"metadata": {},
"source": [
"Determine the underlying stock price and identify the closest strike price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "083edc0e",
"metadata": {},
"outputs": [],
"source": [
"underlying_price = prices.close.iat[-1]\n",
"strike_price = (\n",
" strikes\n",
" .loc[\n",
" (strikes-underlying_price)\n",
" .abs()\n",
" .sort_values(\"strike\")\n",
" .index[0]\n",
" ].strike\n",
")"
]
},
{
"cell_type": "markdown",
"id": "0df0957c",
"metadata": {},
"source": [
"Calculate volatility, maturity, dividend yield, and risk-free rate"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9c2877f3",
"metadata": {},
"outputs": [],
"source": [
"volatility = prices.close.pct_change().std() * np.sqrt(252)\n",
"maturity = ql.Date(\n",
" expiration.day,\n",
" expiration.month,\n",
" expiration.year,\n",
")\n",
"dividend_yield = 0.0056\n",
"risk_free_rate = 0.05\n",
"calculation_date = ql.Date.todaysDate()\n",
"ql.Settings.instance().evaluationDate = calculation_date"
]
},
{
"cell_type": "markdown",
"id": "450e1ab7",
"metadata": {},
"source": [
"Set up the market data including spot price, yield term structure, dividend yield, and volatility"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e239f26",
"metadata": {},
"outputs": [],
"source": [
"spot_handle = ql.QuoteHandle(\n",
" ql.SimpleQuote(underlying_price)\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cc227df6",
"metadata": {},
"outputs": [],
"source": [
"yield_handle = ql.YieldTermStructureHandle(\n",
" ql.FlatForward(\n",
" calculation_date, \n",
" risk_free_rate, \n",
" ql.Actual365Fixed()\n",
" )\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a0ec3ad",
"metadata": {},
"outputs": [],
"source": [
"dividend_handle = ql.YieldTermStructureHandle(\n",
" ql.FlatForward(\n",
" calculation_date, \n",
" dividend_yield, \n",
" ql.Actual365Fixed()\n",
" )\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b5a2786d",
"metadata": {},
"outputs": [],
"source": [
"volatility_handle = ql.BlackVolTermStructureHandle(\n",
" ql.BlackConstantVol(\n",
" calculation_date, \n",
" ql.NullCalendar(), \n",
" volatility, \n",
" ql.Actual365Fixed()\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c3e37c12",
"metadata": {},
"source": [
"Construct a Black-Scholes-Merton process for option pricing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "793fa60b",
"metadata": {},
"outputs": [],
"source": [
"bs_process = ql.BlackScholesMertonProcess(\n",
" spot_handle, \n",
" dividend_handle, \n",
" yield_handle, \n",
" volatility_handle\n",
")"
]
},
{
"cell_type": "markdown",
"id": "f33a6256",
"metadata": {},
"source": [
"Set up the pricing engine using a binomial tree model with 1000 steps"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba4f9584",
"metadata": {},
"outputs": [],
"source": [
"engine = ql.BinomialVanillaEngine(bs_process, \"crr\", steps=1000)"
]
},
{
"cell_type": "markdown",
"id": "cdf6482d",
"metadata": {},
"source": [
"Define the American call option with the calculated strike price and maturity"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "70cd1554",
"metadata": {},
"outputs": [],
"source": [
"exercise = ql.AmericanExercise(calculation_date, maturity) \n",
"call_option = ql.VanillaOption(\n",
" ql.PlainVanillaPayoff(ql.Option.Call, strike_price),\n",
" exercise\n",
")"
]
},
{
"cell_type": "markdown",
"id": "f383f8e7",
"metadata": {},
"source": [
"Assign the pricing engine to the call option"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "321c90a8",
"metadata": {},
"outputs": [],
"source": [
"call_option.setPricingEngine(engine)"
]
},
{
"cell_type": "markdown",
"id": "ff91901c",
"metadata": {},
"source": [
"Calculate and return the theta of the call option, normalized by the number of days in a year"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7770ed99",
"metadata": {},
"outputs": [],
"source": [
"call_theta = call_option.theta() / 365\n",
"call_theta"
]
},
{
"cell_type": "markdown",
"id": "fb0a8d41",
"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
}

View File

@ -0,0 +1,587 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "72b48e39",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "82dd16a7",
"metadata": {},
"source": [
"This notebook demonstrates how to construct and optimize a portfolio using historical asset returns and various risk measures. It utilizes `riskfolio` to model the portfolio and `yfinance` to fetch historical stock data. The notebook calculates expected returns and covariance matrices, integrates benchmark indices, and applies tracking error constraints. It then optimizes the portfolio to maximize risk-adjusted returns, plotting the efficient frontier and optimal asset allocations. This is practical for portfolio managers and financial analysts to enhance investment strategies."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a6872e45",
"metadata": {},
"outputs": [],
"source": [
"import riskfolio as rp\n",
"import pandas as pd\n",
"import yfinance as yf\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "93795c66",
"metadata": {},
"source": [
"Define the list of assets for portfolio construction"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6014ff8a",
"metadata": {},
"outputs": [],
"source": [
"assets = [\n",
" \"JCI\",\n",
" \"TGT\",\n",
" \"CMCSA\",\n",
" \"CPB\",\n",
" \"MO\",\n",
" \"APA\",\n",
" \"MMC\",\n",
" \"JPM\",\n",
" \"ZION\",\n",
" \"PSA\",\n",
" \"BAX\",\n",
" \"BMY\",\n",
" \"LUV\",\n",
" \"PCAR\",\n",
" \"TXT\",\n",
" \"TMO\",\n",
" \"DE\",\n",
" \"MSFT\",\n",
" \"HPQ\",\n",
" \"SEE\",\n",
" \"VZ\",\n",
" \"CNP\",\n",
" \"NI\",\n",
" \"T\",\n",
" \"BA\",\n",
" \"^GSPC\",\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "9b37333e",
"metadata": {},
"source": [
"Fetch historical stock data for the defined assets from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72998b3d",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(assets, start=\"2016-01-01\", end=\"2019-12-30\")"
]
},
{
"cell_type": "markdown",
"id": "bc2f3f83",
"metadata": {},
"source": [
"Select adjusted close prices and rename columns to match asset tickers"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a2267eb",
"metadata": {},
"outputs": [],
"source": [
"data = data.loc[:, (\"Adj Close\", slice(None))]\n",
"data.columns = assets"
]
},
{
"cell_type": "markdown",
"id": "bf01924e",
"metadata": {},
"source": [
"Calculate daily returns of the assets and isolate benchmark returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "229bec20",
"metadata": {},
"outputs": [],
"source": [
"returns = data.pct_change().dropna()\n",
"bench_returns = returns.pop(\"^GSPC\").to_frame()"
]
},
{
"cell_type": "markdown",
"id": "15f6af5c",
"metadata": {},
"source": [
"Build the portfolio object and load the calculated returns into it"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fee416cb",
"metadata": {},
"outputs": [],
"source": [
"port = rp.Portfolio(returns=returns)"
]
},
{
"cell_type": "markdown",
"id": "d28c6840",
"metadata": {},
"source": [
"Estimate expected returns and covariance matrix using historical data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7db95d05",
"metadata": {},
"outputs": [],
"source": [
"port.assets_stats(method_mu=\"hist\", method_cov=\"hist\", d=0.94)"
]
},
{
"cell_type": "markdown",
"id": "41e65884",
"metadata": {},
"source": [
"Set to False to indicate no benchmark weights, using index instead"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dae58fef",
"metadata": {},
"outputs": [],
"source": [
"port.kindbench = False"
]
},
{
"cell_type": "markdown",
"id": "9e80d124",
"metadata": {},
"source": [
"Load benchmark returns into the portfolio object"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9f4a1a21",
"metadata": {},
"outputs": [],
"source": [
"port.benchindex = bench_returns"
]
},
{
"cell_type": "markdown",
"id": "29139cd2",
"metadata": {},
"source": [
"Enable the use of tracking error constraints in the optimization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e856f599",
"metadata": {},
"outputs": [],
"source": [
"port.allowTE = True"
]
},
{
"cell_type": "markdown",
"id": "74cff10c",
"metadata": {},
"source": [
"Define the maximum allowed tracking error relative to benchmark returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3387d4f5",
"metadata": {},
"outputs": [],
"source": [
"port.TE = 0.008"
]
},
{
"cell_type": "markdown",
"id": "6b40e308",
"metadata": {},
"source": [
"Explain the goal of calculating optimal portfolios using different risk measures"
]
},
{
"cell_type": "markdown",
"id": "1aa657fe",
"metadata": {},
"source": [
"Define the model type for optimization, here using historical data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "daa9ab9a",
"metadata": {},
"outputs": [],
"source": [
"model = \"Classic\""
]
},
{
"cell_type": "markdown",
"id": "ba2c93a8",
"metadata": {},
"source": [
"Specify the risk measure to use, in this case, Conditional Value at Risk (CVaR)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "007d98eb",
"metadata": {},
"outputs": [],
"source": [
"rm = \"CVaR\""
]
},
{
"cell_type": "markdown",
"id": "e0aa6b66",
"metadata": {},
"source": [
"Set the objective function to maximize Sharpe ratio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eee2108e",
"metadata": {},
"outputs": [],
"source": [
"obj = \"Sharpe\""
]
},
{
"cell_type": "markdown",
"id": "a6c05fcb",
"metadata": {},
"source": [
"Use historical scenarios for risk measures that depend on scenarios"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1b38bdef",
"metadata": {},
"outputs": [],
"source": [
"hist = True"
]
},
{
"cell_type": "markdown",
"id": "f8182805",
"metadata": {},
"source": [
"Define the risk-free rate for portfolio optimization calculations"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4fb85459",
"metadata": {},
"outputs": [],
"source": [
"rf = 0"
]
},
{
"cell_type": "markdown",
"id": "7714deb9",
"metadata": {},
"source": [
"Set the risk aversion factor, relevant only when the objective is 'Utility'"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b9ad9f21",
"metadata": {},
"outputs": [],
"source": [
"l = 0"
]
},
{
"cell_type": "markdown",
"id": "dc2ea3dc",
"metadata": {},
"source": [
"Perform the portfolio optimization to maximize the Sharpe ratio using CVaR"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "62686a64",
"metadata": {},
"outputs": [],
"source": [
"w = port.optimization(\n",
" model=model,\n",
" rm=rm,\n",
" obj=obj,\n",
" rf=rf,\n",
" l=l,\n",
" hist=hist\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e94c0bf1",
"metadata": {},
"source": [
"Plot the optimized portfolio allocation using a pie chart"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c8ee7569",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_pie(\n",
" w=w,\n",
" title=\"Sharpe Mean CVaR\",\n",
" others=0.05,\n",
" nrow=25,\n",
" cmap=\"tab20\",\n",
" height=6,\n",
" width=10,\n",
" ax=None,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "f0184145",
"metadata": {},
"source": [
"Calculate the efficient frontier for the portfolio using the defined risk measure"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "afe7ab13",
"metadata": {},
"outputs": [],
"source": [
"frontier = port.efficient_frontier(\n",
" model=model, \n",
" rm=rm, \n",
" points=50, \n",
" rf=rf, \n",
" hist=hist\n",
")"
]
},
{
"cell_type": "markdown",
"id": "52a38adc",
"metadata": {},
"source": [
"Plot the efficient frontier and the position of the optimized portfolio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f34ffd29",
"metadata": {},
"outputs": [],
"source": [
"ax = rp.plot_frontier(\n",
" w_frontier=frontier,\n",
" mu=port.mu,\n",
" cov=port.cov,\n",
" returns=port.returns,\n",
" rm=rm,\n",
" rf=rf,\n",
" cmap=\"viridis\",\n",
" w=w,\n",
" label=\"Max Risk Adjusted Return Portfolio\",\n",
" marker=\"*\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "6a5f9663",
"metadata": {},
"source": [
"Define the list of different risk measures to use for portfolio optimization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7359bbf",
"metadata": {},
"outputs": [],
"source": [
"rms = [\n",
" \"MV\",\n",
" \"MAD\",\n",
" \"MSV\",\n",
" \"FLPM\",\n",
" \"SLPM\",\n",
" \"CVaR\",\n",
" \"EVaR\",\n",
" \"WR\",\n",
" \"MDD\",\n",
" \"ADD\",\n",
" \"CDaR\",\n",
" \"UCI\",\n",
" \"EDaR\",\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "c20a237f",
"metadata": {},
"source": [
"Initialize an empty DataFrame to store the weights of optimized portfolios"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aea2e6de",
"metadata": {},
"outputs": [],
"source": [
"w_s = pd.DataFrame([])"
]
},
{
"cell_type": "markdown",
"id": "d6206bbb",
"metadata": {},
"source": [
"Loop through each risk measure, optimize the portfolio, and store the weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e7d9329a",
"metadata": {},
"outputs": [],
"source": [
"for i in rms:\n",
" w = port.optimization(model=model, rm=i, obj=obj, rf=rf, l=l, hist=hist)\n",
" w_s = pd.concat([w_s, w], axis=1)"
]
},
{
"cell_type": "markdown",
"id": "696b88ff",
"metadata": {},
"source": [
"Assign the columns of the DataFrame to the respective risk measures and format the output"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bb0285f3",
"metadata": {},
"outputs": [],
"source": [
"w_s.columns = rms\n",
"w_s.style.format(\"{:.2%}\").background_gradient(cmap=\"YlGn\")"
]
},
{
"cell_type": "markdown",
"id": "6422513e",
"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
}

View File

@ -0,0 +1,278 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c15ea663",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "27de4862",
"metadata": {},
"source": [
"This code simulates Geometric Brownian Motion (GBM) to model asset price paths over time, incorporating randomness and volatility in financial markets. It defines functions to generate a Wiener process (representing Brownian motion) and to compute GBM returns and price levels. Parameters such as initial stock price, volatility, drift, and the number of paths and time increments are used to create and visualize multiple simulated price trajectories. This is useful in practice for financial modeling, risk analysis, and forecasting asset prices."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a00ae207",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"id": "bcf516dd",
"metadata": {},
"source": [
"Define initial stock price, volatility, and drift parameters for the Brownian motion"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f681a18e",
"metadata": {},
"outputs": [],
"source": [
"s0 = 131.00\n",
"sigma = 0.25\n",
"mu = 0.35"
]
},
{
"cell_type": "markdown",
"id": "99903829",
"metadata": {},
"source": [
"Define simulation parameters including number of paths, time increment, and total simulation time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fb7fb1a0",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"paths = 1000\n",
"delta = 1.0 / 252.0\n",
"time = 252 * 5"
]
},
{
"cell_type": "markdown",
"id": "b39aae7c",
"metadata": {},
"source": [
"Generate a Wiener process (or Brownian motion) using normally distributed random values. This process simulates the random fluctuations of an asset's price over time, which is essential for modeling the stochastic component of Geometric Brownian Motion."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b5088fe0",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def wiener_process(delta, sigma, time, paths):\n",
" \"\"\"Returns a Wiener process\n",
" \n",
" Parameters\n",
" ----------\n",
" delta : float\n",
" The increment to downsample sigma\n",
" sigma : float\n",
" Percentage volatility\n",
" time : int\n",
" Number of samples to create\n",
" paths : int\n",
" Number of price simulations to create\n",
" \n",
" Returns\n",
" -------\n",
" wiener_process : np.array\n",
" \n",
" Notes\n",
" -----\n",
" This method returns a Wiener process. \n",
" The Wiener process is also called Brownian \n",
" motion. For more information about the \n",
" Wiener process check out the Wikipedia \n",
" page: http://en.wikipedia.org/wiki/Wiener_process\n",
" \"\"\"\n",
"\n",
" # Generate a Wiener process using normally distributed random values\n",
" return sigma * np.random.normal(loc=0, scale=np.sqrt(delta), size=(time, paths))"
]
},
{
"cell_type": "markdown",
"id": "da8be9d1",
"metadata": {},
"source": [
"Generate returns from a Geometric Brownian Motion (GBM) model by first creating a Wiener process to simulate random fluctuations."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a472551",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def gbm_returns(delta, sigma, time, mu, paths):\n",
" \"\"\"Returns returns from a Geometric brownian motion\n",
" \n",
" Parameters\n",
" ----------\n",
" delta : float\n",
" The increment to downsample sigma\n",
" sigma : float\n",
" Percentage volatility\n",
" time : int\n",
" Number of samples to create\n",
" mu : float\n",
" Percentage drift\n",
" paths : int\n",
" Number of price simulations to create\n",
" \n",
" Returns\n",
" -------\n",
" gbm_returns : np.ndarray\n",
" \n",
" Notes\n",
" -----\n",
" This method constructs random Geometric Brownian \n",
" Motion (GBM).\n",
" \"\"\"\n",
"\n",
" # Generate Wiener process for the simulation\n",
" process = wiener_process(delta, sigma, time, paths)\n",
"\n",
" # Apply the geometric Brownian motion formula to generate returns\n",
" return np.exp(\n",
" process + (mu - sigma**2 / 2) * delta\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "c0bd6fb5",
"metadata": {},
"source": [
"Calculate price paths starting from an initial stock price (s0) using Geometric Brownian Motion (GBM)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e095c104",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def gbm_levels(s0, delta, sigma, time, mu, paths):\n",
" \"\"\"Returns price paths starting at s0\n",
" \n",
" Parameters\n",
" ----------\n",
" s0 : float\n",
" The starting stock price\n",
" delta : float\n",
" The increment to downsample sigma\n",
" sigma : float\n",
" Percentage volatility\n",
" time : int\n",
" Number of samples to create\n",
" mu : float\n",
" Percentage drift\n",
" paths : int\n",
" Number of price simulations to create\n",
" \n",
" Returns\n",
" -------\n",
" gbm_levels : np.ndarray\n",
" \"\"\"\n",
"\n",
" # Generate GBM returns for the given parameters\n",
" returns = gbm_returns(delta, sigma, time, mu, paths)\n",
"\n",
" # Stack an array of ones with the returns and compute cumulative product to get price levels\n",
" stacked = np.vstack([np.ones(paths), returns])\n",
" return s0 * stacked.cumprod(axis=0)"
]
},
{
"cell_type": "markdown",
"id": "997935cd",
"metadata": {},
"source": [
"Generate price paths using the defined GBM model and plot them"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aa8a0403",
"metadata": {},
"outputs": [],
"source": [
"price_paths = gbm_levels(s0, delta, sigma, time, mu, paths)\n",
"plt.plot(price_paths, linewidth=0.25)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "9d99436d",
"metadata": {},
"source": [
"Generate price paths with zero drift to observe behavior and plot them"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58e1b03e",
"metadata": {},
"outputs": [],
"source": [
"price_paths = gbm_levels(s0, delta, sigma, time, 0.0, paths)\n",
"plt.plot(price_paths, linewidth=0.25)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "7c1d6887",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,231 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e0b9b533",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "bbb152ce",
"metadata": {},
"source": [
"This code uses Hidden Markov Models (HMM) to identify regimes in financial time series data. It downloads historical price data, calculates returns and ranges, and uses them as features for the HMM. The model is fitted with the features to identify different market states. The identified states are then plotted to visualize market regime changes. This is useful for understanding and predicting market behavior."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fbd8082e",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import yfinance as yf\n",
"from hmmlearn import hmm"
]
},
{
"cell_type": "markdown",
"id": "e66793fc",
"metadata": {},
"source": [
"Download historical price data for SPY from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b0e43aa",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(\"SPY\")"
]
},
{
"cell_type": "markdown",
"id": "52141649",
"metadata": {},
"source": [
"Calculate log returns of the closing prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c31978ad",
"metadata": {},
"outputs": [],
"source": [
"returns = np.log(data.Close / data.Close.shift(1))"
]
},
{
"cell_type": "markdown",
"id": "1f181add",
"metadata": {},
"source": [
"Calculate the range as the difference between high and low prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7f6a03ba",
"metadata": {},
"outputs": [],
"source": [
"range = (data.High - data.Low)"
]
},
{
"cell_type": "markdown",
"id": "6a286ad5",
"metadata": {},
"source": [
"Concatenate returns and range into a single DataFrame and drop any missing values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b6e0fed",
"metadata": {},
"outputs": [],
"source": [
"features = pd.concat([returns, range], axis=1).dropna()\n",
"features.columns = [\"returns\", \"range\"]"
]
},
{
"cell_type": "markdown",
"id": "b7e18b8d",
"metadata": {},
"source": [
"Initialize a Gaussian Hidden Markov Model with 3 states and fit it to the features"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "51c204c1",
"metadata": {},
"outputs": [],
"source": [
"model = hmm.GaussianHMM(\n",
" n_components=3,\n",
" covariance_type=\"full\",\n",
" n_iter=1000,\n",
")\n",
"model.fit(features)"
]
},
{
"cell_type": "markdown",
"id": "8b338395",
"metadata": {},
"source": [
"Predict the hidden states for the given features and store them in a Series"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b02db04c",
"metadata": {},
"outputs": [],
"source": [
"states = pd.Series(model.predict(features), index=data.index[1:])\n",
"states.name = \"state\""
]
},
{
"cell_type": "markdown",
"id": "649d1abc",
"metadata": {},
"source": [
"Plot a histogram of the hidden states"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c1da4348",
"metadata": {},
"outputs": [],
"source": [
"states.hist()"
]
},
{
"cell_type": "markdown",
"id": "99d0edca",
"metadata": {},
"source": [
"Define a color map for the different states"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7944911f",
"metadata": {},
"outputs": [],
"source": [
"color_map = {\n",
" 0.0: \"green\",\n",
" 1.0: \"orange\",\n",
" 2.0: \"red\"\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "43dda8ae",
"metadata": {},
"source": [
"Concatenate the closing prices and the states, drop missing values, \n",
"set state as a hierarchical index, unstack the state index, and plot the closing prices with different colors for each state"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b77b347f",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" pd.concat([data.Close, states], axis=1)\n",
" .dropna()\n",
" .set_index(\"state\", append=True)\n",
" .Close\n",
" .unstack(\"state\")\n",
" .plot(color=color_map)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "5a9b87dd",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,426 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a43648c6",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "4ed53dc4",
"metadata": {},
"source": [
"This code downloads and processes stock price data, trains an autoencoder to compress and reconstruct the data, and then uses the learned embeddings for clustering and visualization. First, it fetches historical stock price data from Yahoo Finance. It then computes log returns, moving averages, and volatility to form features. A PyTorch autoencoder is trained on these features to learn compressed representations (embeddings). Finally, it uses K-Means clustering on the embeddings and visualizes the clusters using PCA."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5dc8b2b",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf\n",
"import pandas as pd\n",
"import numpy as np\n",
"import torch\n",
"import torch.nn as nn\n",
"from torch.utils.data import DataLoader, TensorDataset\n",
"from sklearn.cluster import KMeans\n",
"from sklearn.decomposition import PCA\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"import warnings"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "165d5792",
"metadata": {},
"outputs": [],
"source": [
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "14014259",
"metadata": {},
"source": [
"Define a list of stock symbols to fetch data for"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "00cf6af0",
"metadata": {},
"outputs": [],
"source": [
"symbols = [\n",
" \"AAPL\", \"MSFT\", \"GOOGL\", \"AMZN\", \"META\",\n",
" \"TSLA\", \"BRK-B\", \"V\", \"JNJ\", \"WMT\", \"JPM\",\n",
" \"MA\", \"PG\", \"UNH\", \"DIS\", \"NVDA\", \"HD\", \n",
" \"PYPL\", \"BAC\", \"VZ\", \"ADBE\", \"CMCSA\", \"NFLX\",\n",
" \"KO\", \"NKE\", \"MRK\", \"PEP\", \"T\", \"PFE\", \"INTC\",\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "bbc075cc",
"metadata": {},
"source": [
"Download adjusted close prices for the specified symbols from Yahoo Finance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4b5e8366",
"metadata": {},
"outputs": [],
"source": [
"stock_data = yf.download(\n",
" symbols, \n",
" start=\"2020-01-01\", \n",
" end=\"2023-12-31\"\n",
")[\"Adj Close\"]"
]
},
{
"cell_type": "markdown",
"id": "a0283e34",
"metadata": {},
"source": [
"Calculate log returns, moving averages, and volatility for the stock data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba5af677",
"metadata": {},
"outputs": [],
"source": [
"log_returns = np.log(stock_data / stock_data.shift(1))\n",
"moving_avg = stock_data.rolling(window=22).mean()\n",
"volatility = stock_data.rolling(window=22).std()"
]
},
{
"cell_type": "markdown",
"id": "da7d71aa",
"metadata": {},
"source": [
"Concatenate the features and standardize them"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a77301f",
"metadata": {},
"outputs": [],
"source": [
"features = pd.concat([log_returns, moving_avg, volatility], axis=1).dropna()\n",
"processed_data = (features - features.mean()) / features.std()"
]
},
{
"cell_type": "markdown",
"id": "c0064e95",
"metadata": {},
"source": [
"Convert the features into PyTorch tensors"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c80d578a",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"tensor = torch.tensor(processed_data.values, dtype=torch.float32)\n",
"dataset = TensorDataset(tensor)\n",
"data_loader = DataLoader(dataset, batch_size=32, shuffle=True)"
]
},
{
"cell_type": "markdown",
"id": "ac181de3",
"metadata": {},
"source": [
"Define an autoencoder neural network for stock data embedding"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d962be87",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"class StockAutoencoder(nn.Module):\n",
" \"\"\"Autoencoder neural network for stock data embedding\n",
" \n",
" This class defines an autoencoder with an encoder and decoder\n",
" to compress and reconstruct stock data.\n",
" \n",
" Parameters\n",
" ----------\n",
" feature_dim : int\n",
" The dimensionality of the input features\n",
" \"\"\"\n",
" \n",
" def __init__(self, feature_dim):\n",
" super(StockAutoencoder, self).__init__()\n",
" self.encoder = nn.Sequential(\n",
" nn.Linear(feature_dim, 64),\n",
" nn.ReLU(),\n",
" nn.Linear(64, 32),\n",
" nn.ReLU(),\n",
" nn.Linear(32, 10), # Latent space\n",
" )\n",
" self.decoder = nn.Sequential(\n",
" nn.Linear(10, 32),\n",
" nn.ReLU(),\n",
" nn.Linear(32, 64),\n",
" nn.ReLU(),\n",
" nn.Linear(64, feature_dim),\n",
" nn.ReLU(),\n",
" )\n",
"\n",
" def forward(self, x):\n",
" x = self.encoder(x)\n",
" x = self.decoder(x)\n",
" return x"
]
},
{
"cell_type": "markdown",
"id": "cc7f8754",
"metadata": {},
"source": [
"Train the autoencoder on the stock data using MSE loss and Adam optimizer"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "413d6d54",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def train(model, data_loader, epochs=100):\n",
" \"\"\"Train the autoencoder model\n",
" \n",
" This function trains the autoencoder using MSE loss and Adam optimizer\n",
" over a specified number of epochs.\n",
" \n",
" Parameters\n",
" ----------\n",
" model : nn.Module\n",
" The autoencoder model to be trained\n",
" data_loader : DataLoader\n",
" DataLoader object to iterate through the dataset\n",
" epochs : int, optional\n",
" Number of epochs to train the model (default is 100)\n",
" \"\"\"\n",
" \n",
" criterion = nn.MSELoss()\n",
" optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
" model.train()\n",
" for epoch in range(epochs):\n",
" for data in data_loader:\n",
" inputs = data[0]\n",
" optimizer.zero_grad()\n",
" outputs = model(inputs)\n",
" loss = criterion(outputs, inputs)\n",
" loss.backward()\n",
" optimizer.step()\n",
" print(f\"Epoch {epoch+1}, Loss: {loss.item()}\")"
]
},
{
"cell_type": "markdown",
"id": "e48b0a76",
"metadata": {},
"source": [
"Initialize and train the autoencoder model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ca7a96ba",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"feature_dim = processed_data.shape[1]\n",
"model = StockAutoencoder(feature_dim)\n",
"train(model, data_loader)"
]
},
{
"cell_type": "markdown",
"id": "07873939",
"metadata": {},
"source": [
"Extract embeddings from the trained autoencoder model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5768c886",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def extract_embeddings(model, data_loader):\n",
" \"\"\"Extract embeddings from the trained autoencoder model\n",
" \n",
" This function extracts embeddings by passing data through the encoder\n",
" part of the autoencoder.\n",
" \n",
" Parameters\n",
" ----------\n",
" model : nn.Module\n",
" The trained autoencoder model\n",
" data_loader : DataLoader\n",
" DataLoader object to iterate through the dataset\n",
" \n",
" Returns\n",
" -------\n",
" embeddings : torch.Tensor\n",
" Tensor containing the extracted embeddings\n",
" \"\"\"\n",
" \n",
" model.eval()\n",
" embeddings = []\n",
" with torch.no_grad():\n",
" for data in data_loader:\n",
" inputs = data[0]\n",
" encoded = model.encoder(inputs)\n",
" embeddings.append(encoded)\n",
" return torch.vstack(embeddings)"
]
},
{
"cell_type": "markdown",
"id": "2e6f9d1e",
"metadata": {},
"source": [
"Extract embeddings from the trained model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "91040de9",
"metadata": {},
"outputs": [],
"source": [
"embeddings = extract_embeddings(model, data_loader)"
]
},
{
"cell_type": "markdown",
"id": "28747eff",
"metadata": {},
"source": [
"Apply K-Means clustering on the embeddings"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a6b69b2",
"metadata": {},
"outputs": [],
"source": [
"kmeans = KMeans(n_clusters=5, random_state=42).fit(embeddings.numpy())\n",
"clusters = kmeans.labels_"
]
},
{
"cell_type": "markdown",
"id": "5ac98fcd",
"metadata": {},
"source": [
"Reduce the dimensionality of embeddings using PCA for visualization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "35295d99",
"metadata": {},
"outputs": [],
"source": [
"pca = PCA(n_components=2)\n",
"embeddings_2d = pca.fit_transform(embeddings.numpy())"
]
},
{
"cell_type": "markdown",
"id": "ef42a090",
"metadata": {},
"source": [
"Plot the clusters in 2D space using PCA components"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f7026ea6",
"metadata": {},
"outputs": [],
"source": [
"sns.scatterplot(\n",
" x=embeddings_2d[:, 0],\n",
" y=embeddings_2d[:, 1],\n",
" hue=clusters,\n",
" palette=sns.color_palette(\"hsv\", len(set(clusters))),\n",
")\n",
"plt.xlabel(\"PCA Dimension 1\")\n",
"plt.ylabel(\"PCA Dimension 2\")\n",
"plt.legend(title=\"Cluster\")\n",
"plt.grid(True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "1044a6a3",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,285 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "464b6cd8",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "e3bcc3f9",
"metadata": {},
"source": [
"This code downloads historical stock data for specified tickers and time periods using yfinance. \n",
"It then visualizes the data using mplfinance, producing various types of financial charts \n",
"such as candlestick, line, and Renko charts. The code also demonstrates how to plot moving \n",
"averages and how to include volume in the charts. This is useful for technical analysis \n",
"and understanding price movements over different timeframes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b6f07ce9",
"metadata": {},
"outputs": [],
"source": [
"import yfinance as yf\n",
"import mplfinance as mpf\n",
"import warnings"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1343f752",
"metadata": {},
"outputs": [],
"source": [
"warnings.filterwarnings('ignore')"
]
},
{
"cell_type": "markdown",
"id": "8a8960a3",
"metadata": {},
"source": [
"Download historical stock data for Apple (AAPL) from yfinance for the specified date range"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14bc43b3",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(\"AAPL\", start=\"2022-01-01\", end=\"2022-06-30\")"
]
},
{
"cell_type": "markdown",
"id": "7ded8f61",
"metadata": {},
"source": [
"Plot the downloaded data using a default chart type"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17fa0ce1",
"metadata": {},
"outputs": [],
"source": [
"mpf.plot(data)"
]
},
{
"cell_type": "markdown",
"id": "1c0611a0",
"metadata": {},
"source": [
"Plot the data using a candlestick chart"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4e278a86",
"metadata": {},
"outputs": [],
"source": [
"mpf.plot(data, type=\"candle\")"
]
},
{
"cell_type": "markdown",
"id": "e285c032",
"metadata": {},
"source": [
"Plot the data using a line chart"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9214073c",
"metadata": {},
"outputs": [],
"source": [
"mpf.plot(data, type=\"line\")"
]
},
{
"cell_type": "markdown",
"id": "61bbcc00",
"metadata": {},
"source": [
"Plot the data using a Renko chart"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0f63a046",
"metadata": {},
"outputs": [],
"source": [
"mpf.plot(data, type=\"renko\")"
]
},
{
"cell_type": "markdown",
"id": "5865feca",
"metadata": {},
"source": [
"Plot the data using an OHLC chart with a 15-day moving average"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7a36a88c",
"metadata": {},
"outputs": [],
"source": [
"mpf.plot(data, type=\"ohlc\", mav=15)"
]
},
{
"cell_type": "markdown",
"id": "e8b33855",
"metadata": {},
"source": [
"Plot the data using a candlestick chart with moving averages of 7, 14, and 21 days"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3bf408dd",
"metadata": {},
"outputs": [],
"source": [
"mpf.plot(data, type=\"candle\", mav=(7, 14, 21))"
]
},
{
"cell_type": "markdown",
"id": "622d2692",
"metadata": {},
"source": [
"Plot the candlestick chart with moving averages and volume"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "82641833",
"metadata": {},
"outputs": [],
"source": [
"mpf.plot(data, type=\"candle\", mav=(7, 14, 21), volume=True)"
]
},
{
"cell_type": "markdown",
"id": "f62ec2a9",
"metadata": {},
"source": [
"Plot the candlestick chart with moving averages, volume, and show non-trading periods"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ad60039d",
"metadata": {},
"outputs": [],
"source": [
"mpf.plot(\n",
" data, \n",
" type=\"candle\", \n",
" mav=(7, 14, 21), \n",
" volume=True, \n",
" show_nontrading=True\n",
")"
]
},
{
"cell_type": "markdown",
"id": "72374bfa",
"metadata": {},
"source": [
"Download intraday stock data for Palantir (PLTR) with 1-minute intervals over the last 5 days"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "600c9dbc",
"metadata": {},
"outputs": [],
"source": [
"intraday = yf.download(tickers=\"PLTR\", period=\"5d\", interval=\"1m\")"
]
},
{
"cell_type": "markdown",
"id": "fc78ad0c",
"metadata": {},
"source": [
"Select the last 100 rows of intraday data for plotting"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27e18b45",
"metadata": {},
"outputs": [],
"source": [
"iday = intraday.iloc[-100:, :]"
]
},
{
"cell_type": "markdown",
"id": "017866b3",
"metadata": {},
"source": [
"Plot the selected intraday data using a candlestick chart with 7 and 12-period moving averages and volume"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "502d5882",
"metadata": {},
"outputs": [],
"source": [
"mpf.plot(iday, type=\"candle\", mav=(7, 12), volume=True)"
]
},
{
"cell_type": "markdown",
"id": "76dc895f",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,552 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "54e72fa4",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "0440e960",
"metadata": {},
"source": [
"This code performs financial analysis by downloading historical stock price data and calculating returns and correlations. It visualizes price movements and correlations, then calculates residuals between sector returns and market returns using OLS regression. Finally, it plots cumulative returns of residuals and evaluates the impact of forecast correlations on portfolio breadth. This is useful for risk management and portfolio optimization."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c23baec",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
"import seaborn as sns\n",
"import statsmodels.api as sm\n",
"import yfinance as yf\n",
"import warnings"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f1010b27",
"metadata": {},
"outputs": [],
"source": [
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "87be8f2d",
"metadata": {},
"source": [
"Define a list of stock tickers to download data for"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b6061c80",
"metadata": {},
"outputs": [],
"source": [
"tickers = [\"WFC\", \"JPM\", \"USB\", \"XOM\", \"VLO\", \"SLB\"]"
]
},
{
"cell_type": "markdown",
"id": "dd06f63f",
"metadata": {},
"source": [
"Download historical stock price data from Yahoo Finance for the specified tickers"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f877b54",
"metadata": {},
"outputs": [],
"source": [
"data = yf.download(tickers, start=\"2015-01-01\", end=\"2023-12-31\")[\"Close\"]"
]
},
{
"cell_type": "markdown",
"id": "cc1b6237",
"metadata": {},
"source": [
"Calculate daily percentage returns and drop any missing values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3bad2658",
"metadata": {},
"outputs": [],
"source": [
"returns = data.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "341b12c7",
"metadata": {},
"source": [
"Create a figure with two subplots to plot stock prices and correlation heatmap"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3b446c6c",
"metadata": {},
"outputs": [],
"source": [
"fig, (ax1, ax2) = plt.subplots(ncols=2)\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "9c7032e5",
"metadata": {},
"source": [
"Calculate the correlation matrix of the returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "71c19d12",
"metadata": {},
"outputs": [],
"source": [
"corr = returns.corr()"
]
},
{
"cell_type": "markdown",
"id": "0d68921e",
"metadata": {},
"source": [
"Plot the historical stock prices on the first subplot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d00a220b",
"metadata": {},
"outputs": [],
"source": [
"left = data.plot(ax=ax1)"
]
},
{
"cell_type": "markdown",
"id": "3fe58d51",
"metadata": {},
"source": [
"Plot the correlation heatmap on the second subplot"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66f9c906",
"metadata": {},
"outputs": [],
"source": [
"right = sns.heatmap(\n",
" corr, ax=ax2, vmin=-1, vmax=1,\n",
" xticklabels=tickers, yticklabels=tickers\n",
")"
]
},
{
"cell_type": "markdown",
"id": "b8b511a8",
"metadata": {},
"source": [
"Calculate and print the average pairwise correlation among the stocks"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "966c48c4",
"metadata": {},
"outputs": [],
"source": [
"average_corr = np.mean(\n",
" corr.values[np.triu_indices_from(corr.values, k=1)]\n",
")\n",
"print(f\"Average pairwise correlation: {average_corr}\")"
]
},
{
"cell_type": "markdown",
"id": "152f6d91",
"metadata": {},
"source": [
"Define stock tickers for market indices and two sectors"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e749269",
"metadata": {},
"outputs": [],
"source": [
"market_symbols = [\"XLF\", \"SPY\", \"XLE\"]\n",
"sector_1_stocks = [\"WFC\", \"JPM\", \"USB\"]\n",
"sector_2_stocks = [\"XOM\", \"VLO\", \"SLB\"]"
]
},
{
"cell_type": "markdown",
"id": "8fb99e34",
"metadata": {},
"source": [
"Combine all tickers into one list"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "31030296",
"metadata": {},
"outputs": [],
"source": [
"tickers = market_symbols + sector_1_stocks + sector_2_stocks"
]
},
{
"cell_type": "markdown",
"id": "67fa30a3",
"metadata": {},
"source": [
"Download historical price data for the combined list of tickers"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0cda0643",
"metadata": {},
"outputs": [],
"source": [
"price = yf.download(tickers, start=\"2015-01-01\", end=\"2023-12-31\").Close"
]
},
{
"cell_type": "markdown",
"id": "7c928e9d",
"metadata": {},
"source": [
"Calculate daily percentage returns and drop any missing values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45653d87",
"metadata": {},
"outputs": [],
"source": [
"returns = price.pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "53640dce",
"metadata": {},
"source": [
"Separate market returns and sector returns from the combined returns data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b35541cd",
"metadata": {},
"outputs": [],
"source": [
"market_returns = returns[\"SPY\"]\n",
"sector_1_returns = returns[\"XLF\"]\n",
"sector_2_returns = returns[\"XLE\"]"
]
},
{
"cell_type": "markdown",
"id": "349719d1",
"metadata": {},
"source": [
"Initialize DataFrames to store residuals after regression against market returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "28459d4f",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"stock_returns = returns.drop(market_symbols, axis=1)\n",
"residuals_market = stock_returns.copy() * 0.0\n",
"residuals = stock_returns.copy() * 0.0"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f2d025e1",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def ols_residual(y, x):\n",
" \"\"\"Calculate OLS residuals between two series\n",
" \n",
" Parameters\n",
" ----------\n",
" y : pd.Series\n",
" Dependent variable series\n",
" x : pd.Series\n",
" Independent variable series\n",
" \n",
" Returns\n",
" -------\n",
" residuals : pd.Series\n",
" Residuals from OLS regression\n",
" \"\"\"\n",
" \n",
" results = sm.OLS(y, x).fit()\n",
" return results.resid"
]
},
{
"cell_type": "markdown",
"id": "29195a5e",
"metadata": {},
"source": [
"Calculate residuals of sector returns after removing market returns influence"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e742d786",
"metadata": {},
"outputs": [],
"source": [
"sector_1_excess = ols_residual(sector_1_returns, market_returns)\n",
"sector_2_excess = ols_residual(sector_2_returns, market_returns)"
]
},
{
"cell_type": "markdown",
"id": "4b930f72",
"metadata": {},
"source": [
"Calculate residuals for each stock in sector 1 after removing market and sector influence"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba96972b",
"metadata": {},
"outputs": [],
"source": [
"for stock in sector_1_stocks:\n",
" residuals_market[stock] = ols_residual(returns[stock], market_returns)\n",
" residuals[stock] = ols_residual(residuals_market[stock], sector_1_excess)"
]
},
{
"cell_type": "markdown",
"id": "a990f544",
"metadata": {},
"source": [
"Calculate residuals for each stock in sector 2 after removing market and sector influence"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d560696",
"metadata": {},
"outputs": [],
"source": [
"for stock in sector_2_stocks:\n",
" residuals_market[stock] = ols_residual(returns[stock], market_returns)\n",
" residuals[stock] = ols_residual(residuals_market[stock], sector_2_excess)"
]
},
{
"cell_type": "markdown",
"id": "4f5a5058",
"metadata": {},
"source": [
"Plot cumulative returns of residuals and the correlation heatmap of residuals"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b85c9ece",
"metadata": {},
"outputs": [],
"source": [
"fig, (ax1, ax2) = plt.subplots(ncols=2)\n",
"fig.tight_layout()\n",
"corr = residuals.corr()"
]
},
{
"cell_type": "markdown",
"id": "8ae9e522",
"metadata": {},
"source": [
"Plot cumulative returns of residuals"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "38612953",
"metadata": {},
"outputs": [],
"source": [
"left = (1 + residuals).cumprod().plot(ax=ax1)"
]
},
{
"cell_type": "markdown",
"id": "39b1f294",
"metadata": {},
"source": [
"Plot correlation heatmap of residuals"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0588ecf",
"metadata": {},
"outputs": [],
"source": [
"right = sns.heatmap(\n",
" corr,\n",
" ax=ax2,\n",
" fmt=\"d\",\n",
" vmin=-1,\n",
" vmax=1,\n",
" xticklabels=residuals.columns,\n",
" yticklabels=residuals.columns,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "4b6393ec",
"metadata": {},
"source": [
"Calculate and print the average pairwise correlation among the residuals"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab1c97bd",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"average_corr = np.mean(corr.values[np.triu_indices_from(corr.values, k=1)])\n",
"print(f\"Average pairwise correlation: {average_corr}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5e9956f6",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"def buckle_BR_const(N, rho):\n",
" \"\"\"Calculate effective breadth based on correlation\n",
" \n",
" Parameters\n",
" ----------\n",
" N : int\n",
" Number of assets\n",
" rho : np.ndarray\n",
" Array of correlation values\n",
" \n",
" Returns\n",
" -------\n",
" effective_breadth : np.ndarray\n",
" Effective number of independent bets\n",
" \"\"\"\n",
" \n",
" return N / (1 + rho * (N - 1))"
]
},
{
"cell_type": "markdown",
"id": "2ab38dbf",
"metadata": {},
"source": [
"Generate a range of correlation values and plot the effective breadth"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "973cd58a",
"metadata": {},
"outputs": [],
"source": [
"corr = np.linspace(start=0, stop=1.0, num=500)\n",
"plt.plot(corr, buckle_BR_const(6, corr))\n",
"plt.ylabel('Effective Breadth (Number of Bets)')\n",
"plt.xlabel('Forecast Correlation')"
]
},
{
"cell_type": "markdown",
"id": "8f9ff2b9",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,477 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fa226836",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "71f198b7",
"metadata": {},
"source": [
"This code calculates the implied and realized volatility of an asset, and evaluates the payoff of a volatility swap. It uses QuantLib to set up financial instruments and yield curves, and NumPy and Pandas for simulations and calculations. First, it calculates the implied volatility from the market price of a European option using the Black-Scholes model. Then, it simulates asset price paths to compute realized volatility. Finally, it calculates the value of a volatility swap based on these volatilities."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aebedeea",
"metadata": {},
"outputs": [],
"source": [
"import QuantLib as ql\n",
"import numpy as np\n",
"import pandas as pd"
]
},
{
"cell_type": "markdown",
"id": "afba19b8",
"metadata": {},
"source": [
"Define financial parameters such as notional amount, volatility strike, days to maturity, and observation period"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8babe8d0",
"metadata": {},
"outputs": [],
"source": [
"notional = 100_000 \n",
"volatility_strike = 0.2438\n",
"days_to_maturity = 148\n",
"observation_period = 252"
]
},
{
"cell_type": "markdown",
"id": "095d1289",
"metadata": {},
"source": [
"Define additional financial parameters such as risk-free rate, dividend yield, and current spot price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0465a508",
"metadata": {},
"outputs": [],
"source": [
"risk_free_rate = 0.0525\n",
"dividend_yield = 0.0052\n",
"spot_price = 188.64"
]
},
{
"cell_type": "markdown",
"id": "5b2c2242",
"metadata": {},
"source": [
"Create calendar and day count convention objects for use in QuantLib calculations"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "184f9cea",
"metadata": {},
"outputs": [],
"source": [
"calendar = ql.NullCalendar()\n",
"day_count = ql.Actual360()"
]
},
{
"cell_type": "markdown",
"id": "c5ab97e8",
"metadata": {},
"source": [
"Set today's date and evaluation date in QuantLib"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9340a5d8",
"metadata": {},
"outputs": [],
"source": [
"today = ql.Date().todaysDate()\n",
"ql.Settings.instance().evaluationDate = today"
]
},
{
"cell_type": "markdown",
"id": "ed0a8506",
"metadata": {},
"source": [
"Create the risk-free rate term structure handle using a flat forward curve"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "176f2878",
"metadata": {},
"outputs": [],
"source": [
"risk_free_ts = ql.YieldTermStructureHandle(\n",
" ql.FlatForward(today, risk_free_rate, day_count)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "5f2dfc50",
"metadata": {},
"source": [
"Create the dividend yield term structure handle using a flat forward curve"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ad960a14",
"metadata": {},
"outputs": [],
"source": [
"dividend_ts = ql.YieldTermStructureHandle(\n",
" ql.FlatForward(today, dividend_yield, day_count)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e3f40a4f",
"metadata": {},
"source": [
"Create the spot price handle using a simple quote"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d1e4912d",
"metadata": {},
"outputs": [],
"source": [
"spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))"
]
},
{
"cell_type": "markdown",
"id": "62fea0f2",
"metadata": {},
"source": [
"Define parameters for the European option including strike price, market price, and expiration date"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2f5df3be",
"metadata": {},
"outputs": [],
"source": [
"strike_price = 190\n",
"option_price = 11.05\n",
"expiration_date = today + ql.Period(days_to_maturity, ql.Days)"
]
},
{
"cell_type": "markdown",
"id": "19846fe1",
"metadata": {},
"source": [
"Set up the option with a plain vanilla payoff and European exercise"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74083fa4",
"metadata": {},
"outputs": [],
"source": [
"payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike_price)\n",
"exercise = ql.EuropeanExercise(expiration_date)\n",
"european_option = ql.VanillaOption(payoff, exercise)"
]
},
{
"cell_type": "markdown",
"id": "9c2377f9",
"metadata": {},
"source": [
"Set up the volatility structure with an initial guess for implied volatility"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da3828fd",
"metadata": {},
"outputs": [],
"source": [
"volatility_handle = ql.BlackVolTermStructureHandle(\n",
" ql.BlackConstantVol(today, calendar, volatility_strike, day_count)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "cfe48958",
"metadata": {},
"source": [
"Use the Black-Scholes-Merton process for option pricing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d5cb1639",
"metadata": {},
"outputs": [],
"source": [
"bsm_process = ql.BlackScholesMertonProcess(\n",
" spot_handle, dividend_ts, risk_free_ts, volatility_handle\n",
")"
]
},
{
"cell_type": "markdown",
"id": "3e912452",
"metadata": {},
"source": [
"Calculate the implied volatility from the market price of the option"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7bf19f08",
"metadata": {},
"outputs": [],
"source": [
"implied_volatility = european_option.impliedVolatility(\n",
" option_price, bsm_process, 1e-4, 1000, 1e-8, 4.0\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30681256",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Implied Volatility: {implied_volatility:.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "2f3ce2ad",
"metadata": {},
"source": [
"Set the random seed for reproducibility and define simulation parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c65b7472",
"metadata": {},
"outputs": [],
"source": [
"np.random.seed(42)\n",
"time_steps = observation_period\n",
"dt = 1 / observation_period"
]
},
{
"cell_type": "markdown",
"id": "99f00915",
"metadata": {},
"source": [
"Initialize an array to store simulated asset prices and set the initial price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c1d53659",
"metadata": {},
"outputs": [],
"source": [
"prices = np.zeros((time_steps + 1, 1))\n",
"prices[0] = spot_price"
]
},
{
"cell_type": "markdown",
"id": "47b026f4",
"metadata": {},
"source": [
"Simulate asset price paths using a geometric Brownian motion model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a86ebdc",
"metadata": {},
"outputs": [],
"source": [
"for t in range(1, time_steps + 1):\n",
" z = np.random.normal(size=1)\n",
" prices[t] = (\n",
" prices[t-1] \n",
" * np.exp(\n",
" (risk_free_rate - 0.5 * implied_volatility**2) * \n",
" dt + \n",
" implied_volatility * \n",
" np.sqrt(dt) * z\n",
" )\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "0129e267",
"metadata": {},
"source": [
"Convert the simulated prices to a pandas DataFrame for further analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a96eb0e0",
"metadata": {},
"outputs": [],
"source": [
"prices_df = pd.DataFrame(prices, columns=['Price'])"
]
},
{
"cell_type": "markdown",
"id": "fa7e51f8",
"metadata": {},
"source": [
"Calculate daily returns from the simulated prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "721ae250",
"metadata": {},
"outputs": [],
"source": [
"prices_df['Return'] = prices_df['Price'].pct_change().dropna()"
]
},
{
"cell_type": "markdown",
"id": "89a06767",
"metadata": {},
"source": [
"Calculate realized volatility from the daily returns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "92d2be43",
"metadata": {},
"outputs": [],
"source": [
"realized_volatility = np.std(prices_df['Return']) * np.sqrt(observation_period)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8190344a",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Realized Volatility: {realized_volatility:.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "bccc2823",
"metadata": {},
"source": [
"Calculate the time to maturity in years"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06aa9217",
"metadata": {},
"outputs": [],
"source": [
"time_to_maturity = days_to_maturity / observation_period"
]
},
{
"cell_type": "markdown",
"id": "c5dd73ce",
"metadata": {},
"source": [
"Calculate the payoff of the volatility swap based on realized and strike volatility"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "830e946c",
"metadata": {},
"outputs": [],
"source": [
"volatility_swap_value = (\n",
" (realized_volatility - volatility_strike) * \n",
" notional * \n",
" np.sqrt(time_to_maturity)\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15e99f26",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Volatility Swap Value: ${volatility_swap_value:.2f}\")"
]
},
{
"cell_type": "markdown",
"id": "2ad683ab",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,274 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "b2530bdd",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "c70588cd",
"metadata": {},
"source": [
"This code prices an American call option using the Heston model, a popular stochastic volatility model in quantitative finance. It sets up the necessary market data, including spot price, dividend yield, risk-free rate, and volatility. The code configures the Heston process with parameters such as initial variance, mean reversion rate, long-term variance, volatility of volatility, and correlation. It then constructs the option payoff and exercise type, sets up the finite difference pricing engine, and calculates the option price. This approach is useful for pricing options that cannot be easily handled by closed-form solutions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b46a1fd",
"metadata": {},
"outputs": [],
"source": [
"import QuantLib as ql"
]
},
{
"cell_type": "markdown",
"id": "44750a62",
"metadata": {},
"source": [
"Set the evaluation date for the option pricing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5af4b1a7",
"metadata": {},
"outputs": [],
"source": [
"evaluation_date = ql.Date(30, 5, 2024)\n",
"ql.Settings.instance().evaluationDate = evaluation_date"
]
},
{
"cell_type": "markdown",
"id": "30b6b6a4",
"metadata": {},
"source": [
"Define the expiry date, strike price, and option type for the American option"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bb0c8ad2",
"metadata": {},
"outputs": [],
"source": [
"expiry_date = ql.Date(20, 9, 2024)\n",
"strike_price = 190\n",
"option_type = ql.Option.Call"
]
},
{
"cell_type": "markdown",
"id": "d791b5ae",
"metadata": {},
"source": [
"Set the spot price, dividend rate, risk-free rate, and volatility of the underlying asset"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "94c43338",
"metadata": {},
"outputs": [],
"source": [
"spot_price = 191.62\n",
"dividend_rate = 0.0053\n",
"risk_free_rate = 0.05\n",
"volatility = 0.2361"
]
},
{
"cell_type": "markdown",
"id": "00066811",
"metadata": {},
"source": [
"Define the term structures for dividends and risk-free rates as flat curves"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cf1c5332",
"metadata": {},
"outputs": [],
"source": [
"dividend_ts = ql.YieldTermStructureHandle(\n",
" ql.FlatForward(\n",
" evaluation_date, \n",
" dividend_rate, \n",
" ql.Actual360()\n",
" )\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2307a2d3",
"metadata": {},
"outputs": [],
"source": [
"risk_free_ts = ql.YieldTermStructureHandle(\n",
" ql.FlatForward(\n",
" evaluation_date, \n",
" risk_free_rate, \n",
" ql.Actual360()\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"id": "45034dc2",
"metadata": {},
"source": [
"Set up the Heston process parameters including initial variance, mean reversion, long-term variance, and correlation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "79b5559d",
"metadata": {},
"outputs": [],
"source": [
"v0 = volatility * volatility\n",
"kappa = 2.0\n",
"theta = volatility * volatility\n",
"sigma = 0.1\n",
"rho = 0.0"
]
},
{
"cell_type": "markdown",
"id": "b669e4f5",
"metadata": {},
"source": [
"Initialize the Heston process using the defined term structures, spot price, and Heston parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9f20cb3b",
"metadata": {},
"outputs": [],
"source": [
"heston_process = ql.HestonProcess(\n",
" risk_free_ts, \n",
" dividend_ts, \n",
" ql.QuoteHandle(\n",
" ql.SimpleQuote(spot_price)\n",
" ), \n",
" v0, \n",
" kappa, \n",
" theta, \n",
" sigma, \n",
" rho\n",
")"
]
},
{
"cell_type": "markdown",
"id": "1d6271b0",
"metadata": {},
"source": [
"Create a Heston model instance using the Heston process"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f590bfdf",
"metadata": {},
"outputs": [],
"source": [
"heston_model = ql.HestonModel(heston_process)"
]
},
{
"cell_type": "markdown",
"id": "c91b69f6",
"metadata": {},
"source": [
"Define the payoff and exercise type for the American option"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6895b95f",
"metadata": {},
"outputs": [],
"source": [
"payoff = ql.PlainVanillaPayoff(option_type, strike_price)\n",
"exercise = ql.AmericanExercise(evaluation_date, expiry_date)\n",
"american_option = ql.VanillaOption(payoff, exercise)"
]
},
{
"cell_type": "markdown",
"id": "2a1be49b",
"metadata": {},
"source": [
"Set up the finite difference engine for pricing the American option using the Heston model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f3683e2c",
"metadata": {},
"outputs": [],
"source": [
"heston_fd_engine = ql.FdHestonVanillaEngine(heston_model)\n",
"american_option.setPricingEngine(heston_fd_engine)"
]
},
{
"cell_type": "markdown",
"id": "3c268400",
"metadata": {},
"source": [
"Calculate and print the net present value (NPV) or option price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b01cd50",
"metadata": {},
"outputs": [],
"source": [
"option_price = american_option.NPV()\n",
"print(f\"Option Price: {option_price:.2f}\")"
]
},
{
"cell_type": "markdown",
"id": "53c45f10",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,278 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a60f3e1c",
"metadata": {},
"source": [
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
]
},
{
"cell_type": "markdown",
"id": "40516b4b",
"metadata": {},
"source": [
"This code calculates the implied volatility of an American call option using the QuantLib library. It sets up the necessary financial parameters, including the spot price, risk-free rate, dividend yield, volatility, days to maturity, strike price, and option price. The code establishes the yield term structure, spot price handle, payoff, exercise type, and volatility structure. It then uses the Black-Scholes-Merton process and a binomial pricing engine to compute the implied volatility. This is useful for financial analysts and traders to evaluate the market's expectation of future volatility."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e7f49c2d",
"metadata": {},
"outputs": [],
"source": [
"import QuantLib as ql"
]
},
{
"cell_type": "markdown",
"id": "3a1591ae",
"metadata": {},
"source": [
"Define the financial parameters including spot price, risk-free rate, dividend yield, volatility, days to maturity, strike price, and option price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ebda590",
"metadata": {},
"outputs": [],
"source": [
"spot_price = 188.64\n",
"risk_free_rate = 0.0525\n",
"dividend_yield = 0.0052\n",
"volatility = 0.20\n",
"days_to_maturity = 148\n",
"strike_price = 190\n",
"option_price = 11.05"
]
},
{
"cell_type": "markdown",
"id": "b500dd31",
"metadata": {},
"source": [
"Set up the calendar, day count convention, and evaluation date for the option pricing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "96d768af",
"metadata": {},
"outputs": [],
"source": [
"calendar = ql.NullCalendar()\n",
"day_count = ql.Actual360()\n",
"today = ql.Date().todaysDate()"
]
},
{
"cell_type": "markdown",
"id": "7d0b1bce",
"metadata": {},
"source": [
"Set the evaluation date to today's date"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8ade3ec",
"metadata": {},
"outputs": [],
"source": [
"ql.Settings.instance().evaluationDate = today"
]
},
{
"cell_type": "markdown",
"id": "661ffd5b",
"metadata": {},
"source": [
"Create a flat yield term structure for the risk-free rate and dividend yield"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d310993f",
"metadata": {},
"outputs": [],
"source": [
"risk_free_ts = ql.YieldTermStructureHandle(\n",
" ql.FlatForward(today, risk_free_rate, day_count)\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "835e2362",
"metadata": {},
"outputs": [],
"source": [
"dividend_ts = ql.YieldTermStructureHandle(\n",
" ql.FlatForward(today, dividend_yield, day_count)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c4e11c17",
"metadata": {},
"source": [
"Set up the handle for the underlying asset's spot price"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12ff1c7c",
"metadata": {},
"outputs": [],
"source": [
"spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))"
]
},
{
"cell_type": "markdown",
"id": "471bc5de",
"metadata": {},
"source": [
"Define the expiration date, payoff, and exercise type for the American option"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8740093",
"metadata": {},
"outputs": [],
"source": [
"expiration_date = today + ql.Period(days_to_maturity, ql.Days)\n",
"payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike_price)\n",
"exercise = ql.AmericanExercise(today, expiration_date)\n",
"american_option = ql.VanillaOption(payoff, exercise)"
]
},
{
"cell_type": "markdown",
"id": "d47c67a8",
"metadata": {},
"source": [
"Create the volatility handle for the Black-Scholes-Merton process using a constant volatility model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b53d34fa",
"metadata": {},
"outputs": [],
"source": [
"volatility_handle = ql.BlackVolTermStructureHandle(\n",
" ql.BlackConstantVol(today, calendar, volatility, day_count)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "9d841ade",
"metadata": {},
"source": [
"Set up the Black-Scholes-Merton process using the spot price, dividend yield, risk-free rate, and volatility"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f7229c5a",
"metadata": {},
"outputs": [],
"source": [
"bsm_process = ql.BlackScholesMertonProcess(\n",
" spot_handle, dividend_ts, risk_free_ts, volatility_handle\n",
")"
]
},
{
"cell_type": "markdown",
"id": "b67dfaa6",
"metadata": {},
"source": [
"Set the pricing engine to a binomial model with 1000 steps and calculate the implied volatility"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af0d3fd5",
"metadata": {},
"outputs": [],
"source": [
"engine = ql.BinomialVanillaEngine(bsm_process, \"crr\", 1000)\n",
"american_option.setPricingEngine(engine)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a48f5bbc",
"metadata": {},
"outputs": [],
"source": [
"implied_volatility = american_option.impliedVolatility(\n",
" option_price, bsm_process, 1e-4, 1000, 1e-8, 4.0\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "779a595f",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Implied Volatility: {implied_volatility:.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "771e5019",
"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
}

Binary file not shown.