<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: jun yin</title>
    <description>The latest articles on DEV Community by jun yin (@jun_yin_6302a0bd8ff9871c1).</description>
    <link>https://dev.to/jun_yin_6302a0bd8ff9871c1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4180904%2F6c8c9ef7-6084-4be5-9a79-546b0aded30b.png</url>
      <title>DEV Community: jun yin</title>
      <link>https://dev.to/jun_yin_6302a0bd8ff9871c1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jun_yin_6302a0bd8ff9871c1"/>
    <language>en</language>
    <item>
      <title>Is backtrader still maintained? Status and four maintained alternatives</title>
      <dc:creator>jun yin</dc:creator>
      <pubDate>Thu, 15 Oct 2026 07:25:20 +0000</pubDate>
      <link>https://dev.to/jun_yin_6302a0bd8ff9871c1/is-backtrader-still-maintained-status-and-four-maintained-alternatives-4ami</link>
      <guid>https://dev.to/jun_yin_6302a0bd8ff9871c1/is-backtrader-still-maintained-status-and-four-maintained-alternatives-4ami</guid>
      <description>&lt;p&gt;&lt;em&gt;I run &lt;a href="https://aiquantnav.com/en" rel="noopener noreferrer"&gt;aiquantnav.com&lt;/a&gt;, a directory that snapshots open-source quant and trading repositories once a day. This post is based on that data; the star counts and dates below are as of 13 October 2026. The &lt;a href="https://aiquantnav.com/en/compare/backtrader-alternatives" rel="noopener noreferrer"&gt;original article&lt;/a&gt; updates daily.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short answer.&lt;/strong&gt; backtrader still installs and still runs, and plenty of people use it. In our 13 October 2026 snapshot, GitHub reported 19 Aug 2024 as the upstream main repository's last push. That raises a maintenance concern for users relying on upstream bug fixes and dependency updates, but does not mean nobody is maintaining Backtrader. Community forks continue development; for example, &lt;a href="https://github.com/cloudQuant/backtrader/releases/tag/v1.4.0" rel="noopener noreferrer"&gt;cloudQuant/backtrader's v1.4.0 release&lt;/a&gt; includes compatibility and testing improvements. It is fine for learning and for strategies you already run. For a new project, pick something that is still being maintained.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we can actually see
&lt;/h2&gt;

&lt;p&gt;We snapshot every repository in this directory once a day. For backtrader, our 13 October 2026 snapshot recorded 23.5k GitHub stars, which puts it among the most-starred backtesting libraries in this directory, and 19 Aug 2024 as GitHub's last reported push for the upstream main repository. A push timestamp is not a latest-commit date or a measure of all issue, pull-request or community activity. The upstream repository was not archived at that snapshot.&lt;/p&gt;

&lt;p&gt;The README still describes what made it popular: an event-driven engine, data feeds from CSV files, online sources or pandas, and live trading through Interactive Brokers, Oanda and Visual Chart. The same README also shows its age. The Interactive Brokers connection depends on the old IbPy package, and the Oanda integration is described as REST only.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a quiet repository costs you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dependency drift.&lt;/strong&gt; Python, pandas, NumPy and matplotlib keep changing. When a new release breaks something, the fix has to come from you or from a fork.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broker connections decay first.&lt;/strong&gt; Backtesting on your own data keeps working for years. Live connections depend on third-party APIs that change without asking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open issues stay open.&lt;/strong&gt; You can still learn a lot from the existing documentation and community posts, but you should not expect answers upstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The licence is GPL-3.0.&lt;/strong&gt; That has not changed, and it matters if you plan to ship software built on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Four alternatives that are still being worked on
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;GitHub stars&lt;/th&gt;
&lt;th&gt;Last activity&lt;/th&gt;
&lt;th&gt;Licence&lt;/th&gt;
&lt;th&gt;Style&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;backtrader&lt;/td&gt;
&lt;td&gt;23.5k&lt;/td&gt;
&lt;td&gt;19 Aug 2024 (upstream last reported push)&lt;/td&gt;
&lt;td&gt;GPL-3.0&lt;/td&gt;
&lt;td&gt;Event-driven&lt;/td&gt;
&lt;td&gt;Backtesting and live trading (Interactive Brokers, Oanda, Visual Chart)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vectorbt&lt;/td&gt;
&lt;td&gt;9.4k&lt;/td&gt;
&lt;td&gt;9 Oct 2026&lt;/td&gt;
&lt;td&gt;Apache 2.0 with Commons Clause&lt;/td&gt;
&lt;td&gt;Vectorized&lt;/td&gt;
&lt;td&gt;Backtesting and strategy research&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zipline-reloaded&lt;/td&gt;
&lt;td&gt;2k&lt;/td&gt;
&lt;td&gt;13 Oct 2026&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;Event-driven&lt;/td&gt;
&lt;td&gt;Backtesting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nautilus_trader&lt;/td&gt;
&lt;td&gt;29.9k&lt;/td&gt;
&lt;td&gt;13 Oct 2026&lt;/td&gt;
&lt;td&gt;LGPL-3.0&lt;/td&gt;
&lt;td&gt;Event-driven, Rust core&lt;/td&gt;
&lt;td&gt;Backtesting and live trading with the same code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PyBroker&lt;/td&gt;
&lt;td&gt;3.6k&lt;/td&gt;
&lt;td&gt;5 Oct 2026&lt;/td&gt;
&lt;td&gt;Apache 2.0 with Commons Clause&lt;/td&gt;
&lt;td&gt;Vectorized engine, rule and model based&lt;/td&gt;
&lt;td&gt;Backtesting, machine-learning strategies&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Stars and repository dates as of 13 October 2026, from the GitHub API. For backtrader, the date is GitHub's last reported push for the upstream main repository, not a measure of all repository or community activity. Licence and scope checked against each repository on 11 October 2026.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  vectorbt: when you want to test thousands of variations
&lt;/h3&gt;

&lt;p&gt;vectorbt takes the opposite approach to backtrader. Instead of stepping through bars one strategy at a time, it packs many parameter combinations into NumPy arrays and runs them together, with Numba and an optional Rust engine doing the heavy lifting. That makes it a good fit for parameter sweeps and research. Two things to know before you commit: the licence is Apache 2.0 with a Commons Clause, which lets you use it for free but not sell a product that is mainly this software, and several features such as limit orders, leverage and parallelization live in the separate paid PRO edition.&lt;/p&gt;

&lt;h3&gt;
  
  
  zipline-reloaded: the closest thing to a classic event-driven engine
&lt;/h3&gt;

&lt;p&gt;The original zipline was Quantopian's engine, and Quantopian closed in late 2020. Its repository shows no activity since 13 Feb 2024. zipline-reloaded is the fork that Stefan Jansen keeps up to date for readers of his machine-learning trading book and the wider community. It supports Python 3.9 and later and has been updated for pandas 2 and NumPy 2. If you like writing strategies as plain Python event handlers, this is the smallest mental jump from backtrader. It is also the slowest-moving of the four: when we checked, the latest release was 3.1.1 from July 2025, with occasional commits since. Its README presents it as a backtesting library, so plan on a separate route to live trading.&lt;/p&gt;

&lt;h3&gt;
  
  
  NautilusTrader: when backtest and live must be the same code
&lt;/h3&gt;

&lt;p&gt;NautilusTrader has a Rust core with a Python layer for strategy logic, and its central promise is that the same strategy code runs in a backtest and in live trading. It ships adapters for Interactive Brokers and for crypto venues such as OKX, Kraken and Hyperliquid. It is the most demanding option here: the concepts are closer to a production trading system than to a research notebook. The licence is LGPL-3.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  PyBroker: when your strategy is a model
&lt;/h3&gt;

&lt;p&gt;PyBroker is built around machine-learning strategies. It offers walk-forward analysis, bootstrapped performance metrics, and data loaders for Alpaca, Yahoo Finance and AKShare, on top of a NumPy and Numba engine. Like vectorbt, it uses Apache 2.0 with a Commons Clause.&lt;/p&gt;

&lt;h2&gt;
  
  
  One that looks maintained but is not
&lt;/h2&gt;

&lt;p&gt;QSTrader often appears in lists like this one, and its README still says it is under active development. The repository tells a different story: no activity since 30 Jun 2024. This is the main reason to check activity dates rather than descriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You are learning backtesting.&lt;/strong&gt; backtrader is still a reasonable teacher, because there is so much written about it. Just do not build your long-term stack on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You research many strategy variations.&lt;/strong&gt; vectorbt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want event-driven backtests in familiar Python.&lt;/strong&gt; zipline-reloaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You intend to trade live with the code you backtest.&lt;/strong&gt; NautilusTrader.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your signals come from trained models.&lt;/strong&gt; PyBroker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;A listing here is not a recommendation to trade, and a backtest is not evidence that a strategy will make money.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>trading</category>
      <category>backtesting</category>
    </item>
    <item>
      <title>Build a daily stock data pipeline in Python: yfinance or EODHD APIs into SQLite</title>
      <dc:creator>jun yin</dc:creator>
      <pubDate>Tue, 13 Oct 2026 03:56:43 +0000</pubDate>
      <link>https://dev.to/jun_yin_6302a0bd8ff9871c1/build-a-daily-stock-data-pipeline-in-python-yfinance-or-eodhd-apis-into-sqlite-1gjp</link>
      <guid>https://dev.to/jun_yin_6302a0bd8ff9871c1/build-a-daily-stock-data-pipeline-in-python-yfinance-or-eodhd-apis-into-sqlite-1gjp</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I run aiquantnav.com, which is an affiliate partner of EODHD APIs. The link to their plans below is an affiliate link: if you sign up through it I may earn a commission, at no extra cost to you. The yfinance version of this pipeline is free and needs no account.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you end up with
&lt;/h2&gt;

&lt;p&gt;One SQLite file, one table of daily bars, and one script of about 80 lines that you can run every day. It only downloads the days it is missing, it never creates duplicate rows, and changing the data source means changing one function. That last part matters: free sources break, and you do not want that to break your backtests too.&lt;/p&gt;

&lt;p&gt;The code is on GitHub: &lt;a href="https://github.com/tomyin1314/daily-stock-data-pipeline" rel="noopener noreferrer"&gt;tomyin1314/daily-stock-data-pipeline&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You need Python 3.10 or later and three packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;yfinance pandas requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The whole script
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;pipeline.py&lt;/code&gt;. The sections below explain the parts that are easy to get wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Daily price pipeline: fetch daily bars and store them in SQLite.

Safe to run as often as you like: it only asks for the days it is missing,
and the primary key stops duplicate rows.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yfinance&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;yf&lt;/span&gt;

&lt;span class="n"&gt;DB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prices.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SYMBOLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MSFT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;START&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# how far back to go on the first run
&lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adj_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_yfinance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Daily bars from Yahoo Finance via yfinance.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auto_adjust&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;multi_level_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;High&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Adj Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adj_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yfinance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_eodhd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Daily bars from EODHD APIs. The &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; token works for AAPL only.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/eod/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# US tickers need the .US suffix
&lt;/span&gt;        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adj_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eodhd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        CREATE TABLE IF NOT EXISTS daily (
            symbol TEXT NOT NULL, date TEXT NOT NULL,
            open REAL, high REAL, low REAL, close REAL, adj_close REAL,
            volume INTEGER, source TEXT,
            PRIMARY KEY (symbol, date)
        )&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;last_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT MAX(date) FROM daily WHERE symbol = ?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,)).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Start again from the last stored day: if the job ran before the close,
&lt;/span&gt;    &lt;span class="c1"&gt;# that bar was incomplete, and INSERT OR REPLACE overwrites it.
&lt;/span&gt;    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;last_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;START&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executemany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT OR REPLACE INTO daily VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;itertuples&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fetch_eodhd&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--eodhd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;fetch_yfinance&lt;/span&gt;
    &lt;span class="n"&gt;symbols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;fetch_eodhd&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;SYMBOLS&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;init_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT COUNT(*) FROM daily WHERE symbol = ?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,)).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: fetched &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; in the database, latest &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;last_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run it twice
&lt;/h2&gt;

&lt;p&gt;The first run downloads everything since the start date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python pipeline.py
AAPL: fetched 696 rows, 696 in the database, latest 2026-10-09
MSFT: fetched 696 rows, 696 in the database, latest 2026-10-09
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it again and it asks only for the last stored day onwards. The row count does not change, because the primary key on &lt;code&gt;(symbol, date)&lt;/code&gt; plus &lt;code&gt;INSERT OR REPLACE&lt;/code&gt; turns a repeat download into an overwrite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python pipeline.py
AAPL: fetched 1 rows, 696 in the database, latest 2026-10-09
MSFT: fetched 1 rows, 696 in the database, latest 2026-10-09
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-fetching the last day is deliberate. If the job ever runs before the market closes, the bar it stores is incomplete, and the next run quietly replaces it with the final one.&lt;/p&gt;

&lt;p&gt;(Output from 12 October 2026, yfinance 1.7.0 and pandas 2.3.3.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Two sources, one shape
&lt;/h2&gt;

&lt;p&gt;Both &lt;code&gt;fetch_yfinance&lt;/code&gt; and &lt;code&gt;fetch_eodhd&lt;/code&gt; return the same nine columns in the same order. Everything after the fetch step, the table, the incremental logic and whatever reads the database later, has no idea which source was used.&lt;/p&gt;

&lt;p&gt;You can see that by pointing the same script at the other source. EODHD APIs publishes a &lt;code&gt;demo&lt;/code&gt; token that works for AAPL without an account, so this runs as written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python pipeline.py --eodhd
AAPL: fetched 1 rows, 696 in the database, latest 2026-10-09

symbol       date      close  adj_close   volume   source
  AAPL 2026-10-09 336.640000 336.640000 37855000    eodhd
  AAPL 2026-10-08 340.420013 340.420013 35332400 yfinance
  AAPL 2026-10-07 336.670013 336.670013 34147900 yfinance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The newest row now comes from the second source, and the close and volume match what yfinance had stored for the same day.&lt;/p&gt;

&lt;p&gt;For anything beyond the demo ticker you need your own token. The free plan allows 20 API calls a day, which covers a small watchlist once a day, but it only returns the past year of history: with &lt;code&gt;START = "2024-01-01"&lt;/code&gt; a free key gives you about one year of bars, not the full range. Paid plans lift both limits. See &lt;a href="https://aiquantnav.com/go/eodhd?src=devto-data-pipeline" rel="noopener noreferrer"&gt;the EODHD APIs plans&lt;/a&gt; for current limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close and adjusted close are different numbers
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;close&lt;/code&gt; is the price the stock actually traded at that day. &lt;code&gt;adj_close&lt;/code&gt; is rewritten after splits and dividends so that returns come out right. Use &lt;code&gt;adj_close&lt;/code&gt; to compute returns in a backtest, and &lt;code&gt;close&lt;/code&gt; when you care about the price level that was on the screen.&lt;/p&gt;

&lt;p&gt;Two details to know. With yfinance you must pass &lt;code&gt;auto_adjust=False&lt;/code&gt;, as the script does, or the adjusted price replaces the close and you lose the raw one. And adjusted history changes over time: after a new dividend, older adjusted prices are restated. The incremental update above does not go back and refresh them, so plan a full reload now and then, by deleting the file and running the script again.&lt;/p&gt;

&lt;p&gt;You may also notice values such as 340.420013 from yfinance. That is floating-point noise from the source, not extra precision. Round when you display, not when you store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schedule it
&lt;/h2&gt;

&lt;p&gt;US markets close at 16:00 New York time. Give the data a few hours to settle and run the job once a day after that. On Linux or macOS, a cron entry for 22:30 UTC on weekdays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;30 22 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1-5 &lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/project &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python pipeline.py &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; pipeline.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, the same thing with Task Scheduler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;schtasks&lt;/span&gt; &lt;span class="na"&gt;/Create /SC &lt;/span&gt;&lt;span class="kd"&gt;WEEKLY&lt;/span&gt; &lt;span class="na"&gt;/D &lt;/span&gt;&lt;span class="kd"&gt;MON&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="kd"&gt;TUE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="kd"&gt;WED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="kd"&gt;THU&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="kd"&gt;FRI&lt;/span&gt; &lt;span class="na"&gt;/ST &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;:30 &lt;span class="na"&gt;/TN &lt;/span&gt;&lt;span class="kd"&gt;PricePipeline&lt;/span&gt; &lt;span class="na"&gt;/TR &lt;/span&gt;&lt;span class="s2"&gt;"python C:/path/to/pipeline.py"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Task Scheduler uses your local time, so adjust the hour. Running on a market holiday is harmless: the source returns nothing new and the script stores nothing new.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it breaks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The free source stops answering.&lt;/strong&gt; yfinance reads Yahoo Finance's public endpoints and can fail without notice. The script prints the latest stored date for every symbol; if that date stops moving, you know the same day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ticker suffixes.&lt;/strong&gt; EODHD APIs wants &lt;code&gt;AAPL.US&lt;/code&gt;, not &lt;code&gt;AAPL&lt;/code&gt;, which is why the fetch function adds the suffix for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limits.&lt;/strong&gt; One request per symbol per day is modest, but a first run over hundreds of symbols is not. Add a short pause between symbols before you scale up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terms of use.&lt;/strong&gt; Yahoo's data is for personal use. If the pipeline feeds anything commercial, use a source whose terms allow it. That applies to paid sources too: EODHD APIs' standard plans are for personal use, and it sells separate Commercial plans.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next step
&lt;/h2&gt;

&lt;p&gt;If you are deciding which source to build on, I compared four of them here: &lt;a href="https://aiquantnav.com/en/compare/yfinance-vs-akshare-vs-openbb-vs-eodhd" rel="noopener noreferrer"&gt;yfinance vs AKShare vs OpenBB vs EODHD APIs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nothing in this post is investment advice. Prices from any source can be wrong or late; check them before you rely on them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>sqlite</category>
      <category>tutorial</category>
      <category>datascience</category>
    </item>
  </channel>
</rss>
