├── .gitignore ├── QS001-openbb-financial-data └── openbb-financial-scripts.bash ├── QS002-automate-trades ├── automate-trades.py └── new_highs.csv ├── QS003-stock-screener-openbb-sdk ├── 01_stock_screener_openbb_sdk.py ├── new_highs.csv └── stock_data.csv ├── QS004-avoid-algo-trading-mistakes ├── 01_exponential_moving_average.py └── moving_average_plot.png ├── QS005-pytimetk-first-look ├── 01_pytimetk_algo_demo.py └── figures │ ├── 01_moving_averages_10_50_200.png │ ├── 02_moving_medians_10_50_200.png │ └── 03_bollinger_bands.png ├── QS006-anomaly-buy-sell └── 01_anomaly_buy_sell.py ├── QS007-ML-in-finance └── 01_ml_trend_detection.py ├── QS008-fast-fourier-transform └── 01_fft.py ├── QS009-average-true-range └── 01_atr.py ├── QS010-relative-strength-index └── 01_rsi.py ├── QS011-skfolio-risk-parity └── 01_risk_parity.py ├── QS012-pytimetk-finance-module └── 01_pytimetk_finance_module.py ├── QS013-macd └── 01_macd.py ├── QS014-riskfolio ├── 01_riskfolio.py └── excel-report.xlsx ├── QS015-alphalens └── 01_alphalens_momentum.py ├── QS016-nancy-pelosi-portfolio └── 01_nancy_pelosi_portfolio.py ├── QS017-quantstats-tearsheets ├── 01_quantstats_tearsheets.py ├── quantstats-tearsheet.html └── quantstats-tearsheet_spy.html ├── QS018-polars └── 01_polars.py ├── QS019-correlation └── 01_correlation.py ├── QS020-ffn └── 01_ffn.py ├── QS021-mplfinance └── 01_mplfinance.py ├── QS022-hrp └── 01_hrp.py ├── QS023-kmeans └── 01_kmeans.py ├── QS024-downside-deviation └── 01_downside_deviation.py ├── QS025-autoencoders └── 01_autoencoders.py ├── QS026-markov └── 01_markov.py ├── QS027-pcr └── 01_pcr.py ├── QS028-flow-effects └── 01_flow_effects.py ├── QS029-buffett └── 01_buffett.py ├── QS030-omega └── 01_omega.py ├── QS031-kelly └── 01_kelly.py ├── QS032-information-ratio └── 01_information_ratio.py ├── QS034-optimize-exits └── 01_optimize_exits.py ├── QS035-autocorrelation └── 01_autocorrelation.py ├── QS037-tensortrade ├── 01_tensortrade.py ├── evaluation.csv └── training.csv ├── QS038-fast-fourier-transform └── 01_fast-fourier-transform.py ├── QS039-graph-optimization └── 01_graph_optimization.py ├── QS040-cvar └── 01_cvar.py ├── QS041-hurst-exponent └── 01_husrt_exponent.py ├── QS043-3-day-pullback └── 01_3_day_pullback.py ├── README.md ├── requirements.txt └── temp ├── 10_lines_of_python └── 10_lines_of_python.py ├── QS011-pca-factor-exposure └── 01_pca_factor_exposure.py ├── QS017-clustering └── 01_nested_cluster_optimization ├── QS018-stanley-druckenmiller-portfolio ├── 01_nancy_pelosi_2_pyfolio.py └── 01_stanley_druckenmiller_pyfolio.py ├── QS020-garch └── 01_garch.py ├── fast-rolling-functions └── 01_fast_rolling_functions.py ├── financial_statements.py ├── put_to_call.py └── quantstats ├── 01_quantstats.py └── quantstats-report.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Jupyter Notebook 2 | .ipynb_checkpoints 3 | *.ipynb 4 | 5 | # vscode project settings 6 | .vscode 7 | 8 | # Mac folder settings 9 | **/.DS_Store 10 | 11 | -------------------------------------------------------------------------------- /QS001-openbb-financial-data/openbb-financial-scripts.bash: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 001: Get a $29,000 Financial Data Terminal for Free 3 | # Copyright: Quant Science, LLC 4 | 5 | # STEP 1: DOWNLOAD OPENBB 6 | # Download the terminal at: https://my.openbb.co/app/terminal/download 7 | 8 | # STEP 2: ANALYZE STOCKS 9 | stocks 10 | 11 | # STEP 3: LOAD A STOCK 12 | load tsla 13 | 14 | # STEP 4: GETTING HELP 15 | help 16 | 17 | # STEP 5: PERFORM TECHNICAL ANALYSIS 18 | ta 19 | 20 | # STEP 6: MAKE A TECHNICAL CHART 21 | sma 22 | 23 | # STEP 7: BACK OUT OF TECHNICAL ANALYSIS & GET STOCKS SUBMENU 24 | q 25 | help 26 | 27 | # STEP 8: TIME SERIES (EXPERIMENTAL) 28 | forecast 29 | autoarima -d TSLA --target-column adj_close -n 14 30 | 31 | # NEXT STEPS: 32 | # Sign up for our Python for Algorithmic Trading Course Waitlist 33 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 34 | -------------------------------------------------------------------------------- /QS002-automate-trades/automate-trades.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 002: Using Python to Automate Trades 3 | # Copyright: Quant Science, LLC 4 | 5 | # Requires openbb==^3.2.4 6 | 7 | import pandas as pd 8 | from openbb_terminal.sdk import openbb 9 | import riskfolio as rp 10 | 11 | # GET NEW HIGHS 12 | new_highs = openbb.stocks.screener.screener_data("new_high") 13 | 14 | new_highs = pd.read_csv("QS002-automate-trades/new_highs.csv") 15 | 16 | port_data = new_highs[(new_highs.Price>15) & (new_highs.Country == "USA")] 17 | 18 | port_data 19 | 20 | tickers = port_data.Ticker.tolist() 21 | tickers 22 | 23 | # GET PRICES & RETURNS 24 | 25 | data = openbb.economy.index(tickers, start_date = "2016-01-01", end_date="2019-12-30") 26 | 27 | data 28 | 29 | returns = data.pct_change()[1:] 30 | 31 | returns.dropna(how="any", axis=1, inplace=True) 32 | 33 | returns 34 | 35 | # RISKFOLIO 36 | 37 | port = rp.Portfolio(returns=returns) 38 | 39 | port.assets_stats(method_mu='hist', method_cov='hist', d=0.94) 40 | 41 | port.lowerret = 0.0008 42 | 43 | w_rp_c = port.rp_optimization( 44 | model = "Classic", 45 | rm = "MV", 46 | hist = True, 47 | rf = 0, 48 | b = None 49 | ) 50 | 51 | w_rp_c 52 | 53 | port_val = 10_000 54 | 55 | w_rp_c["invest_amt"] = w_rp_c * port_val 56 | 57 | w_rp_c["last_price"] = data.iloc[-1] 58 | 59 | w_rp_c["shares"] = (w_rp_c.invest_amt / w_rp_c.last_price).astype(int) 60 | 61 | w_rp_c 62 | 63 | # WANT MORE ALGORITHMIC TRADING HELP? 64 | # Register for our Course Waitlist: 65 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 66 | -------------------------------------------------------------------------------- /QS002-automate-trades/new_highs.csv: -------------------------------------------------------------------------------- 1 | ,Ticker,Company,Sector,Industry,Country,Market Cap,P/E,Price,Change,Volume 2 | 0,RETA,Reata Pharmaceuticals Inc.,Healthcare,Biotechnology,USA,4080000000.0,,167.19,0.5402,12338973.0 3 | 1,URGN,UroGen Pharma Ltd.,Healthcare,Biotechnology,USA,419640000.0,,22.61,0.2638,7906493.0 4 | 2,AMSC,American Superconductor Corporation,Industrials,Specialty Industrial Machinery,USA,255840000.0,,9.84,0.16449999999999998,4860842.0 5 | 3,BZH,Beazer Homes USA Inc.,Consumer Cyclical,Residential Construction,USA,869450000.0,5.44,35.43,0.2772,2457683.0 6 | 4,GHG,GreenTree Hospitality Group Ltd.,Consumer Cyclical,Lodging,China,592590000.0,,6.61,0.13970000000000002,41772.0 7 | 5,ORN,Orion Group Holdings Inc.,Industrials,Engineering & Construction,USA,108310000.0,,3.77,0.12869999999999998,457308.0 8 | 6,EH,EHang Holdings Limited,Industrials,Aerospace & Defense,China,1120000000.0,,21.61,0.1675,2991297.0 9 | 7,HURN,Huron Consulting Group Inc.,Industrials,Consulting Services,USA,1520000000.0,30.75,93.65,0.18109999999999998,415676.0 10 | 8,BSAQ,Black Spade Acquisition Co,Financial,Shell Companies,Hong Kong,71690000.0,43.39,11.15,0.08460000000000001,294467.0 11 | 9,ASCB,A SPAC II Acquisition Corporation,Financial,Shell Companies,Singapore,267670000.00000003,59.83,10.53,-0.004699999999999999,124945.0 12 | 10,SKYW,SkyWest Inc.,Industrials,Airlines,USA,1760000000.0,69.16,44.54,0.1233,1196788.0 13 | 11,LVO,LiveOne Inc.,Communication Services,Entertainment,USA,164910000.0,,1.91,0.0437,382740.0 14 | 12,LI,Li Auto Inc.,Consumer Cyclical,Auto Manufacturers,China,40860000000.0,,42.72,0.09570000000000001,13486062.0 15 | 13,EME,EMCOR Group Inc.,Industrials,Engineering & Construction,USA,9540000000.0,23.25,211.82,0.056100000000000004,857205.0 16 | 14,ITCL,Banco Itau Chile,Financial,Banks - Regional,Chile,2530000000.0,29.42,4.09,0.0225,43633.0 17 | 15,USAP,Universal Stainless & Alloy Products Inc.,Basic Materials,Steel,USA,140450000.0,,16.37,0.056100000000000004,189941.0 18 | 16,IONQ,IonQ Inc.,Technology,Computer Hardware,USA,3120000000.0,,17.93,0.1545,14724758.0 19 | 17,CVLG,Covenant Logistics Group Inc.,Industrials,Trucking,USA,691990000.0,9.15,56.86,0.061399999999999996,127356.0 20 | 18,EDU,New Oriental Education & Technology Group Inc.,Consumer Defensive,Education & Training Services,China,8930000000.0,50.82,56.11,0.06570000000000001,4685005.0 21 | 19,NRDY,Nerdy Inc.,Technology,Software - Application,USA,732590000.0,,4.85,0.1073,1556380.0 22 | 20,ABR,Arbor Realty Trust Inc.,Real Estate,REIT - Mortgage,USA,2930000000.0,11.95,17.62,0.091,10583802.0 23 | 21,EB,Eventbrite Inc.,Technology,Software - Application,USA,1080000000.0,,11.58,0.0732,2029391.0 24 | 22,ARAY,Accuray Incorporated,Healthcare,Medical Devices,USA,385900000.0,,4.2,0.042199999999999994,1460622.0 25 | 23,ONTO,Onto Innovation Inc.,Technology,Semiconductor Equipment & Materials,USA,5740000000.0,30.43,122.16,0.0414,496737.0 26 | 24,SPHR,Sphere Entertainment Co.,Communication Services,Entertainment,USA,1420000000.0,,43.06,0.0482,662591.0 27 | 25,UCTT,Ultra Clean Holdings Inc.,Technology,Semiconductor Equipment & Materials,USA,1620000000.0,188.18,36.13,-0.0006,586872.0 28 | 26,CUBI,Customers Bancorp Inc.,Financial,Banks - Regional,USA,1310000000.0,7.13,41.79,0.0002,635369.0 29 | 27,MUFG,Mitsubishi UFJ Financial Group Inc.,Financial,Banks - Diversified,Japan,97640000000.0,12.35,8.14,0.0449,6986553.0 30 | 28,NYCB,New York Community Bancorp Inc.,Financial,Banks - Regional,USA,9390000000.0,10.82,13.66,0.05,16077011.0 31 | 29,BAH,Booz Allen Hamilton Holding Corporation,Industrials,Consulting Services,USA,14930000000.0,59.66,120.94,0.061200000000000004,1456929.0 32 | 30,GNTX,Gentex Corporation,Consumer Cyclical,Auto Parts,USA,7420000000.0,23.41,32.89,0.0359,1402854.0 33 | 31,NUVL,Nuvalent Inc.,Healthcare,Biotechnology,USA,2640000000.0,,48.5,0.0446,520463.0 34 | 32,KLAC,KLA Corporation,Technology,Semiconductor Equipment & Materials,USA,66180000000.00001,20.84,511.01,0.0594,2013692.0 35 | 33,ATR,AptarGroup Inc.,Healthcare,Medical Instruments & Supplies,USA,7810000000.0,35.62,123.78,0.038900000000000004,513037.0 36 | 34,CLS,Celestica Inc.,Technology,Electronic Components,Canada,2140000000.0000002,17.15,20.72,0.1505,6285225.0 37 | 35,KGS,Kodiak Gas Services LLC,Energy,Oil & Gas Equipment & Services,USA,1370000000.0,21.56,18.97,0.0406,747660.0 38 | 36,LII,Lennox International Inc.,Industrials,Building Products & Equipment,USA,12660000000.0,25.28,363.36,0.0194,531128.0 39 | 37,CARR,Carrier Global Corporation,Industrials,Building Products & Equipment,USA,47380000000.0,19.99,58.99,0.0395,8727139.0 40 | 38,QUAD,Quad/Graphics Inc.,Industrials,Specialty Business Services,USA,305710000.0,,5.79,-0.0017000000000000001,373248.0 41 | 39,BJRI,BJ's Restaurants Inc.,Consumer Cyclical,Restaurants,USA,784620000.0,49.27,36.46,0.09359999999999999,717176.0 42 | 40,DASH,DoorDash Inc.,Communication Services,Internet Content & Information,USA,33430000000.0,,89.65,0.042,4688179.0 43 | 41,CMPR,Cimpress plc,Communication Services,Advertising Agencies,Ireland,1790000000.0,,67.51,-0.0087,151289.0 44 | 42,MFG,Mizuho Financial Group Inc.,Financial,Banks - Regional,Japan,41110000000.0,,3.43,0.0489,904690.0 45 | 43,YELP,Yelp Inc.,Communication Services,Internet Content & Information,USA,2950000000.0,90.67,44.88,0.047400000000000005,1116474.0 46 | 44,TW,Tradeweb Markets Inc.,Financial,Capital Markets,USA,17030000000.000002,51.96,82.67,0.0275,965784.0 47 | 45,NVMI,Nova Ltd.,Technology,Semiconductor Equipment & Materials,Israel,3440000000.0,27.81,123.0,0.026000000000000002,117378.0 48 | 46,CNX,CNX Resources Corporation,Energy,Oil & Gas E&P,USA,3110000000.0,2.45,19.93,0.061799999999999994,8345275.0 49 | 47,WRLD,World Acceptance Corporation,Financial,Credit Services,USA,959560000.0,24.45,156.28,0.017,28224.0 50 | 48,CAMT,Camtek Ltd.,Technology,Semiconductor Equipment & Materials,Israel,2049999999.9999998,28.98,47.29,0.027999999999999997,402375.0 51 | 49,TXG,10x Genomics Inc.,Healthcare,Health Information Services,USA,7050000000.0,,62.91,0.0362,908207.0 52 | 50,SMFG,Sumitomo Mitsui Financial Group Inc.,Financial,Banks - Diversified,Japan,60630000000.0,11.2,9.45,0.0317,2958341.0 53 | 51,MTH,Meritage Homes Corporation,Consumer Cyclical,Residential Construction,USA,5090000000.0,6.61,150.49,0.0874,920524.0 54 | 52,AUPH,Aurinia Pharmaceuticals Inc.,Healthcare,Biotechnology,Canada,1580000000.0,,12.27,0.1084,4329215.0 55 | 53,STLA,Stellantis N.V.,Consumer Cyclical,Auto Manufacturers,Netherlands,63950000000.0,3.45,20.55,0.0379,10209779.0 56 | 54,DAN,Dana Incorporated,Consumer Cyclical,Auto Parts,USA,2710000000.0,,18.96,0.008,1750002.0 57 | 55,ZGN,Ermenegildo Zegna N.V.,Consumer Cyclical,Apparel Manufacturing,Italy,3810000000.0,66.79,15.63,0.024900000000000002,637396.0 58 | 56,GSL,Global Ship Lease Inc.,Industrials,Marine Shipping,United Kingdom,729190000.0,2.76,21.58,0.0506,894695.0 59 | 57,ETRN,Equitrans Midstream Corporation,Energy,Oil & Gas Midstream,USA,4380000000.0,,10.33,0.0218,11178201.0 60 | 58,LYV,Live Nation Entertainment Inc.,Communication Services,Entertainment,USA,22280000000.0,133.93,89.33,-0.0784,12539760.0 61 | 59,QD,Qudian Inc.,Financial,Credit Services,China,503810000.0,18.0,2.43,0.0848,1738928.0 62 | 60,ANF,Abercrombie & Fitch Co.,Consumer Cyclical,Apparel Retail,USA,1950000000.0,65.22,39.98,0.0262,1864064.0 63 | 61,SAIA,Saia Inc.,Industrials,Trucking,USA,10900000000.0,32.09,425.89,0.036699999999999997,1068009.0 64 | 62,AMAT,Applied Materials Inc.,Technology,Semiconductor Equipment & Materials,USA,122720000000.0,20.04,151.93,0.039599999999999996,7765920.0 65 | 63,ORI,Old Republic International Corporation,Financial,Insurance - Diversified,USA,7880000000.0,10.45,27.6,0.026000000000000002,3089783.0 66 | 64,HELE,Helen of Troy Limited,Consumer Defensive,Household & Personal Products,USA,3300000000.0,24.17,141.74,0.0339,542664.0 67 | 65,MSGS,Madison Square Garden Sports Corp.,Communication Services,Entertainment,USA,4540000000.0,62.46,212.24,0.1165,1340804.0 68 | 66,MDXG,MiMedx Group Inc.,Healthcare,Biotechnology,USA,878610000.0,,7.97,0.0487,883726.0 69 | 67,CSWI,CSW Industrials Inc.,Industrials,Specialty Industrial Machinery,USA,2750000000.0,28.71,179.88,0.0171,83897.0 70 | 68,CMT,Core Molding Technologies Inc.,Basic Materials,Specialty Chemicals,USA,197230000.0,14.77,24.17,0.0316,68649.0 71 | 69,EFXT,Enerflex Ltd.,Energy,Oil & Gas Equipment & Services,Canada,1010000000.0,,8.19,0.0393,49461.0 72 | 70,NNBR,NN Inc.,Industrials,Conglomerates,USA,135610000.0,,3.04,0.0236,354344.0 73 | 71,MAR,Marriott International Inc.,Consumer Cyclical,Lodging,USA,59660000000.0,23.37,199.61,0.0197,1484419.0 74 | 72,FIX,Comfort Systems USA Inc.,Industrials,Engineering & Construction,USA,5920000000.0,25.63,173.78,0.0508,196157.0 75 | 73,SKX,Skechers U.S.A. Inc.,Consumer Cyclical,Footwear & Accessories,USA,7910000000.0,18.51,56.11,0.0978,6143265.0 76 | 74,KEX,Kirby Corporation,Industrials,Marine Shipping,USA,4820000000.0,33.56,81.01,0.008100000000000001,432699.0 77 | 75,LZ,LegalZoom.com Inc.,Industrials,Specialty Business Services,USA,2790000000.0,,14.87,0.0199,909958.0 78 | 76,LPG,Dorian LPG Ltd.,Energy,Oil & Gas Midstream,USA,1140000000.0,6.81,29.19,0.0336,875289.0 79 | 77,COHU,Cohu Inc.,Technology,Semiconductor Equipment & Materials,USA,1970000000.0,22.67,42.5,0.0256,220600.0 80 | 78,CARS,Cars.com Inc.,Consumer Cyclical,Auto & Truck Dealerships,USA,1480000000.0,63.38,22.5,0.0126,278496.0 81 | 79,XPO,XPO Inc.,Industrials,Trucking,USA,8180000000.0,49.14,71.6,0.0146,1797123.0 82 | 80,JBHT,J.B. Hunt Transport Services Inc.,Industrials,Integrated Freight & Logistics,USA,20680000000.0,25.09,205.54,0.0271,907954.0 83 | 81,LRCX,Lam Research Corporation,Technology,Semiconductor Equipment & Materials,USA,94300000000.0,21.76,721.26,0.0275,1844458.0 84 | 82,YALA,Yalla Group Limited,Technology,Software - Application,United Arab Emirates,846770000.0,12.05,5.59,0.031400000000000004,258669.0 85 | 83,IX,ORIX Corporation,Financial,Credit Services,Japan,23510000000.0,11.66,96.62,0.011899999999999999,14126.0 86 | 84,MFIN,Medallion Financial Corp.,Financial,Credit Services,USA,214130000.0,4.29,9.4,0.025099999999999997,196824.0 87 | 85,IGIC,International General Insurance Holdings Ltd.,Financial,Insurance - Diversified,Bermuda,419680000.0,5.39,9.37,0.041100000000000005,597190.0 88 | 86,EEX,Emerald Holding Inc.,Communication Services,Advertising Agencies,USA,287920000.0,12.27,4.71,0.028399999999999998,192090.0 89 | 87,MNSO,MINISO Group Holding Limited,Consumer Cyclical,Specialty Retail,China,6660000000.0,32.97,21.0,0.0304,1537850.0 90 | 88,ODFL,Old Dominion Freight Line Inc.,Industrials,Trucking,USA,45190000000.0,37.16,427.58,0.0375,1036610.0 91 | 89,INTU,Intuit Inc.,Technology,Software - Application,USA,139060000000.0,64.68,511.84,0.0308,1855651.0 92 | 90,WLK,Westlake Corporation,Basic Materials,Specialty Chemicals,USA,17210000000.0,9.43,137.08,0.0168,470964.0 93 | 91,TDW,Tidewater Inc.,Energy,Oil & Gas Equipment & Services,USA,2970000000.0,,61.96,0.059500000000000004,1188337.0 94 | 92,SNDR,Schneider National Inc.,Industrials,Trucking,USA,5420000000.0,12.01,31.2,0.027000000000000003,419338.0 95 | 93,NOG,Northern Oil and Gas Inc.,Energy,Oil & Gas E&P,USA,3580000000.0,2.63,39.57,0.027000000000000003,973584.0 96 | 94,CSTM,Constellium SE,Basic Materials,Aluminum,France,2780000000.0,12.17,18.99,0.0037,897262.0 97 | 95,ANDE,The Andersons Inc.,Consumer Defensive,Food Distribution,USA,1660000000.0,17.4,49.55,0.0063,128819.0 98 | 96,ITW,Illinois Tool Works Inc.,Industrials,Specialty Industrial Machinery,USA,77680000000.0,26.02,260.01,0.0172,1294696.0 99 | 97,ABNB,Airbnb Inc.,Consumer Cyclical,Travel Services,USA,94960000000.0,51.09,153.33,0.0325,4142951.0 100 | 98,SAIC,Science Applications International Corporation,Technology,Information Technology Services,USA,6330000000.0,20.52,120.6,0.0231,251959.0 101 | 99,RDY,Dr. Reddy's Laboratories Limited,Healthcare,Drug Manufacturers - Specialty & Generic,India,11170000000.0,19.93,68.81,0.0128,174188.0 102 | 100,FDUS,Fidus Investment Corporation,Financial,Asset Management,USA,520760000.0,13.03,20.84,0.0171,87683.0 103 | 101,FMX,Fomento Economico Mexicano S.A.B. de C.V.,Consumer Defensive,Beverages - Brewers,Mexico,36580000000.0,23.81,113.78,0.0276,592455.0 104 | 102,PNR,Pentair plc,Industrials,Specialty Industrial Machinery,United Kingdom,11350000000.0,23.34,69.69,0.0128,2021546.0 105 | 103,AMNB,American National Bankshares Inc.,Financial,Banks - Regional,USA,433190000.0,12.69,40.11,-0.0167,61503.0 106 | 104,RCL,Royal Caribbean Cruises Ltd.,Consumer Cyclical,Travel Services,USA,28050000000.0,,108.57,-0.0101,4043246.0 107 | 105,CGBD,Carlyle Secured Lending Inc.,Financial,Asset Management,USA,781690000.0,11.27,15.83,0.0286,245045.0 108 | 106,IZM,ICZOOM Group Inc.,Technology,Electronics & Computer Distribution,China,78470000.0,31.33,7.8,0.0263,56147.0 109 | 107,BCC,Boise Cascade Company,Basic Materials,Building Materials,USA,3930000000.0,6.22,101.93,0.0279,335745.0 110 | 108,CRC,California Resources Corporation,Energy,Oil & Gas E&P,USA,3630000000.0,3.98,52.58,0.022000000000000002,479111.0 111 | 109,JOE,The St. Joe Company,Real Estate,Real Estate - Diversified,USA,3770000000.0,44.0,64.51,-0.0008,501628.0 112 | 110,UBER,Uber Technologies Inc.,Technology,Software - Application,USA,94330000000.0,,48.14,0.032799999999999996,17068729.0 113 | 111,MTG,MGIC Investment Corporation,Financial,Insurance - Specialty,USA,4840000000.0,6.07,16.87,0.0,1828410.0 114 | 112,JHX,James Hardie Industries plc,Basic Materials,Building Materials,Ireland,12770000000.0,25.38,29.16,0.015700000000000002,38842.0 115 | 113,AROC,Archrock Inc.,Energy,Oil & Gas Equipment & Services,USA,1740000000.0,30.8,11.52,0.0388,736859.0 116 | 114,LCAA,L Catterton Asia Acquisition Corp,Financial,Shell Companies,Singapore,304180000.0,170.0,10.54,0.0029,1341887.0 117 | 115,GEF,Greif Inc.,Consumer Cyclical,Packaging & Containers,USA,3560000000.0,8.03,73.87,0.0003,104227.0 118 | 116,BBVA,Banco Bilbao Vizcaya Argentaria S.A.,Financial,Banks - Diversified,Spain,47260000000.0,7.06,8.05,0.0255,973119.0 119 | 117,WDAY,Workday Inc.,Technology,Software - Application,USA,59840000000.0,,235.0,0.0226,1591303.0 120 | 118,BA,The Boeing Company,Industrials,Aerospace & Defense,USA,141000000000.0,,238.69,0.021099999999999997,6977403.0 121 | 119,BKNG,Booking Holdings Inc.,Consumer Cyclical,Travel Services,USA,109160000000.0,29.54,3012.25,0.0191,187603.0 122 | 120,MDC,M.D.C. Holdings Inc.,Consumer Cyclical,Residential Construction,USA,3560000000.0,9.47,51.06,0.0495,1104614.0 123 | 121,VECO,Veeco Instruments Inc.,Technology,Semiconductor Equipment & Materials,USA,1390000000.0,11.2,27.53,0.0204,268724.0 124 | 122,CP,Canadian Pacific Kansas City Limited,Industrials,Railroads,Canada,102360000000.0,18.09,83.05,-0.0004,3238889.0 125 | 123,RXST,RxSight Inc.,Healthcare,Medical Devices,USA,1100000000.0,,33.34,0.030299999999999997,346383.0 126 | 124,CBAY,CymaBay Therapeutics Inc.,Healthcare,Biotechnology,USA,1140000000.0,,12.28,0.0478,1314609.0 127 | 125,UBS,UBS Group AG,Financial,Banks - Diversified,Switzerland,76160000000.0,11.37,22.26,0.024900000000000002,2956890.0 128 | 126,CR,Crane Company,Industrials,Specialty Industrial Machinery,USA,5310000000.0,120.68,94.25,0.0076,214884.0 129 | 127,AJG,Arthur J. Gallagher & Co.,Financial,Insurance Brokers,USA,46510000000.0,40.14,216.22,-0.0043,1041802.0 130 | 128,SBS,Companhia de Saneamento Basico do Estado de Sao Paulo,Utilities,Utilities - Regulated Water,Brazil,8140000000.000001,13.4,11.95,0.0084,1088104.0 131 | 129,CVLT,Commvault Systems Inc.,Technology,Software - Application,USA,3400000000.0,,77.89,0.006999999999999999,267285.0 132 | 130,CMCO,Columbus McKinnon Corporation,Industrials,Farm & Heavy Construction Machinery,USA,1200000000.0,25.1,42.2,0.0127,85788.0 133 | 131,WTT,Wireless Telecom Group Inc.,Technology,Communication Equipment,USA,45410000.0,,2.11,-0.004699999999999999,87718.0 134 | 132,ALSN,Allison Transmission Holdings Inc.,Consumer Cyclical,Auto Parts,USA,5250000000.0,8.74,58.84,0.021400000000000002,1466888.0 135 | 133,DXCM,DexCom Inc.,Healthcare,Medical Devices,USA,50140000000.0,191.86,132.38,0.0233,4990801.0 136 | 134,H,Hyatt Hotels Corporation,Consumer Cyclical,Lodging,USA,12930000000.0,23.59,125.18,0.0235,582645.0 137 | 135,MLTX,MoonLake Immunotherapeutics,Healthcare,Biotechnology,Switzerland,3730000000.0,,61.26,0.0173,671035.0 138 | 136,WFRD,Weatherford International plc,Energy,Oil & Gas Equipment & Services,USA,5690000000.0,24.1,83.04,0.051100000000000007,848108.0 139 | 137,AGM,Federal Agricultural Mortgage Corporation,Financial,Credit Services,USA,1680000000.0,11.89,160.06,0.009300000000000001,69872.0 140 | 138,DKNG,DraftKings Inc.,Consumer Cyclical,Gambling,USA,14300000000.0,,32.38,0.045899999999999996,9163385.0 141 | 139,ESCA,Escalade Incorporated,Consumer Cyclical,Leisure,USA,203140000.0,19.14,14.51,-0.0189,64087.0 142 | 140,GOOGL,Alphabet Inc.,Communication Services,Internet Content & Information,USA,1665480000000.0,29.03,132.58,0.0246,36387812.0 143 | 141,AEIS,Advanced Energy Industries Inc.,Industrials,Electrical Equipment & Parts,USA,4510000000.0,23.21,121.16,0.0078000000000000005,152127.0 144 | 142,HAE,Haemonetics Corporation,Healthcare,Medical Instruments & Supplies,USA,4720000000.0,41.31,93.03,-0.0049,250593.0 145 | 143,MGM,MGM Resorts International,Consumer Cyclical,Resorts & Casinos,USA,18220000000.0,10.87,50.9,0.016200000000000003,2912673.0 146 | 144,GOOG,Alphabet Inc.,Communication Services,Internet Content & Information,USA,1674200000000.0,30.3,133.01,0.0242,26835983.0 147 | 145,GPP,Green Plains Partners LP,Energy,Oil & Gas Midstream,USA,336110000.0,8.37,14.5,0.0028000000000000004,42863.0 148 | 146,SMCI,Super Micro Computer Inc.,Technology,Computer Hardware,USA,17180000000.0,31.52,334.5,0.0219,2043095.0 149 | 147,CNM,Core & Main Inc.,Industrials,Industrial Distribution,USA,7310000000.0,15.21,32.33,0.022099999999999998,604511.0 150 | 148,IHG,InterContinental Hotels Group PLC,Consumer Cyclical,Lodging,United Kingdom,12410000000.0,36.53,75.11,0.0178,143994.0 151 | 149,PARR,Par Pacific Holdings Inc.,Energy,Oil & Gas Refining & Marketing,USA,1860000000.0,2.53,31.07,0.0174,359046.0 152 | 150,NECB,Northeast Community Bancorp Inc.,Financial,Banks - Regional,USA,245750000.0,6.35,16.2,-0.0049,59151.0 153 | 151,EXPD,Expeditors International of Washington Inc.,Industrials,Integrated Freight & Logistics,USA,19270000000.0,16.65,127.24,0.009000000000000001,850839.0 154 | 152,NEU,NewMarket Corporation,Basic Materials,Specialty Chemicals,USA,4290000000.0,16.11,448.76,0.0036,34503.0 155 | 153,MHO,M/I Homes Inc.,Consumer Cyclical,Residential Construction,USA,2710000000.0,5.83,99.53,0.0239,606475.0 156 | 154,NXT,Nextracker Inc.,Technology,Solar,USA,6370000000.0,38.36,42.73,-0.033,2254528.0 157 | 155,FOR,Forestar Group Inc.,Real Estate,Real Estate - Development,USA,1450000000.0,10.31,29.98,0.0302,362786.0 158 | 156,META,Meta Platforms Inc.,Communication Services,Internet Content & Information,USA,798830000000.0,36.78,325.48,0.044199999999999996,39045933.0 159 | 157,CPLP,Capital Product Partners L.P.,Industrials,Marine Shipping,Greece,307940000.0,2.84,15.42,0.0239,120206.0 160 | 158,PUCK,Goal Acquisitions Corp.,Financial,Shell Companies,USA,175730000.0,,10.46,0.0037,626366.0 161 | 159,ITT,ITT Inc.,Industrials,Specialty Industrial Machinery,USA,8010000000.0,20.89,98.92,0.0182,597820.0 162 | 160,BTWN,Bridgetown Holdings Limited,Financial,Shell Companies,Hong Kong,309270000.0,98.48,10.34,0.0019,25787.0 163 | 161,ROKU,Roku Inc.,Communication Services,Entertainment,USA,9600000000.0,,89.61,0.3141,60947440.0 164 | 162,ERO,Ero Copper Corp.,Basic Materials,Copper,Canada,2780000000.0,22.3,23.24,0.0256,136372.0 165 | 163,WMT,Walmart Inc.,Consumer Defensive,Discount Stores,USA,428590000000.0,38.54,159.91,0.004699999999999999,3651507.0 166 | 164,BOOT,Boot Barn Holdings Inc.,Consumer Cyclical,Apparel Retail,USA,2830000000.0,16.87,94.76,0.0028000000000000004,465657.0 167 | 165,SIGI,Selective Insurance Group Inc.,Financial,Insurance - Property & Casualty,USA,6250000000.0,25.25,104.42,0.0104,350901.0 168 | 166,NX,Quanex Building Products Corporation,Industrials,Building Products & Equipment,USA,908580000.0,31.26,27.76,0.0069,47277.0 169 | 167,KRT,Karat Packaging Inc.,Consumer Cyclical,Packaging & Containers,USA,382370000.0,14.87,19.38,0.0078000000000000005,13737.0 170 | 168,HYAC-U,Haymaker Acquisition Corp. 4,Financial,Shell Companies,USA,,,10.13,0.0,396757.0 171 | 169,GODN,Golden Star Acquisition Corporation,Financial,Shell Companies,USA,91100000.0,,10.19,-0.001,73158.0 172 | 170,MCAC,Monterey Capital Acquisition Corporation,Financial,Shell Companies,USA,220600000.0,,10.49,0.0019,51004.0 173 | 171,AAC,Ares Acquisition Corporation,Financial,Shell Companies,USA,762410000.0,,10.62,0.0028000000000000004,230895.0 174 | 172,HLT,Hilton Worldwide Holdings Inc.,Consumer Cyclical,Lodging,USA,39920000000.0,32.23,154.17,0.01,2309496.0 175 | 173,THR,Thermon Group Holdings Inc.,Industrials,Specialty Industrial Machinery,USA,947110000.0,27.81,27.75,-0.0132,166776.0 176 | 174,LAUR,Laureate Education Inc.,Consumer Defensive,Education & Training Services,USA,1970000000.0,26.82,12.74,0.0151,707497.0 177 | 175,ACLS,Axcelis Technologies Inc.,Technology,Semiconductor Equipment & Materials,USA,5980000000.0,34.02,192.9,0.0555,759990.0 178 | 176,ON,ON Semiconductor Corporation,Technology,Semiconductors,USA,44100000000.0,25.64,105.09,0.0292,7424474.0 179 | 177,JELD,JELD-WEN Holding Inc.,Industrials,Building Products & Equipment,USA,1500000000.0,25.16,17.74,0.0074,321471.0 180 | 178,NDSN,Nordson Corporation,Industrials,Specialty Industrial Machinery,USA,14140000000.0,28.1,250.26,0.0087,180595.0 181 | -------------------------------------------------------------------------------- /QS003-stock-screener-openbb-sdk/01_stock_screener_openbb_sdk.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 003: Make a Stock Screener with Python 3 | # Copyright: Quant Science, LLC 4 | 5 | # Requires openbb==^3.2.4 6 | 7 | from openbb_terminal.sdk import openbb 8 | import pandas as pd 9 | 10 | # 1.0 RUN A STOCK SCREENER ---- 11 | 12 | # Stock screening with FinViz 13 | new_highs = openbb.stocks.screener.screener_data("new_high") 14 | new_highs 15 | 16 | # Filter Price > $15.00 and Country == USA 17 | portfolio_data = new_highs[ 18 | (new_highs.Price > 15) & 19 | (new_highs.Country == "USA") 20 | ] 21 | 22 | # Save screener portfolio 23 | portfolio_data.to_csv("QS003-openbb-sdk/new_highs.csv") 24 | 25 | # Load screener portfolio 26 | portfolio_data = pd.read_csv("QS003-openbb-sdk/new_highs.csv") 27 | 28 | portfolio_data 29 | 30 | # Get the tickers as a list 31 | tickers = portfolio_data.Ticker.tolist() 32 | 33 | # 2.0 GET STOCK DATA FOR SCREENED TICKERS---- 34 | 35 | # Get Stock Data for each Ticker 36 | # NOTE: Takes a minute to run... 37 | stock_data = openbb.economy.index( 38 | tickers, 39 | start_date="2016-01-01", 40 | end_date="2019-12-30" 41 | ) 42 | 43 | # Save 44 | stock_data.to_csv("QS003-openbb-sdk/stock_data.csv") 45 | 46 | # Load 47 | stock_data = pd.read_csv( 48 | "QS003-openbb-sdk/stock_data.csv", 49 | index_col="Date" 50 | ) 51 | 52 | stock_data 53 | 54 | # Visualize a sample of the stocks 55 | stock_data['DELL'].plot(title="Dell") 56 | 57 | stock_data[['DELL','GOOG', 'V']].plot( 58 | subplots=True 59 | ) 60 | 61 | 62 | # WANT MORE ALGORITHMIC TRADING HELP? 63 | # Register for our Course Waitlist: 64 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 65 | 66 | -------------------------------------------------------------------------------- /QS003-stock-screener-openbb-sdk/new_highs.csv: -------------------------------------------------------------------------------- 1 | ,Ticker,Company,Sector,Industry,Country,Market Cap,P/E,Price,Change,Volume 2 | 0,DELL,Dell Technologies Inc.,Technology,Computer Hardware,USA,50360000000.0,25.97,68.39,0.21600000000000003,22886546.0 3 | 3,NTNX,Nutanix Inc.,Technology,Software - Infrastructure,USA,8390000000.000001,,35.01,0.1259,7319725.0 4 | 5,CEIX,CONSOL Energy Inc.,Energy,Thermal Coal,USA,2870000000.0,4.28,90.56,0.0526,356802.0 5 | 7,CDLX,Cardlytics Inc.,Communication Services,Advertising Agencies,USA,655000000.0,,17.16,0.03,489458.0 6 | 8,GPOR,Gulfport Energy Corporation,Energy,Oil & Gas E&P,USA,2280000000.0,2.04,123.51,0.0467,115183.0 7 | 10,OII,Oceaneering International Inc.,Energy,Oil & Gas Equipment & Services,USA,2480000000.0,38.19,24.21,0.0623,854532.0 8 | 11,AMR,Alpha Metallurgical Resources Inc.,Basic Materials,Coking Coal,USA,2860000000.0,3.74,212.43,0.0473,245186.0 9 | 12,ACMR,ACM Research Inc.,Technology,Semiconductor Equipment & Materials,USA,1090000000.0,17.86,18.33,0.0437,685455.0 10 | 13,RCMT,RCM Technologies Inc.,Industrials,Conglomerates,USA,167610000.0,12.55,21.19,0.0316,31345.0 11 | 14,RVMD,Revolution Medicines Inc.,Healthcare,Biotechnology,USA,3680000000.0,,35.15,0.0347,357677.0 12 | 16,VEEV,Veeva Systems Inc.,Healthcare,Health Information Services,USA,34159999999.999996,60.19,216.37,0.0368,926546.0 13 | 17,ESTE,Earthstone Energy Inc.,Energy,Oil & Gas E&P,USA,2900000000.0,4.92,20.98,0.0292,815222.0 14 | 19,CIVI,Civitas Resources Inc.,Energy,Oil & Gas E&P,USA,7770000000.0,6.89,83.98,0.021400000000000002,353218.0 15 | 21,HOV,Hovnanian Enterprises Inc.,Consumer Cyclical,Residential Construction,USA,705980000.0,4.95,122.78,0.0332,54255.0 16 | 22,AMWD,American Woodmark Corporation,Consumer Cyclical,"Furnishings, Fixtures & Appliances",USA,1320000000.0,14.28,80.22,0.032799999999999996,41259.0 17 | 23,CAL,Caleres Inc.,Consumer Cyclical,Apparel Retail,USA,1080000000.0,6.5,29.76,0.038,340738.0 18 | 24,APPF,AppFolio Inc.,Technology,Software - Application,USA,6880000000.0,,194.43,0.0086,111526.0 19 | 25,PRDO,Perdoceo Education Corporation,Consumer Defensive,Education & Training Services,USA,1110000000.0,9.31,17.26,0.0416,200830.0 20 | 26,NX,Quanex Building Products Corporation,Industrials,Building Products & Equipment,USA,954300000.0,32.4,28.77,0.0663,112328.0 21 | 27,TDW,Tidewater Inc.,Energy,Oil & Gas Equipment & Services,USA,3610000000.0,70.43,67.82,0.0429,584734.0 22 | 29,WFRD,Weatherford International plc,Energy,Oil & Gas Equipment & Services,USA,6420000000.0,26.11,89.93,0.016,597982.0 23 | 30,ASTE,Astec Industries Inc.,Industrials,Farm & Heavy Construction Machinery,USA,1270000000.0,50.84,55.62,0.0146,36319.0 24 | 31,SBOW,SilverBow Resources Inc.,Energy,Oil & Gas E&P,USA,962700000.0,2.23,42.92,0.0033,176791.0 25 | 32,UFPI,UFP Industries Inc.,Basic Materials,Lumber & Wood Production,USA,6640000000.0,11.81,107.48,0.03,97440.0 26 | 33,VRT,Vertiv Holdings Co,Industrials,Electrical Equipment & Parts,USA,14250000000.0,84.26,40.11,0.0183,2208088.0 27 | 36,YETI,YETI Holdings Inc.,Consumer Cyclical,Leisure,USA,4360000000.0,66.87,50.89,0.018799999999999997,813437.0 28 | 37,CSPI,CSP Inc.,Technology,Information Technology Services,USA,77790000.0,21.87,16.14,0.041299999999999996,29638.0 29 | 38,GIC,Global Industrial Company,Industrials,Industrial Distribution,USA,1300000000.0,19.06,34.24,0.0118,26520.0 30 | 39,KE,Kimball Electronics Inc.,Industrials,Electrical Equipment & Parts,USA,767780000.0,12.29,31.16,0.0321,57252.0 31 | 40,BKR,Baker Hughes Company,Energy,Oil & Gas Equipment & Services,USA,37080000000.0,32.5,36.89,0.019299999999999998,4965042.0 32 | 41,NE,Noble Corporation Plc,Energy,Oil & Gas Drilling,USA,7340000000.0,21.37,53.85,0.021,1058599.0 33 | 42,MOD,Modine Manufacturing Company,Consumer Cyclical,Auto Parts,USA,2540000000.0,14.15,49.01,0.0298,476924.0 34 | 44,MOG-A,Moog Inc.,Industrials,Aerospace & Defense,USA,3800000000.0,23.8,119.57,0.0294,109774.0 35 | 47,IIIN,Insteel Industries Inc.,Industrials,Metal Fabrication,USA,679520000.0,13.63,35.54,0.0227,49338.0 36 | 48,BRZE,Braze Inc.,Technology,Software - Application,USA,4540000000.0,,47.24,0.0212,478466.0 37 | 49,OC,Owens Corning,Industrials,Building Products & Equipment,USA,13320000000.0,10.42,146.81,0.0202,191444.0 38 | 52,ANIP,ANI Pharmaceuticals Inc.,Healthcare,Drug Manufacturers - Specialty & Generic,USA,1310000000.0,,65.04,0.0101,107229.0 39 | 53,WDC,Western Digital Corporation,Technology,Computer Hardware,USA,15040000000.0,,46.27,0.0282,2009285.0 40 | 54,CHRD,Chord Energy Corporation,Energy,Oil & Gas E&P,USA,6810000000.0,3.9,165.24,0.0232,578821.0 41 | 55,AEO,American Eagle Outfitters Inc.,Consumer Cyclical,Apparel Retail,USA,3420000000.0,31.5,17.26,0.0177,2329050.0 42 | 56,ROCK,Gibraltar Industries Inc.,Industrials,Building Products & Equipment,USA,2320000000.0,26.7,76.56,0.0204,78821.0 43 | 57,ADBE,Adobe Inc.,Technology,Software - Infrastructure,USA,249330000000.0,53.52,560.5,0.0021,1415957.0 44 | 58,FTAI,FTAI Aviation Ltd.,Industrials,Rental & Leasing Services,USA,3730000000.0,46.35,37.36,0.0108,484209.0 45 | 60,COUR,Coursera Inc.,Consumer Defensive,Education & Training Services,USA,2550000000.0,,17.47,0.0046,575257.0 46 | 61,APP,AppLovin Corporation,Technology,Software - Application,USA,14810000000.0,797.71,43.08,-0.0033,797979.0 47 | 62,KEX,Kirby Corporation,Industrials,Marine Shipping,USA,4930000000.0,29.03,84.12,0.015600000000000001,71986.0 48 | 64,CSCO,Cisco Systems Inc.,Technology,Communication Equipment,USA,233570000000.0,18.82,57.79,0.0078000000000000005,8480242.0 49 | 66,TPX,Tempur Sealy International Inc.,Consumer Cyclical,"Furnishings, Fixtures & Appliances",USA,8150000000.0,20.24,47.23,0.0109,436418.0 50 | 68,ODFL,Old Dominion Freight Line Inc.,Industrials,Trucking,USA,47660000000.0,37.94,436.51,0.021400000000000002,253146.0 51 | 69,TIPT,Tiptree Inc.,Financial,Insurance - Specialty,USA,656160000.0,33.35,17.84,0.0125,39363.0 52 | 71,SPLK,Splunk Inc.,Technology,Software - Infrastructure,USA,19730000000.0,,122.4,0.009399999999999999,1281622.0 53 | 72,EVR,Evercore Inc.,Financial,Capital Markets,USA,5350000000.0,16.75,142.06,0.0144,117291.0 54 | 73,TOL,Toll Brothers Inc.,Consumer Cyclical,Residential Construction,USA,9280000000.0,6.04,84.14,0.027000000000000003,1283992.0 55 | 74,APOG,Apogee Enterprises Inc.,Industrials,Building Products & Equipment,USA,1140000000.0,10.93,51.47,0.02,22228.0 56 | 75,WK,Workiva Inc.,Technology,Software - Application,USA,5750000000.0,,111.2,-0.0058,231087.0 57 | 77,ANET,Arista Networks Inc.,Technology,Computer Hardware,USA,60120000000.0,36.2,196.07,0.0043,1039760.0 58 | 79,WDAY,Workday Inc.,Technology,Software - Application,USA,61210000000.0,,247.72,0.0132,498450.0 59 | 81,ITT,ITT Inc.,Industrials,Specialty Industrial Machinery,USA,8380000000.000001,20.15,103.47,0.0116,116201.0 60 | 82,CELH,Celsius Holdings Inc.,Consumer Defensive,Beverages - Non-Alcoholic,USA,14550000000.0,,193.86,-0.0111,468874.0 61 | 83,RELY,Remitly Global Inc.,Technology,Software - Infrastructure,USA,4480000000.0,,25.16,0.0004,723156.0 62 | 84,ALSN,Allison Transmission Holdings Inc.,Consumer Cyclical,Auto Parts,USA,5440000000.0,9.03,60.86,0.0068000000000000005,260727.0 63 | 85,DLR,Digital Realty Trust Inc.,Real Estate,REIT - Specialty,USA,40230000000.0,100.18,131.33,-0.003,555662.0 64 | 87,CRS,Carpenter Technology Corporation,Industrials,Metal Fabrication,USA,3080000000.0,56.96,64.25,0.0259,181575.0 65 | 88,VST,Vistra Corp.,Utilities,Utilities - Independent Power Producers,USA,11690000000.0,8.71,31.92,0.0159,1619051.0 66 | 91,HWKN,Hawkins Inc.,Basic Materials,Specialty Chemicals,USA,1300000000.0,20.62,62.61,0.0068000000000000005,25699.0 67 | 92,CRC,California Resources Corporation,Energy,Oil & Gas E&P,USA,3850000000.0,4.68,56.79,0.017,144073.0 68 | 93,BRBR,BellRing Brands Inc.,Consumer Defensive,Packaged Foods,USA,5350000000.0,36.4,41.35,-0.0036,341895.0 69 | 94,ANF,Abercrombie & Fitch Co.,Consumer Cyclical,Apparel Retail,USA,2730000000.0,26.44,54.21,0.0086,610612.0 70 | 95,NVR,NVR Inc.,Consumer Cyclical,Residential Construction,USA,21110000000.0,13.82,6476.62,0.015600000000000001,11336.0 71 | 98,AMAT,Applied Materials Inc.,Technology,Semiconductor Equipment & Materials,USA,127610000000.0,20.29,153.93,0.0077,1514887.0 72 | 99,KNF,Knife River Corporation,Basic Materials,Building Materials,USA,2940000000.0,22.25,52.38,0.018000000000000002,241487.0 73 | 100,AVT,Avnet Inc.,Technology,Electronics & Computer Distribution,USA,4660000000.0,6.25,51.59,0.0166,192486.0 74 | 102,IRM,Iron Mountain Incorporated,Real Estate,REIT - Specialty,USA,18250000000.0,48.76,63.19,-0.0055000000000000005,278090.0 75 | 103,ARES,Ares Management Corporation,Financial,Asset Management,USA,31440000000.0,63.37,103.54,0.001,269000.0 76 | 104,CHX,ChampionX Corporation,Energy,Oil & Gas Equipment & Services,USA,7270000000.0,29.95,36.96,0.0241,638453.0 77 | 105,IDYA,IDEAYA Biosciences Inc.,Healthcare,Biotechnology,USA,1710000000.0,,29.99,0.0213,201295.0 78 | 107,TRGP,Targa Resources Corp.,Energy,Oil & Gas Midstream,USA,19480000000.0,23.64,87.48,0.0143,530967.0 79 | 109,IOT,Samsara Inc.,Technology,Software - Infrastructure,USA,16090000000.0,,30.73,0.1232,7039220.0 80 | 110,ALKT,Alkami Technology Inc.,Technology,Software - Application,USA,1720000000.0,,18.04,0.0356,184262.0 81 | 111,KRT,Karat Packaging Inc.,Consumer Cyclical,Packaging & Containers,USA,516169999.99999994,17.31,26.15,0.0501,62485.0 82 | 112,FIX,Comfort Systems USA Inc.,Industrials,Engineering & Construction,USA,6580000000.0,27.48,186.29,0.009300000000000001,64583.0 83 | 113,EXP,Eagle Materials Inc.,Basic Materials,Building Materials,USA,6810000000.0,14.88,195.1,0.030600000000000002,155618.0 84 | 114,SLB,Schlumberger Limited,Energy,Oil & Gas Equipment & Services,USA,85800000000.0,21.98,60.02,0.018000000000000002,3733009.0 85 | 115,CE,Celanese Corporation,Basic Materials,Chemicals,USA,13920000000.0,11.04,128.54,0.0173,396415.0 86 | 116,PAGP,Plains GP Holdings L.P.,Energy,Oil & Gas Midstream,USA,3170000000.0,13.58,16.23,0.0115,497548.0 87 | 118,KNSL,Kinsale Capital Group Inc.,Financial,Insurance - Property & Casualty,USA,9170000000.0,40.97,403.32,0.0118,26777.0 88 | 119,NOG,Northern Oil and Gas Inc.,Energy,Oil & Gas E&P,USA,3920000000.0,3.03,42.94,0.0265,663763.0 89 | 120,SGEN,Seagen Inc.,Healthcare,Biotechnology,USA,38720000000.0,,208.44,0.0115,1352341.0 90 | 121,CNX,CNX Resources Corporation,Energy,Oil & Gas E&P,USA,3630000000.0,2.18,22.74,0.0172,685352.0 91 | 124,NTRA,Natera Inc.,Healthcare,Diagnostics & Research,USA,6660000000.0,,60.11,0.023399999999999997,492453.0 92 | 125,FSLY,Fastly Inc.,Technology,Software - Application,USA,2880000000.0,,24.05,0.0108,1205358.0 93 | 127,EXEL,Exelixis Inc.,Healthcare,Biotechnology,USA,7160000000.0,45.2,22.74,0.0154,619175.0 94 | 129,APO,Apollo Global Management Inc.,Financial,Asset Management,USA,48370000000.0,40.27,85.97,-0.015600000000000001,1640206.0 95 | 130,EME,EMCOR Group Inc.,Industrials,Engineering & Construction,USA,10590000000.0,22.52,226.98,0.012199999999999999,111002.0 96 | 131,MEDP,Medpace Holdings Inc.,Healthcare,Diagnostics & Research,USA,8150000000.0,32.75,273.89,0.0134,53015.0 97 | 132,INTU,Intuit Inc.,Technology,Software - Application,USA,146780000000.0,68.96,545.7,0.0072,616435.0 98 | 133,VCTR,Victory Capital Holdings Inc.,Financial,Asset Management,USA,2250000000.0,10.45,34.33,-0.0026,179533.0 99 | 134,MMP,Magellan Midstream Partners L.P.,Energy,Oil & Gas Midstream,USA,13440000000.0,13.3,66.61,0.0029,426090.0 100 | 135,VRSK,Verisk Analytics Inc.,Industrials,Consulting Services,USA,34810000000.0,50.28,242.6,0.0016,351744.0 101 | 136,HNI,HNI Corporation,Industrials,Business Equipment & Supplies,USA,1530000000.0,20.24,33.22,0.013999999999999999,71059.0 102 | 138,FLS,Flowserve Corporation,Industrials,Specialty Industrial Machinery,USA,5260000000.0,22.27,40.22,0.0163,353427.0 103 | 139,AIT,Applied Industrial Technologies Inc.,Industrials,Industrial Distribution,USA,6020000000.0,17.73,156.73,0.015300000000000001,85130.0 104 | 141,WOR,Worthington Industries Inc.,Industrials,Metal Fabrication,USA,3800000000.0,14.85,77.09,0.0242,44381.0 105 | 142,FLT,FLEETCOR Technologies Inc.,Technology,Software - Infrastructure,USA,19960000000.0,22.03,273.23,0.0055000000000000005,169211.0 106 | 143,LLY,Eli Lilly and Company,Healthcare,Drug Manufacturers - General,USA,522549999999.99994,77.26,556.14,0.0034999999999999996,767960.0 107 | 144,BCC,Boise Cascade Company,Basic Materials,Building Materials,USA,4460000000.0,7.68,111.89,0.023,91990.0 108 | 145,V,Visa Inc.,Financial,Credit Services,USA,498120000000.0,31.45,247.9,0.009000000000000001,2075060.0 109 | 147,AZEK,The AZEK Company Inc.,Industrials,Building Products & Equipment,USA,5180000000.0,255.02,34.68,0.019799999999999998,833616.0 110 | 149,STRL,Sterling Infrastructure Inc.,Industrials,Engineering & Construction,USA,2550000000.0,23.29,83.46,0.0085,160580.0 111 | 151,SKYW,SkyWest Inc.,Industrials,Airlines,USA,1870000000.0,,44.43,-0.0149,194360.0 112 | 154,HLNE,Hamilton Lane Incorporated,Financial,Asset Management,USA,5080000000.0,32.34,94.12,0.0143,49356.0 113 | 155,PWR,Quanta Services Inc.,Industrials,Engineering & Construction,USA,30280000000.0,54.31,212.42,0.012199999999999999,254288.0 114 | 156,GOOG,Alphabet Inc.,Communication Services,Internet Content & Information,USA,1713110000000.0,28.76,136.32,-0.0075,9888623.0 115 | 157,GWRE,Guidewire Software Inc.,Technology,Software - Application,USA,7020000000.0,,86.48,0.0006,159252.0 116 | 159,BR,Broadridge Financial Solutions Inc.,Technology,Information Technology Services,USA,21980000000.0,35.4,187.31,0.0059,106396.0 117 | 160,PSN,Parsons Corporation,Technology,Information Technology Services,USA,6020000000.0,51.77,57.31,0.0051,130137.0 118 | 161,COLD,Americold Realty Trust Inc.,Real Estate,REIT - Industrial,USA,9050000000.0,,33.61,-0.0012,640478.0 119 | 162,ATR,AptarGroup Inc.,Healthcare,Medical Instruments & Supplies,USA,8550000000.000001,35.25,132.77,0.0016,89228.0 120 | 163,CW,Curtiss-Wright Corporation,Industrials,Aerospace & Defense,USA,7920000000.0,25.08,208.38,0.0019,34870.0 121 | 164,ACA,Arcosa Inc.,Industrials,Engineering & Construction,USA,3770000000.0,13.59,78.91,0.0088,22552.0 122 | 165,PSX,Phillips 66,Energy,Oil & Gas Refining & Marketing,USA,51360000000.0,5.08,116.87,0.023700000000000002,1546458.0 123 | 166,LII,Lennox International Inc.,Industrials,Building Products & Equipment,USA,13430000000.0,24.6,381.52,0.0125,65063.0 124 | -------------------------------------------------------------------------------- /QS004-avoid-algo-trading-mistakes/01_exponential_moving_average.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 004: Avoid Mistakes in Algorithmic Trading 3 | # Copyright: Quant Science, LLC 4 | 5 | # 1.0 IMPORT LIBRARIES ---- 6 | 7 | import pandas as pd 8 | import matplotlib.pyplot as plt 9 | import numpy as np 10 | 11 | # 2.0 CREATE STOCK PRICE DATA ---- 12 | 13 | data = { 14 | 'date': pd.date_range('2020-01-01', periods=10, freq='B'), 15 | 'price': [100, 103, 107, 105, 108, 107, 115, 110, 108, 112] 16 | } 17 | df = pd.DataFrame(data) 18 | df['date'] = pd.to_datetime(df['date']) 19 | df.set_index('date', inplace=True) 20 | 21 | df 22 | 23 | # MOVING AVERAGES ---- 24 | 25 | # Window size for moving averages 26 | window_size = 3 27 | 28 | # Simple Moving Average (SMA) 29 | df['sma'] = df['price'].rolling(window=window_size, min_periods=1).mean() 30 | 31 | # Exponential Moving Average (EMA) 32 | df['ema'] = df['price'].ewm(span=window_size, adjust=False).mean() 33 | 34 | # KALMAN FILTER ---- 35 | 36 | # Kalman Filter Initialization 37 | initial_state = df['price'][0] 38 | state_estimate = initial_state 39 | 40 | # Kalman Filter Parameters 41 | # - Can be tuned to find the optimal filter 42 | estimation_error = 0.5 # Initial estimation error. 43 | process_variance = 0.1 # The process variance. 44 | measurement_variance = 0.1 # The measurement variance. 45 | 46 | kf = [] 47 | 48 | for price in df['price']: 49 | # Prediction 50 | prediction = state_estimate # In a 1D constant model, the prediction is the previous state estimate. 51 | prediction_error = estimation_error + process_variance 52 | 53 | # Update 54 | kalman_gain = prediction_error / (prediction_error + measurement_variance) 55 | state_estimate = prediction + kalman_gain * (price - prediction) 56 | estimation_error = (1 - kalman_gain) * prediction_error 57 | 58 | kf.append(state_estimate) 59 | 60 | df['kf'] = kf # Adding Kalman Filter estimates to the DataFrame. 61 | 62 | df 63 | 64 | # 3.0 PLOT ---- 65 | 66 | # Matplotlib Plot 67 | plt.style.use('dark_background') 68 | 69 | plt.figure(figsize=(10,6)) 70 | 71 | # Add lines 72 | plt.plot(df.index, df['price'], label='Original Price', marker='o', alpha=0.5, color='lime') 73 | plt.plot(df.index, df['sma'], label=f'{window_size}-Day SMA', linestyle='dashed', color='cyan') 74 | plt.plot(df.index, df['ema'], label=f'{window_size}-Day EMA', linestyle='dotted', color='magenta') 75 | plt.plot(df.index, df['kf'], label='Kalman Filter', linestyle='solid', color='yellow') 76 | 77 | # Add labels 78 | plt.title('Price with SMA, EMA and Kalman Filter', color='white') 79 | plt.xlabel('Date', color='white') 80 | plt.ylabel('Price', color='white') 81 | 82 | # Format 83 | plt.legend() 84 | plt.grid(True, color='gray', linestyle='--', linewidth=0.5) 85 | plt.tight_layout() 86 | 87 | plt.show() 88 | 89 | 90 | # WANT MORE ALGORITHMIC TRADING HELP? 91 | # Register for our Course Waitlist: 92 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 93 | 94 | -------------------------------------------------------------------------------- /QS004-avoid-algo-trading-mistakes/moving_average_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quant-science/sunday-quant-scientist/a9bf8863c5624fbb1b4ec28805a0be9cb56cf977/QS004-avoid-algo-trading-mistakes/moving_average_plot.png -------------------------------------------------------------------------------- /QS005-pytimetk-first-look/01_pytimetk_algo_demo.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 005: Pytimetk Algorithmic Trading 3 | # Copyright: Quant Science, LLC 4 | 5 | # 1.0 IMPORT LIBRARIES ---- 6 | 7 | # Note: I'm using the development version of pytimetk==0.1.0.9000 8 | # pip install git+https://github.com/business-science/pytimetk.git 9 | 10 | import pandas as pd 11 | import pytimetk as tk 12 | 13 | # 2.0 GET STOCK PRICE DATA ---- 14 | 15 | stocks_df = tk.load_dataset("stocks_daily") 16 | stocks_df['date'] = pd.to_datetime(stocks_df['date']) 17 | 18 | stocks_df.glimpse() 19 | 20 | # 3.0 ADD MOVING AVERAGES ---- 21 | 22 | # Add 2 moving averages (10-day, 50-Day, 200-Day) 23 | windows = [10, 50, 200] 24 | window_funcs = ['mean', 'median'] 25 | 26 | sma_df = stocks_df[['symbol', 'date', 'adjusted']] \ 27 | .groupby('symbol') \ 28 | .augment_rolling( 29 | date_column = 'date', 30 | value_column = 'adjusted', 31 | window = windows, 32 | window_func = window_funcs, 33 | center = False, 34 | ) 35 | 36 | sma_df.glimpse() 37 | 38 | # 4.0 VISUALIZE ---- 39 | 40 | # Mean ---- 41 | (sma_df 42 | 43 | # zoom in on dates 44 | .query('date >= "2023-01-01"') 45 | 46 | # Convert to long format 47 | .melt( 48 | id_vars = ['symbol', 'date'], 49 | value_vars = [ 50 | "adjusted", 51 | "adjusted_rolling_mean_win_10", "adjusted_rolling_mean_win_50", 52 | "adjusted_rolling_mean_win_200", 53 | ] 54 | ) 55 | 56 | # Group on symbol and visualize 57 | .groupby("symbol") 58 | .plot_timeseries( 59 | date_column = 'date', 60 | value_column = 'value', 61 | color_column = 'variable', 62 | title = "Mean: 10, 50, 200 Day Moving Averages", 63 | smooth = False, 64 | facet_ncol = 2, 65 | width = 800, 66 | height = 500, 67 | engine = "plotly" 68 | ) 69 | ) 70 | 71 | 72 | (sma_df 73 | 74 | # zoom in on dates 75 | .query('date >= "2023-01-01"') 76 | 77 | # Convert to long format 78 | .melt( 79 | id_vars = ['symbol', 'date'], 80 | value_vars = [ 81 | "adjusted", 82 | "adjusted_rolling_median_win_10", 83 | "adjusted_rolling_median_win_50", 84 | "adjusted_rolling_median_win_200", 85 | ] 86 | ) 87 | 88 | # Group on symbol and visualize 89 | .groupby("symbol") 90 | .plot_timeseries( 91 | date_column = 'date', 92 | value_column = 'value', 93 | color_column = 'variable', 94 | title = "Median: 10, 50, 200 Day Moving Medians", 95 | smooth = False, 96 | facet_ncol = 2, 97 | width = 800, 98 | height = 500, 99 | engine = "plotly" 100 | ) 101 | ) 102 | 103 | # 5.0 BONUS: BOLINGER BANDS ---- 104 | 105 | bollinger_df = stocks_df[['symbol', 'date', 'adjusted']] \ 106 | .groupby('symbol') \ 107 | .augment_rolling( 108 | date_column = 'date', 109 | value_column = 'adjusted', 110 | window = 20, 111 | window_func = ['mean', 'std'], 112 | center = False 113 | ) \ 114 | .assign( 115 | upper_band = lambda x: x['adjusted_rolling_mean_win_20'] + 2*x['adjusted_rolling_std_win_20'], 116 | lower_band = lambda x: x['adjusted_rolling_mean_win_20'] - 2*x['adjusted_rolling_std_win_20'] 117 | ) 118 | 119 | # Visualize 120 | (bollinger_df 121 | 122 | # zoom in on dates 123 | .query('date >= "2023-01-01"') 124 | 125 | # Convert to long format 126 | .melt( 127 | id_vars = ['symbol', 'date'], 128 | value_vars = ["adjusted", "adjusted_rolling_mean_win_20", "upper_band", "lower_band"] 129 | ) 130 | 131 | # Group on symbol and visualize 132 | .groupby("symbol") 133 | .plot_timeseries( 134 | date_column = 'date', 135 | value_column = 'value', 136 | color_column = 'variable', 137 | title = "Bolinger Bands: 20-Day Moving Average", 138 | # Adjust colors for Bollinger Bands 139 | color_palette =["#2C3E50", "#E31A1C", '#18BC9C', '#18BC9C'], 140 | smooth = False, 141 | facet_ncol = 2, 142 | width = 800, 143 | height = 500, 144 | engine = "plotly" 145 | ) 146 | ) 147 | 148 | # CONCLUSIONS ---- 149 | # - pytimetk is a great tool for adding features like moving averages to stock price data 150 | # - pytimetk is also great for visualizing stock price data 151 | # - we are just scratching the surface of what pytimetk can do 152 | 153 | # WANT TO SEE WHAT PYTIMETK CAN DO FOR ALGO TRADING AND FINANCE?: ---- 154 | # - FREE TRAINING ON TUESDAY, OCTOBER 17TH, 2023: https://us02web.zoom.us/webinar/register/1716838099992/WN_QKYacsmkSryYuYvyUXkW9g 155 | 156 | 157 | # WANT MORE ALGORITHMIC TRADING HELP? 158 | # Register for our Course Waitlist: 159 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 160 | 161 | -------------------------------------------------------------------------------- /QS005-pytimetk-first-look/figures/01_moving_averages_10_50_200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quant-science/sunday-quant-scientist/a9bf8863c5624fbb1b4ec28805a0be9cb56cf977/QS005-pytimetk-first-look/figures/01_moving_averages_10_50_200.png -------------------------------------------------------------------------------- /QS005-pytimetk-first-look/figures/02_moving_medians_10_50_200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quant-science/sunday-quant-scientist/a9bf8863c5624fbb1b4ec28805a0be9cb56cf977/QS005-pytimetk-first-look/figures/02_moving_medians_10_50_200.png -------------------------------------------------------------------------------- /QS005-pytimetk-first-look/figures/03_bollinger_bands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quant-science/sunday-quant-scientist/a9bf8863c5624fbb1b4ec28805a0be9cb56cf977/QS005-pytimetk-first-look/figures/03_bollinger_bands.png -------------------------------------------------------------------------------- /QS006-anomaly-buy-sell/01_anomaly_buy_sell.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 006: Pytimetk Algorithmic Trading 3 | # Copyright: Quant Science, LLC 4 | 5 | # 1.0 IMPORT LIBRARIES ---- 6 | 7 | # Note: I'm using the development version of pytimetk==0.1.0.9001 8 | # pip install git+https://github.com/business-science/pytimetk.git 9 | 10 | import pandas as pd 11 | import pytimetk as tk 12 | 13 | # 2.0 GET STOCK PRICE DATA ---- 14 | 15 | stocks_df = tk.load_dataset("stocks_daily") 16 | stocks_df['date'] = pd.to_datetime(stocks_df['date']) 17 | 18 | stocks_df.glimpse() 19 | 20 | stocks_df.groupby('symbol').plot_timeseries( 21 | date_column = 'date', 22 | value_column = 'adjusted', 23 | facet_ncol = 2, 24 | width = 1100, 25 | height = 800, 26 | title = 'Stock Prices' 27 | ) 28 | 29 | # 3.0 ANOMALY DETECTION: IDENTIFY BUY/SELL REGIONS ---- 30 | 31 | stocks_filtered_df = stocks_df.query('date >= "2021-01-01"') 32 | 33 | stocks_anomalized_df = stocks_filtered_df[['symbol', 'date', 'adjusted']] \ 34 | .groupby('symbol') \ 35 | .anomalize( 36 | date_column = "date", 37 | value_column = "adjusted", 38 | method = 'stl', 39 | iqr_alpha = 0.10 40 | 41 | ) 42 | 43 | stocks_anomalized_df.glimpse() 44 | 45 | # 4.0 VISUALIZE ---- 46 | 47 | stocks_anomalized_df \ 48 | .groupby('symbol') \ 49 | .plot_anomalies( 50 | date_column = "date", 51 | facet_ncol = 2, 52 | width = 1100, 53 | height = 800, 54 | ) 55 | 56 | # WANT MORE ALGORITHMIC TRADING HELP? 57 | # Register for our Course Waitlist: 58 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 59 | 60 | -------------------------------------------------------------------------------- /QS007-ML-in-finance/01_ml_trend_detection.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 007: Machine Learning for SPY Trend Detection 3 | # Credit: Danny Groves for his awesome 4 | # https://twitter.com/drdanobi/status/1729469353282744515?s=46&t=npiSgI5uPxafM5JqdAQNDw 5 | 6 | # Libraries 7 | import pandas as pd 8 | import pytimetk as tk 9 | import plotly.graph_objects as go 10 | 11 | from sklearn.ensemble import RandomForestClassifier 12 | from sklearn.metrics import accuracy_score, precision_score, roc_auc_score 13 | 14 | # INPUTS 15 | 16 | SYMBOL = "SPY" 17 | START = "2015-09-30" 18 | END = "2023-09-30" 19 | TRAIN_UNTIL = "2018-09-30" 20 | 21 | # Load the data 22 | from openbb_terminal.sdk import openbb 23 | data = openbb.stocks.load(SYMBOL, start_date=START, end_date=END) 24 | df = data.reset_index()[['date', 'Open', 'High', 'Low', 'Close']] 25 | df 26 | 27 | # # OPENBB 4 Compatibility: 28 | import openbb as openbb # openbb 4 29 | df = openbb.obb.equity.price.historical(SYMBOL, start_date=START, end_date=END).to_df() # openbb 4 30 | df.index = pd.to_datetime(df.index) 31 | df = df.rename({"close":"Close", "high":"High", "low":"Low", "open":"Open"}, axis=1) 32 | df.reset_index(inplace=True) 33 | 34 | 35 | df \ 36 | .plot_timeseries( 37 | date_column="date", 38 | value_column="Close", 39 | title="SPY Close", 40 | x_lab="Date", 41 | y_lab="Close", 42 | ) 43 | 44 | # Feature Engineering 45 | 46 | # Distance from Moving Averages 47 | for m in [10, 20, 30, 50, 100]: 48 | df[f'feat_dist_from_ma_{m}'] = df['Close']/df['Close'].rolling(m).mean() - 1 49 | 50 | # Distance from n day max/min 51 | for m in [6, 10, 15, 20, 30, 50, 100]: 52 | df[f'feat_dist_from_max_{m}'] = df['Close']/df['High'].rolling(m).max() - 1 53 | df[f'feat_dist_from_min_{m}'] = df['Close']/df['Low'].rolling(m).min() - 1 54 | 55 | # Price Distance 56 | for m in [6, 10, 15, 20, 30, 50, 100]: 57 | df[f'feat_price_dist_{m}'] = df['Close']/df['Close'].shift(m) - 1 58 | 59 | df.glimpse() 60 | 61 | # Target Variable (Predict price above 20SMA in 5 days) 62 | 63 | df['target_ma'] = df['Close'].rolling(20).mean() 64 | df['price_above_ma'] = df['Close'] > df['target_ma'] 65 | df['target'] = df['price_above_ma'].astype(int).shift(-5) 66 | 67 | df.glimpse() 68 | 69 | # Clean and Train Test Split 70 | 71 | df = df.dropna() 72 | 73 | feat_cols = [col for col in df.columns if 'feat' in col] 74 | train_until = TRAIN_UNTIL 75 | 76 | x_train = df[df['date'] <= train_until][feat_cols] 77 | y_train = df[df['date'] <= train_until]['target'] 78 | 79 | x_test = df[df['date'] > train_until][feat_cols] 80 | y_test = df[df['date'] > train_until]['target'] 81 | 82 | # Train Model 83 | 84 | clf = RandomForestClassifier( 85 | n_estimators=100, 86 | max_depth=3, 87 | random_state=42, 88 | class_weight='balanced' 89 | ) 90 | 91 | clf.fit(x_train, y_train) 92 | 93 | y_train_pred = clf.predict(x_train) 94 | y_test_pred = clf.predict(x_test) 95 | 96 | print(f"Train Accuracy: {accuracy_score(y_train, y_train_pred)}") 97 | print(f"Test Accuracy: {accuracy_score(y_test, y_test_pred)}") 98 | 99 | print(f"Train Precision: {precision_score(y_train, y_train_pred)}") 100 | print(f"Test Precision: {precision_score(y_test, y_test_pred)}") 101 | 102 | print(f"Train ROC AUC: {roc_auc_score(y_train, clf.predict_proba(x_train)[:, 1])}") 103 | print(f"Test ROC AUC: {roc_auc_score(y_test, clf.predict_proba(x_test)[:, 1])}") 104 | 105 | # Visualize 106 | 107 | df_test = df[df['date'] > train_until].reset_index(drop=True) 108 | df_test['pred_prob'] = clf.predict_proba(x_test)[:, 1] 109 | df_test['pred'] = df_test['pred_prob'] > 0.5 110 | 111 | fig = df_test \ 112 | .plot_timeseries( 113 | date_column="date", 114 | value_column="Close", 115 | title=f"{SYMBOL} Price with Predicted Patterns", 116 | x_lab="Date", 117 | y_lab="Close", 118 | ) 119 | 120 | fig.add_trace( 121 | go.Line( 122 | x=df_test['date'], 123 | y=df_test['target_ma'], 124 | name="Target 20SMA" 125 | ) 126 | ) 127 | 128 | df_pattern = ( 129 | df_test[df_test['pred']] 130 | .groupby((~df_test['pred']).cumsum())['date'] 131 | .agg(['first', 'last']) 132 | ) 133 | 134 | for idx, row in df_pattern.iterrows(): 135 | fig.add_vrect( 136 | x0=row['first'], 137 | x1=row['last'], 138 | line_width=0, 139 | fillcolor="green", 140 | opacity=0.2, 141 | ) 142 | 143 | fig.update_layout( 144 | width = 800, 145 | height = 600, 146 | xaxis_rangeslider_visible=True, 147 | ) 148 | 149 | fig.show() 150 | 151 | # WANT MORE ALGORITHMIC TRADING HELP? 152 | # Register for our Course Waitlist: 153 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 154 | 155 | -------------------------------------------------------------------------------- /QS008-fast-fourier-transform/01_fft.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 008: Fast Fourier Transform 3 | 4 | # STEP 1: Load the data 5 | 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | import pandas as pd 9 | import pytimetk as tk 10 | 11 | import matplotlib.pyplot as plt 12 | plt.rcdefaults() 13 | 14 | 15 | SYMBOL = "SPY" 16 | START = "2021-09-30" 17 | END = "2023-09-30" 18 | 19 | # Load the data 20 | from openbb_terminal.sdk import openbb 21 | data = openbb.stocks.load(SYMBOL, start_date=START, end_date=END) 22 | 23 | # # OPENBB 4 Compatibility: 24 | import openbb as openbb # openbb 4 25 | df = openbb.obb.equity.price.historical(SYMBOL, start_date=START, end_date=END).to_df() # openbb 4 26 | df.index = pd.to_datetime(df.index) 27 | df = df.rename({"close":"Close"}, axis=1) 28 | data = df 29 | 30 | df = data.reset_index()[['date', 'Close']] 31 | 32 | df.columns = ['date', 'Price'] 33 | 34 | df \ 35 | .plot_timeseries( 36 | date_column="date", 37 | value_column="Price", 38 | title="SPY Close", 39 | x_lab="Date", 40 | y_lab="Price", 41 | smooth=False, 42 | ) 43 | 44 | # STEP 2: Apply FFT 45 | 46 | # Convert to time series 47 | stock_data = df.set_index('date') 48 | 49 | # Apply FFT 50 | fft_result = np.fft.fft(stock_data['Price']) 51 | frequencies = np.fft.fftfreq(len(fft_result), d=1) # Assuming daily data, d=1 52 | 53 | # Compute magnitude and corresponding periods 54 | magnitude = np.abs(fft_result) 55 | periods = 1 / frequencies 56 | 57 | # Plotting 58 | plt.figure(figsize=(10, 8)) # Adjusted for vertical stacking 59 | 60 | # Original Time Series Data 61 | plt.subplot(2, 1, 1) # 2 rows, 1 column, first plot 62 | plt.plot(stock_data.index, stock_data['Price']) 63 | plt.title(f'{SYMBOL} Time Series') 64 | plt.xlabel('Date') 65 | plt.ylabel('Price') 66 | 67 | # Frequency Domain Representation 68 | plt.subplot(2, 1, 2) # 2 rows, 1 column, second plot 69 | plt.plot(periods, magnitude) 70 | plt.title(f'FFT of {SYMBOL}') 71 | plt.xlabel('Period (Days)') 72 | plt.ylabel('Magnitude') 73 | plt.xlim(0, max(periods[1:])) # Limiting x-axis to view significant periods (ignore large periods) 74 | plt.ylim(0, max(magnitude[1:]) * 1.1) # Ignore the zero frequency component 75 | 76 | plt.tight_layout() 77 | plt.show() 78 | 79 | # STEP 3: Recover the original time series 80 | 81 | # Recovering the original time series 82 | recovered = np.fft.ifft(fft_result) 83 | 84 | # Plotting 85 | plt.figure(figsize=(14, 6)) 86 | plt.plot(stock_data.index, stock_data['Price'], label='Original') 87 | plt.plot(stock_data.index, recovered, label='Recovered') 88 | plt.title(f'{SYMBOL} Time Series: FFT Inverse') 89 | plt.xlabel('Date') 90 | plt.ylabel('Price') 91 | plt.legend() 92 | plt.show() 93 | 94 | # STEP 4: Recover the original time series with just the top 25 dominant periods 95 | 96 | # Displaying a few dominant periods 97 | dominant_periods = pd.Series(periods, index=magnitude).nlargest(25) 98 | dominant_periods.to_frame('Period (Days)') 99 | 100 | # Recovering the original time series with just the top 5 dominant periods 101 | top_periods = dominant_periods.index 102 | top_fft_result = fft_result.copy() 103 | top_fft_result[np.abs(frequencies) > 1 / top_periods.min()] = 0 104 | top_recovered = np.fft.ifft(top_fft_result) 105 | 106 | # Plotting 107 | plt.figure(figsize=(14, 6)) 108 | plt.plot(stock_data.index, stock_data['Price'], label='Original') 109 | plt.plot(stock_data.index, top_recovered, label='Recovered') 110 | plt.title(f'{SYMBOL} Time Series: FFT Inverse') 111 | plt.xlabel('Date') 112 | plt.ylabel('Price') 113 | plt.legend() 114 | plt.show() 115 | 116 | # WANT MORE ALGORITHMIC TRADING HELP? 117 | # Register for our Course Waitlist: 118 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 119 | 120 | -------------------------------------------------------------------------------- /QS009-average-true-range/01_atr.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 009: Average True Range (ATR) 3 | 4 | # STEP 1: Load the data 5 | 6 | from openbb_terminal.sdk import openbb 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import pandas as pd 10 | import pytimetk as tk 11 | 12 | import matplotlib.pyplot as plt 13 | plt.rcdefaults() 14 | 15 | 16 | SYMBOL = "SPY" 17 | START = "2021-09-30" 18 | END = "2023-12-13" 19 | 20 | # Load the data 21 | df = openbb.stocks.load(SYMBOL, start_date=START, end_date=END) 22 | 23 | # # OPENBB 4 Compatibility: 24 | # import openbb as openbb # openbb 4 25 | # df = openbb.obb.equity.price.historical(SYMBOL, start_date=START, end_date=END).to_df() # openbb 4 26 | # df.index = pd.to_datetime(df.index) 27 | # df = df.rename({"close":"Close", "open":"Open", "high":"High", "low":"Low"}, axis=1) 28 | 29 | df \ 30 | .reset_index() \ 31 | .plot_timeseries( 32 | date_column="date", 33 | value_column="Close", 34 | title="SPY Close", 35 | x_lab="Date", 36 | y_lab="Close", 37 | ) 38 | 39 | # STEP 2: Apply ATR 40 | def calculate_atr(df, period=14): 41 | df['H-L'] = df['High'] - df['Low'] 42 | df['H-PC'] = abs(df['High'] - df['Close'].shift(1)) 43 | df['L-PC'] = abs(df['Low'] - df['Close'].shift(1)) 44 | df['TR'] = df[['H-L', 'H-PC', 'L-PC']].max(axis=1) 45 | df['ATR'] = df['TR'].rolling(window=period).mean() 46 | df.drop(['H-L', 'H-PC', 'L-PC', 'TR'], axis=1, inplace=True) 47 | return df 48 | 49 | df_with_atr = calculate_atr(df, period = 14) 50 | 51 | # STEP 3: Plotting 52 | 53 | # Plotting both the ATR and the original series (High, Low, Close) on the same chart 54 | plt.figure(figsize=(10, 8)) 55 | 56 | # Plotting High, Low, and Close 57 | plt.subplot(2, 1, 1) 58 | plt.plot(df.index, df['High'], label='High', color='green') 59 | plt.plot(df.index, df['Low'], label='Low', color='red') 60 | plt.plot(df.index, df['Close'], label='Close', color='blue') 61 | plt.title('Stock Prices (High, Low, Close)') 62 | plt.ylabel('Price') 63 | plt.legend() 64 | 65 | # Plotting ATR 66 | plt.subplot(2, 1, 2) 67 | plt.plot(df_with_atr.index, df_with_atr['ATR'], label='ATR', color='orange') 68 | plt.title('Average True Range (ATR)') 69 | plt.xlabel('Date') 70 | plt.ylabel('ATR Value') 71 | plt.legend() 72 | 73 | plt.tight_layout() 74 | plt.show() 75 | 76 | # WANT MORE ALGORITHMIC TRADING HELP? 77 | # Register for our Course Waitlist: 78 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist -------------------------------------------------------------------------------- /QS010-relative-strength-index/01_rsi.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 010: Relative Strength Index (RSI) 3 | 4 | # WANT TO LEARN ALGORITHMIC TRADING? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | # STEP 1: Load the data 9 | 10 | from openbb_terminal.sdk import openbb # openbb 3 11 | 12 | import numpy as np 13 | import matplotlib.pyplot as plt 14 | import pandas as pd 15 | import pytimetk as tk 16 | 17 | import matplotlib.pyplot as plt 18 | plt.rcdefaults() 19 | 20 | SYMBOL = "SPY" 21 | START = "2021-09-30" 22 | END = "2023-12-13" 23 | 24 | # Load the data 25 | df = openbb.stocks.load(SYMBOL, start_date=START, end_date=END) # openbb 3 26 | 27 | # # OPENBB 4 Compatibility: 28 | # import openbb as openbb # openbb 4 29 | # df = openbb.obb.equity.price.historical(SYMBOL, start_date=START, end_date=END).to_df() # openbb 4 30 | # df.index = pd.to_datetime(df.index) 31 | # df = df.rename({"close":"Close"}, axis=1) 32 | 33 | df \ 34 | .reset_index() \ 35 | .plot_timeseries( 36 | date_column="date", 37 | value_column="Close", 38 | title="SPY Close", 39 | x_lab="Date", 40 | y_lab="Close", 41 | ) 42 | 43 | # STEP 2: Apply RSI 44 | 45 | # Calculate the daily price changes 46 | delta = df['Close'].diff() 47 | 48 | # Separate gains and losses 49 | gain = delta.clip(lower=0) 50 | loss = -delta.clip(upper=0) 51 | 52 | # Calculate the exponential moving average (EMA) of gains and losses 53 | window_length = 14 54 | avg_gain = gain.rolling(window=window_length, min_periods=window_length).mean() 55 | avg_loss = loss.rolling(window=window_length, min_periods=window_length).mean() 56 | 57 | # Calculate RS 58 | RS = avg_gain / avg_loss 59 | 60 | # Calculate RSI 61 | RSI = 100 - (100 / (1 + RS)) 62 | 63 | # Add RSI to the data frame 64 | df['RSI'] = RSI 65 | 66 | # Print the data frame 67 | df 68 | 69 | # STEP 3: Plotting 70 | 71 | # Create a figure and a set of subplots 72 | fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), gridspec_kw={'height_ratios': [3, 1]}) 73 | 74 | # Plot Closing Prices 75 | ax1.plot(df['Close'], color='blue') 76 | ax1.set_title(f'{SYMBOL} Closing Price') 77 | ax1.set_ylabel('Price') 78 | 79 | # Plot RSI 80 | ax2.plot(df['RSI'], color='green') 81 | ax2.set_title('Relative Strength Index (RSI)') 82 | ax2.set_ylabel('RSI') 83 | ax2.set_ylim([0, 100]) # RSI ranges from 0 to 100 84 | ax2.axhline(70, color='red', linestyle='--') # Overbought line 85 | ax2.axhline(30, color='red', linestyle='--') # Oversold line 86 | 87 | # Show the plot 88 | plt.tight_layout() 89 | plt 90 | 91 | 92 | -------------------------------------------------------------------------------- /QS011-skfolio-risk-parity/01_risk_parity.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 011: SKFOLIO Mean Risk Optimization 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | 9 | # Libraries and Data 10 | import pandas as pd 11 | import yfinance as yf 12 | 13 | from sklearn.model_selection import train_test_split 14 | 15 | from skfolio import Population, RiskMeasure 16 | from skfolio.optimization import InverseVolatility, RiskBudgeting 17 | from skfolio.preprocessing import prices_to_returns 18 | 19 | prices = yf.download(['AAPL', 'MSFT', 'GOOG', 'AMZN', 'META', 'TSLA', 'NVDA'], start='2015-01-01', end='2024-12-31', progress=False) 20 | 21 | prices = prices['Adj Close'] 22 | 23 | # Train Test Split 24 | 25 | X = prices_to_returns(prices) 26 | X_train, X_test = train_test_split(X, test_size=0.75, shuffle=False) 27 | 28 | X_train.head() 29 | 30 | # Risk Parity 31 | 32 | model = RiskBudgeting( 33 | risk_measure=RiskMeasure.VARIANCE, 34 | portfolio_params=dict(name="Risk Parity - Variance"), 35 | ) 36 | model.fit(X_train) 37 | model.weights_ 38 | 39 | # Benchmark 40 | 41 | benchmark = InverseVolatility( 42 | portfolio_params=dict(name="Inverse Volatility") 43 | ) 44 | benchmark.fit(X_train) 45 | benchmark.weights_ 46 | 47 | 48 | # Predictions AND Evaluation 49 | 50 | pred_model = model.predict(X_test) 51 | pred_bench = benchmark.predict(X_test) 52 | 53 | print(pred_model.annualized_sharpe_ratio) 54 | print(pred_bench.annualized_sharpe_ratio) 55 | 56 | # Visualizations 57 | 58 | population = Population([pred_model, pred_bench]) 59 | 60 | population.plot_composition() 61 | 62 | population.plot_cumulative_returns() 63 | 64 | population.summary() 65 | 66 | pred_model.plot_contribution(RiskMeasure.ANNUALIZED_VARIANCE) 67 | pred_bench.plot_contribution(RiskMeasure.ANNUALIZED_VARIANCE) 68 | 69 | # READY TO LEARN ALGORITHMIC TRADING? 70 | # Register for our Course Waitlist: 71 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 72 | 73 | -------------------------------------------------------------------------------- /QS012-pytimetk-finance-module/01_pytimetk_finance_module.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 012: PYTIMETK Finance Module 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | 9 | # Libraries and Data 10 | import pandas as pd 11 | import yfinance as yf 12 | import pytimetk as tk 13 | 14 | raw_df = yf.download(['AAPL', 'MSFT', 'GOOG', 'AMZN', 'META', 'TSLA', 'NVDA'], start='2015-01-01', end='2023-12-31', progress=False) 15 | 16 | df = raw_df \ 17 | .stack() \ 18 | .reset_index(level=1, names=["", "Symbol"]) \ 19 | .sort_values(by="Symbol") \ 20 | .reset_index() 21 | 22 | df.glimpse() 23 | 24 | # MACD (Polars Backend) 25 | 26 | macd_df = df \ 27 | .groupby('Symbol') \ 28 | .augment_macd( 29 | date_column = 'Date', 30 | close_column = 'Close', 31 | fast_period = 12, 32 | slow_period = 26, 33 | signal_period = 9, 34 | engine = "polars" 35 | ) 36 | 37 | macd_df.glimpse() 38 | 39 | # Bollinger Bands (Polars Backend) 40 | 41 | bbands_df = df \ 42 | .groupby('Symbol') \ 43 | .augment_bbands( 44 | date_column = 'Date', 45 | close_column = 'Close', 46 | periods = [20, 40, 60], 47 | std_dev = 2, 48 | engine = "polars" 49 | ) 50 | 51 | bbands_df.glimpse() 52 | 53 | # CHAINING FEATURES (Polars Backend) 54 | 55 | features_df = df \ 56 | .groupby('Symbol') \ 57 | .augment_macd( 58 | date_column = 'Date', 59 | close_column = 'Close', 60 | fast_period = 12, 61 | slow_period = 26, 62 | signal_period = 9, 63 | engine = "polars" 64 | ) \ 65 | .groupby('Symbol') \ 66 | .augment_bbands( 67 | date_column = 'Date', 68 | close_column = 'Close', 69 | periods = [20, 40, 60], 70 | std_dev = 2, 71 | engine = "polars" 72 | ) \ 73 | .groupby('Symbol') \ 74 | .augment_cmo( 75 | date_column = 'Date', 76 | close_column = 'Close', 77 | periods = [14,28], 78 | engine = "polars" 79 | ) \ 80 | .augment_timeseries_signature( 81 | date_column = 'Date', 82 | ) 83 | 84 | features_df.glimpse() 85 | 86 | 87 | 88 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 89 | # Register for our Course Waitlist: 90 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 91 | 92 | -------------------------------------------------------------------------------- /QS013-macd/01_macd.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 012: Moving Average Convergence Divergence (MACD) 3 | 4 | # STEP 1: Load the data 5 | 6 | from openbb_terminal.sdk import openbb # openbb 3 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import pandas as pd 10 | import pytimetk as tk 11 | 12 | import matplotlib.pyplot as plt 13 | plt.rcdefaults() 14 | 15 | 16 | SYMBOL = "NVDA" 17 | START = "2021-09-30" 18 | END = "2024-03-01" 19 | 20 | # Load the data 21 | df = openbb.stocks.load(SYMBOL, start_date=START, end_date=END) 22 | 23 | # # OPENBB 4 Compatibility: 24 | # import openbb as openbb # openbb 4 25 | # df = openbb.obb.equity.price.historical(SYMBOL, start_date=START, end_date=END).to_df() # openbb 4 26 | # df.index = pd.to_datetime(df.index) 27 | # df = df.rename({"close":"Close"}, axis=1) 28 | 29 | df \ 30 | .reset_index() \ 31 | .plot_timeseries( 32 | date_column="date", 33 | value_column="Close", 34 | title=f"{SYMBOL} Close", 35 | x_lab="Date", 36 | y_lab="Close", 37 | ) 38 | 39 | # STEP 2: Calculate MACD 40 | def calculate_macd(df, short_period=12, long_period=26, signal_period=9): 41 | df['EMA_short'] = df['Close'].ewm(span=short_period, adjust=False).mean() 42 | df['EMA_long'] = df['Close'].ewm(span=long_period, adjust=False).mean() 43 | df['MACD'] = df['EMA_short'] - df['EMA_long'] 44 | df['Signal_Line'] = df['MACD'].ewm(span=signal_period, adjust=False).mean() 45 | df['MACD_Histogram'] = df['MACD'] - df['Signal_Line'] 46 | df['Bullish_Crossover'] = (df['MACD'] > df['Signal_Line']) & (df['MACD'].shift(1) <= df['Signal_Line'].shift(1)) 47 | df['Bearish_Crossover'] = (df['MACD'] < df['Signal_Line']) & (df['MACD'].shift(1) >= df['Signal_Line'].shift(1)) 48 | 49 | return df 50 | 51 | df_with_macd = calculate_macd(df) 52 | 53 | df_with_macd.glimpse() 54 | 55 | # STEP 3: Plotting 56 | 57 | plt.figure(figsize=(10, 12)) # Increase the figure size to accommodate the new subplot 58 | 59 | # Plotting Close Price 60 | plt.subplot(3, 1, 1) # Change subplot grid to 3 rows, 1 column, and this is the 1st subplot 61 | plt.plot(df.index, df['Close'], label='Close', color='blue') 62 | plt.title(f'{SYMBOL} Close Price') 63 | plt.ylabel('Price') 64 | plt.legend() 65 | 66 | # Plotting MACD and Signal Line 67 | plt.subplot(3, 1, 2) # This is the 2nd subplot 68 | plt.plot(df_with_macd.index, df_with_macd['MACD'], label='MACD', color='green') 69 | plt.plot(df_with_macd.index, df_with_macd['Signal_Line'], label='Signal Line', color='red') 70 | 71 | # Highlight Bullish Crossovers 72 | plt.scatter(df_with_macd[df_with_macd['Bullish_Crossover']].index, df_with_macd[df_with_macd['Bullish_Crossover']]['MACD'], color='blue', label='Bullish Crossover', marker='^', alpha=1, s=100) 73 | 74 | # Highlight Bearish Crossovers 75 | plt.scatter(df_with_macd[df_with_macd['Bearish_Crossover']].index, df_with_macd[df_with_macd['Bearish_Crossover']]['MACD'], color='red', label='Bearish Crossover', marker='v', alpha=1, s=100) 76 | 77 | plt.title('Moving Average Convergence Divergence (MACD) and Signal Line') 78 | plt.ylabel('MACD Value') 79 | plt.legend() 80 | 81 | # Plotting MACD Histogram 82 | plt.subplot(3, 1, 3) # This is the 3rd subplot 83 | plt.bar(df_with_macd.index, df_with_macd['MACD_Histogram'], label='MACD Histogram', color='purple') 84 | plt.title('MACD Histogram') 85 | plt.xlabel('Date') 86 | plt.ylabel('Histogram Value') 87 | plt.legend() 88 | 89 | plt.tight_layout() # Adjust layout to not overlap 90 | plt.show() 91 | 92 | 93 | # === 94 | # Correlation Analysis 95 | 96 | # Make forward 5-day returns 97 | df_with_macd['5-day-forward-return'] = df_with_macd['Close'].shift(-5) / df_with_macd['Close'] - 1 98 | 99 | # Calculate rolling correlation 100 | df_with_macd['rolling_corr'] = df_with_macd['5-day-forward-return'] \ 101 | .rolling(window=30) \ 102 | .corr(df_with_macd['MACD']) 103 | 104 | df_with_macd \ 105 | .reset_index() \ 106 | .plot_timeseries( 107 | "date", "rolling_corr" 108 | ) 109 | 110 | df_with_macd['rolling_corr'].describe() -------------------------------------------------------------------------------- /QS014-riskfolio/01_riskfolio.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 013: Portfolio Optimization with Riskfolio 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | # Libraries 9 | import numpy as np 10 | import pandas as pd 11 | import yfinance as yf 12 | import riskfolio as rp 13 | 14 | # Date range 15 | start = '2018-01-01' 16 | end = '2023-12-31' 17 | 18 | # Tickers of assets 19 | assets = ['JCI', 'TGT', 'CMCSA', 'CPB', 'MO', 'APA', 'MMC', 'JPM', 20 | 'ZION', 'PSA', 'BAX', 'BMY', 'LUV', 'PCAR', 'TXT', 'TMO', 21 | 'DE', 'MSFT', 'HPQ', 'SEE', 'VZ', 'CNP', 'NI', 'T', 'NVDA'] 22 | assets.sort() 23 | 24 | # Tickers of factors 25 | factors = ['MTUM', 'QUAL', 'VLUE', 'SIZE', 'USMV'] 26 | factors.sort() 27 | 28 | tickers = assets + factors 29 | tickers.sort() 30 | 31 | # Downloading the data 32 | data = yf.download(tickers, start = start, end = end) 33 | data = data.loc[:,('Adj Close', slice(None))] 34 | data.columns = tickers 35 | returns = data.pct_change().dropna() 36 | 37 | Y = returns[assets] 38 | X = returns[factors] 39 | 40 | # 1.0 Creating the Portfolio Object 41 | port = rp.Portfolio(returns=Y) 42 | 43 | # Choose the risk measure 44 | rm = 'MSV' # Semi Standard Deviation 45 | 46 | # Estimate inputs of the model (historical estimates) 47 | method_mu='hist' # Method to estimate expected returns based on historical data. 48 | method_cov='hist' # Method to estimate covariance matrix based on historical data. 49 | 50 | port.assets_stats(method_mu=method_mu, method_cov=method_cov, d=0.94) 51 | 52 | mu = port.mu 53 | cov = port.cov 54 | 55 | # 2.0 Estimate the portfolio that maximizes the risk adjusted return ratio 56 | w1 = port.optimization(model='Classic', rm=rm, obj='Sharpe', rf=0.0, l=0, hist=True) 57 | 58 | # 3.0 Estimate points in the efficient frontier mean - semi standard deviation 59 | ws = port.efficient_frontier(model='Classic', rm=rm, points=20, rf=0, hist=True) 60 | 61 | # 4.0 Portfolio Cumulative Returns 62 | ax = rp.plot_series(returns=Y, 63 | w=ws, 64 | cmap='tab20', 65 | height=6, 66 | width=10, 67 | ax=None) 68 | 69 | ax = rp.plot_series(returns=Y, 70 | w=w1, 71 | cmap='tab20', 72 | height=6, 73 | width=10, 74 | ax=None) 75 | 76 | # 5.0 Efficient Frontier 77 | label = 'Max Risk Adjusted Return Portfolio' 78 | mu = port.mu 79 | cov = port.cov 80 | returns = port.returns 81 | 82 | ax = rp.plot_frontier(w_frontier=ws, 83 | mu=mu, 84 | cov=cov, 85 | returns=Y, 86 | rm=rm, 87 | rf=0, 88 | alpha=0.05, 89 | cmap='viridis', 90 | w=w1, 91 | label=label, 92 | marker='*', 93 | s=16, 94 | c='r', 95 | height=6, 96 | width=10, 97 | t_factor=252, 98 | ax=None) 99 | 100 | # 6.0 Portfolio Donut Chart 101 | ax = rp.plot_pie(w=w1, 102 | title='Portfolio', 103 | height=6, 104 | width=10, 105 | cmap="tab20", 106 | ax=None) 107 | 108 | # 7.0 Plot Table 109 | ax = rp.plot_table(returns=Y, 110 | w=w1, 111 | MAR=0, 112 | alpha=0.05, 113 | ax=None) 114 | 115 | # 8.0 Plot Risk Contribution 116 | ax = rp.plot_risk_con(w=w1, 117 | cov=cov, 118 | returns=Y, 119 | rm=rm, 120 | rf=0, 121 | alpha=0.05, 122 | color="tab:blue", 123 | height=6, 124 | width=10, 125 | t_factor=252, 126 | ax=None) 127 | 128 | # 9.0 Excel Reports 129 | rp.excel_report(returns, 130 | w1, 131 | rf=0, 132 | alpha=0.05, 133 | t_factor=252, 134 | ini_days=1, 135 | days_per_year=252, 136 | name="QS014-riskfolio/excel-report") -------------------------------------------------------------------------------- /QS014-riskfolio/excel-report.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quant-science/sunday-quant-scientist/a9bf8863c5624fbb1b4ec28805a0be9cb56cf977/QS014-riskfolio/excel-report.xlsx -------------------------------------------------------------------------------- /QS015-alphalens/01_alphalens_momentum.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 015: Factor Analysis with Alphalens 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import yfinance as yf 9 | import pandas as pd 10 | import numpy as np 11 | import alphalens as al 12 | 13 | # Fetch historical stock data 14 | tickers = ["AAPL", "GOOGL", "MSFT", "JPM", "GE"] 15 | start_date = "2020-01-01" 16 | end_date = "2023-01-01" 17 | 18 | # Using yfinance to download data 19 | data = yf.download(tickers, start=start_date, end=end_date) 20 | 21 | # Selecting Adjusted Close prices and forward-filling any missing data 22 | prices = data['Adj Close'].ffill() 23 | 24 | # Calculate momentum 25 | momentum_window = 90 26 | momentum = prices.pct_change(periods=momentum_window).shift(-momentum_window) 27 | 28 | # Prepare the factor data for Alphalens 29 | factor_data = { 30 | (date, ticker): value 31 | for date in momentum.index 32 | for ticker, value in momentum.loc[date].items() if np.isfinite(value) 33 | } 34 | 35 | factor_index = pd.MultiIndex.from_tuples(factor_data.keys(), names=['date', 'asset']) 36 | 37 | factor_values = pd.Series(factor_data.values(), index=factor_index, name='momentum').to_frame() 38 | 39 | factor_values 40 | 41 | # Prepare price data for Alphalens 42 | aligned_prices = prices.stack().reindex(factor_index).unstack() 43 | aligned_prices 44 | 45 | # Run Alphalens analysis 46 | factor_data_al = al.utils.get_clean_factor_and_forward_returns( 47 | factor=factor_values['momentum'], 48 | prices=aligned_prices, 49 | periods=[1, 5, 10], 50 | max_loss=0.5 51 | ) 52 | 53 | factor_data_al 54 | 55 | # Full Tear Sheet 56 | al.tears.create_full_tear_sheet(factor_data_al) 57 | -------------------------------------------------------------------------------- /QS016-nancy-pelosi-portfolio/01_nancy_pelosi_portfolio.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 016: What can we learn about Nancy Pelosi's Portfolio (Riskfolio Analysis)? 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | # Source: 9 | # 8 Top Nancy Pelosi Stocks to Buy 10 | # https://money.usnews.com/investing/articles/top-nancy-pelosi-stocks-to-buy 11 | 12 | # Libraries 13 | import riskfolio as rp 14 | import pandas as pd 15 | import yfinance as yf 16 | 17 | assets = [ 18 | "PANW", 19 | "NVDA", 20 | "AAPL", 21 | "MSFT", 22 | "GOOG", 23 | "TSLA", 24 | "AB", 25 | "DIS", 26 | "AXP", 27 | "^GSPC", # SP500 benchmark 28 | ] 29 | 30 | # Collect and format data 31 | data = yf.download(assets, start = "2018-01-01", end = "2024-12-31") 32 | data = data.loc[:, "Adj Close"] 33 | data 34 | 35 | # Get returns 36 | returns = data.pct_change().dropna() 37 | returns_bench = returns.pop("^GSPC").to_frame() 38 | 39 | # Riskfolio 40 | 41 | port = rp.Portfolio(returns) 42 | port.assets_stats( 43 | method_mu="hist", 44 | method_cov="hist", 45 | d =0.94, 46 | ) 47 | port.benchindex = returns_bench 48 | 49 | # Max Sharpe Portfolio 50 | w = port.optimization( 51 | model = "Classic", 52 | rm = "CVaR", 53 | obj = "Sharpe", 54 | hist = True, 55 | rf = 0, 56 | l = 0 57 | ) 58 | w 59 | 60 | rp.plot_pie( 61 | w = w, 62 | others = 0.05, 63 | nrow = 25, 64 | cmap = "tab20", 65 | height = 6, 66 | width = 10, 67 | ax = None 68 | ) 69 | 70 | rp.plot_series(returns=returns, w=w) 71 | 72 | rp.plot_drawdown(returns, w=w) 73 | 74 | rp.plot_table(returns, w) 75 | 76 | # Can we do better? Efficient Frontier 77 | 78 | wsim = port.efficient_frontier(model='Classic', rm = "MV", points=20, rf=0, hist=True) 79 | wsim 80 | 81 | ax = rp.plot_frontier(wsim, mu = port.mu, cov = port.cov, returns = returns) 82 | 83 | ax = rp.plot_series(returns=returns, 84 | w=wsim, 85 | cmap='tab20', 86 | height=6, 87 | width=10, 88 | ax=None) 89 | 90 | # Max Return Portfolio 91 | 92 | w_maxret = port.optimization( 93 | model = "Classic", 94 | rm = "CVaR", 95 | obj = "MaxRet", 96 | hist = True, 97 | rf = 0, 98 | l = 0 99 | ) 100 | 101 | 102 | ax = rp.plot_pie( 103 | w = w_maxret, 104 | others = 0.05, 105 | nrow = 25, 106 | cmap = "tab20", 107 | height = 6, 108 | width = 10, 109 | ax = None 110 | ) 111 | 112 | rp.plot_series(returns=returns, w=w_maxret) 113 | 114 | rp.plot_drawdown(returns, w_maxret) 115 | 116 | rp.plot_table(returns, w_maxret) 117 | -------------------------------------------------------------------------------- /QS017-quantstats-tearsheets/01_quantstats_tearsheets.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 017: How to make awesome tear sheets in Python 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | 9 | import quantstats as qs 10 | 11 | # fetch the daily returns for a stock 12 | stock = qs.utils.download_returns('META') 13 | 14 | # show sharpe ratio 15 | qs.stats.sharpe(stock) 16 | 17 | # Performance Snapshot Plot 18 | qs.plots.snapshot(stock, title='Facebook Performance', show=True) 19 | 20 | # Create an HTML Tear Sheet 21 | qs.reports.html(stock, "SPY") 22 | -------------------------------------------------------------------------------- /QS018-polars/01_polars.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 018: Algorithmic Trading and Quantitative Finance Analysis with Polars 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | 9 | # LIBRARIES 10 | 11 | import polars as pl 12 | import pandas as pd 13 | import yfinance as yf 14 | 15 | # STANLEY DRUCKENMILLER'S FUND DATA FROM 13F FILING ---- 16 | 17 | stock_list = ["IWM", "MSFT", "CPNG", "TECK", "VST", "NTRA", "NVDA", "COHR", "GE", "WWD", "STX", "ANET", "FLEX", "ZI", "NWSA", "DFS", "VRT", "KMI", "FCX", "KBR", "MRVL", "WAB", "LLY", "PANW", "CHX"] 18 | 19 | # COLLECT STOCK DATA 20 | 21 | stock_data = yf.download(stock_list, start = '2023-07-16', end = '2024-07-16') 22 | 23 | stock_data = stock_data.loc[:, "Adj Close"].dropna() 24 | 25 | stock_data 26 | 27 | # CONVERT TO POLARS 28 | 29 | stock_data_pl = pl.DataFrame(stock_data.reset_index()) 30 | 31 | stock_data_pl 32 | 33 | # PIVOT TO LONG FORMAT 34 | 35 | stock_data_long_pl = stock_data_pl.melt( 36 | id_vars="Date", 37 | value_name="Price", 38 | variable_name="Stock" 39 | ) 40 | 41 | stock_data_long_pl 42 | 43 | # PLOTTING STOCK PRICES 44 | 45 | stock_data_long_pl.plot.line(x="Date", y = "Price", by = "Stock", height = 500) 46 | 47 | # MOVING AVERAGES 48 | 49 | moving_average_pl = stock_data_long_pl.with_columns([ 50 | pl.col("Price").rolling_mean(10).over("Stock").alias("Price_MA10"), 51 | pl.col("Price").rolling_mean(50).over("Stock").alias("Price_MA50"), 52 | ]) 53 | 54 | moving_average_pl 55 | 56 | # VISUALIZE ONE OF THE STOCKS 57 | 58 | STOCK_SYMBOL = "NVDA" 59 | 60 | moving_average_pl.filter( 61 | pl.col("Stock") == STOCK_SYMBOL 62 | ).plot.line( 63 | x = "Date", 64 | y = ["Price", "Price_MA10", "Price_MA50"], 65 | by = "Stock", 66 | groupby = "Stock", 67 | height = 450 68 | ) 69 | 70 | # ADD RETURNS AND ROLLING SHARPE BY GROUP 71 | 72 | window_size = 50 73 | 74 | rolling_sharpe_pl = stock_data_long_pl.with_columns( 75 | (pl.col("Price") / pl.col("Price").shift(1) - 1).over("Stock").alias("Return") 76 | ).with_columns( 77 | (pl.col("Return").rolling_mean(window_size) / pl.col("Return").rolling_std(window_size)).over("Stock").alias("Rolling_Sharpe") 78 | ) 79 | 80 | rolling_sharpe_pl 81 | 82 | rolling_sharpe_pl.plot.line( 83 | x = "Date", 84 | y = ["Rolling_Sharpe"], 85 | by = "Stock", 86 | title = "50-Day Rolling Sharpe Ratio", 87 | height = 700, 88 | ) 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /QS019-correlation/01_correlation.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 019: Correlation Analysis 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | 9 | # * 1.0 Libraries & Data 10 | 11 | import riskfolio as rp 12 | import pandas as pd 13 | import yfinance as yf 14 | import seaborn as sns 15 | import riskfolio as rp 16 | import pyfolio as pf 17 | 18 | assets = [ 19 | "PANW", 20 | "NVDA", 21 | "AAPL", 22 | "MSFT", 23 | "GOOG", 24 | "TSLA", 25 | "DIS", 26 | "AXP", 27 | "GLD", # S 28 | "^GSPC", # SP500 benchmark 29 | ] 30 | 31 | # Collect and format data 32 | data = yf.download(assets, start = "2018-01-01", end = "2024-08-08") 33 | data = data.loc[:, "Adj Close"] 34 | data 35 | 36 | # Returns 37 | returns = data.pct_change().dropna() 38 | returns 39 | 40 | returns.median().sort_values(ascending=False).to_frame(name="median_return") 41 | 42 | # * 2.0 Correlations 43 | 44 | # Get Correlations 45 | corr_df = returns.corr() 46 | corr_df 47 | 48 | # Visualize the Correlations 49 | sns.heatmap( 50 | corr_df, 51 | annot=True, 52 | cmap="coolwarm" 53 | ) 54 | 55 | # Cluster the Correlations 56 | sns.clustermap( 57 | corr_df, 58 | cmap="coolwarm", 59 | metric = "correlation", 60 | annot = True 61 | ) 62 | 63 | # Clustering Correlations 64 | rp.plot_clusters( 65 | returns = returns, 66 | codependence='pearson', 67 | linkage='ward', 68 | k=None, 69 | max_k=10, 70 | leaf_order=True, 71 | dendrogram=True, 72 | ax=None 73 | ) 74 | 75 | # * 3.0 Constructing the Portfolio using Nested Clustered Optimization (NCO) 76 | port = rp.HCPortfolio(returns=returns) 77 | 78 | w = port.optimization( 79 | model = 'NCO', 80 | codependence='pearson', 81 | method_cov = 'hist', 82 | obj='Sharpe', 83 | rm='MV', 84 | rf=0, 85 | l=2, 86 | linkage='ward', 87 | max_k=10, 88 | leaf_order=True 89 | ) 90 | 91 | w 92 | 93 | # * 4.0 Portfolio Analysis 94 | 95 | rp.plot_pie(w) 96 | 97 | rp.plot_risk_con(returns=returns, w=w, cov=port.cov) 98 | 99 | rp.plot_drawdown(returns=returns, w=w) 100 | 101 | rp.plot_table(returns=returns, w=w) 102 | 103 | # * BONUS: Portfolio Performance Comparison vs Benchmark with Pyfolio 104 | 105 | portfolio_returns = (returns * w.weights).sum(axis=1) 106 | portfolio_returns.name = "portfolio_returns" 107 | portfolio_returns 108 | 109 | pf.create_simple_tear_sheet(returns=portfolio_returns, benchmark_rets=returns['^GSPC']) 110 | 111 | # Benchmark: 112 | pf.create_simple_tear_sheet(returns=returns['^GSPC']) -------------------------------------------------------------------------------- /QS020-ffn/01_ffn.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 020: Financial Functions for Python 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | # Libraries and Data 9 | import ffn 10 | import pandas as pd 11 | 12 | prices = ffn.get("AAPL, GOOGL, MSFT, JPM, NVDA", start="2023-01-01", end="2024-09-20") 13 | 14 | prices 15 | 16 | 17 | # Performance Analysis 18 | perf = prices.calc_stats() 19 | perf.display() 20 | 21 | # Lookback Returns 22 | perf.display_lookback_returns() 23 | 24 | # Monthly Returns 25 | for asset in prices.columns: 26 | print(f"\nMonthly Returns for {asset.upper()}:") 27 | perf[asset].display_monthly_returns() 28 | 29 | # Performance Plot 30 | perf.plot() 31 | 32 | # Correlations 33 | perf.plot_correlation() 34 | 35 | # Drawdowns 36 | drawdowns = perf.prices.to_drawdown_series() 37 | drawdowns.plot() 38 | 39 | # BONUS: Get all of the stats as a data frame 40 | df = pd.DataFrame() 41 | for asset in list(perf.keys()): 42 | stats = perf[asset].stats 43 | stats.name = asset 44 | df = pd.concat([df, stats], axis= 1) 45 | df 46 | -------------------------------------------------------------------------------- /QS021-mplfinance/01_mplfinance.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 021: Matplotlib Finance 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import yfinance as yf 9 | import mplfinance as mpf 10 | import warnings 11 | warnings.filterwarnings('ignore') 12 | 13 | data = yf.download("AAPL", start="2022-01-01", end="2022-06-30") 14 | 15 | mpf.plot(data) 16 | 17 | mpf.plot(data, type="candle") 18 | 19 | mpf.plot(data, type="line") 20 | 21 | mpf.plot(data, type="renko") 22 | 23 | mpf.plot(data, type="ohlc", mav=15) 24 | 25 | mpf.plot(data, type="candle", mav=(7, 14, 21)) 26 | 27 | mpf.plot(data, type="candle", mav=(7, 14, 21), volume=True) 28 | 29 | mpf.plot( 30 | data, 31 | type="candle", 32 | mav=(7, 14, 21), 33 | volume=True, 34 | show_nontrading=True 35 | ) 36 | 37 | intraday = yf.download(tickers="PLTR", period="5d", interval="1m") 38 | iday = intraday.iloc[-100:, :] 39 | mpf.plot(iday, type="candle", mav=(7, 12), volume=True) 40 | 41 | 42 | -------------------------------------------------------------------------------- /QS022-hrp/01_hrp.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 022: Hierarchical Risk Parity (HRP) Portfolio Optimization 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import pandas as pd 9 | import riskfolio as rp 10 | import yfinance as yf 11 | import warnings 12 | warnings.filterwarnings("ignore") 13 | 14 | # Define a list of asset symbols for which historical price data will be retrieved 15 | 16 | assets = [ 17 | "XLE", "XLF", "XLU", "XLI", "GDX", 18 | "XLK", "XLV", "XLY", "XLP", "XLB", 19 | "XOP", "IYR", "XHB", "ITB", "VNQ", 20 | "GDXJ", "IYE", "OIH", "XME", "XRT", 21 | "SMH", "IBB", "KBE", "KRE", "XTL", 22 | ] 23 | 24 | # Fetch historical price data for the specified assets and pivot the data into a DataFrame 25 | 26 | data = ( 27 | yf 28 | .download(assets)["Adj Close"] 29 | ) 30 | 31 | # Calculate percentage returns from the historical price data and drop any missing values 32 | 33 | returns = data.pct_change().dropna() 34 | 35 | # Plot a dendrogram to visualize hierarchical clustering of asset returns using Pearson correlation 36 | 37 | ax = rp.plot_dendrogram( 38 | returns=returns, 39 | codependence="pearson", 40 | linkage="single", 41 | k=None, 42 | max_k=10, 43 | leaf_order=True, 44 | ax=None, 45 | ) 46 | 47 | # Create an instance of HCPortfolio with the calculated returns for portfolio optimization 48 | 49 | port = rp.HCPortfolio(returns=returns) 50 | 51 | # Optimize the portfolio using Hierarchical Risk Parity (HRP) with specified parameters 52 | 53 | w = port.optimization( 54 | model="HRP", 55 | codependence="pearson", 56 | rm="MV", 57 | rf=0.05, 58 | linkage="single", 59 | max_k=10, 60 | leaf_order=True, 61 | ) 62 | 63 | # Plot a pie chart to visualize the portfolio allocation resulting from the HRP optimization 64 | 65 | ax = rp.plot_pie( 66 | w=w, 67 | title="HRP Naive Risk Parity", 68 | others=0.05, 69 | nrow=25, 70 | cmap="tab20", 71 | height=8, 72 | width=10, 73 | ax=None, 74 | ) 75 | 76 | # Plot the risk contributions of each asset in the optimized portfolio 77 | 78 | ax = rp.plot_risk_con( 79 | w=w, 80 | cov=returns.cov(), 81 | returns=returns, 82 | rm="MV", 83 | rf=0, 84 | alpha=0.05, 85 | color="tab:blue", 86 | height=6, 87 | width=10, 88 | t_factor=252, 89 | ax=None, 90 | ) 91 | -------------------------------------------------------------------------------- /QS023-kmeans/01_kmeans.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 023: K-Means Clustering for Algorithmic Trading 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | from math import sqrt 9 | import pandas as pd 10 | import matplotlib.pyplot as plt 11 | from sklearn.cluster import KMeans 12 | import yfinance as yf 13 | 14 | # Load Dow Jones data 15 | dji = pd.read_html('https://en.wikipedia.org/wiki/Dow_Jones_Industrial_Average')[2] 16 | symbols = dji.Symbol.tolist() 17 | data = yf.download(symbols, start="2020-01-01", end="2024-12-31")["Adj Close"] 18 | 19 | # Calculate returns and volatility (annualized) 20 | moments = ( 21 | data 22 | .pct_change() 23 | .describe() 24 | .T[["mean", "std"]] 25 | .rename(columns={"mean": "returns", "std": "vol"}) 26 | ) * [252, sqrt(252)] 27 | 28 | # Plot Elbow Curve to find optimal number of clusters 29 | sse = [] 30 | for k in range(2, 15): 31 | kmeans = KMeans(n_clusters=k, n_init=10) 32 | kmeans.fit(moments) 33 | sse.append(kmeans.inertia_) 34 | 35 | plt.figure(figsize=(8, 5)) 36 | plt.plot(range(2, 15), sse, marker='o', linestyle='-', color='b') 37 | plt.title("Elbow Curve for KMeans Clustering") 38 | plt.xlabel("Number of Clusters") 39 | plt.ylabel("Sum of Squared Errors (SSE)") 40 | plt.grid(True) 41 | plt.show() 42 | 43 | # Perform KMeans clustering with k=5 44 | kmeans = KMeans(n_clusters=5, n_init=10).fit(moments) 45 | labels = kmeans.labels_ 46 | 47 | # Scatter plot of Dow Jones stocks by returns and volatility 48 | plt.figure(figsize=(10, 7)) 49 | scatter = plt.scatter( 50 | moments.returns, 51 | moments.vol, 52 | c=labels, 53 | cmap="rainbow", 54 | edgecolor='k', 55 | s=100 56 | ) 57 | plt.title("Dow Jones Stocks by Return and Volatility (K=5)") 58 | plt.xlabel("Annualized Returns") 59 | plt.ylabel("Annualized Volatility") 60 | 61 | # Annotate each point with the stock symbol 62 | for i, symbol in enumerate(moments.index): 63 | plt.annotate( 64 | symbol, 65 | (moments.returns[i], moments.vol[i]), 66 | textcoords="offset points", 67 | xytext=(5, 5), # Offset text to avoid overlap with point 68 | ha='center', 69 | fontsize=8, 70 | weight='bold' 71 | ) 72 | 73 | # Add color bar to indicate clusters 74 | plt.colorbar(scatter, label="Cluster Label") 75 | plt.grid(True) 76 | plt.show() 77 | 78 | # Annotate points with stock symbols and cluster labels 79 | for i, txt in enumerate(moments.index): 80 | plt.annotate(f"{txt} ({labels[i]})", 81 | (moments.returns[i], moments.vol[i] + 0.05), 82 | fontsize=8, 83 | ha="center") 84 | 85 | # Add a color bar to show cluster groups 86 | plt.colorbar(scatter, label="Cluster Label") 87 | plt.grid(True) 88 | plt.show() 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /QS024-downside-deviation/01_downside_deviation.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 024: Downside Deviation vs Volatility 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import numpy as np 9 | import yfinance as yf 10 | 11 | data = yf.download("AAPL") 12 | returns = data["Adj Close"].pct_change() 13 | 14 | 15 | def downside_deviation(returns): 16 | # Initialize an empty array to store downside deviation values 17 | out = np.empty(returns.shape[1:]) 18 | 19 | # Clip returns at zero to focus on negative returns 20 | downside_diff = np.clip(returns, np.NINF, 0) 21 | 22 | # Square the clipped values to calculate the squared deviations 23 | np.square(downside_diff, out=downside_diff) 24 | 25 | # Calculate the mean of squared deviations ignoring NaNs 26 | np.nanmean(downside_diff, axis=0, out=out) 27 | 28 | # Take the square root of the mean squared deviations 29 | np.sqrt(out, out=out) 30 | 31 | # Annualize the downside deviation by multiplying by the square root of 252 32 | np.multiply(out, np.sqrt(252), out=out) 33 | 34 | # Return the annualized downside deviation as a single value 35 | return out.item() 36 | 37 | dd = downside_deviation(returns) 38 | 39 | vol = np.sqrt(np.square(returns).mean()) * np.sqrt(252) 40 | 41 | dd / vol 42 | 43 | # If dd/vol is greater than 1, then the downside deviation is greater than the volatility 44 | 45 | # If dd/vol is less than 1, then the downside deviation is less than the volatility 46 | 47 | # If dd/vol is equal to 1, then the downside deviation is equal to the volatility -------------------------------------------------------------------------------- /QS025-autoencoders/01_autoencoders.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 025: Autoencoders For Algorithmic Trading 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import yfinance as yf 9 | import pandas as pd 10 | import numpy as np 11 | import torch 12 | import torch.nn as nn 13 | from torch.utils.data import DataLoader, TensorDataset 14 | from sklearn.cluster import KMeans 15 | from sklearn.decomposition import PCA 16 | import matplotlib.pyplot as plt 17 | import seaborn as sns 18 | import warnings 19 | warnings.filterwarnings("ignore") 20 | 21 | 22 | symbols = [ 23 | "AAPL", "MSFT", "GOOGL", "AMZN", "META", 24 | "TSLA", "BRK-B", "V", "JNJ", "WMT", "JPM", 25 | "MA", "PG", "UNH", "DIS", "NVDA", "HD", 26 | "PYPL", "BAC", "VZ", "ADBE", "CMCSA", "NFLX", 27 | "KO", "NKE", "MRK", "PEP", "T", "PFE", "INTC", 28 | ] 29 | 30 | stock_data = yf.download( 31 | symbols, 32 | start="2020-01-01", 33 | end="2023-12-31" 34 | )["Adj Close"] 35 | 36 | 37 | log_returns = np.log(stock_data / stock_data.shift(1)) 38 | moving_avg = stock_data.rolling(window=22).mean() 39 | volatility = stock_data.rolling(window=22).std() 40 | 41 | features = pd.concat([log_returns, moving_avg, volatility], axis=1).dropna() 42 | processed_data = (features - features.mean()) / features.std() 43 | 44 | 45 | tensor = torch.tensor(processed_data.values, dtype=torch.float32) 46 | dataset = TensorDataset(tensor) 47 | data_loader = DataLoader(dataset, batch_size=32, shuffle=True) 48 | 49 | class StockAutoencoder(nn.Module): 50 | """Autoencoder neural network for stock data embedding 51 | 52 | This class defines an autoencoder with an encoder and decoder 53 | to compress and reconstruct stock data. 54 | 55 | Parameters 56 | ---------- 57 | feature_dim : int 58 | The dimensionality of the input features 59 | """ 60 | 61 | def __init__(self, feature_dim): 62 | super(StockAutoencoder, self).__init__() 63 | self.encoder = nn.Sequential( 64 | nn.Linear(feature_dim, 64), 65 | nn.ReLU(), 66 | nn.Linear(64, 32), 67 | nn.ReLU(), 68 | nn.Linear(32, 10), # Latent space 69 | ) 70 | self.decoder = nn.Sequential( 71 | nn.Linear(10, 32), 72 | nn.ReLU(), 73 | nn.Linear(32, 64), 74 | nn.ReLU(), 75 | nn.Linear(64, feature_dim), 76 | nn.ReLU(), 77 | ) 78 | 79 | def forward(self, x): 80 | x = self.encoder(x) 81 | x = self.decoder(x) 82 | return x 83 | 84 | def train(model, data_loader, epochs=100): 85 | """Train the autoencoder model 86 | 87 | This function trains the autoencoder using MSE loss and Adam optimizer 88 | over a specified number of epochs. 89 | 90 | Parameters 91 | ---------- 92 | model : nn.Module 93 | The autoencoder model to be trained 94 | data_loader : DataLoader 95 | DataLoader object to iterate through the dataset 96 | epochs : int, optional 97 | Number of epochs to train the model (default is 100) 98 | """ 99 | 100 | criterion = nn.MSELoss() 101 | optimizer = torch.optim.Adam(model.parameters(), lr=0.001) 102 | model.train() 103 | for epoch in range(epochs): 104 | for data in data_loader: 105 | inputs = data[0] 106 | optimizer.zero_grad() 107 | outputs = model(inputs) 108 | loss = criterion(outputs, inputs) 109 | loss.backward() 110 | optimizer.step() 111 | print(f"Epoch {epoch+1}, Loss: {loss.item()}") 112 | 113 | feature_dim = processed_data.shape[1] 114 | model = StockAutoencoder(feature_dim) 115 | train(model, data_loader) 116 | 117 | def extract_embeddings(model, data_loader): 118 | """Extract embeddings from the trained autoencoder model 119 | 120 | This function extracts embeddings by passing data through the encoder 121 | part of the autoencoder. 122 | 123 | Parameters 124 | ---------- 125 | model : nn.Module 126 | The trained autoencoder model 127 | data_loader : DataLoader 128 | DataLoader object to iterate through the dataset 129 | 130 | Returns 131 | ------- 132 | embeddings : torch.Tensor 133 | Tensor containing the extracted embeddings 134 | """ 135 | 136 | model.eval() 137 | embeddings = [] 138 | with torch.no_grad(): 139 | for data in data_loader: 140 | inputs = data[0] 141 | encoded = model.encoder(inputs) 142 | embeddings.append(encoded) 143 | return torch.vstack(embeddings) 144 | 145 | 146 | embeddings = extract_embeddings(model, data_loader) 147 | 148 | kmeans = KMeans(n_clusters=5, random_state=42).fit(embeddings.numpy()) 149 | clusters = kmeans.labels_ 150 | 151 | pca = PCA(n_components=2) 152 | embeddings_2d = pca.fit_transform(embeddings.numpy()) 153 | 154 | plt.figure(figsize=(12, 8)) 155 | scatter = sns.scatterplot( 156 | x=embeddings_2d[:, 0], 157 | y=embeddings_2d[:, 1], 158 | hue=clusters, 159 | palette=sns.color_palette("hsv", len(set(clusters))), 160 | s=100, # Increase marker size 161 | edgecolor='k' 162 | ) 163 | plt.xlabel("PCA Dimension 1") 164 | plt.ylabel("PCA Dimension 2") 165 | plt.title("PCA Plot of Stock Embeddings with Clusters") 166 | plt.legend(title="Cluster") 167 | plt.grid(True) 168 | 169 | # Adding stock symbols with enhanced readability 170 | for i, symbol in enumerate(symbols): 171 | plt.annotate( 172 | symbol, 173 | (embeddings_2d[i, 0], embeddings_2d[i, 1]), 174 | textcoords="offset points", 175 | xytext=(8, 8), # Increase offset to reduce overlap 176 | ha='center', 177 | fontsize=9, 178 | fontweight='bold', 179 | bbox=dict(boxstyle="round,pad=0.3", fc="white", alpha=0.7, edgecolor="gray") 180 | ) 181 | 182 | plt.show() 183 | 184 | 185 | -------------------------------------------------------------------------------- /QS026-markov/01_markov.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 026: Markov Models For Algorithmic Trading 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import numpy as np 9 | import pandas as pd 10 | import yfinance as yf 11 | from hmmlearn import hmm 12 | 13 | # Download historical price data for SPY from Yahoo Finance 14 | 15 | data = yf.download("SPY") 16 | 17 | # Calculate log returns of the closing prices 18 | 19 | returns = np.log(data.Close / data.Close.shift(1)) 20 | 21 | # Calculate the range as the difference between high and low prices 22 | 23 | range = (data.High - data.Low) 24 | 25 | # Concatenate returns and range into a single DataFrame and drop any missing values 26 | 27 | features = pd.concat([returns, range], axis=1).dropna() 28 | features.columns = ["returns", "range"] 29 | 30 | # Initialize a Gaussian Hidden Markov Model with 3 states and fit it to the features 31 | 32 | model = hmm.GaussianHMM( 33 | n_components=3, 34 | covariance_type="full", 35 | n_iter=1000, 36 | ) 37 | model.fit(features) 38 | 39 | # Predict the hidden states for the given features and store them in a Series 40 | 41 | states = pd.Series(model.predict(features), index=data.index[1:]) 42 | states.name = "state" 43 | 44 | # Plot a histogram of the hidden states 45 | 46 | states.hist() 47 | 48 | # Define a color map for the different states 49 | 50 | color_map = { 51 | 0.0: "green", 52 | 1.0: "orange", 53 | 2.0: "red" 54 | } 55 | 56 | # Concatenate the closing prices and the states, drop missing values, 57 | # set state as a hierarchical index, unstack the state index, and plot the closing prices with different colors for each state 58 | 59 | ( 60 | pd.concat([data.Close, states], axis=1) 61 | .dropna() 62 | .set_index("state", append=True) 63 | .Close 64 | .unstack("state") 65 | .plot(color=color_map) 66 | ) 67 | -------------------------------------------------------------------------------- /QS027-pcr/01_pcr.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 027: Principal Component Regression for Algorithmic Trading 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import yfinance as yf 9 | import riskfolio as rf 10 | import pandas as pd 11 | import warnings 12 | pd.options.display.float_format = "{:.4%}".format 13 | warnings.filterwarnings("ignore") 14 | 15 | 16 | mag_7 = [ 17 | "AMZN", 18 | "AAPL", 19 | "NVDA", 20 | "META", 21 | "TSLA", 22 | "MSFT", 23 | "GOOG", 24 | ] 25 | 26 | factors = ["MTUM", "QUAL", "VLUE", "SIZE", "USMV"] 27 | 28 | start = "2020-01-01" 29 | end = "2024-07-31" 30 | 31 | 32 | port_returns = ( 33 | yf 34 | .download( 35 | mag_7, 36 | start=start, 37 | end=end 38 | )["Adj Close"] 39 | .pct_change() 40 | .dropna() 41 | ) 42 | 43 | factor_returns = ( 44 | yf 45 | .download( 46 | factors, 47 | start=start, 48 | end=end 49 | )["Adj Close"] 50 | .pct_change() 51 | .dropna() 52 | ) 53 | 54 | 55 | port = rf.Portfolio(returns=port_returns) 56 | 57 | port.assets_stats(method_mu="hist", method_cov="ledoit") 58 | 59 | port.lowerret = 0.00056488 * 1.5 60 | 61 | loadings = rf.loadings_matrix( 62 | X=factor_returns, 63 | Y=port_returns, 64 | feature_selection="PCR", 65 | n_components=0.95 66 | ) 67 | 68 | loadings.style.format("{:.4f}").background_gradient(cmap='RdYlGn') 69 | 70 | 71 | port.factors = factor_returns 72 | port.factors_stats( 73 | method_mu="hist", 74 | method_cov="ledoit", 75 | dict_risk=dict( 76 | n_components=0.95 # 95% of explained variance. 77 | ) 78 | ) 79 | 80 | w = port.optimization( 81 | model="FM", # Factor model 82 | rm="MV", # Risk measure used, this time will be variance 83 | obj="Sharpe", # Objective function, could be MinRisk, MaxRet, Utility or Sharpe 84 | hist=False, # Use risk factor model for expected returns 85 | ) 86 | 87 | ax = rf.plot_pie( 88 | w=w, 89 | title='Sharpe FM Mean Variance', 90 | others=0.05, 91 | nrow=25, 92 | cmap="tab20" 93 | ) 94 | -------------------------------------------------------------------------------- /QS028-flow-effects/01_flow_effects.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 028: Build a Strategy with a 471.9% return 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import pandas as pd 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | import vectorbt as vbt 12 | import warnings 13 | warnings.filterwarnings("ignore") 14 | 15 | # Download historical price data for TLT ETF from Yahoo Finance and extract the closing prices 16 | 17 | tlt = vbt.YFData.download( 18 | "TLT", 19 | start="2004-01-01", 20 | end="2024-12-01" 21 | ).get("Close").to_frame() 22 | close = tlt.Close 23 | 24 | # Set up empty dataframes to hold trading signals for short and long positions 25 | 26 | short_entries = pd.DataFrame.vbt.signals.empty_like(close) 27 | short_exits = pd.DataFrame.vbt.signals.empty_like(close) 28 | long_entries = pd.DataFrame.vbt.signals.empty_like(close) 29 | long_exits = pd.DataFrame.vbt.signals.empty_like(close) 30 | 31 | # Generate short entry signals on the first day of each new month 32 | 33 | short_entry_mask = ~tlt.index.tz_convert(None).to_period("M").duplicated() 34 | short_entries.iloc[short_entry_mask] = True 35 | 36 | # Generate short exit signals five days after short entry 37 | 38 | short_exit_mask = short_entries.shift(5).fillna(False) 39 | short_exits.iloc[short_exit_mask] = True 40 | 41 | # Generate long entry signals seven days before the end of each month 42 | 43 | long_entry_mask = short_entries.shift(-7).fillna(False) 44 | long_entries.iloc[long_entry_mask] = True 45 | 46 | # Generate long exit signals one day before the end of each month 47 | 48 | long_exit_mask = short_entries.shift(-1).fillna(False) 49 | long_exits.iloc[long_exit_mask] = True 50 | 51 | # Run the simulation and calculate the Sharpe ratio for the trading strategy 52 | 53 | pf = vbt.Portfolio.from_signals( 54 | close=close, 55 | entries=long_entries, 56 | exits=long_exits, 57 | short_entries=short_entries, 58 | short_exits=short_exits, 59 | freq="1d" 60 | ) 61 | pf.stats() 62 | 63 | # Generate a plot with the strategy's performance. 64 | 65 | pf.plot().show() 66 | 67 | -------------------------------------------------------------------------------- /QS029-buffett/01_buffett.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 029: Use Python to save 197,291 financial ratios like Warren Buffet 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import io 9 | import os 10 | import time 11 | import requests 12 | import pandas as pd 13 | import arcticdb as adb 14 | 15 | # First, we establish a connection to the ArcticDB database and define a helper function to construct API URLs. 16 | 17 | arctic = adb.Arctic("lmdb://fundamantals") 18 | lib = arctic.get_library("financial_ratios", create_if_missing=True) 19 | 20 | def build_fmp_url(request, period, year): 21 | apikey = os.environ.get("FMP_API_KEY") 22 | return f"https://financialmodelingprep.com/api/v4/{request}?year={year}&period={period}&apikey={apikey}" 23 | 24 | # Next, we define a function that retrieves financial data from the FMP API and converts it into a pandas DataFrame. 25 | 26 | def get_fmp_data(request, period, year): 27 | url = build_fmp_url(request, period, year) 28 | response = requests.get(url) 29 | csv = response.content.decode("utf-8") 30 | return pd.read_csv(io.StringIO(csv), parse_dates=True) 31 | 32 | ratios = get_fmp_data("ratios-bulk", "quarter", "2020") 33 | ratios 34 | 35 | # Now, we iterate over multiple years to store their financial ratios into the ArcticDB. 36 | 37 | for year in [2020, 2021, 2022]: 38 | ratios = get_fmp_data("ratios-bulk", "quarter", year) 39 | adb_sym = f"financial_ratios/{year}" 40 | adb_fcn = lib.update if lib.has_symbol(adb_sym) else lib.write 41 | adb_fcn(adb_sym, ratios) 42 | time.sleep(10) 43 | 44 | # Finally, we define a function to filter financial data based on specific criteria and return the results as a pandas DataFrame. 45 | 46 | def filter_by_year(year): 47 | cols = [ 48 | "symbol", 49 | "period", 50 | "date", 51 | "debtEquityRatio", 52 | "currentRatio", 53 | "priceToBookRatio", 54 | "returnOnEquity", 55 | "returnOnAssets", 56 | "interestCoverage" 57 | ] 58 | q = adb.QueryBuilder() 59 | filter = ( 60 | (q["debtEquityRatio"] < 0.5) 61 | & ( 62 | (q["currentRatio"] > 1.5) & (q["currentRatio"] < 2.5) 63 | ) 64 | & (q["priceToBookRatio"] < 1.5) 65 | & (q["returnOnEquity"] > 0.08) 66 | & (q["returnOnAssets"] > 0.06) 67 | & (q["interestCoverage"] > 5) 68 | ) 69 | q = q[filter] 70 | return lib.read(f"financial_ratios/{year}", query_builder=q).data[cols].set_index("symbol") 71 | 72 | filter_by_year("2020") 73 | -------------------------------------------------------------------------------- /QS030-omega/01_omega.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 030: Using the Omega ratio for responsible algo trading 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import yfinance as yf 9 | import numpy as np 10 | 11 | # Download the stock data for AAPL from Yahoo Finance for the specified date range 12 | 13 | data = yf.download("AAPL", start="2020-01-01", end="2021-12-31") 14 | 15 | returns = data["Adj Close"].pct_change() 16 | 17 | # Calculate the Omega ratio of a strategy's returns 18 | 19 | def omega_ratio(returns, required_return=0.0): 20 | """Determines the Omega ratio of a strategy. 21 | 22 | Parameters 23 | ---------- 24 | returns : pd.Series or np.ndarray 25 | Daily returns of the strategy, noncumulative. 26 | required_return : float, optional 27 | Minimum acceptance return of the investor. Threshold over which to 28 | consider positive vs negative returns. It will be converted to a 29 | value appropriate for the period of the returns. E.g. An annual minimum 30 | acceptable return of 100 will translate to a minimum acceptable 31 | return of 0.018. 32 | 33 | Returns 34 | ------- 35 | omega_ratio : float 36 | 37 | Note 38 | ----- 39 | See https://en.wikipedia.org/wiki/Omega_ratio for more details. 40 | """ 41 | 42 | # Convert the required return to a daily return threshold 43 | return_threshold = (1 + required_return) ** (1 / 252) - 1 44 | 45 | # Calculate the difference between returns and the return threshold 46 | returns_less_thresh = returns - return_threshold 47 | 48 | # Calculate the numerator as the sum of positive returns above the threshold 49 | numer = sum(returns_less_thresh[returns_less_thresh > 0.0]) 50 | 51 | # Calculate the denominator as the absolute sum of negative returns below the threshold 52 | denom = -1.0 * sum(returns_less_thresh[returns_less_thresh < 0.0]) 53 | 54 | # Return the Omega ratio if the denominator is positive; otherwise, return NaN 55 | if denom > 0.0: 56 | return numer / denom 57 | else: 58 | return np.nan 59 | 60 | # Calculate the Omega ratio for the given returns and required return 61 | 62 | omega_ratio(returns, 0.07) 63 | 64 | # Compute and plot the rolling 30-day Omega ratio of the returns 65 | 66 | returns.rolling(30).apply(omega_ratio).plot() 67 | 68 | # Plot a histogram of the daily returns to visualize their distribution 69 | 70 | returns.hist(bins=50) 71 | -------------------------------------------------------------------------------- /QS031-kelly/01_kelly.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 031: Use the Kelly criterion for optimal position sizing 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import numpy as np 9 | from scipy.optimize import minimize_scalar 10 | from scipy.integrate import quad 11 | from scipy.stats import norm 12 | import yfinance as yf 13 | import warnings 14 | warnings.filterwarnings("ignore") 15 | 16 | # Fetch annual returns for the S&P 500 index since 1950 and compute rolling mean and standard deviation over a 25-year window 17 | 18 | annual_returns = ( 19 | yf.download(["^GSPC"])[["Adj Close"]] 20 | .resample("YE") 21 | .last() 22 | .pct_change() 23 | .dropna() 24 | .rename(columns={"Adj Close": "^GSPC"}) 25 | ) 26 | 27 | return_params = annual_returns["^GSPC"].rolling(25).agg(["mean", "std"]).dropna() 28 | 29 | # Define a function to calculate the negative value of the expected log return 30 | 31 | def norm_integral(f, mean, std): 32 | """Calculates the negative expected log return 33 | 34 | Parameters 35 | ---------- 36 | f : float 37 | Leverage factor 38 | mean : float 39 | Mean return 40 | std : float 41 | Standard deviation of returns 42 | 43 | Returns 44 | ------- 45 | float 46 | Negative expected log return 47 | """ 48 | val, er = quad( 49 | lambda s: np.log(1 + f * s) * norm.pdf(s, mean, std), 50 | mean - 3 * std, 51 | mean + 3 * std, 52 | ) 53 | return -val 54 | 55 | # Define a function to optimize the Kelly fraction using the minimize_scalar method 56 | 57 | def get_kelly(data): 58 | """Optimizes the Kelly fraction 59 | 60 | Parameters 61 | ---------- 62 | data : pd.Series 63 | Contains mean and standard deviation of returns 64 | 65 | Returns 66 | ------- 67 | float 68 | Optimal Kelly fraction 69 | """ 70 | solution = minimize_scalar( 71 | norm_integral, args=(data["mean"], data["std"]), bounds=[0, 2], method="bounded" 72 | ) 73 | return solution.x 74 | 75 | # Calculate the Kelly fraction for each rolling window and add it to the annual returns DataFrame. Then visualize the cumulative compounded returns using the Kelly strategy 76 | 77 | annual_returns["f"] = return_params.apply(get_kelly, axis=1) 78 | 79 | ( 80 | annual_returns[["^GSPC"]] 81 | .assign(kelly=annual_returns["^GSPC"].mul(annual_returns.f.shift())) 82 | .dropna() 83 | .loc["1900":] 84 | .add(1) 85 | .cumprod() 86 | .sub(1) 87 | .plot(lw=2) 88 | ) 89 | 90 | # Pick an arbitrary point for mean and standard deviation to calculate optimal Kelly fraction. Optimize the Kelly fraction for the given mean and standard deviation. 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. 91 | 92 | m = .058 93 | s = .216 94 | 95 | sol = minimize_scalar(norm_integral, args=(m, s), bounds=[0.0, 2.0], method="bounded") 96 | print("Optimal Kelly fraction: {:.4f}".format(sol.x)) 97 | -------------------------------------------------------------------------------- /QS032-information-ratio/01_information_ratio.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 032: How to measure your trading skill with the Information Ratio 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import pandas as pd 9 | import yfinance as yf 10 | 11 | # Download historical price data for QQQ, AAPL, and AMZN from Yahoo Finance 12 | 13 | data = yf.download(["QQQ", "AAPL", "AMZN"], start="2020-01-01", end="2022-07-31") 14 | 15 | # Extract adjusted close prices for the downloaded data 16 | 17 | closes = data['Adj Close'] 18 | benchmark_returns = closes.QQQ.pct_change() 19 | 20 | # Construct a simple portfolio with equal shares of AAPL and AMZN 21 | 22 | aapl_position = closes.AAPL * 50 23 | amzn_position = closes.AMZN * 50 24 | 25 | # Compute the portfolio value over time by summing the positions 26 | 27 | portfolio_value = aapl_position + amzn_position 28 | 29 | # Calculate the portfolio's daily profit and loss (PnL) 30 | 31 | portfolio_pnl = ( 32 | (aapl_position - aapl_position.shift()) 33 | + (amzn_position - amzn_position.shift()) 34 | ) 35 | 36 | # Compute the portfolio's daily return by dividing PnL by the portfolio value 37 | 38 | portfolio_returns = (portfolio_pnl / portfolio_value) 39 | portfolio_returns.name = "Port" 40 | 41 | # Create cumulative returns for both the portfolio and the benchmark 42 | 43 | portfolio_cumulative_returns = (portfolio_returns.fillna(0.0) + 1).cumprod() 44 | benchmark_cumulative_returns = (benchmark_returns.fillna(0.0) + 1).cumprod() 45 | 46 | # Plot the cumulative returns of the portfolio against the benchmark 47 | 48 | portfolio_cumulative_returns = (portfolio_returns.fillna(0.0) + 1).cumprod() 49 | benchmark_cumulative_returns = (benchmark_returns.fillna(0.0) + 1).cumprod() 50 | 51 | pd.concat([portfolio_cumulative_returns, benchmark_cumulative_returns], axis=1).plot() 52 | 53 | def information_ratio(portfolio_returns, benchmark_returns): 54 | """ 55 | Determines the information ratio of a strategy. 56 | 57 | Parameters 58 | ---------- 59 | portfolio_returns : pd.Series or np.ndarray 60 | Daily returns of the strategy, noncumulative. 61 | benchmark_returns : int, float 62 | Daily returns of the benchmark or factor, noncumulative. 63 | 64 | Returns 65 | ------- 66 | information_ratio : float 67 | 68 | Note 69 | ----- 70 | See https://en.wikipedia.org/wiki/Information_ratio for more details. 71 | """ 72 | 73 | # Calculate active return by subtracting benchmark returns from portfolio returns 74 | active_return = portfolio_returns - benchmark_returns 75 | 76 | # Calculate tracking error as the standard deviation of active returns 77 | tracking_error = active_return.std() 78 | 79 | # Return the information ratio, which is the mean active return divided by the tracking error 80 | return active_return.mean() / tracking_error 81 | 82 | # Calculate the information ratio of the portfolio relative to the benchmark 83 | 84 | information_ratio(portfolio_returns, benchmark_returns) 85 | 86 | 87 | -------------------------------------------------------------------------------- /QS034-optimize-exits/01_optimize_exits.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 034: Optimize your strategy to find profitable exits 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import pytz 9 | from datetime import datetime, timedelta 10 | import numpy as np 11 | import pandas as pd 12 | import vectorbt as vbt 13 | 14 | # Define stock symbols to be analyzed 15 | 16 | symbols = [ 17 | "META", 18 | "AMZN", 19 | "AAPL", 20 | "NFLX", 21 | "GOOG", 22 | ] 23 | 24 | # Define the start and end dates for historical data download 25 | 26 | start_date = datetime(2018, 1, 1, tzinfo=pytz.utc) 27 | end_date = datetime(2021, 1, 1, tzinfo=pytz.utc) 28 | 29 | # Define various simulation parameters including number of stocks to trade, window length, seed, window count, exit types, and stop values 30 | 31 | traded_count = 3 32 | window_len = timedelta(days=12 * 21) 33 | 34 | seed = 42 35 | window_count = 400 36 | exit_types = ["SL", "TS", "TP"] 37 | stops = np.arange(0.01, 1 + 0.01, 0.01) 38 | 39 | # Download historical stock data using vectorbt's YFData module and concatenate the data into a single DataFrame 40 | 41 | yfdata = vbt.YFData.download(symbols, start=start_date, end=end_date) 42 | ohlcv = yfdata.concat() 43 | 44 | # Split the OHLCV data into windows defined by the window length and count 45 | 46 | split_ohlcv = {} 47 | 48 | for k, v in ohlcv.items(): 49 | split_df, split_indexes = v.vbt.range_split( 50 | range_len=window_len.days, n=window_count 51 | ) 52 | split_ohlcv[k] = split_df 53 | ohlcv = split_ohlcv 54 | 55 | # Calculate the momentum as the mean percentage change of the closing prices, then select the top stocks based on momentum 56 | 57 | momentum = ohlcv["Close"].pct_change().mean() 58 | 59 | sorted_momentum = ( 60 | momentum 61 | .groupby( 62 | "split_idx", 63 | group_keys=False, 64 | sort=False 65 | ) 66 | .apply( 67 | pd.Series.sort_values 68 | ) 69 | .groupby("split_idx") 70 | .head(traded_count) 71 | ) 72 | 73 | # Select the OHLCV data for the stocks with the highest momentum 74 | 75 | selected_open = ohlcv["Open"][sorted_momentum.index] 76 | selected_high = ohlcv["High"][sorted_momentum.index] 77 | selected_low = ohlcv["Low"][sorted_momentum.index] 78 | selected_close = ohlcv["Close"][sorted_momentum.index] 79 | 80 | # Initialize entry signals to be true on the first day of each window 81 | 82 | entries = pd.DataFrame.vbt.signals.empty_like(selected_open) 83 | entries.iloc[0, :] = True 84 | 85 | # Define stop loss exits using vectorbt's OHLCSTX module 86 | 87 | sl_exits = vbt.OHLCSTX.run( 88 | entries, 89 | selected_open, 90 | selected_high, 91 | selected_low, 92 | selected_close, 93 | sl_stop=list(stops), 94 | stop_type=None, 95 | stop_price=None, 96 | ).exits 97 | 98 | # Define trailing stop exits using vectorbt's OHLCSTX module 99 | 100 | ts_exits = vbt.OHLCSTX.run( 101 | entries, 102 | selected_open, 103 | selected_high, 104 | selected_low, 105 | selected_close, 106 | sl_stop=list(stops), 107 | sl_trail=True, 108 | stop_type=None, 109 | stop_price=None, 110 | ).exits 111 | 112 | # Define take profit exits using vectorbt's OHLCSTX module 113 | 114 | tp_exits = vbt.OHLCSTX.run( 115 | entries, 116 | selected_open, 117 | selected_high, 118 | selected_low, 119 | selected_close, 120 | tp_stop=list(stops), 121 | stop_type=None, 122 | stop_price=None, 123 | ).exits 124 | 125 | # Rename and drop levels for the different exit types to standardize the DataFrame structure 126 | 127 | sl_exits.vbt.rename_levels({"ohlcstx_sl_stop": "stop_value"}, inplace=True) 128 | ts_exits.vbt.rename_levels({"ohlcstx_sl_stop": "stop_value"}, inplace=True) 129 | tp_exits.vbt.rename_levels({"ohlcstx_tp_stop": "stop_value"}, inplace=True) 130 | ts_exits.vbt.drop_levels("ohlcstx_sl_trail", inplace=True) 131 | 132 | # Ensure the last day in the window is always an exit signal for all exit types 133 | 134 | sl_exits.iloc[-1, :] = True 135 | ts_exits.iloc[-1, :] = True 136 | tp_exits.iloc[-1, :] = True 137 | 138 | # Convert exits into first exit signals based on entries, allowing gaps 139 | 140 | sl_exits = sl_exits.vbt.signals.first(reset_by=entries, allow_gaps=True) 141 | ts_exits = ts_exits.vbt.signals.first(reset_by=entries, allow_gaps=True) 142 | tp_exits = tp_exits.vbt.signals.first(reset_by=entries, allow_gaps=True) 143 | 144 | # Concatenate all exit signals into a single DataFrame for further analysis 145 | 146 | exits = pd.DataFrame.vbt.concat( 147 | sl_exits, 148 | ts_exits, 149 | tp_exits, 150 | keys=pd.Index(exit_types, name="exit_type"), 151 | ) 152 | 153 | # Create a portfolio using the selected close prices, entries, and exits 154 | 155 | portfolio = vbt.Portfolio.from_signals(selected_close, entries, exits) 156 | 157 | # Calculate the total return of the portfolio 158 | 159 | total_return = portfolio.total_return() 160 | 161 | # Unstack the total returns by exit type for visualization 162 | 163 | total_return_by_type = total_return.unstack(level="exit_type")[exit_types] 164 | 165 | # Plot histograms of the total returns for each exit type 166 | 167 | total_return_by_type[exit_types].vbt.histplot( 168 | xaxis_title="Total return", 169 | xaxis_tickformat="%", 170 | yaxis_title="Count", 171 | ) 172 | 173 | # Plot boxplots of the total returns for each exit type 174 | 175 | total_return_by_type.vbt.boxplot( 176 | yaxis_title='Total return', 177 | yaxis_tickformat='%' 178 | ) 179 | 180 | # Provide descriptive statistics for the total returns by exit type 181 | 182 | total_return_by_type.describe(percentiles=[]) 183 | -------------------------------------------------------------------------------- /QS035-autocorrelation/01_autocorrelation.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 035: Use autocorrelation to find a trend 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import yfinance as yf 9 | from statsmodels.graphics.tsaplots import plot_acf, plot_pacf 10 | 11 | # Download historical monthly price data for the E-mini S&P 500 Futures 12 | 13 | prices = yf.download("ES=F", start="2022-01-01", interval="1mo") 14 | 15 | # Compute the percentage change in closing prices to determine the returns 16 | 17 | returns = prices.Close.pct_change().dropna() 18 | 19 | # Create plots to visualize the autocorrelation and partial autocorrelation of the returns 20 | 21 | plot_acf(returns, lags=12); 22 | 23 | # The `plot_acf` function generates a plot of the autocorrelation function for the returns. The plot shows how the returns are correlated with their own past values at different lags, up to 12 months. Each vertical bar represents the correlation at a specific lag, and the shaded area indicates the confidence interval. If a bar extends beyond the shaded area, it suggests a statistically significant correlation at that lag. This helps in identifying any patterns or dependencies in the return series. 24 | 25 | plot_pacf(returns, lags=12); 26 | 27 | # The `plot_pacf` function generates a plot of the partial autocorrelation function for the returns. This plot shows the direct correlation between the returns and their lagged values, excluding the influence of intermediate lags. Like the autocorrelation plot, the partial autocorrelation plot includes bars for each lag and shaded confidence intervals. By examining these plots, we can identify significant autoregressive patterns that might be useful for modeling and forecasting future returns. 28 | -------------------------------------------------------------------------------- /QS037-tensortrade/01_tensortrade.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 037: A new library that uses reinforcement learning for trading 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import yfinance 9 | import pandas_ta #noqa 10 | 11 | 12 | TICKER = 'NVDA' # TODO: replace this with your own ticker 13 | TRAIN_START_DATE = '2021-01-01' # TODO: replace this with your own start date 14 | TRAIN_END_DATE = '2022-12-31' # TODO: replace this with your own end date 15 | EVAL_START_DATE = '2023-01-01' # TODO: replace this with your own end date 16 | EVAL_END_DATE = '2023-01-31' # TODO: replace this with your own end date 17 | 18 | yf_ticker = yfinance.Ticker(ticker=TICKER) 19 | 20 | df_training = yf_ticker.history(start=TRAIN_START_DATE, end=TRAIN_END_DATE) 21 | df_training.drop(['Dividends', 'Stock Splits'], axis=1, inplace=True) 22 | df_training["Volume"] = df_training["Volume"].astype(int) 23 | df_training.ta.log_return(append=True, length=16) 24 | df_training.ta.rsi(append=True, length=14) 25 | df_training.ta.macd(append=True, fast=12, slow=26) 26 | df_training.to_csv('training.csv', index=False) 27 | 28 | df_evaluation = yf_ticker.history(start=EVAL_START_DATE, end=EVAL_END_DATE) 29 | df_evaluation.drop(['Dividends', 'Stock Splits'], axis=1, inplace=True) 30 | df_evaluation["Volume"] = df_evaluation["Volume"].astype(int) 31 | df_evaluation.ta.log_return(append=True, length=16) 32 | df_evaluation.ta.rsi(append=True, length=14) 33 | df_evaluation.ta.macd(append=True, fast=12, slow=26) 34 | df_evaluation.to_csv('evaluation.csv', index=False) 35 | 36 | 37 | import pandas as pd 38 | from tensortrade.feed.core import DataFeed, Stream 39 | from tensortrade.oms.instruments import Instrument 40 | from tensortrade.oms.exchanges import Exchange, ExchangeOptions 41 | from tensortrade.oms.services.execution.simulated import execute_order 42 | from tensortrade.oms.wallets import Wallet, Portfolio 43 | import tensortrade.env.default as default 44 | 45 | def create_env(config): 46 | dataset = pd.read_csv(filepath_or_buffer=config["csv_filename"], parse_dates=['Datetime']).fillna(method='backfill').fillna(method='ffill') 47 | ttse_commission = 0.0035 # TODO: adjust according to your commission percentage, if present 48 | price = Stream.source(list(dataset["Close"]), dtype="float").rename("USD-TTRD") 49 | ttse_options = ExchangeOptions(commission=ttse_commission) 50 | ttse_exchange = Exchange("TTSE", service=execute_order, options=ttse_options)(price) 51 | 52 | # Instruments, Wallets and Portfolio 53 | USD = Instrument("USD", 2, "US Dollar") 54 | TTRD = Instrument("TTRD", 2, "TensorTrade Corp") 55 | cash = Wallet(ttse_exchange, 1000 * USD) # This is the starting cash we are going to use 56 | asset = Wallet(ttse_exchange, 0 * TTRD) # And we will start owning 0 stocks of TTRD 57 | portfolio = Portfolio(USD, [cash, asset]) 58 | 59 | # Renderer feed 60 | renderer_feed = DataFeed([ 61 | Stream.source(list(dataset["Datetime"])).rename("date"), 62 | Stream.source(list(dataset["Open"]), dtype="float").rename("open"), 63 | Stream.source(list(dataset["High"]), dtype="float").rename("high"), 64 | Stream.source(list(dataset["Low"]), dtype="float").rename("low"), 65 | Stream.source(list(dataset["Close"]), dtype="float").rename("close"), 66 | Stream.source(list(dataset["Volume"]), dtype="float").rename("volume") 67 | ]) 68 | 69 | features = [] 70 | for c in dataset.columns[1:]: 71 | s = Stream.source(list(dataset[c]), dtype="float").rename(dataset[c].name) 72 | features += [s] 73 | feed = DataFeed(features) 74 | feed.compile() 75 | 76 | reward_scheme = default.rewards.SimpleProfit(window_size=config["reward_window_size"]) 77 | action_scheme = default.actions.BSH(cash=cash, asset=asset) 78 | 79 | env = default.create( 80 | feed=feed, 81 | portfolio=portfolio, 82 | action_scheme=action_scheme, 83 | reward_scheme=reward_scheme, 84 | renderer_feed=renderer_feed, 85 | renderer=[], 86 | window_size=config["window_size"], 87 | max_allowed_loss=config["max_allowed_loss"] 88 | ) 89 | 90 | return env 91 | 92 | 93 | 94 | import ray 95 | import os 96 | from ray import tune 97 | from ray.tune.registry import register_env 98 | 99 | # Let's define some tuning parameters 100 | FC_SIZE = tune.grid_search([[256, 256], [1024], [128, 64, 32]]) # Those are the alternatives that ray.tune will try... 101 | LEARNING_RATE = tune.grid_search([0.001, 0.0005, 0.00001]) # ... and they will be combined with these ones ... 102 | MINIBATCH_SIZE = tune.grid_search([5, 10, 20]) # ... and these ones, in a cartesian product. 103 | 104 | # Get the current working directory 105 | cwd = os.getcwd() 106 | 107 | # Initialize Ray 108 | ray.init() # There are *LOTS* of initialization parameters, like specifying the maximum number of CPUs\GPUs to allocate. For now just leave it alone. 109 | 110 | # Register our environment, specifying which is the environment creation function 111 | register_env("MyTrainingEnv", create_env) 112 | 113 | # Specific configuration keys that will be used during training 114 | env_config_training = { 115 | "window_size": 14, # We want to look at the last 14 samples (hours) 116 | "reward_window_size": 7, # And calculate reward based on the actions taken in the next 7 hours 117 | "max_allowed_loss": 0.10, # If it goes past 10% loss during the iteration, we don't want to waste time on a "loser". 118 | "csv_filename": os.path.join(cwd, 'training.csv'), # The variable that will be used to differentiate training and validation datasets 119 | } 120 | # Specific configuration keys that will be used during evaluation (only the overridden ones) 121 | env_config_evaluation = { 122 | "max_allowed_loss": 1.00, # During validation runs we want to see how bad it would go. Even up to 100% loss. 123 | "csv_filename": os.path.join(cwd, 'evaluation.csv'), # The variable that will be used to differentiate training and validation datasets 124 | } 125 | 126 | analysis = tune.run( 127 | run_or_experiment="PPO", # We'll be using the builtin PPO agent in RLLib 128 | name="MyExperiment1", 129 | metric='episode_reward_mean', 130 | mode='max', 131 | stop={ 132 | "training_iteration": 5 # Let's do 5 steps for each hyperparameter combination 133 | }, 134 | config={ 135 | "env": "MyTrainingEnv", 136 | "env_config": env_config_training, # The dictionary we built before 137 | "log_level": "WARNING", 138 | "framework": "torch", 139 | "ignore_worker_failures": True, 140 | "num_workers": 1, # One worker per agent. You can increase this but it will run fewer parallel trainings. 141 | "num_envs_per_worker": 1, 142 | "num_gpus": 0, # I yet have to understand if using a GPU is worth it, for our purposes, but I think it's not. This way you can train on a non-gpu enabled system. 143 | "clip_rewards": True, 144 | "lr": LEARNING_RATE, # Hyperparameter grid search defined above 145 | "gamma": 0.50, # This can have a big impact on the result and needs to be properly tuned (range is 0 to 1) 146 | "observation_filter": "MeanStdFilter", 147 | "model": { 148 | "fcnet_hiddens": FC_SIZE, # Hyperparameter grid search defined above 149 | }, 150 | "sgd_minibatch_size": MINIBATCH_SIZE, # Hyperparameter grid search defined above 151 | "evaluation_interval": 1, # Run evaluation on every iteration 152 | "evaluation_config": { 153 | "env_config": env_config_evaluation, # The dictionary we built before (only the overriding keys to use in evaluation) 154 | "explore": False, # We don't want to explore during evaluation. All actions have to be repeatable. 155 | }, 156 | }, 157 | num_samples=1, # Have one sample for each hyperparameter combination. You can have more to average out randomness. 158 | keep_checkpoints_num=10, # Keep the last 2 checkpoints 159 | checkpoint_freq=1, # Do a checkpoint on each iteration (slower but you can pick more finely the checkpoint to use later) 160 | ) 161 | -------------------------------------------------------------------------------- /QS037-tensortrade/evaluation.csv: -------------------------------------------------------------------------------- 1 | Open,High,Low,Close,Volume,LOGRET_16,RSI_14 2 | 14.838839325800745,14.983721052232674,14.084457431263646,14.303277969360352,401277000,, 3 | 14.555073030909787,14.840838154777497,14.229339743985019,14.736923217773438,431324000,, 4 | 14.479134517120297,14.552074696828278,14.136414963987127,14.25331974029541,389168000,, 5 | 14.462150107377472,14.997711535520914,14.0225107548316,14.846835136413574,405044000,, 6 | 15.271486598821125,16.042853882336043,15.128603078053724,15.615204811096191,504231000,, 7 | 15.49430474778188,15.948932173589144,15.459333553934178,15.895976066589355,384101000,, 8 | 15.827032288803178,16.0148781028567,15.550258635785255,15.987899780273438,353285000,, 9 | 16.08681762259126,16.623376674174565,15.479314701266622,16.497480392456055,551409000,, 10 | 16.264674246702192,16.908148056338185,16.15176778844737,16.88516616821289,447287000,, 11 | 16.885165521758307,17.713487319827372,16.885165521758307,17.68750762939453,511102000,, 12 | 17.652537414377978,17.858368155388927,17.267852350964912,17.362775802612305,439624000,, 13 | 17.02205215333367,17.182921549244565,16.717303453615347,16.75127410888672,452932000,, 14 | 16.997072985244962,17.84138241393569,16.811226343623662,17.82439613342285,564967000,, 15 | 18.049208922830783,19.22924361779151,17.803411956257197,19.17728614807129,655163000,, 16 | 18.811584516005322,19.479038745727824,18.804590277949554,19.249225616455078,496204000,,82.38509339963672 17 | 18.897514353205867,19.354140898523635,18.56478682693734,19.307178497314453,449537000,,82.59093948400928 18 | 19.684866646452846,20.149486000547537,19.262212714711314,19.785783767700195,489535000,0.32447494866724114,84.22995248207772 19 | 19.446067014153495,20.61111281542293,19.38911447092714,20.34832763671875,542142000,0.3226426004469616,85.9091709823185 20 | 19.9336676842303,20.123510758938984,19.13432150128274,19.146312713623047,488611000,0.2951203056815382,69.00190339499565 21 | -------------------------------------------------------------------------------- /QS038-fast-fourier-transform/01_fast-fourier-transform.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 038: Using the Fast Fourier Transformation 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import numpy as np 9 | import yfinance as yf 10 | import pandas as pd 11 | import matplotlib.pyplot as plt 12 | import warnings 13 | warnings.filterwarnings("ignore") 14 | 15 | # Download the price data from Yahoo 16 | prices = yf.download("GS", start="2020-01-01").Close 17 | 18 | # Fit the transformation and add the absolute value and angle 19 | close_fft = np.fft.fft(prices.GS.values) 20 | fft_df = pd.DataFrame({'fft': close_fft}) 21 | fft_df['absolute'] = fft_df['fft'].apply(lambda x: np.abs(x)) 22 | fft_df['angle'] = fft_df['fft'].apply(lambda x: np.angle(x)) 23 | 24 | # + 25 | # Plot across a series of transformations 26 | plt.figure(figsize=(14, 6)) 27 | fft_list = np.asarray(fft_df['fft'].values) 28 | 29 | for num_ in [3, 6, 9, 100]: 30 | fft_list_m10 = np.copy(fft_list) 31 | fft_list_m10[num_:-num_] = 0 32 | plt.plot(np.fft.ifft(fft_list_m10), label=f'Fourier transform with {num_} components') 33 | 34 | plt.plot(prices.GS.values, label='Real') 35 | plt.xlabel('Days') 36 | plt.ylabel('Price') 37 | plt.title('Goldman Sachs (close) stock prices & Fourier transforms') 38 | plt.legend() 39 | plt.show() 40 | -------------------------------------------------------------------------------- /QS039-graph-optimization/01_graph_optimization.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 039: Using graphs to improve your Sharpe ratio 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import warnings 9 | 10 | import matplotlib.pyplot as plt 11 | import numpy as np 12 | import pandas as pd 13 | import riskfolio as rp 14 | import yfinance as yf 15 | 16 | warnings.filterwarnings("ignore") 17 | pd.options.display.float_format = "{:.4%}".format 18 | 19 | # Date range 20 | start = "2016-01-01" 21 | end = "2019-12-30" 22 | 23 | # Tickers of assets. Use whatever you want. 24 | assets = [ 25 | "JCI", 26 | "TGT", 27 | "CMCSA", 28 | "CPB", 29 | "MO", 30 | "APA", 31 | "MMC", 32 | "JPM", 33 | "ZION", 34 | "PSA", 35 | "BAX", 36 | "BMY", 37 | "LUV", 38 | "PCAR", 39 | "TXT", 40 | "TMO", 41 | "DE", 42 | "MSFT", 43 | "HPQ", 44 | "SEE", 45 | "VZ", 46 | "CNP", 47 | "NI", 48 | "T", 49 | "BA", 50 | ] 51 | assets.sort() 52 | 53 | # Downloading data 54 | data = yf.download(assets, start=start, end=end).Close 55 | 56 | Y = data.pct_change().dropna() 57 | 58 | # Building the portfolio object 59 | port = rp.Portfolio(returns=Y) 60 | 61 | # Calculating optimal portfolio 62 | 63 | # Select method and estimate input parameters: 64 | 65 | method_mu = "hist" # Method to estimate expected returns based on historical data. 66 | method_cov = "hist" # Method to estimate covariance matrix based on historical data. 67 | 68 | port.assets_stats(method_mu=method_mu, method_cov=method_cov) 69 | 70 | # Estimate optimal portfolio: 71 | 72 | model = "Classic" # Could be Classic (historical), BL (Black Litterman) or FM (Factor Model) 73 | rm = "MV" # Risk measure used, this time will be variance 74 | obj = "MinRisk" # Objective function, could be MinRisk, MaxRet, Utility or Sharpe 75 | hist = True # Use historical scenarios for risk measures that depend on scenarios 76 | rf = 0 # Risk free rate 77 | l = 0 # Risk aversion factor, only useful when obj is 'Utility' 78 | 79 | w = port.optimization(model=model, rm=rm, obj=obj, rf=rf, l=l, hist=hist) 80 | 81 | # Plotting the composition of the portfolio in MST 82 | fig, ax = plt.subplots(2, 1, figsize=(10, 16)) 83 | ax = np.ravel(ax) 84 | 85 | ax[0] = rp.plot_network_allocation( 86 | returns=Y, 87 | w=w, 88 | codependence="pearson", 89 | linkage="ward", 90 | alpha_tail=0.05, 91 | node_labels=True, 92 | leaf_order=True, 93 | kind="kamada", 94 | seed=123, 95 | ax=ax[0], 96 | ) 97 | 98 | # Plotting the composition of the portfolio in the Dendrogram Cluster Network 99 | ax[1] = rp.plot_clusters_network_allocation( 100 | returns=Y, w=w, codependence="pearson", linkage="ward", k=None, max_k=10, ax=ax[1] 101 | ) 102 | -------------------------------------------------------------------------------- /QS040-cvar/01_cvar.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 040: Use CVaR to keep the money you make trading 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import numpy as np 9 | import pandas as pd 10 | import yfinance as yf 11 | import matplotlib.pyplot as plt 12 | 13 | # Define a list of stock tickers representing the portfolio components 14 | 15 | oex = ['MMM','T','ABBV','ABT','ACN','ALL','GOOGL','GOOG','MO','AMZN','AXP','AIG','AMGN','AAPL','BAC', 16 | 'BRK-B','BIIB','BLK','BA','BMY','CVS','COF','CAT','CVX','CSCO','C','KO','CL','CMCSA', 17 | 'COP','DHR','DUK','DD','EMC','EMR','EXC','XOM','META','FDX','F','GD','GE','GM','GILD', 18 | 'GS','HAL','HD','HON','INTC','IBM','JPM','JNJ','KMI','LLY','LMT','LOW','MA','MCD','MDT','MRK', 19 | 'MET','MSFT','MS','NKE','NEE','OXY','ORCL','PYPL','PEP','PFE','PM','PG','QCOM', 20 | 'SLB','SPG','SO','SBUX','TGT','TXN','BK','USB','UNP','UPS','UNH','VZ','V','WMT', 21 | 'WBA','DIS','WFC'] 22 | 23 | # Count the number of stocks in the portfolio 24 | 25 | num_stocks = len(oex) 26 | 27 | # Download historical stock data for the defined period 28 | 29 | data = yf.download(oex, start='2014-01-01', end='2016-04-04') 30 | 31 | # Calculate daily returns and de-mean the returns by subtracting the mean 32 | 33 | returns = data.Close.pct_change(fill_method=None) 34 | returns = returns - returns.mean(skipna=True) 35 | 36 | 37 | def scale(x): 38 | return x / np.sum(np.abs(x)) 39 | 40 | # Generate random weights for the portfolio and scale them 41 | 42 | weights = scale(np.random.random(num_stocks)) 43 | plt.bar(np.arange(num_stocks), weights) 44 | 45 | # Define a function to calculate Value at Risk (VaR) 46 | 47 | def value_at_risk(value_invested, returns, weights, alpha=0.95, lookback_days=500): 48 | # Fill missing values in returns with zero and calculate portfolio returns 49 | returns = returns.fillna(0.0) 50 | portfolio_returns = returns.iloc[-lookback_days:].dot(weights) 51 | 52 | # Calculate the VaR as the percentile of portfolio returns 53 | return np.percentile(portfolio_returns, 100 * (1 - alpha)) * value_invested 54 | 55 | # Define parameters for lookback days and confidence level 56 | 57 | # Define a function to calculate Conditional Value at Risk (CVaR) 58 | 59 | def cvar(value_invested, returns, weights, alpha=0.95, lookback_days=500): 60 | # Calculate VaR and portfolio returns for the specified lookback period 61 | var = value_at_risk(value_invested, returns, weights, alpha, lookback_days=lookback_days) 62 | 63 | returns = returns.fillna(0.0) 64 | portfolio_returns = returns.iloc[-lookback_days:].dot(weights) 65 | var_pct_loss = var / value_invested 66 | 67 | # Calculate the mean of returns below the VaR threshold 68 | return np.nanmean(portfolio_returns[portfolio_returns < var_pct_loss]) * value_invested 69 | 70 | # Calculate CVaR using the defined function 71 | 72 | value_invested = 100_000 73 | cvar(value_invested, returns, weights, lookback_days=500) 74 | 75 | # Calculate VaR again for consistency 76 | 77 | value_at_risk(value_invested, returns, weights, lookback_days=500) 78 | 79 | # Calculate portfolio returns using historical data and weights 80 | 81 | lookback_days = 500 82 | 83 | portfolio_returns = returns.fillna(0.0).iloc[-lookback_days:].dot(weights) 84 | 85 | # Calculate VaR and CVaR and express them as returns 86 | 87 | portfolio_VaR = value_at_risk(value_invested, returns, weights) 88 | portfolio_VaR_return = portfolio_VaR / value_invested 89 | 90 | portfolio_CVaR = cvar(value_invested, returns, weights) 91 | portfolio_CVaR_return = portfolio_CVaR / value_invested 92 | 93 | # Plot histogram of portfolio returns, marking VaR and CVaR on the plot 94 | 95 | plt.hist(portfolio_returns[portfolio_returns > portfolio_VaR_return], bins=20) 96 | plt.hist(portfolio_returns[portfolio_returns < portfolio_VaR_return], bins=10) 97 | plt.axvline(portfolio_VaR_return, color='red', linestyle='solid') 98 | plt.axvline(portfolio_CVaR_return, color='red', linestyle='dashed') 99 | plt.legend(['VaR', 'CVaR', 'Returns', 'Returns < VaR']) 100 | plt.title('Historical VaR and CVaR') 101 | plt.xlabel('Return') 102 | plt.ylabel('Observation Frequency') 103 | 104 | 105 | -------------------------------------------------------------------------------- /QS041-hurst-exponent/01_husrt_exponent.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 041: Trend or momentum? Use the Hurst Exponent to find out 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | import pandas as pd 9 | import numpy as np 10 | import yfinance as yf 11 | 12 | # Load historical S&P 500 data from 2000 to 2019 using the OpenBB SDK and select the adjusted close prices 13 | 14 | df = yf.download("^GSPC", start="2000-01-01", end="2019-12-31").Close 15 | 16 | # Plot the S&P 500 adjusted close prices to visualize the historical data 17 | 18 | df.plot(title="S&P 500") 19 | 20 | 21 | def get_hurst_exponent(ts, max_lag=20): 22 | # Define the range of lags to be used in the calculation 23 | lags = range(2, max_lag) 24 | 25 | # Calculate the standard deviation of differences for each lag 26 | tau = [np.std(np.subtract(ts[lag:], ts[:-lag])) for lag in lags] 27 | 28 | # Perform a linear fit to estimate the Hurst exponent 29 | return np.polyfit(np.log(lags), np.log(tau), 1)[0] 30 | 31 | 32 | # Calculate and print the Hurst exponent for various lags using the full dataset 33 | 34 | for lag in [20, 100, 250, 500, 1000]: 35 | hurst_exp = get_hurst_exponent(df.values, lag) 36 | print(f"{lag} lags: {hurst_exp:.4f}") 37 | 38 | 39 | # Select a shorter series from 2005 to 2007 and calculate the Hurst exponent for various lags 40 | 41 | shorter_series = df.loc["2005":"2007"].values 42 | for lag in [20, 100, 250, 500]: 43 | hurst_exp = get_hurst_exponent(shorter_series, lag) 44 | print(f"{lag} lags: {hurst_exp:.4f}") 45 | 46 | 47 | # Calculate rolling volatility using a 30-day window and plot the results to observe changes over time 48 | 49 | rv = df.rolling(30).apply(np.std) 50 | rv.plot() 51 | 52 | 53 | # Calculate and print the Hurst exponent for various lags using the rolling volatility data 54 | 55 | for lag in [20, 100, 250, 500, 1000]: 56 | hurst_exp = get_hurst_exponent(rv.dropna().values, lag) 57 | print(f"{lag} lags: {hurst_exp:.4f}") 58 | -------------------------------------------------------------------------------- /QS043-3-day-pullback/01_3_day_pullback.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 043: A new strategy with 77% win rate (with Python code) 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | 9 | import pandas as pd 10 | import numpy as np 11 | import yfinance as yf 12 | 13 | # Define the ticker symbol and download historical data from Yahoo Finance 14 | 15 | ticker = "SPY" 16 | data = yf.download(ticker).Close[["SPY"]] 17 | data.columns = ["close"] 18 | 19 | # Calculate daily returns by finding the difference between consecutive closing prices 20 | 21 | data["return"] = data.close.diff() 22 | 23 | # Identify days with negative returns to find losing days in the dataset 24 | 25 | data["down"] = data["return"] < 0 26 | 27 | # Identify 3-day losing streaks by checking for three consecutive losing days 28 | 29 | data["3_day_losing_streak"] = ( 30 | data["down"] & data["down"].shift(1) & data["down"].shift(2) 31 | ) 32 | 33 | # Initialize a column to track the number of days since the last 3-day losing streak 34 | 35 | data["days_since_last_streak"] = np.nan 36 | 37 | # Iterate over the data to calculate the days since the last 3-day losing streak 38 | 39 | last_streak_day = -np.inf # Initialize with a very large negative value 40 | 41 | for i in range(len(data)): 42 | if data["3_day_losing_streak"].iloc[i]: 43 | if i - last_streak_day >= 42: # Check if it's been at least 42 trading days 44 | data.loc[data.index[i], "days_since_last_streak"] = i - last_streak_day 45 | last_streak_day = i 46 | 47 | # Filter the data to show only the occurrences that meet the criteria 48 | 49 | result = data.dropna(subset=["days_since_last_streak"]).copy() 50 | 51 | # Calculate future returns following the identified streaks 52 | 53 | result["next_1_day_return"] = data.close.shift(-1) / data.close - 1 54 | result["next_5_day_return"] = data.close.shift(-5) / data.close - 1 55 | result["next_10_day_return"] = data.close.shift(-10) / data.close - 1 56 | result["next_21_day_return"] = data.close.shift(-21) / data.close - 1 57 | 58 | # Print the mean future returns for different time horizons 59 | 60 | cols = [ 61 | "next_1_day_return", 62 | "next_5_day_return", 63 | "next_10_day_return", 64 | "next_21_day_return" 65 | ] 66 | print(result[cols].mean()) 67 | 68 | # Plot the proportion of positive returns for the different time horizons 69 | 70 | result[cols].gt(0).mean().plot.bar() 71 | 72 | # Display the proportion of positive returns for the different time horizons 73 | 74 | result[cols].gt(0).mean() 75 | 76 | 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sunday Quant Scientist Newsletter 2 | 3 | Get our QS Newsletter every Sunday. [**Join Here**](https://learn.quantscience.io/quant-scientist-newsletter-register-9614) 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quant-science/sunday-quant-scientist/a9bf8863c5624fbb1b4ec28805a0be9cb56cf977/requirements.txt -------------------------------------------------------------------------------- /temp/10_lines_of_python/10_lines_of_python.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import yfinance as yf 3 | import matplotlib.pyplot as plt 4 | 5 | # This function will create a simple trading strategy based on moving averages 6 | def trading_strategy(ticker='AAPL', start='2020-01-01',): 7 | df = yf.download(ticker, start=start) 8 | 9 | # Drop the 'Ticker' level from columns 10 | df.columns = df.columns.droplevel(1) # Remove 'AAPL' level 11 | close_col = 'Close' # Now we can use simple column name 12 | 13 | df['SMA20'] = df[close_col].rolling(window=20).mean() 14 | df['SMA50'] = df[close_col].rolling(window=50).mean() 15 | 16 | # Detect crossovers 17 | df['Signal'] = 0 18 | df['Prev_SMA20'] = df['SMA20'].shift(1) 19 | df['Prev_SMA50'] = df['SMA50'].shift(1) 20 | 21 | # Buy signal: SMA20 crosses above SMA50 22 | df.loc[(df['SMA20'] > df['SMA50']) & (df['Prev_SMA20'] <= df['Prev_SMA50']), 'Signal'] = 1 23 | 24 | # Sell signal: SMA20 crosses below SMA50 25 | df.loc[(df['SMA20'] < df['SMA50']) & (df['Prev_SMA20'] >= df['Prev_SMA50']), 'Signal'] = -1 26 | 27 | return df 28 | 29 | result = trading_strategy() 30 | result 31 | 32 | 33 | # Plotting the trading strategy 34 | def plot_trading_strategy(df, ticker='AAPL'): 35 | plt.figure(figsize=(12, 6)) 36 | 37 | # Plot price 38 | plt.plot(df.index, df['Close'], label='Close Price', alpha=0.5) 39 | 40 | # Plot SMAs 41 | plt.plot(df.index, df['SMA20'], label='SMA20', alpha=0.8) 42 | plt.plot(df.index, df['SMA50'], label='SMA50', alpha=0.8) 43 | 44 | # Plot buy signals 45 | plt.scatter(df.index[df['Signal'] == 1], 46 | df['Close'][df['Signal'] == 1], 47 | color='green', 48 | label='Buy', 49 | marker='^', 50 | s=100) 51 | 52 | # Plot sell signals 53 | plt.scatter(df.index[df['Signal'] == -1], 54 | df['Close'][df['Signal'] == -1], 55 | color='red', 56 | label='Sell', 57 | marker='v', 58 | s=100) 59 | 60 | plt.title(f'{ticker} Trading Strategy - SMA Crossover') 61 | plt.xlabel('Date') 62 | plt.ylabel('Price') 63 | plt.legend() 64 | plt.grid(True, alpha=0.3) 65 | plt.tight_layout() 66 | plt.show() 67 | 68 | plot_trading_strategy(result) -------------------------------------------------------------------------------- /temp/QS011-pca-factor-exposure/01_pca_factor_exposure.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import yfinance as yf 5 | import pandas as pd 6 | import numpy as np 7 | 8 | import matplotlib.pyplot as plt 9 | 10 | import sklearn.decomposition as PCA 11 | 12 | 13 | # Load the data 14 | symbols = ["SPY", "QQQ", "IWM", "EFA", "EEM", "TLT", "LQD", "GLD", "USO", "VNQ"] 15 | start_date = "2021-01-31" 16 | end_date = "2023-12-28" 17 | 18 | data = yf.download(symbols, start=start_date, end=end_date, auto_adjust=True) 19 | 20 | portfolio_returns = data["Close"].pct_change().dropna() 21 | 22 | portfolio_returns.plot(title="Portfolio Returns", figsize=(12, 6)) 23 | 24 | pca = PCA.PCA(n_components=3) 25 | pca.fit(portfolio_returns) 26 | 27 | pct = pca.explained_variance_ratio_ 28 | pct 29 | 30 | sum(pct) 31 | 32 | pca_components = pca.components_ 33 | pca_components 34 | 35 | # Factor Returns 36 | 37 | X = np.array(portfolio_returns) 38 | 39 | factor_returns = X.dot(pca_components.T) 40 | 41 | factor_returns = pd.DataFrame( 42 | columns=["factor_1", "factor_2", "factor_3"], 43 | index=portfolio_returns.index, 44 | data=factor_returns, 45 | ) 46 | 47 | 48 | # Factor Exposures 49 | 50 | factor_exposures = pd.DataFrame( 51 | data=pca_components, 52 | index=["factor_1", "factor_2", "factor_3"], 53 | columns=portfolio_returns.columns, 54 | ).T 55 | 56 | # Mean Portfolio Returns by date 57 | 58 | mean_portfolio_returns = portfolio_returns.mean(axis=1) 59 | 60 | # Regress the portfolio returns on the factor exposures 61 | 62 | from statsmodels.api import OLS 63 | 64 | model = OLS(mean_portfolio_returns, factor_returns).fit() 65 | 66 | model.summary() 67 | 68 | beta = model.params 69 | 70 | 71 | hedged_portfolio_returns = -1 * beta * factor_returns + mean_portfolio_returns -------------------------------------------------------------------------------- /temp/QS017-clustering/01_nested_cluster_optimization: -------------------------------------------------------------------------------- 1 | 2 | 3 | from plotly.io import show 4 | from sklearn.cluster import KMeans 5 | from sklearn.model_selection import train_test_split 6 | 7 | from skfolio import Population, RiskMeasure 8 | from skfolio.cluster import HierarchicalClustering, LinkageMethod 9 | from skfolio.datasets import load_sp500_dataset 10 | from skfolio.distance import KendallDistance 11 | from skfolio.optimization import ( 12 | EqualWeighted, 13 | MeanRisk, 14 | NestedClustersOptimization, 15 | ObjectiveFunction, 16 | RiskBudgeting, 17 | ) 18 | from skfolio.preprocessing import prices_to_returns 19 | 20 | prices = load_sp500_dataset() 21 | 22 | X = prices_to_returns(prices) 23 | 24 | X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False) 25 | 26 | 27 | inner_estimator = MeanRisk( 28 | objective_function=ObjectiveFunction.MAXIMIZE_RATIO, 29 | risk_measure=RiskMeasure.VARIANCE, 30 | ) 31 | 32 | outer_estimator = RiskBudgeting(risk_measure=RiskMeasure.CVAR) 33 | 34 | 35 | 36 | inner_estimator = RiskBudgeting(risk_measure=RiskMeasure.CVAR) 37 | 38 | outer_estimator = MeanRisk( 39 | objective_function=ObjectiveFunction.MAXIMIZE_RATIO, 40 | risk_measure=RiskMeasure.VARIANCE, 41 | ) 42 | 43 | clustering_estimator = KMeans(n_init="auto", n_clusters=6) 44 | 45 | model1 = NestedClustersOptimization( 46 | inner_estimator=inner_estimator, 47 | outer_estimator=outer_estimator, 48 | clustering_estimator=clustering_estimator, 49 | n_jobs=-1, 50 | portfolio_params=dict(name="NCO-1"), 51 | ) 52 | model1.fit(X_train) 53 | model1.weights_ 54 | 55 | model1.clustering_estimator_ 56 | 57 | model1.clustering_estimator_ 58 | pred = model1.predict(X_train) 59 | 60 | model1.clustering_estimator_. 61 | 62 | # model1.clustering_estimator_.plot_dendrogram(heatmap=True) 63 | 64 | 65 | # Benchmark 66 | bench = EqualWeighted() 67 | bench.fit(X_train) 68 | bench.weights_ 69 | 70 | # Cummulative Returns 71 | population_test = Population([]) 72 | for model in [model1, bench]: 73 | population_test.append(model.predict(X_test)) 74 | 75 | population_test.plot_cumulative_returns() 76 | -------------------------------------------------------------------------------- /temp/QS018-stanley-druckenmiller-portfolio/01_nancy_pelosi_2_pyfolio.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 018: What can we learn about Nancy Pelosi's Portfolio - Part 2 (Pyfolio Analysis)? 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | # Libraries 9 | import riskfolio as rp 10 | import pandas as pd 11 | import yfinance as yf 12 | import pyfolio as pf 13 | 14 | assets = [ 15 | "PANW", 16 | "NVDA", 17 | "AAPL", 18 | "MSFT", 19 | "GOOG", 20 | "TSLA", 21 | "AB", 22 | "DIS", 23 | "AXP", 24 | "^GSPC", # SP500 benchmark 25 | ] 26 | 27 | # Step 1: Collect and format data 28 | data = yf.download(assets, start = "2018-01-01", end = "2024-07-10") 29 | data = data.loc[:, "Adj Close"] 30 | data 31 | 32 | # Step 2: Get returns 33 | returns = data.pct_change().dropna() 34 | returns_bench = returns.pop("^GSPC").to_frame() 35 | 36 | # Step 3: Create a Portfolio (Max Sharpe) 37 | port = rp.Portfolio(returns) 38 | 39 | w = port.optimization( 40 | model = "Classic", 41 | rm = "CVaR", 42 | obj = "Sharpe", 43 | hist = True, 44 | rf = 0, 45 | l = 0 46 | ) 47 | w 48 | 49 | # Step 4: Calculate the portfolio returns 50 | portfolio_returns = (returns * w.weights).sum(axis=1) 51 | 52 | portfolio_returns 53 | 54 | # Step 5: Analyze Returns with Pyfolio 55 | 56 | pf.show_perf_stats(portfolio_returns) 57 | 58 | pf.plot_drawdown_periods(portfolio_returns) 59 | 60 | pf.plot_drawdown_underwater(portfolio_returns) 61 | 62 | pf.plot_rolling_sharpe(portfolio_returns) 63 | 64 | pf.create_full_tear_sheet( 65 | portfolio_returns, 66 | benchmark_rets=returns_bench['^GSPC'] 67 | ) 68 | 69 | -------------------------------------------------------------------------------- /temp/QS018-stanley-druckenmiller-portfolio/01_stanley_druckenmiller_pyfolio.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 018: What can we learn about Stanley Druckenmiller's Portfolio (Pyfolio Analysis)? 3 | 4 | # READY TO LEARN ALGORITHMIC TRADING WITH US? 5 | # Register for our Course Waitlist: 6 | # https://learn.quantscience.io/python-algorithmic-trading-course-waitlist 7 | 8 | # https://x.com/marketplunger1/status/1813311433075286424 9 | 10 | 11 | # Libraries 12 | 13 | import pandas as pd 14 | import pandas as pd 15 | import yfinance as yf 16 | import riskfolio as rp 17 | import pyfolio as pf 18 | 19 | # DATA FROM 13F FILING ---- 20 | 21 | data = { 22 | "stock": ["IWM", "MSFT", "CPNG", "TECK", "VST", "NTRA", "NVDA", "COHR", "GE", "WWD", "STX", "ANET", "FLEX", "ZI", "NWSA", "DFS", "VRT", "KMI", "FCX", "KBR", "MRVL", "WAB", "LLY", "PANW", "CHX"], 23 | "sector": ["FINANCE", "INFORMATION TECHNOLOGY", "CONSUMER DISCRETIONARY", "MATERIALS", "ENERGY", "HEALTH CARE", "INFORMATION TECHNOLOGY", "INDUSTRIALS", "INDUSTRIALS", "INDUSTRIALS", "INFORMATION TECHNOLOGY", "INFORMATION TECHNOLOGY", "INFORMATION TECHNOLOGY", "COMMUNICATIONS", "COMMUNICATIONS", "FINANCE", "FINANCE", "UTILITIES AND TELECOMMUNICATIONS", "MATERIALS", "INDUSTRIALS", "INFORMATION TECHNOLOGY", "INDUSTRIALS", "HEALTH CARE", "INFORMATION TECHNOLOGY", "ENERGY"], 24 | "shares_held_or_principal_amt": [3157900, 1112270, 22452850, 452247, 2625231, 1929380, 1759430, 2525070, 1068928.04, 954230, 1438983, 428252, 3863155, 5818906, 3312025, 645205, 998510, 3880500, 1378875, 981425, 822875, 386835, 61464, 138718, 848710], 25 | "market_value": [664106000, 467954000, 399561000, 208396000, 182847000, 176461000, 158975000, 153070000, 149744000, 147066000, 133897000, 124185000, 110525000, 94287000, 86709000, 84580000, 81548000, 71168000, 64835000, 62478000, 58325000, 56354000, 47972000, 39414000, 36766000], 26 | "pct_of_portfolio": [15.12, 10.65, 9.10, 4.72, 4.16, 4.02, 3.62, 3.49, 3.41, 3.35, 3.05, 2.83, 2.52, 2.15, 1.97, 1.93, 1.86, 1.55, 1.48, 1.42, 1.33, 1.28, 1.09, 0.90, 0.84], 27 | "previous_pct_of_portfolio": [0, 12.19, 11.07, 0, 2.74, 1.67, 9.12, 0, 2.80, 1.64, 5.39, 1.65, 1.03, 0.51, 2.96, 0, 3.32, 0, 0.60, 1.92, 0.65, 0, 7.00, 0.57, 1.71], 28 | "rank": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 29 | "change_in_shares": [3157900, 26150, -455990, -972375, 240170, 1036350, -4415510, 2525070, 146691, 54595, -677125, 194067, 2728755, 4957086, -733095, 645205, -1314990, 3880500, 907340, -177110, 459290, 386835, -340886, 74043, -483915], 30 | "pct_change": ["New", 2.41, -1.98, -17.71, 10.07, 116.05, -71.51, "New", 15.89, 135.82, -32.00, 82.87, 240.55, 536.50, -18.12, "New", -56.84, "New", 192.42, -15.29, 126.32, "New", -84.68, 114.35, -36.31] 31 | } 32 | 33 | df = pd.DataFrame(data) 34 | 35 | df 36 | 37 | # PORTFOLIO ANALYSIS AND OPTIMIZATION ---- 38 | 39 | # Step 1: Collect and Format Data 40 | 41 | stock_data = yf.download(df['stock'].tolist(), start = '2023-07-16', end = '2024-07-16') 42 | 43 | stock_data = stock_data.loc[:, "Adj Close"] 44 | 45 | stock_data 46 | 47 | stock_data.dropna().plot() 48 | 49 | # Step 2: Get Returns 50 | 51 | returns = stock_data.pct_change().dropna() 52 | 53 | returns 54 | 55 | # Step 3: Get Portfolio Returns 56 | 57 | w = df[['stock', 'pct_of_portfolio']].set_index('stock').sort_index() 58 | 59 | portfolio_returns = (returns * w.pct_of_portfolio).sum(axis=1) 60 | 61 | portfolio_returns.cumsum().plot() 62 | 63 | portfolio_returns.plot() 64 | 65 | pf.show_perf_stats(portfolio_returns) 66 | 67 | pf.plot_drawdown_periods(portfolio_returns) 68 | 69 | pf.plot_rolling_sharpe(portfolio_returns) 70 | 71 | 72 | # Step 4: Optimization 73 | 74 | port = rp.Portfolio(returns) 75 | 76 | w = port.optimization( 77 | model = "Classic", 78 | rm = "CVaR", 79 | obj = "Sharpe", 80 | hist = True, 81 | rf = 0, 82 | l = 0 83 | ) 84 | w 85 | 86 | -------------------------------------------------------------------------------- /temp/QS020-garch/01_garch.py: -------------------------------------------------------------------------------- 1 | # TODO -------------------------------------------------------------------------------- /temp/fast-rolling-functions/01_fast_rolling_functions.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 007: Pytimetk Algorithmic Trading System 3 | # Copyright: Quant Science, LLC 4 | 5 | # 1.0 IMPORT LIBRARIES ---- 6 | 7 | # Note: I'm using pytimetk==0.2.0 (just released) 8 | 9 | import pandas as pd 10 | import pytimetk as tk 11 | 12 | # 2.0 GET STOCK PRICE DATA ---- 13 | 14 | stocks_df = tk.load_dataset("stocks_daily") 15 | stocks_df['date'] = pd.to_datetime(stocks_df['date']) 16 | 17 | stocks_df.groupby('symbol').plot_timeseries( 18 | date_column = 'date', 19 | value_column = 'adjusted', 20 | facet_ncol = 2, 21 | width = 1100, 22 | height = 800, 23 | title = 'Stock Prices' 24 | ) 25 | 26 | # 3.0 Fast Rolling Computations ---- 27 | 28 | # 3.1 Pandas Lambda Functions ---- 29 | %%timeit 30 | rolled_df_pandas_slow = stocks_df \ 31 | .groupby('symbol') \ 32 | .augment_rolling( 33 | date_column = 'date', 34 | value_column = 'adjusted', 35 | window = [20, 50, 200], 36 | window_func = ('mean', lambda x: x.mean()), 37 | engine = 'pandas', 38 | show_progress = False 39 | ) 40 | # 288 ms ± 19.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 41 | 42 | # 3.2 Pandas Fast Rolling Functions ---- 43 | %%timeit 44 | rolled_df_pandas_fast = stocks_df \ 45 | .groupby('symbol') \ 46 | .augment_rolling( 47 | date_column = 'date', 48 | value_column = 'adjusted', 49 | window = [20, 50, 200], 50 | window_func = 'mean', 51 | engine = 'pandas', 52 | show_progress = False 53 | ) 54 | # 21.1 ms ± 2.84 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 55 | 56 | # 3.3 Polars Fast Rolling Functions ---- 57 | # 32X Faster than Pandas Slow 58 | # 2.4X Faster than Pandas Fast 59 | %%timeit 60 | rolled_df_polars = stocks_df \ 61 | .groupby('symbol') \ 62 | .augment_rolling( 63 | date_column = 'date', 64 | value_column = 'adjusted', 65 | window = [20, 50, 200], 66 | window_func = 'mean', 67 | engine = 'polars', 68 | show_progress = False 69 | ) 70 | # 8.86 ms ± 322 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 71 | 72 | 73 | # Bonus - plotting the results 74 | 75 | rolled_df_pandas_fast[['symbol', 'date', 'adjusted','adjusted_rolling_mean_win_20','adjusted_rolling_mean_win_50','adjusted_rolling_mean_win_200']] \ 76 | .melt( 77 | id_vars = ['symbol', 'date'], 78 | var_name = 'type', 79 | value_name = 'value' 80 | ) \ 81 | .groupby('symbol') \ 82 | .plot_timeseries( 83 | date_column = 'date', 84 | value_column = 'value', 85 | color_column = 'type', 86 | facet_ncol = 2, 87 | smooth = False, 88 | width = 1100, 89 | height = 800, 90 | title = 'Stock Prices with Rolling Means' 91 | ) 92 | -------------------------------------------------------------------------------- /temp/financial_statements.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /temp/put_to_call.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from openbb_terminal.sdk import openbb 4 | import pandas as pd 5 | import pytimetk as tk 6 | 7 | SYMBOL = "NVDA" 8 | START = pd.Timestamp.today() - pd.Timedelta(days=365) 9 | END = pd.Timestamp.today() 10 | 11 | prices_df = openbb.stocks.load(SYMBOL, start_date=START, end_date=END) 12 | 13 | vix = openbb.stocks.load("^VIX", start_date=START, end_date=END) 14 | 15 | pcr = openbb.stocks.options.pcr(SYMBOL) 16 | 17 | chains = openbb.stocks.options.chains(SYMBOL, "YahooFinance") 18 | 19 | pcr.reset_index() \ 20 | .plot_timeseries( 21 | date_column="Date", 22 | value_column="PCR", 23 | smooth_frac=0.05, 24 | title = f"{SYMBOL}: Put/Call Ratio", 25 | x_axis_date_labels = '%Y-%m-%d', 26 | ) 27 | 28 | 29 | -------------------------------------------------------------------------------- /temp/quantstats/01_quantstats.py: -------------------------------------------------------------------------------- 1 | # The Quant Science Newsletter 2 | # QS 007: Quantstats for Portfolio Analysis 3 | # Copyright: Quant Science, LLC 4 | 5 | # Libraries 6 | from openbb_terminal.sdk import openbb 7 | import quantstats as qs 8 | import pandas as pd 9 | import pytimetk as tk 10 | import matplotlib.pyplot as plt 11 | 12 | # Load the data 13 | data = openbb.stocks.load("AAPL", start_date="2012-06-01", end_date="2022-06-30") 14 | 15 | # Visaulize the data 16 | data \ 17 | .reset_index() \ 18 | .plot_timeseries( 19 | date_column="date", 20 | value_column="Adj Close", 21 | title="AAPL Adj Close", 22 | x_lab="Date", 23 | y_lab="Adj Close", 24 | ) 25 | 26 | # Calculate the returns 27 | aapl_returns = data['Adj Close'].pct_change() 28 | 29 | # Calculate the metrics 30 | qs.reports.metrics( 31 | aapl_returns, 32 | mode="full" 33 | ) 34 | 35 | # Plot the snapshot 36 | qs.plots.snapshot(aapl_returns) 37 | 38 | --------------------------------------------------------------------------------