Savings Plans: Recurring Investments using Workflows

Learn how to set up recurring investment schemes using workflows to automate Dollar Cost Averaging (savings plans).

Recurring investments are one of the pillars of investment success. In fact, it's so popular that different regions have established totally different terms for the same thing: Dollar Cost Averaging (US) or Savings Plans (DE; Sparpläne) are popular names; however, they all share the same fundamental properties: For each of these, a fixed amount of money is invested in the same security periodically, e.g. every month.

At lemon.markets, we manage programmatic investment instructions like this using workflows. A workflow is a building block allowing automatic creation of our existing order types. As such, it complements the existing API as a pure add-on.

Create a Workflow

Workflows support two cadences:

  • weekly: Execute weekly on a set day. Use day_of_week to set a custom weekday (monday–friday); otherwise defaults to the first trading day of the week.
  • monthly: Execute monthly on a set day. Use day_of_month to set a custom day (1–31); otherwise defaults to the first trading day of the month.

Monthly Workflow

The following example creates a workflow for account cusa_12345 to invest 12.50 € every month into an ETF:

curl -sS -H 'LMG-Data-Privacy-Access-Principal: backend-cusa_12345' \
    -H 'LMG-Data-Privacy-Access-Justification: app.create_workflow' \
    -H 'Authorization: Bearer <your-api-key>' \
    -H 'Content-Type: application/json; charset: utf-8' \
    'https://sandbox.api.lemon.markets/v1/accounts/cusa_12345/workflows?unified_orders=true' \
    --data-binary '
    {
      "action": {
        "type": "order.create_and_confirm",
        "order": {
          "amount": "12.5",
          "currency": "EUR",
          "fee": "0",
          "instrument": "LU0290358497",
          "side": "buy",
          "type": "batch"
        }
      },
      "trigger": {
        "cadence": "monthly",
        "type": "schedule"
      }
    }' | jq

To execute on a specific day of the month — e.g. the 15th — add day_of_month to the trigger:

"trigger": {
  "type": "schedule",
  "cadence": "monthly",
  "day_of_month": 15
}

If the chosen day exceeds the number of days in a given month, it is clamped to the last day of a month (e.g. day 31 in February → 28/29). If the resolved day falls on a weekend or trading holiday, execution rolls forward to the next trading day.

As a result, you'll see a response like this:

{
  "id": "wf_c34bf754daa34de1ba7ee668a06788c9",
  "trigger": {
    "type": "schedule",
    "cadence": "monthly"
  },
  "action": {
    "type": "order.create_and_confirm",
    "order": {
      "side": "buy",
      "instrument": "LU0290358497",
      "amount": "12.50",
      "fee": "0.00",
      "currency": "EUR",
      "type": "batch"
    }
  },
  "regulatory_disclosures": {
    "kid": "https://kid.dev-sandbox.lemon.markets/2023-11-17/LU0290358497-ac81558ef3e50c8eb4a01f7a68800963.pdf",
    "estimated_price": "141.3010",
    "estimated_quantity": "0.08846",
    "estimated_amount": "12.50",
    "service_costs_total": "0.00",
    "service_costs_total_pct": "0.03",
    "entry_costs": "0.00",
    "entry_costs_pct": "0.00",
    "…": "…"
  },
  "sca": {
    "required": false,
    "challenge": "aREfflZxxtFqKlxqgSAAHA=="
  },
  "appropriateness_consent": {
    "required": false
  },
  "history": [
    {
      "status": "created",
      "timestamp": "2024-04-22T14:39:34.292276+00:00"
    }
  ]
}

Weekly Workflow

To invest every week instead, set cadence to "weekly":

curl -sS -H 'LMG-Data-Privacy-Access-Principal: backend-cusa_12345' \
    -H 'LMG-Data-Privacy-Access-Justification: app.create_workflow' \
    -H 'Authorization: Bearer <your-api-key>' \
    -H 'Content-Type: application/json; charset: utf-8' \
    'https://sandbox.api.lemon.markets/v1/accounts/cusa_12345/workflows?unified_orders=true' \
    --data-binary '
    {
      "action": {
        "type": "order.create_and_confirm",
        "order": {
          "amount": "12.5",
          "currency": "EUR",
          "fee": "0",
          "instrument": "LU0290358497",
          "side": "buy",
          "type": "batch"
        }
      },
      "trigger": {
        "cadence": "weekly",
        "type": "schedule"
      }
    }' | jq

To execute on a specific weekday — e.g. every Wednesday — add day_of_week to the trigger:

"trigger": {
  "type": "schedule",
  "cadence": "weekly",
  "day_of_week": "wednesday"
}

Valid values for day_of_week are monday, tuesday, wednesday, thursday, and friday. If the resolved day falls on a trading holiday, execution rolls forward to the next trading day.

The response follows the same structure as the monthly workflow above, with cadence set to "weekly".

Confirm a Workflow

The created workflow requires confirmation by the customer. You can confirm a workflow on the sandbox without strong customer authentication (SCA) like this:

curl -sS -H 'LMG-Data-Privacy-Access-Principal: backend-erika.mustermann%40example.com' \
    -H 'LMG-Data-Privacy-Access-Justification: create_workflow' \
    -H 'Authorization: Bearer <your-api-key>' \
    -H 'Content-Type: application/json; charset: utf-8' \
    'https://sandbox.api.lemon.markets/v1/accounts/cusa_12345/workflows/wf_c34bf754daa34de1ba7ee668a06788c9/confirm' \
    --data-binary '{}'

The response contains the updated workflow. Note that regulatory disclosures, strong customer authentication, and appropriateness consent are omitted once the workflow is confirmed. The history shows the updated status, and next_run_date the actual date the workflow will run, adjusted for non-trading days.

{
  "id": "wf_c34bf754daa34de1ba7ee668a06788c9",
  "trigger": {
    "type": "schedule",
    "cadence": "monthly",
    "next_run_date": "2024-05-02"
  },
  "action": {
    "type": "order.create_and_confirm",
    "order": {
      "side": "buy",
      "instrument": "LU0290358497",
      "amount": "12.50",
      "fee": "0.00",
      "currency": "EUR",
      "type": "batch"
    }
  },
  "history": [
    {
      "status": "confirmed",
      "timestamp": "2024-04-22T14:46:07.907882+00:00"
    },
    {
      "status": "created",
      "timestamp": "2024-04-22T14:39:34.292276+00:00"
    }
  ]
}

Order Creation

On the next execution day, there are three relevant points in time:

  • Workflow Cut-Off Time: In the morning of the trading day, the lemon.markets platform prepares all savings plans for execution by creating and confirming new batch orders. You will see events of type order.created, order.confirmed, and either order.accepted or order.rejected for this.

    How cash availability is handled at this point depends on your cash setup:

    • Bundled cash offering: lemon.markets checks the customer's cash balance during order confirmation. If funds are insufficient, the order is rejected.
    • Unbundled cash offering: cash availability is managed on your side. Subscribe to the order.created webhook and create a cash disposition for the savings plan order. If the disposition fails (e.g. due to insufficient funds), cancel the order before the batch order cut-off time.

    NOTE: At this point in time, cancelling a workflow will not cancel the order that was created. Instead, both the workflow and the created order have to be canceled separately.

  • Batch Order Cut-Off Time: Later, all confirmed customer batch orders are aggregated into a single institutional order that is routed to the market. After this cut-off, individual batch orders can no longer be cancelled.

  • Execution Time: The institutional order is executed on the market (e.g. via Tradegate). The corresponding executions are reflected on the underlying customer batch orders.

As the workflow captures information like regulatory disclosures, strong customer authentication and appropriateness consent, this information will not be exposed on orders created by a workflow. Instead, the order will refer to the workflow using the workflow property:

{
  "id": "bord_abd426e1a0e5478e851fca7379ea7765",
  "workflow": "wf_c34bf754daa34de1ba7ee668a06788c9",
  "created_at": "2024-04-23T07:00:03.740574+00:00",
  "side": "buy",
  "instrument": "LU0290358497",
  "amount": "12.50",
  "quantity": null,
  "fee": "0.00",
  "currency": "EUR",
  "status": "accepted",
  "history": [
    {
      "status": "accepted",
      "timestamp": "2024-04-23T07:00:06.607181+00:00"
    },
    {
      "status": "created",
      "timestamp": "2024-04-23T07:00:03.740574+00:00"
    }
  ]
}

Sequence Diagram

sequenceDiagram
    participant User
    participant App
    participant Backend
    participant 🍋.markets

    App->>User: Configure Plan
    User->>App: Select Instrument,<br/>Amount, Schedule
    App->>Backend: Create Plan
    Backend->>🍋.markets: POST /workflows
    🍋.markets->>Backend: 201 Created<br/>{id: wf_1234}
    Backend->>App: Plan Created
    App->>User: Prompt Confirmation:<br/>Instrument, Amount,<br/>Schedule, …
    User->>App: Confirm Plan
    App->>Backend: Confirm Plan
    Backend->>🍋.markets: POST<br/>/workflows/wf_1234/confirm
    🍋.markets->>Backend: 200 Success
    Backend->>App: Success
    App->>User: Present Confirmed Plan

Current Restrictions

  • Execution is restricted to a monthly or weekly schedule. The sandbox also provides a daily schedule for testing.
  • Workflows are immutable. If the amount should be changed, cancel the old workflow and create a new one.
  • Workflows can only buy shares for now. Divestment plans are not supported yet.

If your use-case exceeds these capabilities, feel free to contact us. We love expanding our product with new partners.


Did this page help you?