Broker API HubMenu

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.

Independent and unofficialCode run

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

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

Next: Measure drawdown

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.

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.

  1. Prices from a file
  2. Dates and time zones
  3. Read and write JSON
  4. Money with Decimal
  5. Build a trade journalYou are here
  6. Measure drawdown

The whole path, every stage

Repositories to read next

The full repository list

Back: Money with DecimalNext: Measure drawdown