import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from eodhd import APIClientWeek 1: Introduction to Python and Financial Data Analysis
Version 5.0 - August 2026
1 Week 1
Python is one of the four standard languages of quantitative finance, alongside R, Julia and MATLAB, and is the most widely used. Financial institutions use Python for portfolio optimisation, risk measurement, regulatory reporting and algorithmic trading. This course uses it on a smaller scale to obtain market data, manipulate it and draw conclusions about risk.
Python on its own does little of this. The work is done by a handful of libraries built on top of it, introduced below and listed in full in the course setup guide.
For more on Python and its use in risk forecasting, see the Python risk forecasting notebook.
Complete the setup guide, open your main course folder in Positron, select Anaconda’s Python, and have your EODHD token available. Week 1 creates no file needed by later seminars. The permanent course data files are created in Week 2.
You should be able to download one asset, inspect its data, calculate log returns, explain the missing first return, and produce labelled price and return plots.
AI use is permitted and encouraged in this course. Python is also the language of most artificial intelligence work and the one AI assistants write best, and the editor used in this course has one built in. They will write plausible code for anything in this course, some of which will be wrong in ways only a reader who understands the material will notice. Keep the last working version, ask for one limited change at a time, read the proposed code, rerun the analysis, check the result against the financial meaning and the checkpoints below, and be able to explain the resulting code and output. Never give an AI an API token.
1.1 The plan for this week
- Familiarisation with Python and the course environment
- Read the small set of Python notation used in the seminars
- Set up EOD Historical Data access
- Download and inspect one asset
- Calculate returns
- Create labelled price and return plots
1.2 The software
The course uses two applications. Anaconda supplies Python and most of the libraries. Positron is where you write and run code.
On LSE computers both are installed and configured, so there is nothing to do. On your own computer they have to be installed, Positron has to be pointed at Anaconda’s Python rather than at any other Python on the machine, and two further libraries have to be added. The setup guide covers all of it:
- www.financialriskforecasting.com/seminars/html/0.html
- www.financialriskforecasting.com/seminars/pdf/0.pdf
Work through it before the first seminar rather than during it.
1.3 The libraries
A Python library is a collection of reusable code written for a particular purpose. For example, pandas provides tools for working with data tables, while matplotlib provides plotting tools. A library is made available with import, at the top of a document or a session rather than part-way down, so that a reader can see at a glance what the code depends on. numpy, pandas and matplotlib are conventionally given short names, and those abbreviations are used everywhere in this course and in almost all Python code you will meet elsewhere. Each week imports the libraries it uses and nothing else. This one uses four:
1.4 Basic Python commands
1 + 1
2 ** 5
a = 2
b = 100
np.exp(a) + b / 10
result = np.exp(a) + b
print("the answer is:", np.log(a), "or", result)the answer is: 0.6931471805599453 or 107.38905609893065
1.5 Reading the Python used in this course
| Notation | Meaning |
|---|---|
a = 2 |
= gives a value a name |
print(a) |
parentheses call a function |
prices["date"] |
square brackets select data |
"date" |
quotation marks enclose text |
prices.head() |
a dot accesses a method or attribute belonging to an object |
# explanation |
# begins a comment that Python does not execute |
| indented lines | indentation groups code inside a loop, function or conditional block |
A displayed code block can be placed in a Python file, a notebook cell or a QMD code chunk. A notebook or QMD normally displays the value of the last expression in a block automatically. In a .py file, use print() for a value you want to see. Figures appear when plt.show() runs.
Keep the data files created in Week 2 in the main course folder opened in Positron. The seminars use relative filenames such as "Returns.csv", not paths naming a particular account or computer.
1.6 How to work through a seminar
Work in a file rather than typing into the Console alone. Anything typed into the Console is gone when the session ends, and a seminar is worth keeping.
- Use
File > New File, choose Python File, and save it asweek1.pyin the main course folder. - Type or paste the code from this seminar into it as you go. Typing it is slower and teaches more.
- Run the current line, or a block you have selected, with
Cmd-Enteron macOS orCtrl-Enteron Windows. Positron sends it to the Console and leaves the file as it is. - Read the text output in the Console and look for figures in the Plots pane.
- If the state of the session becomes unclear, restart it and run the file again from the top. That is also the quickest way to find out whether the file works on its own.
Weeks 2 and 3 work the same way. Week 4 introduces QMD, which keeps the code and the report it produces in one document.
1.7 Setting up EOD Historical Data access
1.7.1 API token setup
An API token is like a digital key that identifies you to the EODHD service. The token is a unique string of characters that tells EODHD who you are and what data you are allowed to access.
If you are in my courses, you will get access to EODHD. For others, you can register at eodhistoricaldata.com for a free or paid account.
1.8 Downloading financial data
1.8.1 Setup
Put the token in your code and hand it to the client:
token = "demo"
api = APIClient(token)Treat a personal token like a password. Never paste it into an AI conversation, screenshot or shared file. If you use a QMD report, hide the token block from the rendered report with #| echo: false, and replace the real value with "YOUR_TOKEN" before sharing the source. Revoke and replace a token that is exposed.
1.8.2 What works with demo vs personal tokens
Demo tokens can access basic price data for a small set of symbols, including AAPL.US, TSLA.US, VTI.US, AMZN.US, BTC-USD.CC and EURUSD.FOREX, but not the full exchange coverage a personal token provides.
A personal token can access all functions without that restriction, though tiers of access still apply above the base plan.
With EODHD, we can download data directly in Python using ticker symbols in the form TICKER.EXCHANGE, for example AAPL.US. The get_eod_historical_stock_market_data() method of APIClient takes the symbol and the sampling period. Start with a single stock, Apple:
# Download Apple stock data
raw = api.get_eod_historical_stock_market_data(symbol="AAPL.US", period="d", order="a")
prices = pd.DataFrame(raw)
prices["date"] = pd.to_datetime(prices["date"])- The first line asks EODHD for daily Apple prices in ascending date order and stores the returned records in
raw. - The second line converts those records into a
pandasDataFrame calledprices. - The third line converts the
datecolumn from text intopandasdates, so that Python can sort, select and plot observations by date.
1.8.3 Understanding the data structure
An EOD price download includes:
date: trading dateopen,high,low,close: daily price range and closing priceadjusted_close: close price adjusted for splits and dividendsvolume: number of shares traded
The eodhd client returns prices only, with no returns column, so we compute returns ourselves below.
print("Shape:", prices.shape)
print("\nFirst five rows:")
print(prices.head())
print("\nColumns:", prices.columns.tolist())
print("\nAdjusted-close summary:")
print(prices["adjusted_close"].describe())Shape: (6470, 2)
First five rows:
date adjusted_close
0 2000-01-03 0.84010000
1 2000-01-04 0.76930000
2 2000-01-05 0.78050000
3 2000-01-06 0.71300000
4 2000-01-07 0.74680000
Columns: ['date', 'adjusted_close']
Adjusted-close summary:
count 6470.00000000
mean 46.10645808
std 66.05807253
min 0.19690000
25% 2.09690000
50% 15.41770000
75% 49.04847500
max 258.10380000
Name: adjusted_close, dtype: float64
prices should have one row per trading day. Its columns should include date and adjusted_close, the dates should run forwards, and adjusted prices should be positive.
1.8.4 Computing returns
y = np.log(prices["adjusted_close"]).diff()
y.head()0 NaN
1 -0.08803992
2 0.01445373
3 -0.09045332
4 0.04631599
Name: adjusted_close, dtype: float64
np.log() takes the natural logarithm of every adjusted price. .diff() then subtracts each day’s log price from the next one, so
\[ y_t = \log(P_t) - \log(P_{t-1}) = \log\left(\frac{P_t}{P_{t-1}}\right). \]
The resulting Series, stored as y, contains the daily continuously compounded returns.
diff() on a pandas Series is aligned by construction. It returns a Series of the same length as its input, with the first entry set to NaN rather than a number. The first return is undefined because it would need a price from the day before the sample starts, which we do not have. Keeping the length unchanged means the returns still line up with their dates, so nothing has to be padded back in by hand.
We can therefore add y directly as a column:
prices["return"] = y
prices[["date", "adjusted_close", "return"]].head()| date | adjusted_close | return | |
|---|---|---|---|
| 0 | 2000-01-03 | 0.84010000 | NaN |
| 1 | 2000-01-04 | 0.76930000 | -0.08803992 |
| 2 | 2000-01-05 | 0.78050000 | 0.01445373 |
| 3 | 2000-01-06 | 0.71300000 | -0.09045332 |
| 4 | 2000-01-07 | 0.74680000 | 0.04631599 |
The first return should be NaN, and later returns should be decimal numbers, so a one per cent move is approximately 0.01 or -0.01, not 1 or -1.
describe() ignores missing values, so no trimmed copy of the DataFrame is needed:
prices["return"].describe()count 6469.00000000
mean 0.00088318
std 0.02518992
min -0.73134737
25% -0.00984434
50% 0.00094095
75% 0.01250754
max 0.14261758
Name: return, dtype: float64
1.9 Visualising stock data
Plots come from matplotlib, which needs no extra styling to produce something usable.
fig, ax = plt.subplots()
ax.plot(prices["adjusted_close"])
plt.show()This plot needs improvement. To begin with, we want dates on the x-axis rather than a plain row count.
fig, ax = plt.subplots()
ax.plot(prices["date"], prices["adjusted_close"])
plt.show()We can do better:
fig, ax = plt.subplots()
ax.plot(prices["date"], prices["adjusted_close"])
ax.set_title("Apple stock prices")
ax.set_xlabel("Date")
ax.set_ylabel("Adjusted price")
plt.show()Regular closing prices can show sudden jumps and drops caused by stock splits and dividend payments rather than company performance. For example, Apple had a 4-for-1 stock split on 31 August 2020, when the unadjusted price fell from around $500 to $125 without destroying three quarters of the company’s value. Adjusted prices correct for these corporate actions, which is why the risk calculations throughout this course use the adjusted series.
Or perhaps with a log y-axis, which is often more informative for a price series spanning decades:
fig, ax = plt.subplots()
ax.plot(prices["date"], prices["adjusted_close"])
ax.set_title("Apple stock prices")
ax.set_xlabel("Date")
ax.set_ylabel("Adjusted price")
ax.set_yscale("log")
plt.show()The return series can be plotted in the same way:
fig, ax = plt.subplots()
ax.plot(prices["date"], prices["return"], color="darkred")
ax.set_title("Apple daily log returns")
ax.set_xlabel("Date")
ax.set_ylabel("Log return")
plt.show()EODHD also provides dividend and split information through api.get_dividends(symbol=...) and api.get_splits(symbol=...), the latter requiring a personal token. These are left for the final exercises because they add another data operation without changing this week’s central analysis.
1.10 Recap
1.10.1 In this seminar we have covered:
- Setting up the course Python environment and the
eodhdclient - Understanding API tokens and how to use them securely
- Understanding demo token limitations
- Downloading and exploring stock price data
- Understanding the difference between regular and adjusted prices
- Calculating log returns from price data with the aligned
pandasmethoddiff() - Creating progressively better visualisations:
- Basic plots
- Adding dates to axes
- Improving labels and formatting
- Using a log scale for better long-term visualisation
1.10.2 Key skills learned:
- Working with
pandasSeries and DataFrames - Using
np.log()and.diff()for return calculations - Understanding why the first return in a sample is undefined
- Customising
matplotlibplots with titles, labels and axis scales - Understanding why adjusted prices matter for analysis
1.10.3 Some new functions used:
APIClient()— creates an authenticated EODHD clientapi.get_eod_historical_stock_market_data()— downloads historical price data for a symbolpd.DataFrame()— builds a data frame from raw recordspd.read_csv()— reads a CSV file into a data frame.shape,.head(),.columns— inspect a data frame’s dimensions, first rows and column names.describe()— summary statistics for a Series or DataFramenp.log()— computes the natural logarithm.diff()— calculates differences between consecutive elements, alignedplt.subplots(),ax.plot()— create and populate a figure
1.11 Exercises
1.11.1 Apply the method
Choose another stock available through your EODHD account. Change the symbol, download it, calculate its returns and update both plot titles. Before running the return calculation, predict what the first return will contain. Explain in one or two sentences why the price and return plots look so different.
1.11.2 Compare results
Compare Apple with one other stock. Use the same calculations for both, then compare their mean return and standard deviation. A larger return standard deviation means greater historical volatility. Check that this conclusion is consistent with the return plots.
1.11.3 Extend the analysis
- Download three stocks, plot their adjusted prices with a legend and create a table comparing their return volatility.
- Use
api.get_dividends()for Apple, Microsoft and Exxon (XOM.US). Compare payment frequency and plot the dividend amounts over time.