Stage 2 of 4, lesson 11 of 31. Beginner
Build a trade journal from a list of closed trades
Read closed trades from a file, work out the win rate, average win and loss and the total per market, and write the result to a new file. It introduces grouping with a dictionary, list comprehensions and writing CSV.
Risk warning: CFDs and spread bets are complex instruments and come with a high risk of losing money rapidly due to leverage. You can lose money on these products, and each provider publishes its own loss figure under FCA rules. You should consider whether you understand how they work and whether you can afford to take the high risk of losing your money. Automated and API trading can amplify losses through bugs, connectivity failures and runaway orders.
You need: The lessons on reading prices from a file and on functions. No broker account.
A trade journal is the most useful thing most traders never keep, because typing it by hand is tedious. Code does not find it tedious.
The script reads a file of closed trades, works out the profit or loss on each, and then answers the questions a journal exists to answer: how often do I win, how big are the wins against the losses, and where does the money come from?
The grouping step uses a dictionary, which stores a value under a name. Here the name is the market and the value is the running total. It is the structure you will use most when working with broker data.
The trades in the sample file are invented. When you move to your own history, the only part that changes is the first few lines that read the file, because each broker names its columns differently.
Files for this lesson
- sample-trades.csv, sample data with invented values
The code
# Turn a list of closed trades into a journal summary.
# Download sample-trades.csv from this lesson and keep it beside this script.
# The trades in it are invented.
import csv
trades = []
with open("sample-trades.csv", newline="") as f:
for row in csv.DictReader(f):
entry, exit_price, size = float(row["entry"]), float(row["exit"]), float(row["size"])
points = exit_price - entry if row["direction"] == "BUY" else entry - exit_price
trades.append({"date": row["date"], "market": row["market"], "profit": round(points * size, 2)})
wins = [t["profit"] for t in trades if t["profit"] > 0]
losses = [t["profit"] for t in trades if t["profit"] < 0]
print("Trades:", len(trades))
print("Winners:", len(wins), "Losers:", len(losses))
print("Win rate:", round(100 * len(wins) / len(trades), 1), "percent")
print("Average win:", round(sum(wins) / len(wins), 2))
print("Average loss:", round(sum(losses) / len(losses), 2))
print("Total:", round(sum(t["profit"] for t in trades), 2))
# Group by market with a dictionary: the key is the market, the value is the running total.
by_market = {}
for t in trades:
by_market[t["market"]] = round(by_market.get(t["market"], 0) + t["profit"], 2)
for market in sorted(by_market):
print(" ", market, by_market[market])
# Write the journal out, so a spreadsheet can open it.
with open("journal.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["date", "market", "profit"])
writer.writeheader()
writer.writerows(trades)
print("Wrote journal.csv")What you should see
Trades: 40 Winners: 21 Losers: 19 Win rate: 52.5 percent Average win: 4.04 Average loss: -3.95 Total: 9.8 EUR/USD (mock) -17.7 FTSE 100 (mock) 15.6 Gold (mock) 11.9 Wrote journal.csv
Run with Python 3.12 on Windows against the sample files on this page. No network and no account.
Check yourself
Answer each question from the code above before you open it. The answers were written from the script on this page and what it printed.
The first time a market is met, by_market has no entry for it. Why does the script not fail?
It reads the running total with by_market.get(market, 0). When the market is not there yet, get returns the 0 given as the default, and the profit is added to that.
Would a trade that made exactly nothing count as a winner or a loser?
Neither. Winners are profits above zero and losers are profits below zero. Here 21 and 19 add up to all 40 trades, so the sample has none.
What is in journal.csv after the run?
A header line with date, market and profit, then one line for each of the 40 trades, written by DictWriter from the same list the summary was worked out from.
Exercise
Print the largest single win and the largest single loss on one new line, straight after the total.
What the changed script prints
Trades: 40 Winners: 21 Losers: 19 Win rate: 52.5 percent Average win: 4.04 Average loss: -3.95 Total: 9.8 Largest win: 9.3 Largest loss: -10.5 EUR/USD (mock) -17.7 FTSE 100 (mock) 15.6 Gold (mock) 11.9 Wrote journal.csv
Captured from a run of the solution on . Run with Python 3.12 on Windows against the sample files on this page. No network and no account.
One solution
# Exercise solution: the lesson script with the one change the exercise asks for.
# Turn a list of closed trades into a journal summary.
# Download sample-trades.csv from this lesson and keep it beside this script.
# The trades in it are invented.
import csv
trades = []
with open("sample-trades.csv", newline="") as f:
for row in csv.DictReader(f):
entry, exit_price, size = float(row["entry"]), float(row["exit"]), float(row["size"])
points = exit_price - entry if row["direction"] == "BUY" else entry - exit_price
trades.append({"date": row["date"], "market": row["market"], "profit": round(points * size, 2)})
wins = [t["profit"] for t in trades if t["profit"] > 0]
losses = [t["profit"] for t in trades if t["profit"] < 0]
print("Trades:", len(trades))
print("Winners:", len(wins), "Losers:", len(losses))
print("Win rate:", round(100 * len(wins) / len(trades), 1), "percent")
print("Average win:", round(sum(wins) / len(wins), 2))
print("Average loss:", round(sum(losses) / len(losses), 2))
print("Total:", round(sum(t["profit"] for t in trades), 2))
print("Largest win:", max(wins), "Largest loss:", min(losses))
# Group by market with a dictionary: the key is the market, the value is the running total.
by_market = {}
for t in trades:
by_market[t["market"]] = round(by_market.get(t["market"], 0) + t["profit"], 2)
for market in sorted(by_market):
print(" ", market, by_market[market])
# Write the journal out, so a spreadsheet can open it.
with open("journal.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["date", "market", "profit"])
writer.writeheader()
writer.writerows(trades)
print("Wrote journal.csv")Your progress
Your marks are kept in this browser only, in its local storage. They are not sent anywhere, there is no account, and they will not follow you to another browser or device. To remove them, use Clear my progress on the learning track page, or clear this site's data in your browser's settings. Marking needs JavaScript. With scripts off the track reads the same, without the ticks.
This browser refused to store anything for this page, so marks cannot be kept here.
Where this sits in the path
Stage 2 of 4: Working with data. Read prices and trades from files, get dates and money right, and turn a list of trades into a journal.
- Prices from a fileDone
- Dates and time zonesDone
- Read and write JSONDone
- Money with DecimalDone
- Build a trade journalDoneYou are here
- Measure drawdownDone
Repositories to read next
- openbq-org/OpenBB73,862 stars, read . Open Data Platform for analysts, quants and AI agents.
- ranaroussi/yfinance25,435 stars, read . Download market data from Yahoo! Finance's API
- tradingview/lightweight-charts17,477 stars, read . Performant financial charts built with HTML5 canvas
- TA-Lib/ta-lib-python12,268 stars, read . Python wrapper for TA-Lib (http://ta-lib.org/).
- bukosabino/ta5,233 stars, read . Technical Analysis Library using Pandas and Numpy
- pydata/pandas-datareader3,271 stars, read . Extract data from a wide range of Internet sources into a pandas DataFrame.