Guide

Capital gains with lots

Match each sale of shares to the purchases it came from by putting them in lots, so the book records the realised gain of every sale as GnuCash does.

When you buy the same security several times at different prices and sell part of it, the gain depends on which shares you sold. A lot is a group of splits in one security account that opens with a purchase and closes when its shares are all sold. Putting each sale's shares into the lots they came from fixes the cost of each, and so the gain, a figure tax rules often ask for. The usual rule is first in, first out (FIFO): a sale uses the oldest lot first.

The gain is written into the book as a split of its own in an income account, in the same transaction as the sale, so the books keep balancing in dollars.

Steps

  1. Record the buys and the sale as in Buy and sell shares. At this point the sale's value is its proceeds; no gain is recorded anywhere.
  2. Assign by FIFO with lot.scrubAccount. It puts the account's splits into lots, splits a sale that spans two lots into one piece per lot, and calculates the gains. It lists the lots, transactions and accounts it created or changed; the gains go to an income account it creates, Orphaned Gains-USD.
  3. Look at the lots with lot.list: each lot's open and close dates, its remaining shares (balance), whether it is closed, and its realizedGains.
  4. Choose the lots yourself? Create a lot with lot.create, put splits in it one at a time with lot.assignSplit and its lot (a split larger than the lot needs is cut into pieces and the rest is returned as rest), and compute its gains with lot.computeGains. To fix a lot after other edits, use lot.scrub.
  5. Use your own gains account. Pass gainsAccount to lot.computeGains to credit an income account you made, instead of Orphaned Gains-USD.

Example

Twenty shares are bought in two lots, at 150.00 and at 170.00, and fifteen are sold for 2,700.00 (180.00 each). FIFO sells all of the first lot, a gain of 300.00, and five of the second, a gain of 50.00.

Example: with a new, empty book open as book.gnucash, at 2024-07-01T12:00:00Z.

JSON-RPC

Request:

{"jsonrpc":"2.0","id":1,"method":"commodity.create","params":{"namespace":"NASDAQ","mnemonic":"AAPL","fullname":"Apple Inc"}}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "…"
}

Request:

{"jsonrpc":"2.0","id":2,"method":"account.create","params":{"name":"Brokerage","type":"ASSET","commodity":"USD","placeholder":true}}

Response:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": "…"
}

Request:

{"jsonrpc":"2.0","id":3,"method":"account.create","params":{"name":"Cash","type":"BANK","parent":"Brokerage"}}

Response:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": "…"
}

Request:

{"jsonrpc":"2.0","id":4,"method":"account.create","params":{"name":"AAPL","type":"STOCK","parent":"Brokerage","commodity":"NASDAQ::AAPL"}}

Response:

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": "…"
}

Request:

{"jsonrpc":"2.0","id":5,"method":"account.create","params":{"name":"Opening Balances","type":"EQUITY","commodity":"USD"}}

Response:

{
  "jsonrpc": "2.0",
  "id": 5,
  "result": "…"
}

Request:

{"jsonrpc":"2.0","id":6,"method":"transaction.post","params":{"datePosted":"2024-01-02","description":"Deposit","splits":[{"account":"Brokerage:Cash","value":"10000.00"},{"account":"Opening Balances","value":"-10000.00"}]}}

Response:

{
  "jsonrpc": "2.0",
  "id": 6,
  "result": "…"
}

Request:

{"jsonrpc":"2.0","id":7,"method":"transaction.post","params":{"datePosted":"2024-02-01","description":"Buy 10 AAPL","currency":"USD","splits":[{"account":"Brokerage:AAPL","value":"1500.00","amount":"10"},{"account":"Brokerage:Cash","value":"-1500.00"}]}}

Response:

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": "…"
}

Request:

{"jsonrpc":"2.0","id":8,"method":"transaction.post","params":{"datePosted":"2024-03-01","description":"Buy 10 AAPL","currency":"USD","splits":[{"account":"Brokerage:AAPL","value":"1700.00","amount":"10"},{"account":"Brokerage:Cash","value":"-1700.00"}]}}

Response:

{
  "jsonrpc": "2.0",
  "id": 8,
  "result": "…"
}

Request:

{"jsonrpc":"2.0","id":9,"method":"transaction.post","params":{"datePosted":"2024-06-03","description":"Sell 15 AAPL","currency":"USD","splits":[{"account":"Brokerage:Cash","value":"2700.00"},{"account":"Brokerage:AAPL","value":"-2700.00","amount":"-15"}]}}

Response:

{
  "jsonrpc": "2.0",
  "id": 9,
  "result": "…"
}

Request:

{"jsonrpc":"2.0","id":10,"method":"lot.scrubAccount","params":{"account":"Brokerage:AAPL"}}

Response:

{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "lotsCreated": [
      "…",
      "…"
    ],
    "accountsCreated": [
      "…"
    ],
    "…": "…"
  }
}

Request:

{"jsonrpc":"2.0","id":11,"method":"lot.list","params":{"account":"Brokerage:AAPL"}}

Response:

{
  "jsonrpc": "2.0",
  "id": 11,
  "result": [
    {
      "closed": true,
      "openDate": "2024-02-01",
      "closeDate": "2024-06-03",
      "realizedGains": {
        "num": "30000",
        "denom": "100"
      },
      "…": "…"
    },
    {
      "closed": false,
      "openDate": "2024-03-01",
      "realizedGains": {
        "num": "5000",
        "denom": "100"
      },
      "…": "…"
    }
  ]
}

Command line

red-ale exec --book book.gnucash --script - <<'EOF'
commodity.create {"namespace":"NASDAQ","mnemonic":"AAPL","fullname":"Apple Inc"}
account.create {"name":"Brokerage","type":"ASSET","commodity":"USD","placeholder":true}
account.create {"name":"Cash","type":"BANK","parent":"Brokerage"}
account.create {"name":"AAPL","type":"STOCK","parent":"Brokerage","commodity":"NASDAQ::AAPL"}
account.create {"name":"Opening Balances","type":"EQUITY","commodity":"USD"}
transaction.post {"datePosted":"2024-01-02","description":"Deposit","splits":[{"account":"Brokerage:Cash","value":"10000.00"},{"account":"Opening Balances","value":"-10000.00"}]}
transaction.post {"datePosted":"2024-02-01","description":"Buy 10 AAPL","currency":"USD","splits":[{"account":"Brokerage:AAPL","value":"1500.00","amount":"10"},{"account":"Brokerage:Cash","value":"-1500.00"}]}
transaction.post {"datePosted":"2024-03-01","description":"Buy 10 AAPL","currency":"USD","splits":[{"account":"Brokerage:AAPL","value":"1700.00","amount":"10"},{"account":"Brokerage:Cash","value":"-1700.00"}]}
transaction.post {"datePosted":"2024-06-03","description":"Sell 15 AAPL","currency":"USD","splits":[{"account":"Brokerage:Cash","value":"2700.00"},{"account":"Brokerage:AAPL","value":"-2700.00","amount":"-15"}]}
lot.scrubAccount {"account":"Brokerage:AAPL"}
lot.list {"account":"Brokerage:AAPL"}
EOF

It prints:

[
  "…",
  "…",
  "…",
  "…",
  "…",
  "…",
  "…",
  "…",
  "…",
  {
    "lotsCreated": [
      "…",
      "…"
    ],
    "accountsCreated": [
      "…"
    ],
    "…": "…"
  },
  [
    {
      "closed": true,
      "openDate": "2024-02-01",
      "closeDate": "2024-06-03",
      "realizedGains": {
        "num": "30000",
        "denom": "100"
      },
      "…": "…"
    },
    {
      "closed": false,
      "openDate": "2024-03-01",
      "realizedGains": {
        "num": "5000",
        "denom": "100"
      },
      "…": "…"
    }
  ]
]

MCP tool call

Tool call:

{"name":"commodity.create","arguments":{"namespace":"NASDAQ","mnemonic":"AAPL","fullname":"Apple Inc"}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "\"…\""
    }
  ]
}

Tool call:

{"name":"account.create","arguments":{"name":"Brokerage","type":"ASSET","commodity":"USD","placeholder":true}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "\"…\""
    }
  ]
}

Tool call:

{"name":"account.create","arguments":{"name":"Cash","type":"BANK","parent":"Brokerage"}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "\"…\""
    }
  ]
}

Tool call:

{"name":"account.create","arguments":{"name":"AAPL","type":"STOCK","parent":"Brokerage","commodity":"NASDAQ::AAPL"}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "\"…\""
    }
  ]
}

Tool call:

{"name":"account.create","arguments":{"name":"Opening Balances","type":"EQUITY","commodity":"USD"}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "\"…\""
    }
  ]
}

Tool call:

{"name":"transaction.post","arguments":{"datePosted":"2024-01-02","description":"Deposit","splits":[{"account":"Brokerage:Cash","value":"10000.00"},{"account":"Opening Balances","value":"-10000.00"}]}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "\"…\""
    }
  ]
}

Tool call:

{"name":"transaction.post","arguments":{"datePosted":"2024-02-01","description":"Buy 10 AAPL","currency":"USD","splits":[{"account":"Brokerage:AAPL","value":"1500.00","amount":"10"},{"account":"Brokerage:Cash","value":"-1500.00"}]}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "\"…\""
    }
  ]
}

Tool call:

{"name":"transaction.post","arguments":{"datePosted":"2024-03-01","description":"Buy 10 AAPL","currency":"USD","splits":[{"account":"Brokerage:AAPL","value":"1700.00","amount":"10"},{"account":"Brokerage:Cash","value":"-1700.00"}]}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "\"…\""
    }
  ]
}

Tool call:

{"name":"transaction.post","arguments":{"datePosted":"2024-06-03","description":"Sell 15 AAPL","currency":"USD","splits":[{"account":"Brokerage:Cash","value":"2700.00"},{"account":"Brokerage:AAPL","value":"-2700.00","amount":"-15"}]}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "\"…\""
    }
  ]
}

Tool call:

{"name":"lot.scrubAccount","arguments":{"account":"Brokerage:AAPL"}}

Result:

{
  "structuredContent": {
    "lotsCreated": [
      "…",
      "…"
    ],
    "accountsCreated": [
      "…"
    ],
    "…": "…"
  }
}

Tool call:

{"name":"lot.list","arguments":{"account":"Brokerage:AAPL"}}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "[{\"closed\":true,\"openDate\":\"2024-02-01\",\"closeDate\":\"2024-06-03\",\"realizedGains\":{\"num\":\"30000\",\"denom\":\"100\"},\"…\":\"…\"},{\"closed\":false,\"openDate\":\"2024-03-01\",\"realizedGains\":{\"num\":\"5000\",\"denom\":\"100\"},\"…\":\"…\"}]"
    }
  ]
}

To choose lots by hand, create one and assign a split to it:

Example: with a copy of the reports/investment-lots example book open as book.gnucash.

JSON-RPC

Request:

{"jsonrpc":"2.0","id":1,"method":"lot.create","params":{"account":"Root:Assets:Broker:AAPL"}}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "closed": false,
    "splitCount": 0,
    "…": "…"
  }
}

Request:

{"jsonrpc":"2.0","id":2,"method":"lot.assignSplit","params":{"split":"d0000000000000000000000000000001"}}

Response:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "pieces": [
      {
        "split": "d0000000000000000000000000000001",
        "lot": "…"
      }
    ],
    "rest": null,
    "newLots": [
      "…"
    ]
  }
}

Command line

red-ale exec --book book.gnucash --script - <<'EOF'
lot.create {"account":"Root:Assets:Broker:AAPL"}
lot.assignSplit {"split":"d0000000000000000000000000000001"}
EOF

It prints:

[
  {
    "closed": false,
    "splitCount": 0,
    "…": "…"
  },
  {
    "pieces": [
      {
        "split": "d0000000000000000000000000000001",
        "lot": "…"
      }
    ],
    "rest": null,
    "newLots": [
      "…"
    ]
  }
]

MCP tool call

Tool call:

{"name":"lot.create","arguments":{"account":"Root:Assets:Broker:AAPL"}}

Result:

{
  "structuredContent": {
    "closed": false,
    "splitCount": 0,
    "…": "…"
  }
}

Tool call:

{"name":"lot.assignSplit","arguments":{"split":"d0000000000000000000000000000001"}}

Result:

{
  "structuredContent": {
    "pieces": [
      {
        "split": "d0000000000000000000000000000001",
        "lot": "…"
      }
    ],
    "rest": null,
    "newLots": [
      "…"
    ]
  }
}