<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Andreas Elledge on Medium]]></title>
        <description><![CDATA[Stories by Andreas Elledge on Medium]]></description>
        <link>https://medium.com/@andreaselledge?source=rss-2260c2b57e51------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*wU7DEJZ9o4CbZ2SeE9wxUg.png</url>
            <title>Stories by Andreas Elledge on Medium</title>
            <link>https://medium.com/@andreaselledge?source=rss-2260c2b57e51------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 00:55:33 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@andreaselledge/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[My Takeaways from Paris Blockchain Week 2024 and the Institutional Buzz Around BUIDL]]></title>
            <link>https://medium.com/coinmonks/my-takeaways-from-paris-blockchain-week-2024-and-the-institutional-buzz-around-buidl-747c869f6f03?source=rss-2260c2b57e51------2</link>
            <guid isPermaLink="false">https://medium.com/p/747c869f6f03</guid>
            <category><![CDATA[blackrock]]></category>
            <category><![CDATA[treasury-management]]></category>
            <category><![CDATA[cryptocurrency]]></category>
            <category><![CDATA[digital-asset-management]]></category>
            <category><![CDATA[blockchain]]></category>
            <dc:creator><![CDATA[Andreas Elledge]]></dc:creator>
            <pubDate>Sun, 12 May 2024 20:08:58 GMT</pubDate>
            <atom:updated>2024-05-13T16:22:31.049Z</atom:updated>
            <content:encoded><![CDATA[<p>A Paradigm Shift in Money Market Funds: BlackRock USD Institutional Digital Liquidity Fund (BUIDL)</p><p>On April 10th, I was invited to the event “<em>Treasury in the Digital Money Era</em>” hosted by the Paris Blockchain Society in collaboration with top-tier firms like Accenture, Onyx by J.P. Morgan, Citi Treasury and Trade Solutions, Fortris, and Societe Generale-FORGE.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*Mhtv7oTTx3XymJpA57rsoQ.jpeg" /><figcaption>Hosted by <em>Paris Blockchain Society</em></figcaption></figure><p>As a professional working in the evolving crypto industry who had previous experience in cash management at BNP PARIBAS, I was looking forward to hearing what institutions had to say about BUIDL — BlackRock USD Institutional Digital Liquidity Fund. I was not disappointed.</p><h4>Exploring Digital Treasury Innovations with Accenture, Onyx by J.P. Morgan, Citi, Fortris, and Societe Generale — FORGE</h4><p>This event brought together CAC40 CFOs, Treasurers, and Payments C-Suite Executives, as well as professionals from the crypto industry. Discussions focused on the integration of digital assets in treasury operations, cash management, and cross-border payments, providing rich insights into the current state and future possibilities of digital finance.</p><p>The speakers were:</p><ul><li>Basak Toprak, Executive Director, EMEA Head of Coin Systems at Onyx by J.P. Morgan</li><li>David Cunningham, Global Head of Strategy and Partnerships for Digital Assets at Citi’s Treasury and Trade Solutions</li><li>Noah Herman, CRO at Fortris</li><li>Guillaume Chatain, Head of Sales at Societe Generale-Forge</li><li>John Cronin, CEO Europe at Zodia Custody</li><li>Alexandre Laizet, Digital Assets Lead at Accenture (Moderator)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MiYlCD093mZtFev_Q2gJhw.jpeg" /><figcaption>“Treasury in the Digital Money Era” speakers</figcaption></figure><p>Amid the variety of topics discussed — from the potential of an ETH ETF to the strategic marketing behind memecoins — the subject that captured the audience’s attention was BlackRock’s BUIDL, the newly launched tokenized money market fund.</p><p>As a quick reminder, here is the definition of a money market fund from <em>Investopedia</em>:</p><blockquote>“A money market fund is a type of mutual fund that <strong>invests in high-quality, short-term debt instruments, cash, and cash equivalents.</strong></blockquote><blockquote>Though not quite as safe as cash, money market funds are considered extremely <strong>low-risk</strong> on the investment spectrum.</blockquote><blockquote>A money market fund generates income (taxable or tax-free, depending on its portfolio), but little capital appreciation.</blockquote><blockquote>Money market funds should be used as a <strong>place to park money temporarily</strong> before investing elsewhere or making an anticipated cash outlay; they are not suitable as long-term investments.”</blockquote><p>Now that we all have the definition in mind, let’s take a deep dive into BlackRock’s BUIDL’s main characteristics and reflect on how it reshapes traditional money market funds:</p><ul><li><strong>Fund Manager:</strong> BlackRock</li><li><strong>Fund Custodian:</strong> BNY Mellon</li><li><strong>Fund Administrator:</strong> BNY Mellon</li><li><strong>Transfer Agent:</strong> Securitize, LLC. The leader in tokenizing real-world assets</li><li><strong>Settlement Asset:</strong> USDC, a dollar-pegged stablecoin by Circle</li><li><strong>Blockchain Deployment:</strong> Launched on the Ethereum blockchain, BUIDL offers a stable value of $1 per token. It targets institutional investors</li><li><strong>Professional Fund:</strong> BUIDL operates as a professional fund. It consists of various low-risk investments such as short-term U.S. Treasury bills and repurchase agreements (with assets&#39; maturity within three months) for high liquidity and low duration risk</li><li><strong>Access:</strong> The fund is only accessible to whitelisted investors after completing a KYC and AML process</li><li><strong>On-chain Liquidity:</strong> USDC liquidity facility managed via smart contracts, allowing 24/7 exchange of BUIDL tokens for USDC. Investors can exchange their BUIDL tokens for USDC at any time, 24/7 through a USDC - BUIDL pool accessible via the interface of Securitize Markets and/or through direct interaction with the smart contract</li><li><strong>Dividends Distribution:</strong> Dividends accrue daily at 3:00 PM (New York time) for the registered shareholder of the fund and are distributed in kind (the fund&#39;s token) on the first business day of the following month. Each shareholder&#39;s dividend corresponds to their proportional share of the net income from that day&#39;s investments, minus fees related to the commission—like what exists already in a decentralized finance (DeFi) pool system</li><li><strong>DeFi:</strong> BUIDL is an ERC-20 token. It would be conceivable to imagine a &quot;wBUIDL&quot; (Wrapped BUIDL) to integrate into decentralized finance (DeFi) protocols</li><li><strong>Tokenized Funds:</strong> There are already tokenized funds deployed on public blockchains: Franklin Templeton Benji (300 million - Polygon, Stellar), Ondo Finance OUSG (104 million - Ethereum, Polygon, Solana), Matrixdock STBT (80 million - Ethereum)</li></ul><h4><strong>The bottom line is.</strong></h4><p>Blockchain technology is driving disruptive financial innovations. Tokenized money market funds, smart contracts, and stablecoins are reshaping traditional finance and the way companies, banks, and institutions conduct business and interact with each other.</p><blockquote>“I think what was most important for the audience to take away is that stablecoins are not going away. They are the killer app of crypto and they will have an impact not just on banks but on global business processes. It will create new industries and it will eradicate some existing industries.” — John Cronin, CEO Europe of Zodia Custody</blockquote><p>Thanks for reading and stay tuned for more articles! If you enjoyed reading this article, <strong>please follow and clap</strong> for crypto and blockchain contents.</p><h4><strong>Resources:</strong></h4><ol><li><strong>BlackRock Launches Its First Tokenized Fund, BUIDL, on the Ethereum Network.</strong> Business Wire, March 20, 2024. Available at <a href="https://www.businesswire.com/news/home/20240320771318/en/BlackRock-Launches-Its-First-Tokenized-Fund-BUIDL-on-the-Ethereum-Network">https://www.businesswire.com/news/home/20240320771318/en/BlackRock-Launches-Its-First-Tokenized-Fund-BUIDL-on-the-Ethereum-Network</a>.</li><li><strong>New BlackRock Tokenized Fund Garners $160 Million in Deposits.</strong> The Block, March 22, 2024. Available at <a href="https://www.theblock.co/post/285142/new-blackrock-tokenized-fund-garners-160-million-in-deposits">https://www.theblock.co/post/285142/new-blackrock-tokenized-fund-garners-160-million-in-deposits</a>.</li><li><strong>Conference Highlights: Exploring Treasury in the Digital Money Era.</strong> Paris Blockchain Society, LinkedIn Post, April 10, 2024. Available at <a href="https://www.linkedin.com/posts/paris-blockchain-society_conference-highlights-exploring-treasury-activity-7189680198005465088-D40Z?utm_source=share&amp;utm_medium=member_desktop">https://www.linkedin.com/posts/paris-blockchain-society_conference-highlights-exploring-treasury-activity-7189680198005465088-D40Z</a>.</li></ol><h4>Disclaimer</h4><p>The information contained in this article is provided for general informational purposes only and is not intended to be, and should not be relied upon as, legal, financial, or professional advice. This article is based on the information available at the time of its preparation and is subject to change without notice. The Author assumes no liability or responsibility for any errors or omissions in the content of this assessment or for any actions taken based on the information provided herein. You should seek professional advice from a qualified attorney or financial advisor before making any decisions or taking any actions based on the information contained in this article. The Author disclaims all liability and responsibility for any actions taken or not taken in reliance on the information contained in this article.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=747c869f6f03" width="1" height="1" alt=""><hr><p><a href="https://medium.com/coinmonks/my-takeaways-from-paris-blockchain-week-2024-and-the-institutional-buzz-around-buidl-747c869f6f03">My Takeaways from Paris Blockchain Week 2024 and the Institutional Buzz Around BUIDL</a> was originally published in <a href="https://medium.com/coinmonks">Coinmonks</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[3 Free APIs to Kickstart Your Automated Trading Strategies Today]]></title>
            <link>https://medium.com/@andreaselledge/3-free-apis-to-kickstart-your-automated-trading-strategies-today-047865c5cb69?source=rss-2260c2b57e51------2</link>
            <guid isPermaLink="false">https://medium.com/p/047865c5cb69</guid>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[cryptocurrency]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Andreas Elledge]]></dc:creator>
            <pubDate>Wed, 11 Oct 2023 08:33:14 GMT</pubDate>
            <atom:updated>2023-10-11T08:33:14.595Z</atom:updated>
            <content:encoded><![CDATA[<p>…And Their Full Python Scripts</p><p>Access market data with these 3 free APIs using only a few lines of code.</p><p>Gain instant access to OHLCV (Open, High, Low, Close, Volume) data and more for stocks, digital assets, and forex.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X2tyu4apAYnDdrxbpWcVvw.png" /><figcaption>DALL-E generated image</figcaption></figure><ul><li>You can use the <em>ccxt</em> library to fetch data from <strong>Kraken</strong>.</li><li>You can use direct HTTP requests to get data from <strong>Binance</strong>.</li><li>You can use the yfinance library to get data from <strong>Yahoo Finance</strong>.</li></ul><p>We will fetch the market data without defining functions for simplification purposes. That way, beginner programmers can still access market data while understanding what’s happening in the code.</p><h3>1. Fetching Data from Kraken using ccxt</h3><p>By running the following script, you will obtain 720 days of OHLCV market data.</p><pre>import ccxt<br>import pandas as pd<br><br># Here we initialize the Kraken &quot;connection&quot;<br>kraken = ccxt.kraken()<br><br># Setting the trading pair and the timeframe<br>symbol = &#39;BTC/USD&#39;<br>timeframe = &#39;1d&#39;<br><br># Fetch OHLCV data<br>ohlcv = kraken.fetch_ohlcv(symbol, timeframe)<br><br># Then we convert the fetched data into a DataFrame and process timestamps<br>data_kraken = pd.DataFrame(ohlcv, columns=[&#39;timestamp&#39;, &#39;open&#39;, &#39;high&#39;, &#39;low&#39;, &#39;close&#39;, &#39;volume&#39;])<br>data_kraken[&#39;timestamp&#39;] = pd.to_datetime(data_kraken[&#39;timestamp&#39;], unit=&#39;ms&#39;)<br>data_kraken.set_index(&#39;timestamp&#39;, inplace=True)<br><br>data_kraken.tail()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/656/1*vWfVrHaqQLyKq5ooOhjCig.png" /><figcaption>Kraken market data</figcaption></figure><h3>2. Fetching Data from Binance using HTTP Requests</h3><p>By running the following script, you will obtain 365 days of market data. Note that you can fetch more than just the OHLCV data.</p><pre>import requests<br><br># Below we fefine the symbol, interval, and time range<br>symbol_binance = &#39;BTCUSDT&#39;<br>interval = &#39;1d&#39;<br>start_time = &#39;1640995200000&#39;  # Example timestamp for 01-01-2022<br>end_time = &#39;1672531199000&#39;    # Example timestamp for 01-01-2023<br><br># Set the URL and fetching raw data<br>url = f&quot;https://api.binance.com/api/v3/klines?symbol={symbol_binance}&amp;interval={interval}&amp;startTime={start_time}&amp;endTime={end_time}&quot;<br>response = requests.get(url)<br>data_binance_raw = response.json()<br><br># Then we convert the raw data into a DataFrame and process timestamps<br>columns_binance = [&#39;Open time&#39;, &#39;Open&#39;, &#39;High&#39;, &#39;Low&#39;, &#39;Close&#39;, &#39;Volume&#39;, &#39;Close time&#39;, &#39;Quote asset volume&#39;, &#39;Number of trades&#39;, &#39;Taker buy base asset volume&#39;, &#39;Taker buy quote asset volume&#39;, &#39;Ignore&#39;] # Choose what you need<br>data_binance = pd.DataFrame(data_binance_raw, columns=columns_binance)<br>data_binance[&#39;Open time&#39;] = pd.to_datetime(data_binance[&#39;Open time&#39;], unit=&#39;ms&#39;)<br>data_binance[&#39;Close time&#39;] = pd.to_datetime(data_binance[&#39;Close time&#39;], unit=&#39;ms&#39;)<br><br>data_binance.tail()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*il_vIMbbQCzLPRRihDEacQ.png" /><figcaption>Binance market data</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*FqhKcL3cRz6kj29yiE2DrQ.png" /><figcaption>data_binance.info()</figcaption></figure><p>Here, the variables fetched from Binance are:</p><ul><li><strong>Open time</strong>: The opening time of the time interval (in datetime format).</li><li><strong>Open</strong>: The opening price of the asset for the given interval (as a string, convert to float for numerical operations).</li><li><strong>High</strong>: The highest price of the asset during the interval (as a string, convert to float for numerical operations).</li><li><strong>Low</strong>: The lowest price of the asset during the interval (as a string, convert to float for numerical operations).</li><li><strong>Close</strong>: The closing price of the asset for the given interval (as a string, convert to float for numerical operations).</li><li><strong>Volume</strong>: The trading volume of the asset during the interval (as a string, convert to float for numerical operations).</li><li><strong>Close time</strong>: The closing time of the time interval (in datetime format).</li><li><strong>Quote asset volume</strong>: The trading volume measured in the quote currency (as a string, convert to float for numerical operations).</li><li><strong>Number of trades</strong>: The number of trades that occurred during the interval (as an integer).</li><li><strong>Taker buy base asset volume</strong>: The volume of taker buy orders measured in the base asset (as a string, convert to float for numerical operations).</li><li><strong>Taker buy quote asset volume</strong>: The volume of taker buy orders measured in the quote asset (as a string, convert to float for numerical operations).</li><li><strong>Ignore</strong>: A placeholder for future use (as a string, generally ignored in analysis).</li></ul><p>You can also choose to only fetch the OHLCV data and leave the other variables.</p><h4>How to convert to float for numerical operations</h4><pre>data_binance[&#39;Open&#39;] = data_binance[&#39;Open&#39;].astype(float)</pre><h3>3. Fetching Data from Yahoo Finance using yfinance</h3><p>You can use Yahoo Finance to retrieve stock data.</p><pre>import yfinance as yf<br><br># Define the symbol and fetch data from Yahoo Finance<br>symbol_yahoo = &#39;TSLA&#39;<br>data_yahoo = yf.download(symbol_yahoo, start=&quot;2022-01-01&quot;, end=&quot;2023-01-01&quot;)<br><br>data_yahoo</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/868/1*bTrOmckxVZNm5js4kNb8zg.png" /><figcaption>Yahoo Finance market data</figcaption></figure><h3>Conclusion</h3><p>You can now access market data from Kraken, Binance, and Yahoo Finance with just a few lines of code.</p><p>Don’t forget API rate limits and always verify the data before using it in a trading strategy!</p><p>Thanks for reading and stay tuned for more articles! If you enjoyed reading this article, please follow and clap for more full Python scripts.</p><p>Follow us <a href="https://financepy.substack.com/">on Substack</a></p><p><strong><em>Disclaimer</em></strong><em>: While this article uses real market data for demonstration purposes, it is imperative to note that the content is provided for informational and educational purposes only. This article (python script included) is not under any circumstances financial advice. This article and code aim to enhance financial knowledge and Python programming skills. Always seek advice from a qualified financial professional before making any investment decisions. Investing involves risks, including the potential loss of principal.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=047865c5cb69" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The A-Z of Coding Your Way Through Stock Market Analytics]]></title>
            <link>https://medium.com/coinmonks/the-a-z-of-coding-your-way-through-stock-market-analytics-39d6f182f147?source=rss-2260c2b57e51------2</link>
            <guid isPermaLink="false">https://medium.com/p/39d6f182f147</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[stock-market]]></category>
            <category><![CDATA[investing]]></category>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[data-visualization]]></category>
            <dc:creator><![CDATA[Andreas Elledge]]></dc:creator>
            <pubDate>Mon, 09 Oct 2023 20:15:36 GMT</pubDate>
            <atom:updated>2023-10-10T14:16:57.398Z</atom:updated>
            <content:encoded><![CDATA[<p>A Python Series.</p><p>Extracting valuable financial insights to inform better investment decisions is the goal of every investor and stakeholder. More than analyzing data quickly, it’s also about reproducing that analysis over and over again with different assets. Having the same analytical starting point is crucial. And Python is the perfect tool for this kind of analysis.</p><p>In this article, we will go through a complete financial analytic Python script step by step. This script is designed to fetch, preprocess, analyze, and visualize stock data. We will understand how automated analyses can offer valuable and systematic insights to investors and stakeholders.</p><p>Our analysis will focus on the AI/tech industry from January 1, 2020, to January 1, 2023. The stocks analyzed include ‘GOOGL’ (Google), ‘NVDA’ (NVIDIA), ‘AMD’ (AMD), ‘INTC’ (Intel), ‘TSLA’ (Tesla), ‘IBM’ (IBM), ‘MSFT’ (Microsoft), and ‘ZM’ (Zoom). Each company presents a unique narrative within the AI/tech industry.</p><p>Of course, you can add more stocks to the analysis, look at stocks from another industry, compare two stocks, change the period… The possibilities are endless. The purpose of this Python script is that it can be reproduced. You can replicate the analysis with any stocks!</p><h4>Outline</h4><p>The Python script that we will create is divided into the following sections:</p><ol><li><strong>Data Retrieval and Preprocessing Function</strong></li><li><strong>Data Visualization Function</strong></li><li><strong>Daily Returns Distribution Visualization Function</strong></li><li><strong>Risk and Return Analysis Visualization Function</strong></li><li><strong>Correlation Analysis Visualization Function</strong></li></ol><p><strong>This is the final result.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wU7DEJZ9o4CbZ2SeE9wxUg.png" /><figcaption>Adjusted Close, Daily Return, and Volume for our chosen stocks</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*spsBkhx7jqO0fXycytgjjA.png" /><figcaption>Distribution of daily returns for our chosen stocks</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uAyQa_gRmcq_0FFJ_UmI_g.png" /><figcaption><strong>Trade-off</strong> between risk and returns for our chosen stocks</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A3BK_L4bSWlYleJIDu3OLQ.png" /><figcaption>Correlation heatmap of the returns of our chosen stocks</figcaption></figure><p><strong><em>Disclaimer</em></strong><em>: While this article uses real market data for demonstration purposes, it is imperative to note that the content is provided for informational and educational purposes only. This article (python script included) is not under any circumstances financial advice. This article and code aim to enhance financial knowledge and Python programming skills. Always seek advice from a qualified financial professional before making any investment decisions. Investing involves risks, including the potential loss of principal.</em></p><h3>Part 1: Data Retrieval and Preprocessing Function</h3><p>We first create the <em>fetch_and_preprocess</em> function. The data is from Yahoo Finance and you can add or remove stocks from the stocks list (see in the python script below). The <em>fetch_and_preprocess</em> function returns a dataframe with all the stock data. We thus get the OHLCV data and <em>adjusted close price</em> for all desired stocks.</p><blockquote><em>“Adjusted close is the closing price after adjustments for all applicable splits and dividend distributions.” — </em><a href="https://help.yahoo.com/kb/SLN28256.html#:~:text=Adjusted%20close%20is%20the%20closing,Security%20Prices%20(CRSP)%20standards"><em>Yahoo Finance</em></a><em>.</em></blockquote><pre>import pandas as pd<br>import matplotlib.pyplot as plt<br>import seaborn as sns<br>import yfinance as yf<br>from scipy.stats import skew, kurtosis<br><br><br>def fetch_and_preprocess(ticker, start_date, end_date):<br>    &quot;&quot;&quot;<br>    This function fetches stock data from Yahoo Finance and preprocess the stocks.<br>    <br>    Parameters:<br>    - ticker (str): The stock symbol.<br>    - start_date (str): The start date for fetching the data.<br>    - end_date (str): The end date for fetching the data.<br>    &quot;&quot;&quot;<br>    data = yf.download(ticker, start=start_date, end=end_date)<br>    data.reset_index(inplace=True)<br>    data[&#39;Date&#39;] = pd.to_datetime(data[&#39;Date&#39;])<br>    data[&#39;Daily Return&#39;] = data[&quot;Adj Close&quot;].pct_change()<br>    return data</pre><p>In this <em>fetch_and_preprocess</em> function, we reset the index, ensure our <em>Date</em> variable is in the correct datetime format, and calculate the <em>Daily Return</em> variable using the adjusted close variable fetched from Yahoo Finance.</p><p>Daily returns give us insights into the stock’s volatility and overall price movement. It can provide information about the stability and risk associated with a particular stock.</p><p>The <em>fetch_and_preprocess</em> function has the following parameters:</p><ul><li><em>ticker</em> (str): The stock symbol.</li><li><em>start_date</em> (str): The start date for fetching the data.</li><li><em>end_date</em> (str): The end date for fetching the data.</li></ul><p>In the script, we will call this function as follows:<br><em>list_of_dataframes = [fetch_and_preprocess(stock, ‘2018–01–01’, ‘2023–01–01’) for stock in stocks]</em></p><p>Where <em>stock</em> is the list of stocks with the tickers:<br><em>stocks = [‘GOOGL’, ‘NVDA’, ‘AMD’, ‘INTC’, ‘TSLA’, ‘IBM’, ‘MSFT’, ‘ZM’]</em></p><h3>Part 2: Data Visualization Functions</h3><p>The <em>plot_attributes</em> function plots the following attributes: <em>Adjusted Close, Daily Return</em>, and <em>Volume</em>. This means it will plot these variables for all stocks. That is the first 3 graphs on our analysis.</p><pre>def plot_attributes(list_of_dataframes, titles):<br>    &quot;&quot;&quot;<br>    This function plots the following attributes: Adjusted Close, Daily Return, and Volume.<br>    <br>    Parameters:<br>    - list_of_dataframes (list): List of dataframes containing stock data.<br>    - titles (list): List of titles for the stocks.<br>    &quot;&quot;&quot;<br>    attributes = [&quot;Adj Close&quot;, &quot;Daily Return&quot;, &quot;Volume&quot;]<br>    plt.figure(figsize=(25, 20))<br>    for idx, attribute in enumerate(attributes, 1):<br>        plt.subplot(3, 1, idx)<br>        for df, title in zip(list_of_dataframes, titles):<br>            plt.plot(df[&quot;Date&quot;], df[attribute], label=title)<br>        plt.title(attribute + &quot; Over Time&quot;)<br>        plt.xlabel(&quot;Date&quot;)<br>        plt.ylabel(attribute)<br>        plt.legend()<br>    plt.tight_layout()</pre><p>The function’s parameters are:</p><ul><li>list_of_dataframes (list): List of dataframes containing stock data.</li><li>titles (list): List of titles/labels for the stocks.</li></ul><p>Where <em>titles</em> is the list of the stocks titles:<br><em>titles = [“Google”, “NVIDIA”, “AMD”, “Intel”, “Tesla”, “IBM”, “Microsoft”, “Zoom”]</em></p><p>Let’s take a moment to review the intricacies of the code.</p><h4>Explaining enumerate</h4><p><em>enumerate</em> iterates over an iterable (like a list in our code) and access the index of the current item being processed.</p><p>The general syntax is the following:</p><pre>for index, element in enumerate(iterable, start_index):</pre><ul><li><em>index</em>: current index in the iteration, which starts from <em>start_index</em>.</li><li><em>element</em>: current element in the iteration.</li><li><em>iterable</em>: object to iterate over.</li><li><em>start_index</em>: starting index, default is 0.</li></ul><p>This is what we have in our code:</p><pre>for idx, attribute in enumerate(attributes, 1):</pre><ul><li><em>idx</em>: The index, starting from 1 (for reading purposes we start with 1).</li><li><em>attribute</em>: The element, which will be each string in <em>attributes</em> on each iteration.</li><li><em>attributes</em>: our list of strings <em>[“Adj Close”, “Daily Return”, “Volume”]</em> that will be iterated.</li></ul><h4>Explaining zip</h4><p><em>zip</em> iterates over two or more iterables in parallel. With two iterables, <em>zip</em> groups the first elements of each iterable, then the second elements.</p><p>The general syntax is the following:</p><pre>for element1, element2 in zip(iterable1, iterable2):</pre><ul><li><em>element1</em>: current element from <em>iterable1</em>.</li><li><em>element2</em>: current element from <em>iterable2</em>.</li></ul><p>This is what we have in our code:</p><pre>for df, title in zip(list_of_dataframes, titles):</pre><ul><li><em>df</em>: a “dataframe” from the <em>list_of_dataframes</em> list.</li><li><em>title</em>: its corresponding title from the <em>titles</em> list.</li></ul><h4>Breakdown of plot_attributes Function:</h4><ol><li><strong>Attributes List</strong>: These are the variables we want to plot.</li></ol><pre>attributes = [&quot;Adj Close&quot;, &quot;Daily Return&quot;, &quot;Volume&quot;]</pre><p><strong>2. Figure Size</strong>: We initialize a new figure with a specified size (width 25, height 20) to plot.</p><pre>plt.figure(figsize=(25, 20))</pre><p><strong>3. First Loop</strong>: We iterate over each attribute with the index starting from 1.</p><pre>for idx, attribute in enumerate(attributes, 1):</pre><p><strong>4. Subplot</strong>: We create a subplot grid of 3 rows and 1 column.</p><pre>plt.subplot(3, 1, idx)</pre><p><strong>5. Second Loop</strong>: We iterate concurrently over <em>list_of_dataframes</em> and <em>titles</em>.</p><pre>for df, title in zip(list_of_dataframes, titles):</pre><p><strong>6. Plotting Line</strong>: We plot the attribute.</p><pre>plt.plot(df[&quot;Date&quot;], df[attribute], label=title)</pre><p><strong>7. Title, Labels, and Legend</strong>: We define the title, the x and y-axis labels, and finally add a legend</p><pre>plt.title(attribute + &quot; Over Time&quot;)<br>plt.xlabel(&quot;Date&quot;)<br>plt.ylabel(attribute)<br>plt.legend()</pre><p><strong>8. Layout Adjustment</strong>: We make sure that the subplots fit</p><pre>plt.tight_layout()</pre><p>When we call the <em>plot_attributes</em> function <strong>we generate a 3-row subplot</strong>. We get each attribute for all stocks “Adj Close”, “Daily Return”, and “Volume” over the time period that we defined.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wU7DEJZ9o4CbZ2SeE9wxUg.png" /><figcaption>Adjusted Close, Daily Return, and Volume for our chosen stocks</figcaption></figure><h3>Part 3: Daily Returns Distribution Visualization Function</h3><p>The <em>plot_daily_return_distribution</em> function plots the distribution of daily returns for all stocks while also calculating the average expected return, the asymmetry of the returns distribution (skewness), and the “tailedness” of the returns distribution (kurtosis).</p><pre>def plot_daily_return_distribution(list_of_dataframes, titles):<br>    &quot;&quot;&quot;<br>    This function plots the distribution of daily returns along with the mean, skewness, and kurtosis.<br>    <br>    Parameters:<br>    - list_of_dataframes (list): List of dataframes containing stock data.<br>    - titles (list): List of titles for the stocks.<br>    &quot;&quot;&quot;<br>    plt.figure(figsize=(20, 15))<br>    for idx, df in enumerate(list_of_dataframes):<br>        plt.subplot(4, 2, idx + 1)<br>        sns.histplot(df[&quot;Daily Return&quot;].dropna(), color=&quot;blue&quot;, kde=True, bins=50)<br>        <br>        # We calculate the mean, skewness, and kurtosis<br>        mean_return = df[&quot;Daily Return&quot;].mean()<br>        skewness_return = skew(df[&quot;Daily Return&quot;].dropna())<br>        kurtosis_return = kurtosis(df[&quot;Daily Return&quot;].dropna())<br>        <br>        # Then we add a vertical line for the mean<br>        plt.axvline(mean_return, color=&#39;r&#39;, linestyle=&#39;--&#39;, label=f&#39;Mean: {mean_return:.6f}&#39;)<br>        <br>        # And we add text for the calculated statistics<br>        stats_text = f&quot;Skewness: {skewness_return:.2f}\nKurtosis: {kurtosis_return:.2f}&quot;<br>        plt.annotate(stats_text, xy=(0.05, 0.95), xycoords=&#39;axes fraction&#39;, fontsize=9, ha=&#39;left&#39;, va=&#39;top&#39;)<br>        <br>        plt.title(titles[idx] + &quot;: Distribution of Daily Returns&quot;)<br>        plt.xlabel(&quot;Daily Return&quot;)<br>        plt.ylabel(&quot;Frequency&quot;)<br>        plt.legend()<br>    plt.tight_layout()</pre><p>The function’s parameters are:</p><ul><li><em>list_of_dataframes</em> (list): List of dataframes containing stock data.</li><li><em>titles</em> (list): List of titles for the stocks.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*spsBkhx7jqO0fXycytgjjA.png" /><figcaption>Distribution of daily returns for our chosen stocks</figcaption></figure><h4>Financially, it’s interesting to visualize the distribution of daily returns for our set of stocks.</h4><p>First of all, the histograms allow us to make a quick assessment of the risk and volatility of the given stocks by looking at their spreads and tendencies. Wider spreads indicate higher volatility, which is generally riskier.</p><p>Second, by visualizing the histograms’ distributions, we can quickly perform a normality check. Certain financial models, like the Black-Scholes model, assume that returns are normally distributed.</p><p>Third, we can calculate <strong>the mean of the distribution for each stock</strong>, which would give us the expected returns. Also, it’s important to look at the proportion of negative returns and get a sense of the risk of losses. The mean (i.e., the expected return) is a critical parameter for many financial models as it indicates the average expected return. A daily mean close to zero is common due to upward and downward movements.</p><p>We also calculate <strong>skewness</strong>, which measures the asymmetry of the returns distribution. Positive skew indicates that the distribution tail is skewed towards the right, while negative skew indicates a left tail. What does this mean financially? It provides insight into the asymmetry of returns. Investors might prefer positive skewness, indicating a higher probability of large positive returns. Skewness also indicates the likelihood of extreme returns on one side of the mean.</p><p>We also calculate <strong>kurtosis</strong>, measuring the “tailedness” of the distribution. High kurtosis indicates heavy tails and sharp peaks (more prone to outliers), while low kurtosis indicates light tails and a flatter peak. Financially, high kurtosis suggests a higher likelihood of extreme returns (either high or low). Understanding kurtosis helps in risk management, particularly in evaluating the risk of extreme movements.</p><p>Fourth, examining the histograms of our various stocks allows us to compare their <strong>risk-return profiles</strong> and make better-informed investment decisions. If some stocks have similar (or dissimilar) return distributions, identifying them could help us in our portfolio management decision: how do we diversify our portfolio and/or how do we optimize our asset allocation?</p><p>Finally, visualizing histograms allows us to identify outliers. Spotting the extreme values helps us understand the likelihood of extreme returns or losses.</p><h3>Part 4: Risk and Return Analysis Visualization Function</h3><p>The <em>plot_risk_vs_expected_returns</em> function plots a scatter plot where each point represents a stock.</p><pre>def plot_risk_vs_expected_returns(returns, titles):<br>    &quot;&quot;&quot;<br>    This function plots risk vs expected returns.<br>    <br>    Parameters:<br>    - returns (pd.DataFrame): Dataframe containing stock returns.<br>    - titles (list): List of titles for the stocks.<br>    &quot;&quot;&quot;<br>    plt.figure(figsize=(20, 10))<br>    plt.scatter(returns.std(), returns.mean(), s=100)<br>    plt.ylabel(&#39;Expected Return&#39;)<br>    plt.xlabel(&#39;Risk&#39;)<br>    for label, x, y in zip(titles, returns.std(), returns.mean()):<br>        plt.annotate(label, xy=(x, y), xytext=(10, 0), textcoords=&#39;offset points&#39;)<br>    plt.title(&quot;Risk vs Expected Returns&quot;)<br>    plt.tight_layout()</pre><p>The function has the following parameters:</p><ul><li><em>returns</em> (pd.DataFrame): it is a DataFrame containing all stock returns (see below).</li><li><em>titles</em> (list): a list of titles for our chosen stocks.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/934/1*gNXkPCw0jpcVtNRq7RtWLw.png" /><figcaption>returns DataFrame</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uAyQa_gRmcq_0FFJ_UmI_g.png" /><figcaption>Trade-off between risk and returns for our chosen stocks</figcaption></figure><p><strong>This scatter plot is an interesting visualization because it allows us to visualize the trade-off between risk and returns for our chosen stocks.</strong></p><p>On the x-axis, we have the standard deviation of the stocks’ returns, representing risk. The standard deviation of returns is a proxy for a stock’s risk; the higher the standard deviation, the higher the risk due to increased price volatility.</p><p>On the y-axis, we have the mean of returns, representing the expected/average return.</p><p>The interpretation of this scatter plot goes as follows: On the x-axis, the further right, the higher risk/volatility. On the y-axis, the higher up, the higher expected return.</p><blockquote><strong><em>While a higher standard deviation indicates higher ‘risk’, it does not necessarily mean ‘bad’ as it depends on an investor’s risk tolerance.</em></strong></blockquote><h4>What kind of investment insights can we derive from this scatter plot?</h4><p>First of all, this plot illustrates that seeking higher returns usually means accepting higher risk. The <strong>risk-reward trade-off</strong> is well depicted on this scatter plot.</p><p>Also, we can use this plot for our portfolio diversification. Here, we can identify stocks that could enhance our portfolio diversification by analyzing their return and risk in relation to existing investments. For instance, if you already own Google and Tesla stocks, it could be interesting to visualize the next ones you want to purchase on this particular plot to enhance diversification.</p><p>Finally, this plot aids us in our stock selection. It enables us to see which ones offer the highest returns with the lowest risk.</p><p>Note that investors should balance between risk and return based on their investment strategy and risk tolerance; some might prefer a low-risk, low-return strategy, while others might be comfortable with a high-risk, high-return approach.</p><h3>Part 5. Correlation Analysis Visualization Function</h3><p>The <em>plot_correlations</em> function creates a correlation heatmap and calculates the correlation matrix of the returns of our chosen stocks.</p><pre>def plot_correlations(returns):<br>    &quot;&quot;&quot;<br>    Plot correlations heatmap.<br>    <br>    Parameters:<br>    - returns (pd.DataFrame): Dataframe containing stock returns.<br>    &quot;&quot;&quot;<br>    plt.figure(figsize=(12, 8))<br>    sns.heatmap(returns.corr(), annot=True, cmap=&quot;YlGnBu&quot;)<br>    plt.title(&quot;Correlation Matrix of Daily Returns&quot;)<br>    plt.tight_layout()</pre><p>The function has the following parameter:</p><ul><li><em>returns</em> (pd.DataFrame): it is a DataFrame containing all stock returns.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A3BK_L4bSWlYleJIDu3OLQ.png" /><figcaption>Correlation heatmap of the returns of our chosen stocks</figcaption></figure><p><strong>This correlation heatmap is interesting because it allows us to visualize the pairwise correlation between the returns of our chosen stocks.</strong></p><p>Here, we calculate the correlation coefficients that have a range between -1 to 1. This is how we interpret them: a coefficient of -1 indicates a perfect negative correlation (one stock increases in value while the other decreases), 1 indicates a perfect positive correlation (stocks move in the same direction), and 0 indicates no correlation. Note that the darker the color, the stronger the correlation.</p><blockquote><strong><em>Correlation does not imply causation.</em></strong></blockquote><p>Financially, we can use this correlation heatmap for diversification. By identifying stocks that are less positively/negatively correlated, we can build a diversified portfolio. This strategic stock allocation can help us optimize returns while managing risk. Understanding correlations allows us to anticipate potential impacts on our portfolio when a particular stock (or sector, for example) increases or decreases in value. This visual summary of how the returns of different stocks move relative to each other is key for informed investment decisions. It goes without saying that other factors, like the companies’ financial health, macroeconomic indicators, and/or industry trends should be taken into account.</p><h3>What Now?</h3><p>Finance with Python is powerful. We’ve created a suite of visualization functions that highlight hidden patterns and intricate relationships between stocks from the tech industry.</p><p>What do we have exactly that could be reproduced for stock analysis:</p><ul><li><strong>Time-series plots</strong> that chart stock prices and trading volumes, revealing trends and volatile periods that might be useful for investment decisions.</li><li><strong>Daily return distributions</strong> through histograms with the overlaying mean, skewness, and kurtosis. This statistical visualization illustrates the inherent risks and potential rewards of our chosen stocks.</li><li><strong>A risk-return scatter plot</strong> that serves as a guided investment decision tool by aligning stocks’ risk with return expectations.</li><li>Lastly, the <strong>correlation heatmap</strong> allows us to detect how stocks might move relative to each other, thus helping us design diversified portfolios.</li></ul><h4>Please find below the full Python script</h4><pre>import pandas as pd<br>import matplotlib.pyplot as plt<br>import seaborn as sns<br>import yfinance as yf<br>from scipy.stats import skew, kurtosis<br><br><br>def fetch_and_preprocess(ticker, start_date, end_date):<br>    &quot;&quot;&quot;<br>    This function fetches stock data from Yahoo Finance and preprocess the stocks.<br>    <br>    Parameters:<br>    - ticker (str): The stock symbol.<br>    - start_date (str): The start date for fetching the data.<br>    - end_date (str): The end date for fetching the data.<br>    &quot;&quot;&quot;<br>    data = yf.download(ticker, start=start_date, end=end_date)<br>    data.reset_index(inplace=True)<br>    data[&#39;Date&#39;] = pd.to_datetime(data[&#39;Date&#39;])<br>    data[&#39;Daily Return&#39;] = data[&quot;Adj Close&quot;].pct_change()<br>    return data<br><br>def plot_attributes(list_of_dataframes, titles):<br>    &quot;&quot;&quot;<br>    This function plots the following attributes: Adjusted Close, Daily Return, and Volume.<br>    <br>    Parameters:<br>    - list_of_dataframes (list): List of dataframes containing stock data.<br>    - titles (list): List of titles for the stocks.<br>    &quot;&quot;&quot;<br>    attributes = [&quot;Adj Close&quot;, &quot;Daily Return&quot;, &quot;Volume&quot;]<br>    plt.figure(figsize=(25, 20))<br>    for idx, attribute in enumerate(attributes, 1):<br>        plt.subplot(3, 1, idx)<br>        for df, title in zip(list_of_dataframes, titles):<br>            plt.plot(df[&quot;Date&quot;], df[attribute], label=title)<br>        plt.title(attribute + &quot; Over Time&quot;)<br>        plt.xlabel(&quot;Date&quot;)<br>        plt.ylabel(attribute)<br>        plt.legend()<br>    plt.tight_layout()<br><br>def plot_daily_return_distribution(list_of_dataframes, titles):<br>    &quot;&quot;&quot;<br>    This function plots the distribution of daily returns along with the mean, skewness, and kurtosis.<br>    <br>    Parameters:<br>    - list_of_dataframes (list): List of dataframes containing stock data.<br>    - titles (list): List of titles for the stocks.<br>    &quot;&quot;&quot;<br>    plt.figure(figsize=(20, 15))<br>    for idx, df in enumerate(list_of_dataframes):<br>        plt.subplot(4, 2, idx + 1)<br>        sns.histplot(df[&quot;Daily Return&quot;].dropna(), color=&quot;blue&quot;, kde=True, bins=50)<br>        <br>        # We calculate the mean, skewness, and kurtosis<br>        mean_return = df[&quot;Daily Return&quot;].mean()<br>        skewness_return = skew(df[&quot;Daily Return&quot;].dropna())<br>        kurtosis_return = kurtosis(df[&quot;Daily Return&quot;].dropna())<br>        <br>        # Then we add a vertical line for the mean<br>        plt.axvline(mean_return, color=&#39;r&#39;, linestyle=&#39;--&#39;, label=f&#39;Mean: {mean_return:.6f}&#39;)<br>        <br>        # And we add text for the calculated statistics<br>        stats_text = f&quot;Skewness: {skewness_return:.2f}\nKurtosis: {kurtosis_return:.2f}&quot;<br>        plt.annotate(stats_text, xy=(0.05, 0.95), xycoords=&#39;axes fraction&#39;, fontsize=9, ha=&#39;left&#39;, va=&#39;top&#39;)<br>        <br>        plt.title(titles[idx] + &quot;: Distribution of Daily Returns&quot;)<br>        plt.xlabel(&quot;Daily Return&quot;)<br>        plt.ylabel(&quot;Frequency&quot;)<br>        plt.legend()<br>    plt.tight_layout()<br><br><br>def plot_risk_vs_expected_returns(returns, titles):<br>    &quot;&quot;&quot;<br>    This function plots risk vs expected returns.<br>    <br>    Parameters:<br>    - returns (pd.DataFrame): Dataframe containing stock returns.<br>    - titles (list): List of titles for the stocks.<br>    &quot;&quot;&quot;<br>    plt.figure(figsize=(20, 10))<br>    plt.scatter(returns.std(), returns.mean(), s=100)<br>    plt.ylabel(&#39;Expected Return&#39;)<br>    plt.xlabel(&#39;Risk&#39;)<br>    for label, x, y in zip(titles, returns.std(), returns.mean()):<br>        plt.annotate(label, xy=(x, y), xytext=(10, 0), textcoords=&#39;offset points&#39;)<br>    plt.title(&quot;Risk vs Expected Returns&quot;)<br>    plt.tight_layout()<br><br>def plot_correlations(returns):<br>    &quot;&quot;&quot;<br>    Plot correlations heatmap.<br>    <br>    Parameters:<br>    - returns (pd.DataFrame): Dataframe containing stock returns.<br>    &quot;&quot;&quot;<br>    plt.figure(figsize=(12, 8))<br>    sns.heatmap(returns.corr(), annot=True, cmap=&quot;YlGnBu&quot;)<br>    plt.title(&quot;Correlation Matrix of Daily Returns&quot;)<br>    plt.tight_layout()<br><br># List of AI/Tech industry stocks and their names<br>stocks = [&#39;GOOGL&#39;, &#39;NVDA&#39;, &#39;AMD&#39;, &#39;INTC&#39;, &#39;TSLA&#39;, &#39;IBM&#39;, &#39;MSFT&#39;, &#39;ZM&#39;]<br>titles = [&quot;Google&quot;, &quot;NVIDIA&quot;, &quot;AMD&quot;, &quot;Intel&quot;, &quot;Tesla&quot;, &quot;IBM&quot;, &quot;Microsoft&quot;, &quot;Zoom&quot;]<br><br># Fetch and preprocess data for each stock<br>list_of_dataframes = [fetch_and_preprocess(stock, &#39;2020-01-01&#39;, &#39;2023-01-01&#39;) for stock in stocks]<br><br># Calculate returns for subsequent plots<br>returns = pd.DataFrame({title: df[&quot;Adj Close&quot;] for title, df in zip(titles, list_of_dataframes)}).pct_change().dropna()<br><br># Plotting<br>plot_attributes(list_of_dataframes, titles)<br>plot_daily_return_distribution(list_of_dataframes, titles)<br>plot_risk_vs_expected_returns(returns, titles)<br>plot_correlations(returns)<br><br>plt.show()</pre><h3>Conclusion</h3><p>While our analysis provides valuable insights, we should acknowledge their limitations; macroeconomic indicators, companies’ fundamentals, and external market forces are not factored in. These metrics are essential when creating a comprehensive investment strategy.</p><p>Moreover, we could go further with our analysis by creating more advanced financial models like the Efficient Frontier one or by leveraging machine learning algorithms. These enhancements could help us uncover patterns and/or trends imperceptible to traditional financial analysis.</p><p>We now have a good starting point for stock market analysis. Reproduce, compare, change industries, build from this Python script. See you next time!</p><p>Thanks for reading and stay tuned for more articles! If you enjoyed reading this article, <strong>please follow and clap</strong> for more full Python scripts.</p><p>Follow us <a href="https://financepy.substack.com/">on Substack</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=39d6f182f147" width="1" height="1" alt=""><hr><p><a href="https://medium.com/coinmonks/the-a-z-of-coding-your-way-through-stock-market-analytics-39d6f182f147">The A-Z of Coding Your Way Through Stock Market Analytics</a> was originally published in <a href="https://medium.com/coinmonks">Coinmonks</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Naive Persistence Model: A Baseline Forecasting Technique in Quantitative Finance]]></title>
            <link>https://medium.com/coinmonks/naive-persistence-model-a-baseline-forecasting-technique-in-quantitative-finance-3b1bf9b2e197?source=rss-2260c2b57e51------2</link>
            <guid isPermaLink="false">https://medium.com/p/3b1bf9b2e197</guid>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[forecasting]]></category>
            <category><![CDATA[quantitative-finance]]></category>
            <category><![CDATA[cryptocurrency]]></category>
            <dc:creator><![CDATA[Andreas Elledge]]></dc:creator>
            <pubDate>Thu, 05 Oct 2023 20:16:35 GMT</pubDate>
            <atom:updated>2023-10-06T13:36:48.005Z</atom:updated>
            <content:encoded><![CDATA[<p>Implementing and Evaluating a Naive Persistence Model Using Bitcoin Prices.</p><p>Accurately predicting the prices of stocks or cryptocurrencies is a coveted skill. When dealing with time series forecasting, it is essential to have a baseline model that serves as a benchmark against which we can compare the performance of more sophisticated models.</p><p>In a previous article, we covered the random binary prediction model as a baseline for predicting the stock price of Dassault Systèmes. This model, rooted in random chance, helped us evaluate the efficacy of more advanced predictive models.</p><p>Today we will have a look at another fundamental baseline model: <strong>the naive persistence model. </strong>This model predicts that the future price of an asset will be the same as its most recent price in the context of finance and trading. Unlike the random binary prediction model, the naive persistence model does not rely on randomness. Hence, the omission of <em>np.random.seed()</em> in our implementation since no random numbers are generated.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v-zVAv2vdwmLqTZKCTUNWA.jpeg" /><figcaption>DALL-E generated</figcaption></figure><p>This article will guide you step by step in implementing and evaluating a naive persistence model. <strong>We will focus on the price of Bitcoin</strong>.</p><h4>Outline:</h4><ul><li><strong>Part 1. Implementing the naive persistence model</strong></li><li><strong>Part 2. Understanding and evaluating the naive persistence model</strong></li></ul><p>Although we’ll use real market data, please keep in mind that this article is a tutorial, not financial advice, and aims to enhance our finance knowledge and Python skills.</p><h3>Part 1. Implementing the naive persistence model</h3><p>The naive persistence model is straightforward and does not account for market dynamics or historical trends. It takes the assumption that <strong>future prices will mirror current prices.</strong> The naive persistence model predicts… persistence in price. Tomorrow’s Bitcoin price will be the same as it is today!</p><blockquote><strong><em>The random binary prediction model employs random chance to predict prices movements. The naive persistence model predicts persistence in price.</em></strong></blockquote><pre>import pandas as pd<br>import yfinance as yf<br>from sklearn.metrics import mean_squared_error<br>from math import sqrt<br><br># Retrieving BTC prices<br>data = yf.download(&quot;BTC-USD&quot;, start=&quot;2017-01-01&quot;, end=&quot;2023-01-01&quot;)<br>btc_prices = data[&#39;Close&#39;].values<br><br># We split the data to get the training and testing sets<br>train, test = btc_prices[0:-10], btc_prices[-10:]<br><br># Naive persistence model<br>predictions = train[-10:]<br><br># Evaluating the model<br>rmse = sqrt(mean_squared_error(test, predictions))<br>print(&#39;RMSE: %.3f&#39; % rmse)</pre><h3>Part 2. Understanding and evaluating the naive persistence model</h3><p>We first fetch Bitcoin prices from Yahoo Finance. The data is then split into training and testing sets.</p><p>Here we have:</p><ul><li><em>train</em> that contains all the price data except for the last 10 closing prices.</li><li><em>test</em> that contains those last 10 closing prices.</li></ul><p>The naive persistence model then “predicts” that the next 10 prices will be the same as the last 10 observed prices in our training set. Persistence assumption is in action! In our code, the last 10 points (closing prices) of <em>train</em> are taken as <em>predictions</em>.</p><h4>Evaluating the naive persistence model</h4><p>To recap, we have the following arrays:</p><ul><li><em>test</em> that contains the actual values (last 10 true prices).</li><li><em>predictions</em> that contains the predicted values (last 10 prices from the training set).</li></ul><p>We evaluate the model with the <strong>root mean squared error or RMSE</strong>. It’s simply the square root of the mean squared error or MSE.</p><blockquote><em>The mean squared error or MSE measures the average of the squares of the errors, i.e., the average squared difference between the estimated values (the predicted prices) and the actual value (the true prices).</em></blockquote><h4>Diving into RMSE</h4><p>The RMSE is a performance indicator. It gives an estimate of how well a model is able to predict the target value, meaning that it gives us an <strong>indication of how accurate our predictions are compared to the actual values.</strong> It serves as a baseline metric.</p><p>The lower the RMSE, the better the model and its predictions. A higher RMSE indicates that on average the predictions are deviating more from the actual values, implying larger errors between the predicted and actual values: the RMSE is sensitive to the scale of the errors.</p><p><strong>Note that the RMSE gives higher weight to larger errors.</strong> This means that the RMSE is useful when large errors are undesirable. In the context of predicting the price of bitcoin, we are particularly interested in minimizing large prediction errors due to the potential significant impacts on trading decisions.</p><p>Keep in mind that a “good” or “acceptable” RMSE depends on the context and the specific application. Sometimes a model with a relatively high RMSE might provide valuable insights.</p><h4>Interpretation</h4><p>We get an RMSE (Root Mean Squared Error) of 514.714. It indicates that on average the model’s predictions are approximately 514.714 units away from the actual observed values. Here, we are trying to predict the price of Bitcoin so the model’s predictions are on average 514.714 USD off.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/988/1*5MKFZONp7oJgKXvuclEG7Q.png" /><figcaption>RMSE: 514.714</figcaption></figure><p><strong>We now have our benchmark metric.</strong> In the future, more sophisticated models should aim to have a lower RMSE to improve predictive accuracy.</p><p>Remember, the RMSE provides valuable insights into a model’s performance but decisions should be made holistically!</p><h3>Moving Forward</h3><p>To sum up, the naive persistence model can be used as an essential baseline for forecasting. The model assumes that future prices will mirror current ones and offers a starting point against which we can evaluate more complex models. Always refer back to it to ensure that newer models provide added predictive value!</p><p>Thanks for reading and stay tuned for more articles! If you enjoyed reading this article, <strong>please follow and clap</strong> for more full Python scripts.</p><p>You can also follow <a href="https://financepy.substack.com/">FinancePy on Substack</a></p><h4>Ressources</h4><p>“Mean Squared Error (MSE) | Lesson 9.1.5.” ProbabilityCourse.com, <a href="https://www.probabilitycourse.com/chapter9/9_1_5_mean_squared_error_MSE.php">https://www.probabilitycourse.com/chapter9/9_1_5_mean_squared_error_MSE.php</a>. Accessed 05/10/2023.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3b1bf9b2e197" width="1" height="1" alt=""><hr><p><a href="https://medium.com/coinmonks/naive-persistence-model-a-baseline-forecasting-technique-in-quantitative-finance-3b1bf9b2e197">Naive Persistence Model: A Baseline Forecasting Technique in Quantitative Finance</a> was originally published in <a href="https://medium.com/coinmonks">Coinmonks</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Laying the Groundwork for Quantitative Trading with Baseline Modeling]]></title>
            <link>https://medium.com/@andreaselledge/laying-the-groundwork-for-quantitative-trading-with-baseline-modeling-cb5f09cc540b?source=rss-2260c2b57e51------2</link>
            <guid isPermaLink="false">https://medium.com/p/cb5f09cc540b</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[quantitative-finance]]></category>
            <category><![CDATA[trading]]></category>
            <category><![CDATA[investing]]></category>
            <category><![CDATA[finance]]></category>
            <dc:creator><![CDATA[Andreas Elledge]]></dc:creator>
            <pubDate>Tue, 03 Oct 2023 21:22:33 GMT</pubDate>
            <atom:updated>2023-10-06T13:43:11.105Z</atom:updated>
            <content:encoded><![CDATA[<p>How Random Binary Prediction Models Serve as a Benchmark for More Complex and Profitable Strategies</p><p>Baseline models are essential to building accurate and profitable trading strategies. They serve as the starting point for any complex and more advanced models. Indeed, how can we measure the performance of our newly created strategy without a benchmark model for comparison?</p><blockquote><strong><em>The goal of a baseline model is to establish a benchmark against which we can compare the performance of more sophisticated models.</em></strong></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TqD1s2_7tt-LytqSS8ZeXA.jpeg" /><figcaption>DALL-E generated</figcaption></figure><p>There are several baseline models that we can pick from in the context of stock price prediction. This article will focus on <strong>random binary prediction modeling</strong>.</p><p>This model predicts one of two possible outcomes with an equal probability of 50%: stock price will go up, or down. We refer to the principle of random chance when using the random binary prediction model.</p><p>This article will guide you step by step in implementing, evaluating, and visualizing a baseline model. We will focus on the stock price of Dassault Systèmes (ticker: DSY).</p><h4><strong>Outline</strong></h4><ul><li><strong>Part 1. Fetching Dassault Systèmes stock price data and preprocessing it</strong></li><li><strong>Part 2. Implementing Single Run Baseline Model</strong></li><li><strong>Part 3. Exploring Variability in Baseline Model</strong></li><li><strong>Part 4. Visualizing the Baseline Accuracies</strong></li></ul><p>Although we’ll use real market data, please keep in mind that this article is a tutorial, not financial advice, and aims to enhance our finance knowledge and Python skills.</p><h3>Part 1. Fetching Dassault Systèmes stock price data and preprocessing it</h3><p>We fetch the stock data from Yahoo Finance.</p><pre># Importing Libraries<br>import pandas as pd<br>import numpy as np<br>import matplotlib.pyplot as plt<br>from sklearn.metrics import accuracy_score<br>import yfinance as yf<br><br># Fetching Dassault Systèmes data<br>ds_data = yf.download(&#39;DSY.PA&#39;, start = &#39;2010-01-01&#39;, end = &#39;2023-01-01&#39;)<br><br>ds_data</pre><pre># Calculating the daily return<br>ds_data[&#39;Return&#39;] = ds_data[&#39;Close&#39;].pct_change()<br><br># Drop NaN values<br>ds_data = ds_data.dropna()<br><br># Let&#39;s have a look<br>ds_data</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/980/1*bGGW18z6Mzwdua1GaL3Gng.png" /><figcaption>ds_data with daily returns</figcaption></figure><h3>Part 2. Implementing Single Run Baseline Model: the random binary prediction model</h3><pre># Setting the random seed for reproducibility<br>np.random.seed(44)<br><br># We then create random predictions: 1 for price increase, 0 for decrease / no change<br>random_predictions = np.random.randint(0, 2, len(ds_data[&#39;Return&#39;]))</pre><p>Let’s take a moment to understand the <strong>random seed</strong> concept.</p><p>The seed is an initial number that we set — it could be anything (123, 0, or 44) — for the computer to generate a “random” number. By defining a specific seed, the sequence of random numbers the computer will generate will be the same, i.e., <strong>our results can be reproduced</strong>.</p><blockquote><strong><em>If we didn’t define a seed, the results would change every time we run the code. We would not be able to compare different models.</em></strong></blockquote><p>Now let’s look at the <em>randint</em> function that generates random integers.</p><ul><li>0: start of the interval. Generated integers will be greater than or equal to 0.</li><li>2: end of the interval. Generated integers will be less than 2.</li><li><em>len(ds_data[‘Return’])</em>: Determines how many random integers to generate. For each entry in the <em>Return</em> variable a random integer will be created.</li></ul><h4>Application</h4><p>Let’s say <em>ds_data[‘Return’]</em> has 5 entries. We will generate 5 random integers between 0 (inclusive) and 2 (exclusive), for example <em>[1, 0, 1, 1, 0]</em>. We would thus have <em>[increase, decrease, increase, increase, decrease]</em> for the stock price movements.</p><p>Why do we generate these random predictions? To compare them with the actual price movements and see how well a completely random guess would perform.</p><p><strong>This helps us understand whether future complex models are genuinely predictive or merely performing as well as random chance.</strong></p><pre># We then convert actual returns to binary: 1 for increase, 0 for decrease / no change<br># Application np.where(condition, x, y)<br>actual_binary = np.where(ds_data[&#39;Return&#39;] &gt; 0, 1, 0)</pre><p>This is the binary representation of the stock price going up or not. With 1 representing an increase in Dassault Systèmes’ stock price and 0 a decrease or no change.</p><p>For instance, this binary array will often be used as the <em>true</em> values to train a classification model. It will also be used to assess accuracy by comparing it with the predictions <em>random_predictions</em>.</p><pre># Now let&#39;s calculate the accuracy score<br>baseline_accuracy = accuracy_score(actual_binary, random_predictions)<br>print(f&#39;Baseline model accuracy: {baseline_accuracy * 100:.2f}%&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/566/1*RJpi62_EOFJ4Co-Pd1qG2Q.png" /></figure><p>The accuracy score is the ratio of the number of correct predictions to the total number of predictions.</p><p><em>actual_binary</em>: contains the actual (true) values, ie. whether the stock price really went up (1) or not (0) for each day.</p><p><em>random_predictions</em>: contains the predicted (random guesses in this baseline model case) values, ie. our model’s prediction regarding whether the stock price went up (1) or not (0) for each day.</p><p><strong>The accuracy score is a decimal between 0 and 1</strong>: 1 for perfect accuracy (all predictions were correct) and 0 for no accuracy (all predictions were incorrect).</p><h4>Interpreting 50.08% accuracy</h4><p>Our score of 50.08% is what we might expect for binary classification; predictions essentially being a “coin flip”. We are therefore close to random. The 0.08% might indicate a tiny bit of predictive power but it’s too marginal to be considered as such and is probably due to random chance.</p><p>This being our baseline model, more complex ones should ideally achieve a higher accuracy.</p><h3>Part 3. Exploring Variability in Baseline Model</h3><p>We have calculated the accuracy score (50.08%) for a SINGLE run of random predictions against actual data. Let’s run it 1000 times with different random seeds.</p><p>The process of running 1000 random predictions and visualizing the distribution of accuracy scores is important. Indeed, this process will establish a distribution of accuracies we might expect from random guesses, build confidence in our baseline by observing the distribution, and finally inform us about the statistical significance of the results from any future developed models.</p><pre># Create 1000 random baseline accuracies<br>baseline_accuracies = []<br><br># Setting the seed inside the loop to get different accuracies<br>for _ in range(1000):<br>    np.random.seed(_)<br>    random_preds = np.random.randint(0, 2, len(ds_data[&#39;Return&#39;]))<br>    baseline_accuracies.append(accuracy_score(actual_binary, random_preds))</pre><h3>Part 4. Visualizing the Baseline Accuracies</h3><pre># Plotting<br>plt.figure(figsize=(12, 6))<br>plt.hist(baseline_accuracies, bins=30, color=&#39;skyblue&#39;, edgecolor=&#39;k&#39;)<br>plt.axvline(x=np.mean(baseline_accuracies), color=&#39;red&#39;, linestyle=&#39;--&#39;)<br>plt.title(&#39;Baseline Model Accuracies Distribution&#39;)<br>plt.xlabel(&#39;Accuracy&#39;)<br>plt.ylabel(&#39;Frequency&#39;)<br>plt.grid(axis=&#39;y&#39;)<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/987/1*O3fs_CE6o-b_67_O21IWDw.png" /></figure><h3>Where do we go from here?</h3><p>Our baseline model serves as a crucial reference point going forward. We now have a benchmark against which we can evaluate more complex and sophisticated models. In the future, a model that does not significantly outperform our baseline model might not be adding predictive insights.</p><p>Start building on this foundation: investigate financial indicators and key metrics that would enhance the predictive power of more complex models, explore different predictive models, look at risk management… The path is now paved towards crafting profitable quantitative trading strategies.</p><p>Thanks for reading and stay tuned for more articles! If you enjoyed reading this article, please follow FinancePy for more full Python scripts.</p><p>You can also follow <a href="https://financepy.substack.com/">FinancePy on Substack</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cb5f09cc540b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[You Need Personalized Crypto Universes for Optimal Profits]]></title>
            <link>https://medium.com/coinmonks/you-need-personalized-crypto-universes-for-optimal-profits-51cf8769a0c8?source=rss-2260c2b57e51------2</link>
            <guid isPermaLink="false">https://medium.com/p/51cf8769a0c8</guid>
            <category><![CDATA[investing]]></category>
            <category><![CDATA[cryptocurrency]]></category>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[trading-ideas]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Andreas Elledge]]></dc:creator>
            <pubDate>Mon, 02 Oct 2023 18:30:38 GMT</pubDate>
            <atom:updated>2023-10-04T17:41:47.318Z</atom:updated>
            <content:encoded><![CDATA[<p>Leveraging CoinMarketCap’s Tags to Harness Full Crypto Potential.</p><p>With over ten thousand cryptocurrencies listed on CoinMarketCap, the task of categorizing each one into a specific and accurate universe is daunting, if not nearly impossible. It’s an oversimplification to pigeonhole a cryptocurrency into just one universe given the multifaceted nature of digital assets and the rapid pace of development in the blockchain industry. Yet, the importance of establishing clear and accurate universes cannot be overstated; they are often the key ingredient in making profitable trading models and optimal portfolio allocations.</p><p>Meticulously defined universes can tremendously change the way we see the market, how we trade, and how we tweak models.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/519/1*57MCqvznuvPelcGAfVtYzA.jpeg" /><figcaption>DALL-E generated</figcaption></figure><p>Now, everyone has heard of key sectors and categories in crypto, and everyone has bet on a trending universe. But not so many can properly define the DeFi universe or be ultra specific about an Enterprise-solutions sub-universe. And that’s okay.</p><p>However, to truly stand out and gain a competitive edge with our trading models, it’s crucial to start crafting custom universes. Indeed, relying on conventional universes and categorizations might leave potential opportunities unexplored. But they can still help us as a starting point. By fetching CoinMarketCap’s tags and integrating in-depth industry knowledge, traders can easily (and rapidly) define their own unique universes.</p><blockquote><strong><em>Traders should define unique universes, tailor-made to their investment thesis and market outlook.</em></strong></blockquote><p>This custom-made approach not only offers a more refined lens to understand the industry but can lead to significant advantage. Note that defining a personalized universes demands research and knowledge, and discretionary decision making will be needed. However, in these times of “systematic” trading models and AI-driven strategies, it could be interesting to tweak our models based on our understanding of the crypto industry.</p><p>Let’s set the stage for innovative personalized crypto universes and potentially more profitable trading strategies.</p><p>Although we’ll use real market data, please keep in mind that this article is a tutorial, not financial advice, and aims to enhance our finance knowledge and Python skills.</p><h3>Part 1. Fetching All Tags for the Top 100 Coins from CoinMarketCap</h3><p>You will need to have a FREE CoinMarketCap account. Go to this <a href="https://pro.coinmarketcap.com/features/">link</a> and select the free API plan. You will then have your own personal API dashboard where you will be able to fetch your personalized CoinMarketCap API key. Just save it and replace ‘YOUR API KEY’ in the following code if you are following along.</p><p>To kick things off, we’ll tap into the CoinMarketCap API to retrieve all available tags.</p><p>The DataFrame will showcase the top 100 coins, their respective tags, and a placeholder for a future ‘universe’ variable.</p><pre>import requests<br>import pandas as pd<br><br># YOUR API KEY FROM COINMARKETCAP<br>api_key = &#39;YOUR API KEY&#39;<br><br># URL for the CoinMarketCap endpoint. We fetch the top 100 coins<br>url = &#39;https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest&#39;<br><br># Our request<br>headers = {&#39;X-CMC_PRO_API_KEY&#39;: api_key}<br>parameters = {&#39;start&#39;: &#39;1&#39;, &#39;limit&#39;: &#39;100&#39;, &#39;convert&#39;: &#39;USD&#39;}<br><br># Sending the request<br>response = requests.get(url, headers = headers, params = parameters)<br><br># &#39;200&#39; if the request is successful<br>if response.status_code == 200:<br>    data = response.json()[&#39;data&#39;]<br>    <br>    rows = []<br><br>    # Iterate through the top 100 coins<br>    for rank, crypto in enumerate(data, 1):<br>        name = crypto[&#39;name&#39;]<br>        symbol = crypto[&#39;symbol&#39;]<br>        tags = crypto[&#39;tags&#39;] if &#39;tags&#39; in crypto else []<br>        quote = crypto[&#39;quote&#39;][&#39;USD&#39;]<br>        market_cap = &quot;${:,.0f}&quot;.format(quote[&#39;market_cap&#39;])<br>        price = &quot;${:,.1f}&quot;.format(quote[&#39;price&#39;])<br><br>        # Collecting each row as a dictionary<br>        rows.append({<br>            &#39;Rank&#39;: rank,<br>            &#39;Name&#39;: name,<br>            &#39;Symbol&#39;: symbol,<br>            &#39;Market Cap&#39;: market_cap,<br>            &#39;Price&#39;: price,<br>            &#39;Tags&#39;: &quot;, &quot;.join(tags),<br>            &#39;Universe&#39;: &#39;Placeholder&#39;  # We add the &#39;Universe&#39; column. This is where you will custom your universe<br>        })<br><br>    df = pd.DataFrame(rows)<br><br>else:<br>    print(f&quot;Error fetching data: {response.status_code}&quot;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-6WhaAYD5s3YMhvYKP49EA.png" /><figcaption>Our DataFrame ‘df’ with tags</figcaption></figure><p>We can see that each coin has several tags, like ‘<em>mineable</em>’, ‘<em>pow</em>’, ‘<em>smart</em>-<em>contracts</em>’, ‘<em>payments</em>’, ‘<em>solana-ecosystem</em>’, or ‘<em>winklevoss-capital-portfolio</em>’.</p><p><strong>CoinMarketCap’s tags provide valuable information that will help us discretionarily define universes for digital assets.</strong></p><p>For instance, Ethereum has the tag ‘<em>layer-1</em>’ similar to BNB. But should we categorize both Ethereum and BNB into the ‘<em>layer-1</em>’ universe? This is where discretionary decisions come into play, and depending on different investment theses, the answer (and thus the universes) might differ.</p><p>Subscribe to get FinancePy discretionary universes.</p><h3>Part 2. Analyzing the Occurrences of Each Tag</h3><p>Let’s dive deeper into CoinMarketCap’s tags and analyze the occurrences of each tag.</p><pre>from collections import Counter<br><br># First we extract the tags from &#39;df&#39; and split it by &#39;, &#39;<br>all_tags = df[&#39;Tags&#39;].str.split(&#39;, &#39;).tolist()<br><br># Flatten the list of lists<br>flattened_tags = []<br>for sublist in all_tags:<br>    for tag in sublist:<br>        flattened_tags.append(tag)<br><br># Then we count the occurrences of each tag<br>tag_counts = Counter(flattened_tags)<br><br># We create a DataFrame for better visualization<br>tag_counts_df = pd.DataFrame(tag_counts.items(), columns = [&#39;Tag&#39;, &#39;Count&#39;]).sort_values(by = &#39;Count&#39;, ascending = False)<br><br>tag_counts_df.head(50)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lU3AqV_TyljEp-wVySm6iA.png" /><figcaption>Occurrences of each tag</figcaption></figure><h3>What Now?</h3><p>It’s interesting to see that many tags represent several well-known portfolios. The ‘<em>defi</em>’ tag appears the most with a count of 26.</p><p>We could simply get the top coins by marketcap from each of the top 10 portfolios and create a <strong>new ‘<em>top-portfolio</em>’ universe</strong>. Or we could define<strong> new ‘<em>defi</em>’ sub-universes</strong> and base our strategy on a specific ‘<em>defi</em>’ sub-universe.</p><p>The possibilities are endless. Explore them and discover untapped potential.</p><p>Thanks for reading and stay tuned for more articles! If you enjoyed reading this article, please follow FinancePy for more full Python scripts.</p><p>You can also follow <a href="https://financepy.substack.com/">FinancePy on Substack</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=51cf8769a0c8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/coinmonks/you-need-personalized-crypto-universes-for-optimal-profits-51cf8769a0c8">You Need Personalized Crypto Universes for Optimal Profits</a> was originally published in <a href="https://medium.com/coinmonks">Coinmonks</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Blocks for Python-Driven Trading Analysis. A Toolkit Series]]></title>
            <link>https://medium.com/coinmonks/building-blocks-for-python-driven-trading-analysis-a-toolkit-series-8af2498b488a?source=rss-2260c2b57e51------2</link>
            <guid isPermaLink="false">https://medium.com/p/8af2498b488a</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data]]></category>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[cryptocurrency-investment]]></category>
            <category><![CDATA[trading]]></category>
            <dc:creator><![CDATA[Andreas Elledge]]></dc:creator>
            <pubDate>Sun, 01 Oct 2023 18:35:44 GMT</pubDate>
            <atom:updated>2023-10-04T16:32:28.086Z</atom:updated>
            <content:encoded><![CDATA[<p>Most at-home traders have the urge to create a trading strategy as fast as possible. Yet, many of them backtest their own strategies without ever having heard of feature engineering or mastering fundamental finance concepts and Python skills. Only a few seem to understand the ins and outs of financial analysis using Python, real-time data aggregation, and strategy origination.</p><p>While the intertwined worlds of finance and Python are undeniably fascinating, knowing where to start can be overwhelming and challenging.</p><p>If you feel lost in this jungle of financial concepts and incomplete Python scripts, FinancePy could be your new go-to resource. This article will guide you step by step — and provide the full Python script — to retrieve Bitcoin OHLCV data from Kraken’s API for the last 720 days, compute key financial metrics such as the log return or rolling averages, and finally help you visualize the results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UkfUvO8kP-d8faEj-iy7gA.jpeg" /></figure><p>This article serves as a primer and aims to equip anyone with the foundational skills to create their own simple strategy in the future.</p><p>See this article as your gateway to “systematic” finance and discover how you can leverage the power of Python to create your own trading strategy from the ground up. More advanced articles will follow.</p><p>Although we’ll use real market data, please keep in mind that this article is a tutorial, not financial advice, and aims at enhancing our finance knowledge and Python skills. Note that this article focuses on Bitcoin as our chosen asset but that the following script can be applied to other assets of your choice.</p><h3>Part 1: Fetching 720 Days of Historical Data from Kraken</h3><p>We first import our required libraries and then initialize our public API. Here we are specifically fetching data from Kraken but note that the “ccxt” Python library can be used to fetch data from other exchanges like Binance. Be aware that symbol and timeframe formats may vary between exchanges, therefore you might need to check “ccxt” documentation before fetching new data from other exchanges.</p><p>We then define the asset’s symbol and the timeframe. We are looking for Bitcoin “BTC” traded in USD on Kraken “BTC/USD” and daily data. At the time of writing this article, we fetched the data on October 1, 2023. Please be aware that when fetching data at a later date results will change.</p><p>`fetch_ohlcv` retrieves the OHLCV (Open, High, Low, Close, Volume) data for the symbol and timeframe specified: OHLCV data is fundamental for conducting various types of technical analysis or creating a strategy.</p><p>Here, our DataFrame `data` has 720 entries. 1 entry represents 1 day. It’s important to determine the number of days of historical data available for technical indicators and backtesting purposes and to look at the data type of each of our variables.</p><pre>import ccxt<br>import pandas as pd<br>import numpy as np<br>import matplotlib.pyplot as plt<br><br># Initializing the connection with Kraken<br>kraken = ccxt.kraken()<br><br># We determine the symbol and desired timeframe<br>symbol = &#39;BTC/USD&#39;<br>timeframe = &#39;1d&#39;<br><br># We extract OHLCV data through `fetch_ohlcv()`<br>ohlcv = kraken.fetch_ohlcv(symbol, timeframe)<br><br># We create the DataFrame `data` from our `ohlcv` array<br>data = pd.DataFrame(ohlcv, columns = [&#39;timestamp&#39;, &#39;open&#39;, &#39;high&#39;, &#39;low&#39;, &#39;close&#39;, &#39;volume&#39;])<br>data[&#39;timestamp&#39;] = pd.to_datetime(data[&#39;timestamp&#39;], unit = &#39;ms&#39;)<br>data.set_index(&#39;timestamp&#39;, inplace = True)<br>data</pre><pre># Let&#39;s get more information from our data<br>data.info()<br>data.describe()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/784/1*H1UF06cXOvd4y-MHNB5nqA.png" /><figcaption>data.describe() — quick overview of the OHLCV BTC data</figcaption></figure><h3>Part 2: Calculating Advanced Financial Metrics and Technical Indicators</h3><p>Now that we have our Bitcoin `data` ready, we get to the interesting part, calculating key financial metrics:</p><ol><li>Logarithmic Returns (log return)</li><li>Rolling Volatility</li><li>50-day and 200-day Rolling Averages</li><li>Sharpe Ratio</li></ol><h4>Understanding Our Variables</h4><p><strong>1. Logarithmic Returns</strong>: Unlike raw price differences, logarithmic returns give a consistent metric to compare returns over time. You calculate it by taking the natural log of the ratio of consecutive prices. Log returns are important because they account for the compounding effect. They also give a more accurate representation of returns over time. You can see how we calculate the log returns in the code below.</p><p>Cool characteristics about log returns!</p><ul><li><strong>Compounding</strong>: They can be added together over time, which is not the case for simple returns.</li><li><strong>Statistical Properties</strong>: They are approximately normally distributed for many assets.</li><li><strong>Stability</strong>: They are more stable and less skewed than simple returns.</li></ul><p>Note that for small return values the difference between log returns and simple returns is minimal… But as returns become larger, the difference is more pronounced.</p><p><strong>2. Rolling Volatility:</strong> The rolling volatility measures the degree of variation of a trading price series over time. Here, our 21-day rolling volatility is the standard deviation of the log returns over the past 21 days. We get a moving snapshot of how volatile the Bitcoin prices have been in the past two years and can assess its potential market risks.</p><p><strong>3. 50-day and 200-day Rolling Averages</strong>: These metrics smooth out our price data to reveal underlying trends. They’re crucial in trend-following strategies, helping us identify potential buy or sell opportunities based on crossovers or relative positions of the Bitcoin price to these averages.</p><p><strong>4. Sharpe Ratio</strong>: This is a measure of risk-adjusted return. It describes how much excess return you can expect to receive for the extra volatility of holding a riskier asset, like Bitcoin in this case. A higher Sharpe ratio indicates a better risk-adjusted performance. The formula involves subtracting the risk-free rate from the asset’s return and then dividing by its volatility. This gives us a sense of the ‘price’ of the risk taken to achieve a certain level of return.</p><p>In this tutorial we arbitrarily set the risk-free rate to 1% but just know for now that the choice of the risk-free rate depends on the analysis. Often, in U.S. markets, the three-month U.S. Treasury bill is set as the risk-free rate of return, given the very low risk of default by the American government. We will cover this topic in a next article.</p><blockquote><strong>Focus: As of June 30, 2023, the S&amp;P 500 Portfolio Sharpe ratio is 0.88. (source Investopedia).</strong></blockquote><pre># Computing log returns<br>data[&#39;log_return&#39;] = np.log(data[&#39;close&#39;] / data[&#39;close&#39;].shift(1))<br><br># Computing rolling volatility<br>data[&#39;rolling_volatility&#39;] = data[&#39;log_return&#39;].rolling(window=21).std()<br><br># Computing 50-day and 200-day moving averages<br>data[&#39;50_MA&#39;] = data[&#39;close&#39;].rolling(window=50).mean()<br>data[&#39;200_MA&#39;] = data[&#39;close&#39;].rolling(window=200).mean()<br><br># Computing sharp ratio<br>risk_free_rate = 0.01  <br>annualized_sharpe_ratio = (data[&#39;log_return&#39;].mean() - risk_free_rate) / data[&#39;log_return&#39;].std() * np.sqrt(252) # or 365 days in the case of assets traded all year</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0n0juQiXDtEgxWunBW5MlQ.png" /><figcaption>New variables added</figcaption></figure><h4>Rolling Averages Focus</h4><p>Let’s take a moment to talk about rolling averages, also known as moving averages. They are a type of data smoothing technique. By averaging out data over specific intervals, rolling averages help in revealing underlying trends and patterns. These are extremely useful to get a first understanding of our data.</p><p>The ‘window size’ like 50 days or 200 days in our case, determines the number of days to average and also defines the lag from the data. A 50-day rolling average will use the previous 50 days’ data points to compute an average for a specific day, thus giving a smoother line that can potentially signal long-term trends. A 200-day rolling average is considered even more long-term.</p><p>In financial analysis, these rolling averages have multiple purposes:</p><ol><li><strong>Trend Identification</strong>: A rising moving average typically suggests an uptrend, while a falling moving average indicates a downtrend.</li><li><strong>Support and Resistance Levels</strong>: In chart analysis, moving averages can act as support or resistance lines.</li><li><strong>Signal Generation</strong>: When a short-term moving average (like the 50-day MA) crosses above a long-term moving average (like the 200-day MA), it’s considered a bullish signal, and vice versa.</li></ol><p>If you wanted to create a trading strategy, or just follow some key metrics for your technical analysis, the rolling averages can be a good starting point.</p><h4>Understanding NaNs in our Data</h4><p>In our DataFrame ‘data’, we can observe some entries with NaN (Not a Number). These are a result of certain calculations; the <em>rolling metrics</em>.</p><p>This concept is straightforward to understand. When computing a 50-day rolling average, the first 49 entries won’t have a value because they don’t have 50 preceding days to average from. Therefore, these entries are populated with NaN. This is a common occurrence in time series analysis.</p><h3>Part 3: Visualizing our data and metrics</h3><p>We plot BTC closing prices as well as the moving averages for the period. It can also be interesting to visualize the average price and volatility of the asset. We can represent them by dotted red lines.</p><pre># Background grid<br>plt.style.use(&#39;seaborn-whitegrid&#39;)<br><br># Plotting BTC closing prices (last 720 days) and average with the 50 and 200 MAs<br>average_price = data[&#39;close&#39;].mean()<br><br>plt.figure(figsize = (14, 7))<br>data[[&#39;close&#39;, &#39;50_MA&#39;, &#39;200_MA&#39;]].plot(ax = plt.gca(), title = &quot;BTC/USD Price Trends &amp; Moving Averages&quot;)<br>plt.axhline(average_price, color = &#39;red&#39;, linestyle = &#39;--&#39;, label = f&quot;BTC Average Price: ${average_price:.2f}&quot;)<br>plt.ylabel(&quot;Price (USD)&quot;)<br>plt.legend()<br>plt.show()<br><br># Visualizing Rolling Volatility with average volatility<br>average_volatility = data[&#39;rolling_volatility&#39;].mean()<br><br>plt.figure(figsize = (14, 7))<br>data[&#39;rolling_volatility&#39;].plot(title = &quot;Bitcoin&#39;s 21-Day Rolling Volatility&quot;)<br>plt.axhline(average_volatility, color = &#39;red&#39;, linestyle = &#39;--&#39;, label = f&quot;Average Volatility: {average_volatility:.6f}&quot;)<br>plt.ylabel(&quot;Volatility&quot;)<br>plt.legend()<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y45oyNEt9NtZjDh1ECeRNA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g5zM4m6Uy69WM7qEeo9Rgw.png" /></figure><h3>Conclusion</h3><p>These building blocks, though “systematic”, are just the tip of the iceberg. You now know how to fetch live data using the ccxt library, calculate key trading metrics, and visualize them.</p><p>With this foundational knowledge, you are now better equipped to tackle more advanced topics. The real challenge, however, lies in integrating financial concepts with python programming to uncover profitable trading strategies. It takes time, practice and extensive research, but it’s a fascinating and rewarding journey.</p><p>In future articles, we will explore backtesting, the discovery and tweaking of trading features, selecting a trading universe, and much more.</p><p>Thanks for reading and stay tuned for more articles in this <strong><em>Toolkit series</em></strong>!</p><p><strong>If you enjoyed reading this article, please follow FinancePy for more full Python scripts.</strong></p><p><em>You can also follow FinancePy on </em><a href="https://financepy.substack.com/"><em>Substack</em></a></p><h4>Ressources:</h4><p><strong>Eng Guan</strong>. (2023, Mar 3). Magic of Log Returns: Concept — Part 1. Updated Mar 10. Retrieved on September 30, 2023, from <a href="https://www.allquant.co/post/magic-of-log-returns-concept-part-1">allquant</a>.</p><p><strong>Hayes, A</strong>. (2023, May 21). What Is the Risk-Free Rate of Return, and Does It Really Exist? Reviewed by Cierra Murry. Fact checked by Pete Rathburn. Investopedia. Consulted on September 29, 2023. Retrieved from <a href="https://www.investopedia.com/terms/r/risk-freerate.asp">investopedia</a>.</p><p><strong>Fernando, J</strong>. (2023, May 11). Sharpe Ratio: Definition, Formula, and Examples. Reviewed by Margaret James. Fact checked by Katrina MuniChiello. Investopedia. Consulted on September 30, 2023. Retrieved from <a href="https://www.investopedia.com/terms/s/sharperatio.asp">investopedia</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8af2498b488a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/coinmonks/building-blocks-for-python-driven-trading-analysis-a-toolkit-series-8af2498b488a">Building Blocks for Python-Driven Trading Analysis. A Toolkit Series</a> was originally published in <a href="https://medium.com/coinmonks">Coinmonks</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The A-Z of Coding a Quantitative Trading Strategy. A Python Series]]></title>
            <link>https://medium.com/@andreaselledge/the-a-z-of-creating-a-quantitative-trading-strategy-a-python-series-d0f00df0d62?source=rss-2260c2b57e51------2</link>
            <guid isPermaLink="false">https://medium.com/p/d0f00df0d62</guid>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[quantitative-trading]]></category>
            <category><![CDATA[cryptocurrency]]></category>
            <category><![CDATA[quantitative-strategies]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Andreas Elledge]]></dc:creator>
            <pubDate>Tue, 16 May 2023 08:43:22 GMT</pubDate>
            <atom:updated>2023-10-07T12:00:34.308Z</atom:updated>
            <content:encoded><![CDATA[<h3>The A-Z of Coding a Trading Strategy. A Python Series</h3><p>A comprehensive tutorial to coding your first trading strategy.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/942/1*mxph_dkRhbzqguBthG7Mug.png" /><figcaption>DALL-E generated image</figcaption></figure><h4>Introduction</h4><p>Creating a personal trading strategy is becoming more and more popular amongst at-home traders and/or Python enthusiasts. This article will go through the complete lifecycle of a trading strategy: starting from procuring market data, computing technical indicators, generating trading signals and finally assessing the efficacy of the strategy.</p><p>We will use $ETH prices in USD from Kraken’s API and build two strategies based on the Moving Average Convergence/Divergence (MACD) and the Awesome Oscillator (AO).</p><p>By the end of this article, one should have a robust foundation to build on, and the capability to test its own trading strategies and assess their potential.</p><p>That said, it’s critical to note that the strategies we are deploying here are simplified versions designed for demonstrative purposes. There are additional factors that a pragmatic trading strategy ought to consider, such as trading costs, market influence, risk management, and more. Moreover, tangible trading conditions, like existing portfolio holdings, can significantly influence the outcome of a strategy. Although we’ll use real market data, keep in mind that this article is a tutorial, not financial advice, and aims at enhancing our Python skills.</p><p>With this in mind, let’s create our own trading strategy!</p><h4>Part 1: Fetching historical data from Kraken</h4><p>We first import our required libraries and then initialize our public API. While we are fetching data from Kraken, note that the “ccxt” Python library can be used to fetch data from multiple exchanges like Binance. Be aware that symbol and timeframe formats may vary between exchanges, consequently you might need to check “ccxt” documentation before fetching new data from other exchanges.</p><p>We define the symbol and the timeframe. We are looking for Ethereum “$ETH” traded in USD on Kraken “ETH/USD” and daily data.</p><p>“fetch_ohlcv” retrieves the OHLCV (Open, High, Low, Close, Volume) data for the symbol and timeframe specified: OHLCV data is fundamental for creating a strategy or conducting various types of technical analysis.</p><pre>import ccxt<br>import pandas as pd<br><br># Kraken API<br>exchange = ccxt.kraken()<br><br># We define the symbol and timeframe<br>symbol = &#39;ETH/USD&#39;<br>timeframe = &#39;1d&#39;<br><br># We fetch OHLCV data<br>ohlcv = exchange.fetch_ohlcv(symbol, timeframe)<br><br># We first convert to DataFrame then to Timestamp for a more readable format<br>df = pd.DataFrame(ohlcv, columns=[&#39;Timestamp&#39;, &#39;Open&#39;, &#39;High&#39;, &#39;Low&#39;, &#39;Close&#39;, &#39;Volume&#39;])<br>df[&#39;Timestamp&#39;] = pd.to_datetime(df[&#39;Timestamp&#39;], unit=&#39;ms&#39;)<br>df.set_index(&#39;Timestamp&#39;, inplace=True)<br><br># We now have our DataFrame<br>df</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BXLI7QZ86jl3GScrn6U6Ww.png" /><figcaption>Our OHLCV data from Kraken</figcaption></figure><p>Here, let’s keep in mind that our DataFrame “df” has 720 entries. 1 entry represents 1 day. It’s important to determine the number of days of historical data available for technical indicators and backtesting purposes.</p><h4>Part 2: Calculating the MACD and the AO</h4><p>We now calculate the MACD and AO from our fetch data. The definitions below come from Investopedia and IG International. You will find the links in the ressources section.</p><blockquote>“The Moving Average Convergence/Divergence (MACD) is a trend-following momentum indicator that shows the relationship between two exponential moving averages (EMAs) of a security’s price. The MACD line is calculated by subtracting the 26-period EMA from the 12-period EMA.</blockquote><blockquote>The result of that calculation is the MACD line. A nine-day EMA of the MACD line is called the signal line, which is then plotted on top of the MACD line, which can function as a trigger for buy or sell signals.” <strong>— Investopedia</strong></blockquote><p>For the MACD, we compute the MACD Line, the Signal Line, and the MACD Histogram in order to visualize it completely and develop a proper strategy.</p><blockquote>“The Awesome Oscillator (AO) is a market momentum indicator which compares recent market movements to historic market movements. It uses a zero line in the centre, either side of which price movements are plotted according to a comparison of two different moving averages.” — <strong>IG International</strong></blockquote><p>For the AO, we compute a histogram anchored around a zero line.</p><p>We calculate both indicators using the “ta” Python library.</p><pre># Calculate MACD Line, Signal Line and MACD Histogram<br>macd = ta.trend.MACD(df[&#39;Close&#39;])<br><br>df[&#39;macd_line&#39;] = macd.macd()  # MACD Line<br>df[&#39;macd_signal&#39;] = macd.macd_signal()  # Signal Line<br>df[&#39;macd_hist&#39;] = macd.macd_diff()  # MACD Histogram (MACD Line - Signal Line)<br><br># Calculate Awesome Oscillator<br>ao = ta.momentum.AwesomeOscillatorIndicator(df[&#39;High&#39;], df[&#39;Low&#39;])<br><br>df[&#39;ao&#39;] = ao.awesome_oscillator()  # Awesome Oscillator<br><br>df</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0eLfODr6Vko4BH8GoCrcZQ.png" /><figcaption>Our DataFrame “df” with the newly calculated indicators</figcaption></figure><h4>Part 3: Dropping the NaN values from our DataFrame</h4><p>This is a brief but crucial part of the tutorial. Given that the data for MACD and AO can’t be calculated due to the absence of historical prices before 2021–05–13, we drop all the <em>NaN</em> values from our DataFrame “df”. Always understand an indicator before adding it to your strategy!</p><pre># Drop NaN values<br>df.dropna(inplace=True)<br>df.info()</pre><p>We now have prices for 687 days: from the <em>2021–06–15</em> to the <em>2023–05–02</em>. Note that if you reproduce this code at home, you will fetch data that will have a different timeframe from this article (since Kraken only allows you to get data from the last 720 days, there will be a lag). But not to worry, it’s still the same process!</p><h4>Part 4: Plotting the ETH prices and the indicators</h4><pre>import matplotlib.pyplot as plt<br>import matplotlib.dates as mdates<br><br>fig, ax = plt.subplots(3, figsize=(12,12), dpi=200)<br><br># Plot ETH prices<br>ax[0].plot(df.index, df[&#39;Close&#39;], label=&#39;ETH/USD&#39;)<br>ax[0].set_title(&#39;ETH/USD Prices - Last 687 days&#39;)<br>ax[0].legend()<br>ax[0].set_xlim([df.index.min(), df.index.max()])<br><br># Plot MACD Line, Signal Line and Histogram<br>ax[1].plot(df.index, df[&#39;macd_line&#39;], label=&#39;MACD Line&#39;) # Plot MACD Line and Signal Line<br>ax[1].plot(df.index, df[&#39;macd_signal&#39;], label=&#39;Signal Line&#39;) # Plot MACD Line and Signal Line<br>ax[1].bar(df.index, df[&#39;macd_hist&#39;], alpha=0.5, label=&#39;MACD Histogram&#39;) # Plot MACD Histogram as bar plot<br>ax[1].set_title(&#39;MACD&#39;)<br>ax[1].legend()<br>ax[1].set_xlim([df.index.min(), df.index.max()])<br><br># Plot Awesome Oscillator<br>ax[2].bar(df.index, df[&#39;ao&#39;], alpha=0.5, label=&#39;Awesome Oscillator&#39;, color=&#39;gray&#39;)<br>ax[2].set_title(&#39;Awesome Oscillator&#39;)<br>ax[2].legend()<br>ax[2].set_xlim([df.index.min(), df.index.max()])<br><br># Set date format<br>date_format = mdates.DateFormatter(&#39;%b %y&#39;)<br>ax[0].xaxis.set_major_formatter(date_format)<br>ax[1].xaxis.set_major_formatter(date_format)<br>ax[2].xaxis.set_major_formatter(date_format)<br><br># Show the plot<br>plt.tight_layout()<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Bq7UBg-QQJRg8iITTWlmdA.png" /><figcaption>ETH/USD Prices with MACD and AO — Last 687 days</figcaption></figure><h4>Part 5: Generating trading signals</h4><p>We’ll define two strategies based on the MACD and AO signals.</p><blockquote><strong>Strategy 1</strong>: <em>Buy when both MACD and AO are positive, sell when they’re both negative.</em></blockquote><blockquote><strong>Strategy 2</strong>: <em>Buy when MACD crosses above the signal line and AO crosses above zero, sell when MACD crosses below the signal line and AO crosses below zero.</em></blockquote><pre>import numpy as np<br><br># Strategy 1<br># Generating the buy &quot;signal1&quot;<br>df[&#39;signal1&#39;] = np.where((df[&#39;macd_line&#39;] &gt; df[&#39;macd_signal&#39;]) &amp; (df[&#39;ao&#39;] &gt; 0), 1, 0)<br># Generating the &quot;sell_signal1&quot;<br>df[&#39;sell_signal1&#39;] = np.where((df[&#39;macd_line&#39;] &lt; df[&#39;macd_signal&#39;]) &amp; (df[&#39;ao&#39;] &lt; 0), 1, 0)<br><br># Strategy 2<br># Generating the buy &quot;signal2&quot; <br>df[&#39;signal2&#39;] = np.where((df[&#39;macd_line&#39;] &gt; df[&#39;macd_signal&#39;]) &amp; (df[&#39;macd_line&#39;].shift() &lt; df[&#39;macd_signal&#39;].shift()) &amp; (df[&#39;ao&#39;] &gt; 0) &amp; (df[&#39;ao&#39;].shift() &lt; 0), 1, 0)<br># Generating the &quot;sell_signal2&quot;<br>df[&#39;sell_signal2&#39;] = np.where((df[&#39;macd_line&#39;] &lt; df[&#39;macd_signal&#39;]) &amp; (df[&#39;macd_line&#39;].shift() &gt; df[&#39;macd_signal&#39;].shift()) &amp; (df[&#39;ao&#39;] &lt; 0) &amp; (df[&#39;ao&#39;].shift() &gt; 0), 1, 0)<br><br># Counting the buy/sell signals<br>num_signals1 = df[&#39;signal1&#39;].sum()<br>num_sells1 = df[&#39;sell_signal1&#39;].sum()<br>num_signals2 = df[&#39;signal2&#39;].sum()<br>num_sells2 = df[&#39;sell_signal2&#39;].sum()<br><br>print(f&quot;Number of buy signals in Strategy 1: {num_signals1}&quot;)<br>print(f&quot;Number of sell signals in Strategy 1: {num_sells1}&quot;)<br>print(f&quot;Number of buy signals in Strategy 2: {num_signals2}&quot;)<br>print(f&quot;Number of sell signals in Strategy 2: {num_sells2}&quot;)</pre><p>In summary, both strategies rely on the MACD Line, Signal Line, and AO value to generate buy and sell signals. Strategy 1 is based on simple intersections of the MACD Line and Signal Line, along with the AO value being above or below zero. Strategy 2, on the other hand, focuses on the crossing points of the MACD Line and Signal Line, as well as the crossing points of the AO value above and below zero.</p><blockquote>Strategy 2 is more sensitive because it is looking for the points at which the MACD Line crosses the Signal Line and when the AO value crosses zero. These crossing points typically represent changes in market momentum, which is why the strategy is said to be sensitive to changes in momentum.</blockquote><h4>Part 6: Plotting the signals on the graph</h4><pre>fig, ax = plt.subplots(4, figsize=(12, 30))<br><br># Plot ETH prices<br>ax[0].plot(df.index, df[&#39;Close&#39;], label=&#39;ETH/USD&#39;)<br>ax[0].scatter(df[df[&#39;signal1&#39;] == 1].index, df[df[&#39;signal1&#39;] == 1][&#39;Close&#39;], color=&#39;g&#39;, marker=&#39;^&#39;, label=&#39;Buy Signal - Strategy 1&#39;)<br>ax[0].scatter(df[df[&#39;sell_signal1&#39;] == 1].index, df[df[&#39;sell_signal1&#39;] == 1][&#39;Close&#39;], color=&#39;r&#39;, marker=&#39;v&#39;, label=&#39;Sell Signal - Strategy 1&#39;)<br>ax[0].scatter(df[df[&#39;signal2&#39;] == 1].index, df[df[&#39;signal2&#39;] == 1][&#39;Close&#39;], color=&#39;b&#39;, marker=&#39;o&#39;, label=&#39;Buy Signal - Strategy 2&#39;, alpha=0.7)<br>ax[0].scatter(df[df[&#39;sell_signal2&#39;] == 1].index, df[df[&#39;sell_signal2&#39;] == 1][&#39;Close&#39;], color=&#39;orange&#39;, marker=&#39;x&#39;, label=&#39;Sell Signal - Strategy 2&#39;, alpha=0.7)<br>ax[0].set_title(&#39;ETH/USD Prices - Last 687 days&#39;)<br>ax[0].legend()<br>ax[0].set_xlim([df.index.min(), df.index.max()])<br><br># Plot MACD Line, Signal Line and Histogram<br>ax[1].plot(df.index, df[&#39;macd_line&#39;], label=&#39;MACD Line&#39;)<br>ax[1].plot(df.index, df[&#39;macd_signal&#39;], label=&#39;Signal Line&#39;)<br>ax[1].bar(df.index, df[&#39;macd_hist&#39;], alpha=0.5, label=&#39;MACD Histogram&#39;)<br>ax[1].set_title(&#39;MACD&#39;)<br>ax[1].legend()<br>ax[1].set_xlim([df.index.min(), df.index.max()])<br><br># Plot Awesome Oscillator<br>ax[2].bar(df.index, df[&#39;ao&#39;], alpha=0.5, label=&#39;Awesome Oscillator&#39;, color=&#39;gray&#39;)<br>ax[2].set_title(&#39;Awesome Oscillator&#39;)<br>ax[2].legend()<br>ax[2].set_xlim([df.index.min(), df.index.max()])<br><br># Let&#39;s also set the appropriate date format<br>date_format = mdates.DateFormatter(&#39;%b %y&#39;)<br>for a in ax:<br>    a.xaxis.set_major_formatter(date_format)<br><br># Plot<br>plt.tight_layout()<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NugetusUtov9ZzJuUaklSA.png" /><figcaption>Hundreds of buy and sell signals for strat1, only 3 for strat2</figcaption></figure><h4>Part 7: Backtesting</h4><p>To evaluate our strategies, we calculate the daily returns based on the generated buy and sell signals: we calculate the daily returns as the percentage change in the closing price.</p><p>Then, we calculate the returns for each strategy by multiplying the daily returns with the respective signals (shifted by one day to avoid forward-looking bias). Cumulative returns are then calculated by cumulatively multiplying the daily returns (plus one); it simulates the compounding of returns as if you reinvested the proceeds of each trade.</p><p>Strategy 2 generated only 1 buy and 2 sell signals. We won’t be backtesting this strategy.</p><blockquote>We recommend using the “<em>quantstats</em>” Python library for detailed reports.</blockquote><pre>import quantstats as qs<br><br># We calculate the daily returns<br>df[&#39;returns&#39;] = df[&#39;Close&#39;].pct_change()<br><br># Strategy 1 returns<br>df[&#39;strategy1_returns&#39;] = df[&#39;returns&#39;] * df[&#39;signal1&#39;].shift()<br>df[&#39;strategy1_cum_returns&#39;] = (1 + df[&#39;strategy1_returns&#39;]).cumprod()<br><br># Drop the NaN values<br>df.dropna(inplace=True)<br><br># Let&#39;s print the report for Strategy 1<br>print(&quot;Strategy 1:&quot;)<br>qs.reports.full(df[&#39;strategy1_returns&#39;])</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9wv5fV1c9cBrvsnvomPVuA.png" /><figcaption>Report generated by Quantstats — Strat1</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9-NYnR2SoneLQSEtfUecLQ.png" /><figcaption>Report generated by Quantstats — Strat1</figcaption></figure><p>Quantstats gives us the EOY returns, the distribution of monthly returns, daily returns, the rolling volatility (6-months), the rolling sharpe (6-months), the rolling sortino (6-months), and so on.</p><p>Here, we only got 720 historical prices from Kraken’s API and are currently using 687 for backtesting. It’s enough for our tutorial but keep in mind that when conducting a backtest you need to have data that includes all market conditions (bull markets, bear markets, periods of high volatility, periods of low volatility); therefore sometimes much more than 687 days! Indeed, a commonly recommended minimum for many strategies would be at least 5–10 years.</p><p>Also, in more advanced backtesting, you would ideally simulate the strategy as closely as possible to how it would be implemented in real trading conditions. Like keeping track of whether you’re currently holding a position or not. It would also include transaction costs, slippage, and other practical considerations.</p><p>And it’s done. We hope you now have a robust foundation to build new strategies and assess their potential!</p><p><strong>Thanks for reading and stay tuned for more articles in this Python series!</strong></p><h4><strong>Ressources:</strong></h4><p><em>Brian Dolan, “</em>MACD Indicator Explained, with Formula, Examples, and Limitations<em>”, </em><a href="https://www.investopedia.com/terms/m/macd.asp"><em>Investopedia</em></a><em>, March 15, 2023.</em></p><p><em>IG International, “</em>A trader’s guide to using the awesome oscillator<em>”, </em><a href="https://www.ig.com/en/trading-strategies/a-traders-guide-to-using-the-awesome-oscillator-200130"><em>IG International</em></a><em>, Updated Monthly.</em></p><p><em>QuantStats Library</em>, <a href="https://pypi.org/project/QuantStats/">QuantStats Documentation</a>, <em>last</em> <em>checked on May 10, 2023</em>.</p><p><em>Ta Library,</em> <a href="https://pypi.org/project/ta/">Ta Documentation</a>, <em>last</em> <em>checked on April 28, 2023</em>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d0f00df0d62" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Are There Any Cross-Exchange Arbitrage Opportunities Left? A Python Series]]></title>
            <link>https://medium.com/@andreaselledge/are-there-any-cross-exchange-arbitrage-opportunities-left-a-python-series-40ffb51626f5?source=rss-2260c2b57e51------2</link>
            <guid isPermaLink="false">https://medium.com/p/40ffb51626f5</guid>
            <category><![CDATA[trading]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[arbitrage]]></category>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Andreas Elledge]]></dc:creator>
            <pubDate>Tue, 25 Apr 2023 19:29:13 GMT</pubDate>
            <atom:updated>2023-05-16T08:46:30.077Z</atom:updated>
            <content:encoded><![CDATA[<p>Comparing Binance and Kraken prices.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u-JcWbexNXf6ucH6y1aW_w.png" /><figcaption>DALL-E generated image</figcaption></figure><h4>Introduction</h4><p>Cross-exchange Arbitrage was highly profitable in the early days of cryptocurrency trading. Simple models coded in Python allowed savvy traders to capitalize on price discrepancies between crypto exchanges. But as the market matures and models become more advanced, the question arises: Are there any cross-exchange arbitrage opportunities left?</p><p>Let’s focus on Binance and Kraken, two of the largest and most popular exchanges. We’ll obtain real market data from both their respective APIs. We will analyze Bitcoin ($BTC) prices in USD from April 20th, 2023, from 00:00:00 to 23:45:00, at 15-minute intervals.</p><p>This article will showcase how one can leverage the power of Python to analyze real-time data and determine the viability of arbitrage strategies in today’s crypto landscape. Although we’ll use real market data, keep in mind that this article is a tutorial, not financial advice, and aims at enhancing our Python skills. Join us as we delve into the fascinating realm of cross-exchange arbitrage using Python!</p><h4>Part 1: Import libraries</h4><pre>import pandas as pd<br>import numpy as np<br>import matplotlib.pyplot as plt<br>import requests<br>from datetime import datetime, timedelta</pre><h4>Part 2: Defining functions to fetch data from Binance and Kraken APIs</h4><pre># This function fetches data from Binance API<br>def get_binance_data(symbol, interval, start_time, end_time):<br>    url = f&quot;https://api.binance.com/api/v3/klines?symbol={symbol}&amp;interval={interval}&amp;startTime={start_time}&amp;endTime={end_time}&quot;<br>    response = requests.get(url)<br>    data = response.json()<br><br>    df = pd.DataFrame(data, columns=[&#39;Open time&#39;, &#39;Open&#39;, &#39;High&#39;, &#39;Low&#39;, &#39;Close&#39;, &#39;Volume&#39;, &#39;Close time&#39;, &#39;Quote asset volume&#39;, &#39;Number of trades&#39;, &#39;Taker buy base asset volume&#39;, &#39;Taker buy quote asset volume&#39;, &#39;Ignore&#39;])<br>    df[&#39;Open time&#39;] = pd.to_datetime(df[&#39;Open time&#39;], unit=&#39;ms&#39;)<br>    df[&#39;Close time&#39;] = pd.to_datetime(df[&#39;Close time&#39;], unit=&#39;ms&#39;)<br>    return df[[&#39;Open time&#39;, &#39;Close time&#39;, &#39;Open&#39;, &#39;High&#39;, &#39;Low&#39;, &#39;Close&#39;, &#39;Volume&#39;]]</pre><pre># This function fetches data from Kraken API<br>def get_kraken_data(pair, interval, since):<br>    url = f&quot;https://api.kraken.com/0/public/OHLC?pair={pair}&amp;interval={interval}&amp;since={since}&quot;<br>    response = requests.get(url)<br>    data = response.json()[&#39;result&#39;][pair]<br><br>    df = pd.DataFrame(data, columns=[&#39;Open time&#39;, &#39;Open&#39;, &#39;High&#39;, &#39;Low&#39;, &#39;Close&#39;, &#39;Volume&#39;, &#39;Weighted average price&#39;, &#39;Number of trades&#39;])<br>    df[&#39;Open time&#39;] = pd.to_datetime(df[&#39;Open time&#39;], unit=&#39;s&#39;)<br>    df[&#39;Close time&#39;] = df[&#39;Open time&#39;] + pd.to_timedelta(interval, unit=&#39;m&#39;)<br>    return df[[&#39;Open time&#39;, &#39;Close time&#39;, &#39;Open&#39;, &#39;High&#39;, &#39;Low&#39;, &#39;Close&#39;, &#39;Volume&#39;]]</pre><p>The query parameters to get data from the APIs are not necessarily the same. For Binance, the query parameters are “symbol”, “interval”, “start_time”, “end_time”, while for Kraken they are “pair”, “interval”, “since”. The goal here is to create functions that will both return a DataFrame with the same desired columns.</p><h4>Part 3: Fetching and merging data from both APIs</h4><pre># Let&#39;s first define a function to convert a datetime object to timestamp in milliseconds (trading data usually is in milliseconds)<br>def datetime_to_timestamp(dt):<br>    return int(dt.timestamp() * 1000)<br><br># Setting the date range for fetching our data<br>start_date = datetime(2023, 4, 19)<br>end_date = datetime(2023, 4, 22)<br><br># Using our datetime object to timestamp function<br>start_timestamp = datetime_to_timestamp(start_date)<br>end_timestamp = datetime_to_timestamp(end_date)<br><br># Fetching Bitcoin data from Binance and Kraken APIs<br>binance_data = get_binance_data(&#39;BTCUSDT&#39;, &#39;15m&#39;, start_timestamp, end_timestamp)<br>kraken_data = get_kraken_data(&#39;XXBTZUSD&#39;, 15, start_timestamp)<br><br># Using the &quot;Open time&quot; column as the key for merging both Binance and Kraken data<br>merged_data = pd.merge(binance_data, kraken_data, on=&#39;Open time&#39;, suffixes=(&#39;_binance&#39;, &#39;_kraken&#39;))<br><br># Price discrepancies: We finish by calculating the price difference between the closing prices from Binance and Kraken<br>merged_data[&#39;Price difference&#39;] = merged_data[&#39;Close_binance&#39;].astype(float) - merged_data[&#39;Close_kraken&#39;].astype(float)<br><br># We then set the 20th of April from the DataFrame merged_data<br>filter_start = datetime(2023, 4, 20, 0, 0, 0)<br>filter_end = datetime(2023, 4, 20, 23, 45, 0)<br><br># We filter the merged_data DataFrame<br>filtered_data = merged_data[(merged_data[&#39;Open time&#39;] &gt;= filter_start) &amp; (merged_data[&#39;Open time&#39;] &lt;= filter_end)]<br>filtered_data</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/760/1*QSuu7-rX1OVO6xNRZaLg8A.png" /><figcaption><strong>filtered_data</strong>: Our merged and filtered DataFrame</figcaption></figure><p>We now have our data for the 20th of April and have also calculated the price differences between the closing prices from Binance and Kraken. Let’s plot them.</p><h4>Part 4: Plot the 15-minute prices discrepancies</h4><pre>fig, ax = plt.subplots(figsize=(12, 6), dpi=200)<br><br>ax.plot(filtered_data[&#39;Open time&#39;], filtered_data[&#39;Close_binance&#39;].astype(float), label=&#39;Binance&#39;, linewidth=2)<br>ax.plot(filtered_data[&#39;Open time&#39;], filtered_data[&#39;Close_kraken&#39;].astype(float), label=&#39;Kraken&#39;, linewidth=2)<br><br>ax.set_title(&#39;BTC 15-Minute Prices on Binance and Kraken (April 20, 2023)&#39;)<br>ax.set_xlabel(&#39;Time&#39;)<br>ax.set_ylabel(&#39;Price (USD)&#39;)<br>ax.legend()<br><br>plt.show()</pre><p>The graph below plots the closing prices of $BTC from Binance and Kraken on the 20th of April 2023. The Binance and Kraken price trends are very similar, but they occasionally diverge. This disparity, called spread, is the cross-exchange arbitrage opportunity that traders capitalize on: buy BTC at a lower price on one exchange and sell at a higher price on the other, hence generating profit.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YH7SQQzX9AlVo5IYzk1BAA.png" /></figure><p>We could then directly plot the newly created variable “Price difference” but then the spread would be represented as a negative and positive value. We are however not interested in the direction but the <em>magnitude</em> of the difference between the prices. Therefore, we will create a line graph representing the <strong>absolute difference</strong> between the closing prices of the two exchanges. Let’s plot these price differences, let’s visualize the spread!</p><pre>fig, ax = plt.subplots(figsize=(12, 6), dpi=200)<br><br># We calculate the absolute difference between Binance and Kraken closing prices with the abs() function<br>plt.plot(filtered_data[&#39;Open time&#39;], abs(filtered_data[&#39;Close_binance&#39;].astype(float) - filtered_data[&#39;Close_kraken&#39;].astype(float)), label=&#39;Spread Binance vs Kraken&#39;, color=&#39;green&#39;)<br><br>plt.title(&#39;BTC Price Spread: Binance vs Kraken (April 20, 2023) - Absolute difference&#39;)<br>plt.legend()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9SeXBGekrQJtniLjwovfag.png" /></figure><h4>Part 5: Calculating the total spread for the day</h4><p>In a perfect theoretical trading environment where one could capitalize from every spread, it would be possible to profit from the price discrepancies between Binance and Kraken. Let’s compute the sum of all the spreads. The total spread for the day is 922 USD. <em>This figure is theoretical.</em></p><pre># We calculate the total spread for the day in an theoretical environment<br>total_spread = abs(filtered_data[&#39;Close_binance&#39;].astype(float) - filtered_data[&#39;Close_kraken&#39;].astype(float)).sum()<br><br>print(f&quot;Total Spread for the day: {total_spread} USD&quot;)</pre><h4>Part 6: Introducing trading fees &amp; Co</h4><p>In real-world conditions there are several factors to keep in mind such as fees, slippage and other variables like the rapidity of the trading execution.</p><p>Exchanges like Binance and Kraken typically charge <em>maker</em> and <em>taker</em> fees. When you execute an arbitrage strategy, it’s vital to include theses fees in your profit calculations.</p><blockquote><strong>Maker fee</strong>: A maker is someone who places an order that doesn’t immediately match with an existing order on the order book. Instead, it “makes” liquidity by adding to the pool of open orders, waiting to be matched. As makers contribute to the overall liquidity of the market, their fees are generally lower than taker fees.</blockquote><blockquote><strong>Taker fee</strong>: A taker is someone who places an order that is immediately filled by matching it with an existing order on the order book. Takers “take” liquidity from the market by reducing the number of open orders. As a result, taker fees are typically higher than maker fees.</blockquote><p>Suppose you buy 1 BTC on Binance at a lower price and sell it on Kraken at a higher price. Placing a limit order to buy 1 BTC on Binance classifies you as a maker and you’ll have to pay a maker fee when the order is executed. Then, placing a market order on this 1 BTC on Kraken will classify you as a taker and you’ll have to pay a taker fee. Please refer to the chart below to see the different fee structures across exchanges.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*wg4QPFj3dpMwkOqnFUpsow.png" /><figcaption>Source: <a href="https://insights.deribit.com/market-research/maker-taker-fees-on-crypto-exchanges-a-market-structure-analysis/">https://insights.deribit.com/market-research/maker-taker-fees-on-crypto-exchanges-a-market-structure-analysis/</a></figcaption></figure><p>When you execute an arbitrage strategy, it’s vital to include theses trading fees in your profit calculations. Indeed, the arbitrage opportunity may not generate a profit after taking into account the fees associated to both exchanges.</p><p>After examining the fee structures of both Binance and Kraken, we decided to apply an average 0.2% trading fee.</p><pre># We add and calculate the absolute difference and fees columns in our filtered_data DataFrame<br>filtered_data[&#39;Absolute Price Diff&#39;] = abs(filtered_data[&#39;Close_binance&#39;].astype(float) - filtered_data[&#39;Close_kraken&#39;].astype(float))<br>filtered_data[&#39;Fees&#39;] = 0.002 * (filtered_data[&#39;Close_binance&#39;].astype(float) + filtered_data[&#39;Close_kraken&#39;].astype(float))<br><br># We create a new DataFrame with the desired columns using dictionary comprehension<br>arbitrage_df = pd.DataFrame({column: filtered_data[column] for column in [&#39;Open time&#39;, &#39;Close_binance&#39;, &#39;Close_kraken&#39;, &#39;Absolute Price Diff&#39;, &#39;Fees&#39;]})<br><br># We then rename the columns in the DataFrame &#39;Close_binance&#39; and &#39;Close_kraken&#39;<br>arbitrage_df.columns = [&#39;Open time&#39;, &#39;Binance BTC&#39;, &#39;Kraken BTC&#39;, &#39;Absolute Price Diff&#39;, &#39;Fees&#39;]<br>arbitrage_df</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/396/1*k4ju-6hvPWtxjawQE2dXNw.png" /><figcaption>We now have an <strong>arbitrage_df</strong> DataFrame with the absolute difference prices and the fees associated</figcaption></figure><h4>Part 7: On which exchange should we buy? Sell?</h4><p>Now, let’s generate buy and sell signals.</p><blockquote>We will return <strong>“0”</strong> when Binance BTC price is superior to Kraken BTC price (<em>buy on Kraken and sell on Binance</em>)</blockquote><blockquote>We will return <strong>“1”</strong> when Kraken BTC price is superior to Binance BTC price (<em>buy on Binance and sell on Kraken</em>)</blockquote><pre># np.where(condition, x, y) returns an array with the same shape as the condition<br># If the condition is True, it returns the corresponding element from x, otherwise from y<br>signals_data = np.where(arbitrage_df[&#39;Binance BTC&#39;] &gt; arbitrage_df[&#39;Kraken BTC&#39;], 0, 1)<br><br># We create a new DataFrame to store the signals<br># Set the index of signals_df to match the index of arbitrage_df<br>signals_df = pd.DataFrame(signals_data, columns=[&#39;BuyKrakenSellBinance&#39;], index=arbitrage_df.index)<br><br># We then add the signals to the arbitrage_df DataFrame<br>arbitrage_df = pd.concat([arbitrage_df, signals_df], axis=1)<br>arbitrage_df</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/511/1*CWUdYzOcXUqvHEZBc6vsHA.png" /></figure><h4>Part 8: Executing a profitable trade</h4><p>To finish, we have to determine if the arbitrage opportunity is profitable after accounting for trading fees.</p><blockquote>If the absolute price difference is greater than the fees, set the value to <strong>“0”</strong> (execute the trade). <strong>This is a profitable trade !</strong></blockquote><blockquote>If the absolute price difference is less than or equal to the fees, set the value to <strong>“1”</strong> (do not execute the trade)</blockquote><pre># Create a new column &#39;Execute Trade&#39; in the arbitrage_df DataFrame<br>arbitrage_df[&#39;Execute Trade&#39;] = (arbitrage_df[&#39;Absolute Price Diff&#39;] &lt;= arbitrage_df[&#39;Fees&#39;]).astype(int)<br>arbitrage_df</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/584/1*kwwh96KXYJ2WcUm8qWNfVA.png" /><figcaption>Our final DataFrame</figcaption></figure><pre># Let&#39;s then count the number of trade profitable (marked with &quot;0&quot;)<br>number_of_profitable_trades = (arbitrage_df[&#39;Execute Trade&#39;] == 0).sum()<br>print(f&quot;Number of profitable arbitrage trades: {number_of_profitable_trades}&quot;)</pre><p>There are unfortunately no trades that are profitable on the 20th of April for us. But nothing stops us from running this Python script every day!</p><p>We could also use this Python script on less liquid assets such as altcoins. Or pull data from other smaller exchanges where price differences can be important… We might catch, at last, a great cross-exchange arbitrage opportunity.</p><p><strong>Thanks for reading and stay tuned for more articles in this Python series!</strong></p><h4>Ressources:</h4><p><em>Huobi, “Crypto Arbitrage Opportunities: All About Cross-Asset And Exchange Arbitrage!”, </em><a href="https://medium.com/huobi-global/crypto-arbitrage-opportunities-all-about-cross-asset-and-exchange-arbitrage-d007ccf9d150"><em>medium</em></a><em>, Aug 23, 2022.</em></p><p><em>Spencer Pao, “Quant Strategy: Arbitrage Trading Algorithm (Cross-Exchange)”, </em><a href="https://www.youtube.com/watch?v=TNJNCJe9fDY"><em>youtube</em></a><em>, Nov 23, 2021.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=40ffb51626f5" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>