{
  "id": "ZfwuCVdff9lGumXB",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Automated IBKR Trade Report to Google Sheets Journal",
  "tags": [],
  "nodes": [
    {
      "id": "f1ab6f5d-f612-42da-8972-2f5662d1860e",
      "name": "1. Request Flex Report",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Starts Flex Report generation",
      "position": [
        656,
        688
      ],
      "parameters": {
        "url": "https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.SendRequest",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "t",
              "value": "YOUR_FLEX_TOKEN"
            },
            {
              "name": "q",
              "value": "YOUR_QUERY_ID"
            },
            {
              "name": "v",
              "value": "3"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "b67bbf48-a427-4a96-b451-ceecbd24ca25",
      "name": "2. Extract Reference Code",
      "type": "n8n-nodes-base.code",
      "position": [
        880,
        688
      ],
      "parameters": {
        "jsCode": "// Extract Reference Code from XML Response\n// Supports various response formats\n\nlet xml = '';\n\n// Check different possible structures\nif (typeof $input.item.json === 'string') {\n  xml = $input.item.json;\n} else if ($input.item.json.body) {\n  xml = $input.item.json.body;\n} else if ($input.item.json.data) {\n  xml = $input.item.json.data;\n} else {\n  // Fallback: convert entire JSON to String\n  xml = JSON.stringify($input.item.json);\n}\n\n// Debug output\nconsole.log('XML Response:', xml);\n\n// Extract Reference Code\nconst match = xml.match(/<ReferenceCode>(.*?)<\\/ReferenceCode>/);\n\nif (match && match[1]) {\n  return [{\n    json: {\n      referenceCode: match[1],\n      token: 'YOUR_FLEX_TOKEN',\n      debug_xml: xml.substring(0, 200)\n    }\n  }];\n} else {\n  throw new Error('No Reference Code found. Response: ' + xml.substring(0, 500));\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "9bf9aef5-7a6b-4913-9144-bcf6989c35d4",
      "name": "Wait 10 Seconds",
      "type": "n8n-nodes-base.wait",
      "notes": "IBKR needs time to generate",
      "position": [
        1104,
        688
      ],
      "parameters": {
        "amount": 10
      },
      "typeVersion": 1.1
    },
    {
      "id": "72cf587f-5f87-42e7-b599-4c6434b4f7af",
      "name": "3. Download Report",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1328,
        688
      ],
      "parameters": {
        "url": "https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.GetStatement",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "t",
              "value": "={{ $json.token }}"
            },
            {
              "name": "q",
              "value": "={{ $json.referenceCode }}"
            },
            {
              "name": "v",
              "value": "3"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "32848fdb-2b1a-49af-9ec1-263c59631660",
      "name": "5. Split Trades",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1776,
        688
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "FlexQueryResponse.FlexStatements.FlexStatement.Trades.Trade"
      },
      "typeVersion": 1
    },
    {
      "id": "e7e7c804-d9ec-48c3-95a0-76cc8f9955c8",
      "name": "6. Format Trade Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        2000,
        688
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b53e8a2a-e1b3-42c9-a38e-598df8461469",
              "name": "tradeDate",
              "type": "string",
              "value": "={{ $json.tradeDate }}"
            },
            {
              "id": "f986af54-8f07-47b9-abba-1a3304f64991",
              "name": "symbol",
              "type": "string",
              "value": "={{ $json.symbol }}"
            },
            {
              "id": "7ada5c33-76a9-4eb5-a6b5-e934c4d5428e",
              "name": "quantity",
              "type": "string",
              "value": "={{ $json.quantity }}"
            },
            {
              "id": "e087da0d-838c-44c6-9c16-d946a5977ffa",
              "name": "buySell",
              "type": "string",
              "value": "={{ $json.buySell }}"
            },
            {
              "id": "0828c1c4-f3cc-4616-a823-f02312918af6",
              "name": "tradeID",
              "type": "string",
              "value": "={{ $json.tradeID }}"
            },
            {
              "id": "6d37d15b-943c-49e6-9f54-f3b6b0a2a4e1",
              "name": "dateTime",
              "type": "string",
              "value": "={{ $json.dateTime }}"
            },
            {
              "id": "cbf18b22-7d5f-4b39-9013-2c04887fd500",
              "name": "tradePrice",
              "type": "string",
              "value": "={{ $json.tradePrice }}"
            },
            {
              "id": "4df82b35-8ecb-4519-beea-61ec962d2330",
              "name": "currency",
              "type": "string",
              "value": "={{ $json.currency }}"
            },
            {
              "id": "ad553c6c-9d45-4326-a71d-1973ed0cf1e4",
              "name": "fxRateToBase",
              "type": "string",
              "value": "={{ $json.fxRateToBase }}"
            },
            {
              "id": "57c32bbb-f9ea-43b5-ac8e-2333c2356180",
              "name": "description",
              "type": "string",
              "value": "={{ $json.description }}"
            },
            {
              "id": "8cf0f751-5177-4a5c-adfd-9b7ffb557621",
              "name": "tradeMoney",
              "type": "string",
              "value": "={{ $json.tradeMoney }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "9ae3859b-870f-42d7-9b28-81bb6d8d738c",
      "name": "7. Save to Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2224,
        688
      ],
      "parameters": {
        "columns": {
          "value": {
            "symbol": "={{ $json.symbol }}",
            "buySell": "={{ $json.buySell }}",
            "tradeID": "={{ $json.tradeID }}",
            "currency": "={{ $json.currency }}",
            "dateTime": "={{ $json.dateTime }}",
            "quantity": "={{ $json.quantity }}",
            "tradeDate": "={{ $json.tradeDate }}",
            "tradeMoney": "={{ $json.tradeMoney }}",
            "tradePrice": "={{ $json.tradePrice }}",
            "description": "={{ $json.description }}",
            "fxRateToBase": "={{ $json.fxRateToBase }}"
          },
          "schema": [
            {
              "id": "tradeDate",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "tradeDate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "dateTime",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "dateTime",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "symbol",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "symbol",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "quantity",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "quantity",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "buySell",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "buySell",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "tradePrice",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "tradePrice",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "tradeMoney",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "tradeMoney",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "currency",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "currency",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "fxRateToBase",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "fxRateToBase",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "tradeID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "tradeID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "tradeID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "gid=0"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "bba25f41-c980-43b7-a99e-8ce025e46154",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -160,
        -32
      ],
      "parameters": {
        "width": 480,
        "height": 688,
        "content": "# Automated IBKR Trade Report to Google Sheets Journal\n\n### How it works\n1. A daily schedule triggers a FlexStatement request to Interactive Brokers to start generating a trade report.\n2. The workflow extracts the reference code from IBKR's response, waits for the report to be ready, then downloads the Flex report XML.\n3. The XML is parsed and split into individual Trade records.\n4. Key fields (tradeDate, symbol, quantity, buySell, tradeID, price, money, currency, fxRateToBase) are normalized.\n5. Each trade is appended or updated in the configured Google Sheet, matching on tradeID to avoid duplicates and keep the journal current.\n\n### Setup steps\n- [ ] Set schedule to run at your desired time (default: 08:00)\n- [ ] Add or verify IBKR request token/reference parameters (Details in Sticky Note \"Setup Flex Query in Interactive Brokers\")\n- [ ] Adjust wait time if IBKR needs longer to generate reports\n- [ ] Select the Google Sheet and target sheet/tab\n- [ ] Connect Google Sheets OAuth2 account\n- [ ] Confirm column mapping and that tradeID is the matching column\n- [ ] Run a manual test to validate parsing and row updates"
      },
      "typeVersion": 1
    },
    {
      "id": "25c0cc38-6bb9-4210-aab8-284efe39be3d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        560
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 80,
        "content": "## 1. Setup Flex Query in Interactive Brokers\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d32a94ec-d30e-47cb-92bd-4931a42d1cc4",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2128,
        368
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 272,
        "content": "## 3. Save to Google Sheet\n\n### Enter your Google Sheet ID:\nIn node '7. Save to Google Sheet'\nReplace YOUR_GOOGLE_SHEET_ID\n\n### Connect Google credentials:\nIn node 7, select your Google OAuth2 credentials"
      },
      "typeVersion": 1
    },
    {
      "id": "e66d7524-83c9-4370-91c9-0a4852897b5e",
      "name": "Daily Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        432,
        688
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a6741176-6eec-44f1-8629-f08faeed1f17",
      "name": "4. Convert XML to JSON",
      "type": "n8n-nodes-base.xml",
      "position": [
        1552,
        688
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "456a309d-ef03-475c-981e-69e9f8495057",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        560
      ],
      "parameters": {
        "color": 7,
        "width": 992,
        "height": 80,
        "content": "## 2. Download trade journal and format fields\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "3b095fed-0345-46a4-b8af-8d46185cc950",
  "connections": {
    "5. Split Trades": {
      "main": [
        [
          {
            "node": "6. Format Trade Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 10 Seconds": {
      "main": [
        [
          {
            "node": "3. Download Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Download Report": {
      "main": [
        [
          {
            "node": "4. Convert XML to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Request Flex Report": {
      "main": [
        [
          {
            "node": "2. Extract Reference Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Convert XML to JSON": {
      "main": [
        [
          {
            "node": "5. Split Trades",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Format Trade Fields": {
      "main": [
        [
          {
            "node": "7. Save to Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Schedule Trigger": {
      "main": [
        [
          {
            "node": "1. Request Flex Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Extract Reference Code": {
      "main": [
        [
          {
            "node": "Wait 10 Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}