import%20marimo%0A%0A__generated_with%20%3D%20%220.15.2%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%20Relative%20Rotation%20Graph%20(RRG)%20Implementation%0A%0A%20%20%20%20This%20script%20implements%20the%20famous%20**Relative%20Rotation%20Graph%20(RRG)**%20visualization%20technique%0A%20%20%20%20for%20analyzing%20relative%20strength%20and%20momentum%20of%20any%20list%20of%20assets%20against%20a%20benchmark.%0A%0A%20%20%20%20A%20Relative%20Rotation%20Graph%20(RRG)%20plots%20two%20normalized%20indicators%20for%20a%20universe%20of%20securities%20versus%20a%20benchmark%3A%0A%0A%20%20%20%20-%20**JdK%20RS-Ratio%20(horizontal%20axis)**%20%E2%80%94%20measures%20trend%20in%20relative%20performance%0A%20%20%20%20-%20**JdK%20RS-Momentum%20(vertical%20axis)**%20%E2%80%94%20momentum%20(rate-of-change)%20of%20the%20RS-Ratio%0A%0A%20%20%20%20RRG%20was%20developed%20by%20Julius%20de%20Kempenaer%20and%20visualizes%20relative%20performance%20in%20four%20quadrants%3A%0A%0A%20%20%20%20-%20%F0%9F%9F%A2%20**Leading%20(top-right)%3A**%20The%20asset%20is%20stronger%20than%20the%20benchmark%20and%20it's%20getting%20stronger%20(positive%20momentum).%0A%20%20%20%20-%20%F0%9F%9F%A1%20**Weakening**%20(bottom-right)%3A**%20The%20asset%20is%20stronger%20than%20the%20benchmark%2C%20but%20it's%20getting%20weaker%20(negative%20momentum).%0A%20%20%20%20-%20%F0%9F%94%B4%20**Lagging**%20(bottom-left)%3A**%20The%20asset%20is%20weaker%20than%20the%20benchmark%20and%20it's%20getting%20weaker.%0A%20%20%20%20-%20%F0%9F%94%B5%20**Improving**%20(top-right)%3A**%20The%20asset%20is%20weaker%20than%20the%20benchmark%2C%20but%20it's%20getting%20stronger.%0A%0A%20%20%20%20%2F%2F%2F%20details%20%7C%20References%3A%20%0A%20%20%20%20-%20%5BRRG%20Weights%20-%20Optuma%20Whitepaper%5D(https%3A%2F%2Fwww.optuma.com%2Fwp-content%2Fuploads%2F2023%2F02%2FRRG-Weights.pdf)%0A%20%20%20%20-%20%5BStockCharts%20%E2%80%93%20Relative%20Rotation%20Graphs%20(RRG)%5D(%5Bhttps%3A%2F%2Fschool.stockcharts.com%2Fdoku.php%3Fid%3Dchart_analysis%3Arrg_charts%5D(https%3A%2F%2Fchartschool.stockcharts.com%2Ftable-of-contents%2Ftechnical-indicators-and-overlays%2Ftechnical-indicators%2Frrg-relative-strength%3Futm_source%3Dchatgpt.com))%0A%20%20%20%20-%20%5BInvestopedia%20%E2%80%93%20Relative%20Rotation%20Graph%20(RRG)%5D(%5Bhttps%3A%2F%2Fwww.investopedia.com%2Fterms%2Fr%2Frelative-rotation-graph-rrg.asp%5D(https%3A%2F%2Fwww.investopedia.com%2Frelative-rotation-graph-8418457))%0A%20%20%20%20-%20%5BOfficial%20RRG%20educational%20page%5D(%5Bhttps%3A%2F%2Frelativerotationgraphs.com%2Fblog%2F%5D(https%3A%2F%2Frelativerotationgraphs.com%2Feducational%2F))%0A%0A%20%20%20%20%2F%2F%2F%0A%0A%20%20%20%20**Author%3A**%20Marcelo%20Wizenberg%0A%0A%20%20%20%20**Date%3A**%20September%2C%202025%0A%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%201.%20Library%20Imports%20%26%20Config%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20import%20os%0A%20%20%20%20import%20requests%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20yfinance%20as%20yf%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20matplotlib.patches%20as%20patches%0A%20%20%20%20from%20typing%20import%20List%2C%20Tuple%2C%20Optional%0A%20%20%20%20from%20datetime%20import%20datetime%2C%20timedelta%0A%20%20%20%20import%20warnings%0A%20%20%20%20warnings.filterwarnings('ignore')%0A%0A%20%20%20%20%23%20Configure%20matplotlib%20for%20better%20plots%0A%20%20%20%20plt.style.use('default')%0A%20%20%20%20plt.rcParams%5B'figure.figsize'%5D%20%3D%20(14%2C%2010)%0A%20%20%20%20plt.rcParams%5B'font.size'%5D%20%3D%2010%0A%0A%20%20%20%20%23%20Global%20configuration%20variables%0A%0A%20%20%20%20%23%20Available%20resampling%20frequencies%0A%20%20%20%20RESAMPLES%20%3D%20%5B'D'%2C%20'W'%2C%20'M'%5D%0A%0A%20%20%20%20%23%20Available%20asset%20universes%0A%20%20%20%20UNIVERSE%20%3D%20%5B'Brazilian%20Stocks'%2C%20'US%20Stocks'%2C%20'US%20Sectors'%2C%20'World%20iShares%20MSCI%20ETFs'%5D%0A%0A%20%20%20%20%23%20Fallback%20tickers%20for%20different%20universes%0A%20%20%20%20FALLBACK_TICKERS%20%3D%20%7B%0A%20%20%20%20%20%20%20%20'Brazilian%20Stocks'%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20'PETR4.SA'%2C%20'VALE3.SA'%2C%20'ITUB4.SA'%2C%20'BBDC4.SA'%2C%20'ABEV3.SA'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20'BBAS3.SA'%2C%20'WEGE3.SA'%2C%20'MGLU3.SA'%2C%20'B3SA3.SA'%2C%20'SUZB3.SA'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20'RENT3.SA'%2C%20'LREN3.SA'%2C%20'JBSS3.SA'%2C%20'EMBR3.SA'%2C%20'CIEL3.SA'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20'HAPV3.SA'%2C%20'RADL3.SA'%2C%20'UGPA3.SA'%2C%20'CCRO3.SA'%2C%20'GGBR4.SA'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20'CSNA3.SA'%2C%20'USIM5.SA'%2C%20'GOAU4.SA'%2C%20'SBSP3.SA'%2C%20'ELET3.SA'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20'CMIG4.SA'%2C%20'TAEE11.SA'%2C%20'CSAN3.SA'%2C%20'RAIL3.SA'%2C%20'AZUL4.SA'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20'GOLL4.SA'%2C%20'CVCB3.SA'%2C%20'FLRY3.SA'%2C%20'QUAL3.SA'%2C%20'PCAR3.SA'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20'COGN3.SA'%2C%20'YDUQ3.SA'%2C%20'MRFG3.SA'%2C%20'BEEF3.SA'%2C%20'SMTO3.SA'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20'JHSF3.SA'%2C%20'EZTC3.SA'%2C%20'MRVE3.SA'%2C%20'CYRE3.SA'%2C%20'EVEN3.SA'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20'MULT3.SA'%2C%20'GFSA3.SA'%2C%20'TCSA3.SA'%2C%20'TOTS3.SA'%2C%20'VIVT3.SA'%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20'US%20Stocks'%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20'AAPL'%2C%20'GOOGL'%2C%20'MSFT'%2C%20'AMZN'%2C%20'TSLA'%2C%20'META'%2C%20'NVDA'%2C%20'JPM'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20'JNJ'%2C%20'PG'%2C%20'UNH'%2C%20'HD'%2C%20'BAC'%2C%20'XOM'%2C%20'DIS'%2C%20'ADBE'%2C%20'CRM'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20'NFLX'%2C%20'KO'%2C%20'PFE'%2C%20'ABBV'%2C%20'TMO'%2C%20'COST'%2C%20'AVGO'%2C%20'WMT'%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20'US%20Sectors'%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20'XLE'%2C%20%20%20%23%20Energy%0A%20%20%20%20%20%20%20%20%20%20%20%20'XLU'%2C%20%20%20%23%20Utilities%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20'XLB'%2C%20%20%20%23%20Materials%0A%20%20%20%20%20%20%20%20%20%20%20%20'XLP'%2C%20%20%20%23%20Consumer%20Staples%0A%20%20%20%20%20%20%20%20%20%20%20%20'XLK'%2C%20%20%20%23%20Technology%0A%20%20%20%20%20%20%20%20%20%20%20%20'XLV'%2C%20%20%20%23%20Healthcare%0A%20%20%20%20%20%20%20%20%20%20%20%20'XLI'%2C%20%20%20%23%20Industrials%0A%20%20%20%20%20%20%20%20%20%20%20%20'XLF'%2C%20%20%20%23%20Financials%0A%20%20%20%20%20%20%20%20%20%20%20%20'XLY'%20%20%20%20%23%20Consumer%20Discretionary%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20'World%20iShares%20MSCI%20ETFs'%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWA'%2C%20%20%20%23%20Australia%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWC'%2C%20%20%20%23%20Canada%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWD'%2C%20%20%20%23%20Sweden%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWG'%2C%20%20%20%23%20Germany%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWH'%2C%20%20%20%23%20Hong%20Kong%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWI'%2C%20%20%20%23%20Italy%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWJ'%2C%20%20%20%23%20Japan%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWK'%2C%20%20%20%23%20Belgium%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWL'%2C%20%20%20%23%20Switzerland%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWM'%2C%20%20%20%23%20Malaysia%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWN'%2C%20%20%20%23%20Netherlands%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWO'%2C%20%20%20%23%20Austria%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWP'%2C%20%20%20%23%20Spain%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWQ'%2C%20%20%20%23%20France%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWS'%2C%20%20%20%23%20Singapore%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWU'%2C%20%20%20%23%20United%20Kingdom%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWW'%2C%20%20%20%23%20Mexico%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWT'%2C%20%20%20%23%20Taiwan%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWY'%2C%20%20%20%23%20South%20Korea%0A%20%20%20%20%20%20%20%20%20%20%20%20'EWZ'%2C%20%20%20%23%20Brazil%0A%20%20%20%20%20%20%20%20%20%20%20%20'EZA'%20%20%20%20%23%20South%20Africa%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%7D%0A%0A%20%20%20%20%23%20Available%20benchmarks%0A%20%20%20%20BENCHMARKS%20%3D%20%7B%0A%20%20%20%20%20%20%20%20'Brazilian%20Stocks'%3A%20%5B'%5EBVSP'%5D%2C%20%23%20Ibovespa%0A%20%20%20%20%20%20%20%20'US%20Stocks'%3A%20%5B'%5EGSPC'%5D%2C%20%23%20S%26P%20500%0A%20%20%20%20%20%20%20%20'US%20Sectors'%3A%20%5B'%5EGSPC'%5D%2C%20%23%20S%26P%20500%0A%20%20%20%20%20%20%20%20'World%20iShares%20MSCI%20ETFs'%3A%20%5B'XWD.TO'%5D%20%23%20iShares%20MSCI%20World%0A%20%20%20%20%7D%0A%20%20%20%20SMOOTHING_WINDOW%20%3D%2010%20%20%23%20Rolling%20window%20for%20smoothing%20RS%20calculations%0A%20%20%20%20TRAIL%20%3D%2012%20%20%20%20%20%20%23%20Number%20of%20periods%20to%20show%20in%20the%20trail%0A%0A%20%20%20%20%23%20Available%20resampling%20frequencies%0A%20%20%20%20RESAMPLES%20%3D%20%5B'D'%2C%20'W'%2C%20'M'%5D%0A%0A%20%20%20%20%23%20Default%20date%20range%20(2%20years%20back%20from%20today)%0A%20%20%20%20END_DATE%20%3D%20datetime.now().strftime('%25Y-%25m-%25d')%0A%20%20%20%20START_DATE%20%3D%20(datetime.now()%20-%20timedelta(days%3D730)).strftime('%25Y-%25m-%25d')%0A%0A%20%20%20%20%23%20Export%20files%0A%20%20%20%20%23%20OUTPUT_DIR%20%3D%20r'%2Fdata%2Frrg%2F%7BEND_DATE%7D'%0A%20%20%20%20%23%20os.makedirs(OUTPUT_DIR%2C%20exist_ok%3DTrue)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20BENCHMARKS%2C%0A%20%20%20%20%20%20%20%20END_DATE%2C%0A%20%20%20%20%20%20%20%20FALLBACK_TICKERS%2C%0A%20%20%20%20%20%20%20%20List%2C%0A%20%20%20%20%20%20%20%20Optional%2C%0A%20%20%20%20%20%20%20%20RESAMPLES%2C%0A%20%20%20%20%20%20%20%20SMOOTHING_WINDOW%2C%0A%20%20%20%20%20%20%20%20START_DATE%2C%0A%20%20%20%20%20%20%20%20TRAIL%2C%0A%20%20%20%20%20%20%20%20Tuple%2C%0A%20%20%20%20%20%20%20%20UNIVERSE%2C%0A%20%20%20%20%20%20%20%20np%2C%0A%20%20%20%20%20%20%20%20patches%2C%0A%20%20%20%20%20%20%20%20pd%2C%0A%20%20%20%20%20%20%20%20plt%2C%0A%20%20%20%20%20%20%20%20requests%2C%0A%20%20%20%20%20%20%20%20yf%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%202.%20Input%20Parameters%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(UNIVERSE%2C%20mo)%3A%0A%20%20%20%20universe_dropdown%20%3D%20mo.ui.dropdown(options%3DUNIVERSE%2C%20value%3D'Brazilian%20Stocks'%2C%20label%3D'Select%20universe%20of%20tickers')%0A%20%20%20%20return%20(universe_dropdown%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(BENCHMARKS%2C%20RESAMPLES%2C%20SMOOTHING_WINDOW%2C%20TRAIL%2C%20mo%2C%20universe_dropdown)%3A%0A%20%20%20%20benchmark%20%3D%20mo.ui.text(value%3DBENCHMARKS%5Buniverse_dropdown.value%5D%5B0%5D%2C%20placeholder%3D'Benchmark')%0A%20%20%20%20fallback_switch%20%3D%20mo.ui.switch(label%3D'Fallback%20tickers%20for%20Brazilian%20Stocks'%2C)%0A%20%20%20%20frequency_dropdown%20%3D%20mo.ui.dropdown(options%3DRESAMPLES%2C%20value%3D'W'%2C%20label%3D'Select%20timeframe')%0A%20%20%20%20smoothing_window_slider%20%3D%20mo.ui.slider(start%3D1%2C%20stop%3D63%2C%20label%3D'Smoothing%20window'%2C%20value%3DSMOOTHING_WINDOW)%0A%20%20%20%20trail_slider%20%3D%20mo.ui.slider(start%3D3%2C%20stop%3D20%2C%20label%3D'Trail'%2C%20value%3DTRAIL)%0A%0A%20%20%20%20mo.hstack(%5B%0A%20%20%20%20%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20universe_dropdown%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20fallback_switch%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20benchmark%0A%0A%20%20%20%20%20%20%20%20%5D)%2C%0A%20%20%20%20%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20frequency_dropdown%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20smoothing_window_slider%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20trail_slider%0A%20%20%20%20%20%20%20%20%5D)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20benchmark%2C%0A%20%20%20%20%20%20%20%20fallback_switch%2C%0A%20%20%20%20%20%20%20%20frequency_dropdown%2C%0A%20%20%20%20%20%20%20%20smoothing_window_slider%2C%0A%20%20%20%20%20%20%20%20trail_slider%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%203.%20Get%20appropriate%20tickers%20based%20on%20universe%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(FALLBACK_TICKERS%2C%20List%2C%20UNIVERSE%2C%20fallback_switch%2C%20requests)%3A%0A%20%20%20%20def%20get_asset_tickers(universe%3A%20str%2C%20limit%3A%20int%20%3D%2050)%20-%3E%20List%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Get%20asset%20tickers%20based%20on%20the%20specified%20universe%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20universe%3A%20Asset%20universe%20('Brazilian%20Stocks'%2C%20'US%20Stocks'%2C%20'US%20Sectors'%2C%20'World%20iShares%20MSCI%20ETFs')%0A%20%20%20%20%20%20%20%20%20%20%20%20limit%3A%20Maximum%20number%20of%20assets%20to%20fetch%20(only%20applies%20to%20'Brazilian%20Stocks')%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20List%20of%20asset%20tickers%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20universe%20not%20in%20UNIVERSE%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(f%22Universe%20must%20be%20one%20of%3A%20%7BUNIVERSE%7D%22)%0A%0A%20%20%20%20%20%20%20%20if%20universe%20%3D%3D%20'Brazilian%20Stocks'%20and%20not%20fallback_switch.value%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20fetch_brazilian_stocks(limit)%0A%20%20%20%20%20%20%20%20elif%20universe%20%3D%3D%20'US%20Stocks'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Using%20US%20Stocks%20fallback%20list%20(%7Blen(FALLBACK_TICKERS%5Buniverse%5D)%7D%20tickers)%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20FALLBACK_TICKERS%5Buniverse%5D%5B%3Alimit%5D%0A%20%20%20%20%20%20%20%20elif%20universe%20%3D%3D%20'US%20Sectors'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Using%20US%20Sectors%20MSCI%20ETFs%20(%7Blen(FALLBACK_TICKERS%5Buniverse%5D)%7D%20sectors)%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20FALLBACK_TICKERS%5Buniverse%5D%0A%20%20%20%20%20%20%20%20elif%20universe%20%3D%3D%20'World%20iShares%20MSCI%20ETFs'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Using%20World%20iShares%20MSCI%20ETFs%20(%7Blen(FALLBACK_TICKERS%5Buniverse%5D)%7D%20countries)%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20FALLBACK_TICKERS%5Buniverse%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20FALLBACK_TICKERS%5Buniverse%5D%5B%3Alimit%5D%0A%0A%20%20%20%20def%20fetch_brazilian_stocks(limit%3A%20int%20%3D%2050)%20-%3E%20List%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Fetch%20Brazilian%20stock%20tickers%20from%20brapi.dev%20API%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20limit%3A%20Maximum%20number%20of%20stocks%20to%20fetch%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20List%20of%20stock%20tickers%20formatted%20for%20Yahoo%20Finance%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20print(%22Fetching%20Brazilian%20stock%20tickers%20from%20brapi.dev...%22)%0A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Try%20to%20fetch%20without%20authentication%20first%20(public%20endpoint)%0A%20%20%20%20%20%20%20%20%20%20%20%20url%20%3D%20%22https%3A%2F%2Fbrapi.dev%2Fapi%2Fquote%2Flist%3Ftype%3Dstock%26sortBy%3Dmarket_cap_basic%26sortOrder%3Ddesc%22%0A%20%20%20%20%20%20%20%20%20%20%20%20response%20%3D%20requests.get(url%2C%20timeout%3D10)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20response.status_code%20%3D%3D%20200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%20%3D%20response.json()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20'stocks'%20in%20data%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tickers%20%3D%20%5Bstock%5B'stock'%5D%20for%20stock%20in%20data%5B'stocks'%5D%20if%20not%20stock%5B'stock'%5D.endswith('F')%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Limit%20list%20of%20tickers%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tickers%20%3D%20tickers%5B%3Alimit%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Convert%20to%20Yahoo%20Finance%20format%20(add%20.SA%20suffix)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20yahoo_tickers%20%3D%20%5Bf%22%7Bticker%7D.SA%22%20for%20ticker%20in%20tickers%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Successfully%20fetched%20%7Blen(yahoo_tickers)%7D%20stock%20tickers%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20yahoo_tickers%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Fallback%20to%20predefined%20list%20if%20API%20fails%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22API%20request%20failed%2C%20using%20predefined%20stock%20list...%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20FALLBACK_TICKERS%5B'Brazilian%20Stocks'%5D%5B%3Alimit%5D%0A%0A%20%20%20%20%20%20%20%20except%20Exception%20as%20e%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Error%20fetching%20tickers%3A%20%7Be%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22Using%20fallback%20ticker%20list...%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20FALLBACK_TICKERS%5B'Brazilian%20Stocks'%5D%5B%3Alimit%5D%20%20%20%20%20%20%20%20%0A%20%20%20%20return%20(get_asset_tickers%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(get_asset_tickers%2C%20universe_dropdown)%3A%0A%20%20%20%20if%20universe_dropdown.value%20%3D%3D%20'US%20Sectors'%3A%0A%20%20%20%20%20%20%20%20tickers%20%3D%20get_asset_tickers(universe_dropdown.value)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20tickers%20%3D%20get_asset_tickers(universe_dropdown.value%2C%20limit%3D25)%0A%20%20%20%20print('Tickers%3A'%2C%20tickers)%0A%20%20%20%20return%20(tickers%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%202.%20RRG%20Class%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(%0A%20%20%20%20END_DATE%2C%0A%20%20%20%20List%2C%0A%20%20%20%20Optional%2C%0A%20%20%20%20START_DATE%2C%0A%20%20%20%20Tuple%2C%0A%20%20%20%20np%2C%0A%20%20%20%20patches%2C%0A%20%20%20%20pd%2C%0A%20%20%20%20plt%2C%0A%20%20%20%20tickers%2C%0A%20%20%20%20yf%2C%0A)%3A%0A%20%20%20%20class%20RRG%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Relative%20Rotation%20Graph%20Implementation%0A%0A%20%20%20%20%20%20%20%20This%20class%20creates%20RRG%20visualizations%20showing%20relative%20strength%20and%20momentum%20%0A%20%20%20%20%20%20%20%20of%20any%20list%20of%20assets%20against%20a%20specified%20benchmark.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(self%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tickers%3A%20List%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20benchmark%3A%20str%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20frequency%3A%20str%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20smoothing_window%3A%20int%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20start_date%3A%20Optional%5Bstr%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20end_date%3A%20Optional%5Bstr%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20trail%3A%20int%20%3D%2012)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Initialize%20the%20RRG%20analyzer%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tickers%3A%20List%20of%20asset%20tickers%20to%20analyze%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20benchmark%3A%20Benchmark%20ticker%20for%20comparison%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20frequency%3A%20Data%20frequency%20('D'%20for%20daily%2C%20'W'%20for%20weekly%2C%20'M'%20for%20monthly)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lookback_periods%3A%20Number%20of%20periods%20for%20RS%20calculations%20(default%3A%2052)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20start_date%3A%20Start%20date%20in%20YYYY-MM-DD%20format%20(default%3A%202%20years%20ago)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20end_date%3A%20End%20date%20in%20YYYY-MM-DD%20format%20(default%3A%20today)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20trail%3A%20Number%20of%20steps%20to%20show%20in%20the%20trail%20(default%3A%2012)%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.tickers%20%3D%20tickers%0A%20%20%20%20%20%20%20%20%20%20%20%20self.benchmark%20%3D%20benchmark%0A%20%20%20%20%20%20%20%20%20%20%20%20self.frequency%20%3D%20frequency.upper()%0A%20%20%20%20%20%20%20%20%20%20%20%20self.smoothing_window%20%3D%20smoothing_window%0A%20%20%20%20%20%20%20%20%20%20%20%20self.start_date%20%3D%20start_date%20or%20START_DATE%0A%20%20%20%20%20%20%20%20%20%20%20%20self.end_date%20%3D%20end_date%20or%20END_DATE%0A%20%20%20%20%20%20%20%20%20%20%20%20self.trail%20%3D%20trail%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Validate%20frequency%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.frequency%20not%20in%20%5B'D'%2C%20'W'%2C%20'M'%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22Frequency%20must%20be%20'D'%20(daily)%2C%20'W'%20(weekly)%2C%20or%20'M'%20(monthly)%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Set%20lookback%20periods%20based%20on%20frequency%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20frequency%20%3D%3D%20'D'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.lookback_periods%20%3D%20252%20%20%23%20~1%20year%20of%20trading%20days%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20frequency%20%3D%3D%20'W'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.lookback_periods%20%3D%2052%20%20%20%23%2052%20weeks%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%20%20%23%20Monthly%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.lookback_periods%20%3D%2012%20%20%20%23%2012%20months%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.stocks_data%3A%20Optional%5Bpd.DataFrame%5D%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20self.benchmark_data%3A%20Optional%5Bpd.Series%5D%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20self.rrg_data%3A%20Optional%5Bpd.DataFrame%5D%20%3D%20None%0A%0A%20%20%20%20%20%20%20%20def%20fetch_price_data(self)%20-%3E%20pd.DataFrame%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fetch%20historical%20price%20data%20for%20stocks%20and%20benchmark%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20DataFrame%20with%20resampled%20adjusted%20close%20prices%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22Fetching%20price%20data%20for%20%7Blen(self.tickers)%7D%20tickers...%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22Tickers%3A%20%7B'%2C'.join(self.tickers)%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22Benchmark%3A%20%7Bself.benchmark%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22Date%20range%3A%20%7Bself.start_date%7D%20to%20%7Bself.end_date%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22Frequency%3A%20%7Bself.frequency%7D%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Add%20benchmark%20to%20ticker%20list%0A%20%20%20%20%20%20%20%20%20%20%20%20all_tickers%20%3D%20tickers%20%2B%20%5Bself.benchmark%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Fetch%20data%0A%20%20%20%20%20%20%20%20%20%20%20%20data%20%3D%20yf.download(all_tickers%2C%20start%3Dself.start_date%2C%20end%3Dself.end_date%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20interval%3D'1d'%2C%20auto_adjust%3DTrue%2C%20progress%3DFalse)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20len(all_tickers)%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Handle%20single%20ticker%20case%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20prices%20%3D%20data%5B'Close'%5D.to_frame()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20prices.columns%20%3D%20all_tickers%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Handle%20multiple%20tickers%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20prices%20%3D%20data%5B'Close'%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Resample%20based%20on%20frequency%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.frequency%20%3D%3D%20'D'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20resampled_prices%20%3D%20prices%20%20%23%20Keep%20daily%20data%20as%20is%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20self.frequency%20%3D%3D%20'W'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20resampled_prices%20%3D%20prices.resample('W-FRI').last()%20%20%23%20Weekly%20(Friday%20close)%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20self.frequency%20%3D%3D%20'M'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20resampled_prices%20%3D%20prices.resample('M').last()%20%20%23%20Monthly%20(end%20of%20month)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Drop%20rows%20with%20all%20NaN%20values%0A%20%20%20%20%20%20%20%20%20%20%20%20resampled_prices%20%3D%20resampled_prices.dropna(how%3D'all')%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Forward%20fill%20missing%20values%20and%20then%20drop%20remaining%20NaN%0A%20%20%20%20%20%20%20%20%20%20%20%20resampled_prices%20%3D%20resampled_prices.fillna(method%3D'ffill').dropna()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22Data%20shape%20after%20processing%3A%20%7Bresampled_prices.shape%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22Date%20range%3A%20%7Bresampled_prices.index%5B0%5D%7D%20to%20%7Bresampled_prices.index%5B-1%5D%7D%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20resampled_prices%0A%0A%20%20%20%20%20%20%20%20def%20calculate_rs_ratio(self%2C%20stock_prices%3A%20pd.Series%2C%20benchmark_prices%3A%20pd.Series)%20-%3E%20pd.Series%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Calculate%20RS-Ratio%20(Relative%20Strength%20Ratio)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20The%20RS-Ratio%20measures%20the%20trend%20of%20relative%20performance%20using%20a%20momentum-based%0A%20%20%20%20%20%20%20%20%20%20%20%20approach%20normalized%20around%20100.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stock_prices%3A%20Stock%20price%20series%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20benchmark_prices%3A%20Benchmark%20price%20series%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20RS-Ratio%20series%20normalized%20around%20100%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Calculate%20price%20relative%20(stock%20%2F%20benchmark)%0A%20%20%20%20%20%20%20%20%20%20%20%20price_relative%20%3D%20stock_prices%20%2F%20benchmark_prices%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Calculate%20RS-Ratio%20using%20rate%20of%20change%20over%20lookback%20period%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20This%20is%20a%20simplified%20version%20-%20the%20actual%20JdK%20formula%20is%20proprietary%0A%20%20%20%20%20%20%20%20%20%20%20%20rs_raw%20%3D%20price_relative.pct_change(periods%3Dself.lookback_periods%2F%2F4)%20*%20100%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Normalize%20around%20100%20using%20a%20rolling%20mean%20approach%0A%20%20%20%20%20%20%20%20%20%20%20%20rs_ratio%20%3D%20100%20%2B%20rs_raw.rolling(window%3Dself.smoothing_window%2C%20min_periods%3D1).mean()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20rs_ratio%20%23.fillna(100)%0A%0A%20%20%20%20%20%20%20%20def%20calculate_rs_momentum(self%2C%20rs_ratio%3A%20pd.Series)%20-%3E%20pd.Series%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Calculate%20RS-Momentum%20(Rate%20of%20change%20of%20RS-Ratio)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20RS-Momentum%20measures%20the%20momentum%20of%20the%20RS-Ratio%2C%20normalized%20around%20100.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rs_ratio%3A%20RS-Ratio%20series%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20RS-Momentum%20series%20normalized%20around%20100%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Calculate%20rate%20of%20change%20of%20RS-Ratio%0A%20%20%20%20%20%20%20%20%20%20%20%20rs_momentum_raw%20%3D%20rs_ratio.pct_change(periods%3D4)%20*%20100%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Normalize%20around%20100%0A%20%20%20%20%20%20%20%20%20%20%20%20rs_momentum%20%3D%20100%20%2B%20rs_momentum_raw.rolling(window%3D2%2C%20min_periods%3D1).mean()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20rs_momentum%20%23.fillna(100)%0A%0A%20%20%20%20%20%20%20%20def%20calculate_rrg_data(self%2C%20prices_df%3A%20pd.DataFrame)%20-%3E%20pd.DataFrame%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Calculate%20RRG%20data%20(RS-Ratio%20and%20RS-Momentum)%20for%20all%20stocks%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20prices_df%3A%20DataFrame%20with%20stock%20and%20benchmark%20prices%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20DataFrame%20with%20RS-Ratio%20and%20RS-Momentum%20for%20each%20stock%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22Calculating%20RRG%20metrics...%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20benchmark_prices%20%3D%20prices_df%5Bself.benchmark%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20stock_columns%20%3D%20%5Bcol%20for%20col%20in%20prices_df.columns%20if%20col%20!%3D%20self.benchmark%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20rrg_results%20%3D%20%7B%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20stock%20in%20stock_columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20stock%20in%20prices_df.columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stock_prices%20%3D%20prices_df%5Bstock%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Skip%20if%20not%20enough%20data%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20len(stock_prices.dropna())%20%3C%20self.lookback_periods%2F%2F2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Calculate%20RS-Ratio%20and%20RS-Momentum%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rs_ratio%20%3D%20self.calculate_rs_ratio(stock_prices%2C%20benchmark_prices)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rs_momentum%20%3D%20self.calculate_rs_momentum(rs_ratio)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Store%20results%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20i%2C%20date%20in%20enumerate(prices_df.index)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20date%20not%20in%20rrg_results%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rrg_results%5Bdate%5D%20%3D%20%7B%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rrg_results%5Bdate%5D%5Bstock%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'rs_ratio'%3A%20rs_ratio.iloc%5Bi%5D%20if%20i%20%3C%20len(rs_ratio)%20else%20np.nan%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'rs_momentum'%3A%20rs_momentum.iloc%5Bi%5D%20if%20i%20%3C%20len(rs_momentum)%20else%20np.nan%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Convert%20to%20DataFrame%20format%0A%20%20%20%20%20%20%20%20%20%20%20%20rrg_df_data%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20date%2C%20stocks_data%20in%20rrg_results.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20stock%2C%20metrics%20in%20stocks_data.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rrg_df_data.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'date'%3A%20date%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'stock'%3A%20stock%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'rs_ratio'%3A%20metrics%5B'rs_ratio'%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'rs_momentum'%3A%20metrics%5B'rs_momentum'%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20rrg_df%20%3D%20pd.DataFrame(rrg_df_data)%0A%20%20%20%20%20%20%20%20%20%20%20%20rrg_df%20%3D%20rrg_df.dropna()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22RRG%20data%20calculated%20for%20%7Blen(rrg_df%5B'stock'%5D.unique())%7D%20stocks%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20rrg_df%0A%0A%20%20%20%20%20%20%20%20def%20get_quadrant_info(self%2C%20rs_ratio%3A%20float%2C%20rs_momentum%3A%20float)%20-%3E%20Tuple%5Bstr%2C%20str%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Determine%20which%20quadrant%20a%20stock%20is%20in%20based%20on%20RS-Ratio%20and%20RS-Momentum%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rs_ratio%3A%20RS-Ratio%20value%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rs_momentum%3A%20RS-Momentum%20value%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Tuple%20of%20(quadrant_name%2C%20color)%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20rs_ratio%20%3E%3D%20100%20and%20rs_momentum%20%3E%3D%20100%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%22Leading%22%2C%20%22green%22%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20rs_ratio%20%3C%20100%20and%20rs_momentum%20%3E%3D%20100%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%22Improving%22%2C%20%22blue%22%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20rs_ratio%20%3C%20100%20and%20rs_momentum%20%3C%20100%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%22Lagging%22%2C%20%22red%22%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%20%20%23%20rs_ratio%20%3E%3D%20100%20and%20rs_momentum%20%3C%20100%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%22Weakening%22%2C%20%22gold%22%0A%0A%20%20%20%20%20%20%20%20def%20plot_rrg_chart(self%2C%20save_path%3A%20Optional%5Bstr%5D%20%3D%20None)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Create%20the%20RRG%20visualization%20with%20trails%20for%20the%20last%2012%20steps%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20save_path%3A%20Optional%20path%20to%20save%20the%20chart%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.rrg_data%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22No%20RRG%20data%20available.%20Run%20calculate_rrg_data%20first.%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22Creating%20RRG%20visualization...%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Get%20the%20last%20N%20periods%20of%20data%0A%20%20%20%20%20%20%20%20%20%20%20%20latest_dates%20%3D%20sorted(self.rrg_data%5B'date'%5D.unique())%5B-self.trail%3A%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20trail_data%20%3D%20self.rrg_data%5Bself.rrg_data%5B'date'%5D.isin(latest_dates)%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Create%20the%20plot%0A%20%20%20%20%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(figsize%3D(14%2C%2010))%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Set%20up%20the%20coordinate%20system%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xlim(80%2C%20120)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_ylim(80%2C%20120)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Add%20quadrant%20lines%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.axhline(y%3D100%2C%20color%3D'black'%2C%20linewidth%3D2%2C%20alpha%3D0.8)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.axvline(x%3D100%2C%20color%3D'black'%2C%20linewidth%3D2%2C%20alpha%3D0.8)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Leading%20quadrant%20(top-right)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.add_patch(patches.Rectangle((100%2C%20100)%2C%2020%2C%2020%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20facecolor%3D'green'%2C%20alpha%3D0.1))%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Improving%20quadrant%20(top-left)%20-%20Blue%20background%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.add_patch(patches.Rectangle((80%2C%20100)%2C%2020%2C%2020%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20facecolor%3D'blue'%2C%20alpha%3D0.1))%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Lagging%20quadrant%20(bottom-left)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.add_patch(patches.Rectangle((80%2C%2080)%2C%2020%2C%2020%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20facecolor%3D'red'%2C%20alpha%3D0.1))%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Weakening%20quadrant%20(bottom-right)%20-%20Yellow%20background%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.add_patch(patches.Rectangle((100%2C%2080)%2C%2020%2C%2020%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20facecolor%3D'yellow'%2C%20alpha%3D0.1))%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Add%20quadrant%20labels%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.text(110%2C%20110%2C%20'Leading'%2C%20fontsize%3D14%2C%20fontweight%3D'bold'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ha%3D'center'%2C%20va%3D'center'%2C%20color%3D'green')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.text(90%2C%20110%2C%20'Improving'%2C%20fontsize%3D14%2C%20fontweight%3D'bold'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ha%3D'center'%2C%20va%3D'center'%2C%20color%3D'blue')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.text(90%2C%2090%2C%20'Lagging'%2C%20fontsize%3D14%2C%20fontweight%3D'bold'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ha%3D'center'%2C%20va%3D'center'%2C%20color%3D'red')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.text(110%2C%2090%2C%20'Weakening'%2C%20fontsize%3D14%2C%20fontweight%3D'bold'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ha%3D'center'%2C%20va%3D'center'%2C%20color%3D'gold')%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Plot%20trails%20for%20each%20stock%0A%20%20%20%20%20%20%20%20%20%20%20%20stocks%20%3D%20trail_data%5B'stock'%5D.unique()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20stock%20in%20stocks%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stock_data%20%3D%20trail_data%5Btrail_data%5B'stock'%5D%20%3D%3D%20stock%5D.sort_values('date')%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20len(stock_data)%20%3C%202%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Get%20the%20trail%20coordinates%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x_coords%20%3D%20stock_data%5B'rs_ratio'%5D.values%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_coords%20%3D%20stock_data%5B'rs_momentum'%5D.values%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Determine%20current%20quadrant%20for%20color%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20current_rs_ratio%20%3D%20x_coords%5B-1%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20current_rs_momentum%20%3D%20y_coords%5B-1%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quadrant%2C%20color%20%3D%20self.get_quadrant_info(current_rs_ratio%2C%20current_rs_momentum)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Plot%20the%20trail%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.plot(x_coords%2C%20y_coords%2C%20color%3Dcolor%2C%20alpha%3D0.6%2C%20linewidth%3D1.5)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Plot%20trail%20points%20with%20increasing%20size%20(older%20%3D%20smaller)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sizes%20%3D%20np.linspace(10%2C%2040%2C%20len(x_coords))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.scatter(x_coords%5B%3A-1%5D%2C%20y_coords%5B%3A-1%5D%2C%20c%3Dcolor%2C%20s%3Dsizes%5B%3A-1%5D%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alpha%3D0.4%2C%20edgecolors%3D'white'%2C%20linewidths%3D0.5)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Plot%20current%20position%20(larger%20point)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.scatter(current_rs_ratio%2C%20current_rs_momentum%2C%20c%3Dcolor%2C%20s%3D80%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alpha%3D0.8%2C%20edgecolors%3D'black'%2C%20linewidths%3D1)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Add%20stock%20label%20for%20current%20position%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stock_label%20%3D%20stock.replace('.SA'%2C%20'')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.annotate(stock_label%2C%20(current_rs_ratio%2C%20current_rs_momentum)%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20xytext%3D(5%2C%205)%2C%20textcoords%3D'offset%20points'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fontsize%3D8%2C%20fontweight%3D'bold')%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Customize%20the%20plot%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xlabel('RS-Ratio'%2C%20fontsize%3D14%2C%20fontweight%3D'bold')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_ylabel('RS-Momentum'%2C%20fontsize%3D14%2C%20fontweight%3D'bold')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(f'Relative%20Rotation%20Graph%20(RRG)%20-%20Assets%20vs%20%7Bself.benchmark%7D%5Cn'%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f'Trail%3A%20Last%20%7Bself.trail%7D%20periods%20(%7Bself.frequency%7D)'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fontsize%3D16%2C%20fontweight%3D'bold'%2C%20pad%3D20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Add%20grid%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.grid(True%2C%20alpha%3D0.3)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Add%20explanation%20text%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20explanation%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20%20%20%22RRG%20Quadrants%3A%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20%20%20%22%E2%80%A2%20Leading%20(Green)%3A%20Strong%20performance%2C%20positive%20momentum%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20%20%20%22%E2%80%A2%20Improving%20(Blue)%3A%20Weak%20performance%2C%20improving%20momentum%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20%20%20%22%E2%80%A2%20Lagging%20(Red)%3A%20Weak%20performance%2C%20negative%20momentum%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20%20%20%22%E2%80%A2%20Weakening%20(Yellow)%3A%20Strong%20performance%2C%20declining%20momentum%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20ax.text(0.02%2C%200.98%2C%20explanation%2C%20transform%3Dax.transAxes%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20%20%20%20%20%20fontsize%3D9%2C%20verticalalignment%3D'top'%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20%20%20%20%20%20bbox%3Ddict(boxstyle%3D'round'%2C%20facecolor%3D'white'%2C%20alpha%3D0.8))%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20plt.tight_layout()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20save_path%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20plt.savefig(save_path%2C%20dpi%3D300%2C%20bbox_inches%3D'tight')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Chart%20saved%20to%3A%20%7Bsave_path%7D%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20plt.show()%0A%0A%20%20%20%20%20%20%20%20def%20get_current_positions(self)%20-%3E%20pd.DataFrame%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Get%20current%20quadrant%20positions%20for%20all%20stocks%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20DataFrame%20with%20current%20stock%20positions%20and%20quadrants%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.rrg_data%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22No%20RRG%20data%20available.%20Run%20calculate_rrg_data%20first.%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Get%20the%20most%20recent%20data%0A%20%20%20%20%20%20%20%20%20%20%20%20latest_date%20%3D%20self.rrg_data%5B'date'%5D.max()%0A%20%20%20%20%20%20%20%20%20%20%20%20current_data%20%3D%20self.rrg_data%5Bself.rrg_data%5B'date'%5D%20%3D%3D%20latest_date%5D.copy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Add%20quadrant%20information%0A%20%20%20%20%20%20%20%20%20%20%20%20quadrant_info%20%3D%20current_data.apply(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20row%3A%20self.get_quadrant_info(row%5B'rs_ratio'%5D%2C%20row%5B'rs_momentum'%5D)%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20axis%3D1%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20current_data%5B'quadrant'%5D%20%3D%20%5Binfo%5B0%5D%20for%20info%20in%20quadrant_info%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20current_data%5B'color'%5D%20%3D%20%5Binfo%5B1%5D%20for%20info%20in%20quadrant_info%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Clean%20up%20stock%20names%0A%20%20%20%20%20%20%20%20%20%20%20%20current_data%5B'stock_clean'%5D%20%3D%20current_data%5B'stock'%5D.str.replace('.SA'%2C%20'')%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Sort%20by%20quadrant%20and%20RS-Ratio%0A%20%20%20%20%20%20%20%20%20%20%20%20quadrant_order%20%3D%20%5B'Leading'%2C%20'Improving'%2C%20'Lagging'%2C%20'Weakening'%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20current_data%5B'quadrant_order'%5D%20%3D%20current_data%5B'quadrant'%5D.map(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bq%3A%20i%20for%20i%2C%20q%20in%20enumerate(quadrant_order)%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20result%20%3D%20current_data.sort_values(%5B'quadrant_order'%2C%20'rs_ratio'%5D%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ascending%3D%5BTrue%2C%20False%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20result%5B%5B'stock_clean'%2C%20'rs_ratio'%2C%20'rs_momentum'%2C%20'quadrant'%5D%5D%0A%0A%20%20%20%20%20%20%20%20def%20run_analysis(self%2C%20num_stocks%3A%20int%20%3D%2030)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Run%20the%20complete%20RRG%20analysis%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20num_stocks%3A%20Number%20of%20stocks%20to%20analyze%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22%3D%22%20*%2060)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22RELATIVE%20ROTATION%20GRAPH%20(RRG)%20ANALYSIS%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22%3D%22%20*%2060)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Assets%3A%20%7Blen(self.tickers)%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Benchmark%3A%20%7Bself.benchmark%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Frequency%3A%20%7Bself.frequency%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Date%20range%3A%20%7Bself.start_date%7D%20to%20%7Bself.end_date%7D%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Step%201%3A%20Fetch%20stock%20tickers%0A%20%20%20%20%20%20%20%20%20%20%20%20price_data%20%3D%20self.fetch_price_data()%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Selected%20%7Blen(tickers)%7D%20stocks%20for%20analysis%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Step%202%3A%20Fetch%20price%20data%0A%20%20%20%20%20%20%20%20%20%20%20%20price_data%20%3D%20self.fetch_price_data()%0A%20%20%20%20%20%20%20%20%20%20%20%20self.stocks_data%20%3D%20price_data%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Step%203%3A%20Calculate%20RRG%20metrics%0A%20%20%20%20%20%20%20%20%20%20%20%20self.rrg_data%20%3D%20self.calculate_rrg_data(price_data)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Step%204%3A%20Display%20current%20positions%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22%5CnCurrent%20Stock%20Positions%3A%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22-%22%20*%2040)%0A%20%20%20%20%20%20%20%20%20%20%20%20current_positions%20%3D%20self.get_current_positions()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20quadrant%20in%20%5B'Leading'%2C%20'Improving'%2C%20'Lagging'%2C%20'Weakening'%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stocks_in_quadrant%20%3D%20current_positions%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20current_positions%5B'quadrant'%5D%20%3D%3D%20quadrant%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%5Cn%7Bquadrant.upper()%7D%20(%7Blen(stocks_in_quadrant)%7D%20stocks)%3A%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_%2C%20row%20in%20stocks_in_quadrant.iterrows()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20%7Brow%5B'stock_clean'%5D%3A8%7D%20%7C%20RS-Ratio%3A%20%7Brow%5B'rs_ratio'%5D%3A6.1f%7D%20%7C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22RS-Momentum%3A%20%7Brow%5B'rs_momentum'%5D%3A6.1f%7D%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Step%205%3A%20Create%20visualization%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22%5CnGenerating%20RRG%20chart...%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.plot_rrg_chart()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22%5CnAnalysis%20complete!%22)%0A%20%20%20%20return%20(RRG%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%20Run%20Analysis%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(%0A%20%20%20%20RRG%2C%0A%20%20%20%20benchmark%2C%0A%20%20%20%20frequency_dropdown%2C%0A%20%20%20%20smoothing_window_slider%2C%0A%20%20%20%20tickers%2C%0A%20%20%20%20trail_slider%2C%0A)%3A%0A%20%20%20%20%23%20Initialize%20and%20run%20analysis%0A%20%20%20%20rrg_analyzer%20%3D%20RRG(%0A%20%20%20%20%20%20%20%20tickers%3Dtickers%2C%0A%20%20%20%20%20%20%20%20benchmark%3Dbenchmark.value%2C%0A%20%20%20%20%20%20%20%20frequency%3Dfrequency_dropdown.value%2C%0A%20%20%20%20%20%20%20%20smoothing_window%3Dsmoothing_window_slider.value%2C%0A%20%20%20%20%20%20%20%20trail%3Dtrail_slider.value%0A%20%20%20%20)%0A%0A%20%20%20%20rrg_analyzer.run_analysis()%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
bad8b3870b740f924a3254e64b486897eac4d35e984fd83dd9e3823779e08f99