Orders

In GiroPro, an order is considered a scheduled command to execute changes in an investor’s assets. Orders are created by the investor, and occasionally by the giroprovider or by GiroPro itself. Orders are used to buy, sell, or in other ways change the Assets in the portfolio of an InvestorAccount.


orderType

Orders have multiple different purposes. To distinguish the purpose of an order they have a property called orderType. GiroPro recognizes 12 different orderTypes:

  • Buy: buys units of an individual Asset.
  • Sell: sells units of an individual Asset.
  • AssetMix: buys units of an AssetMix at the ratio of that AssetMix.
  • AssetMixSell: sells units of Assets in a portfolio so the ratio of the remaining Assets more closely resembles the configured AssetMix.
  • Rebalance: buys and sells units of Assets so that the value-distribution of the Assets in the portfolio matches the ratio of the AssetMix.
  • ProRata: sells units of Assets following the ratio of distribution of the Assets in the portfolio.
  • Switch: sells units of one Asset and buys units of another Asset on the same MarketDay.
  • IndirectSwitch: sells units of one Asset on one MarketDay and buys units of another Asset on the subsequent MarketDay. An order of type Switch automatically becomes an IndirectSwitch when it’s value in percentage of the total portfolio value surpasses the TradeSafetyMargin.
  • InternalSwitch: sells all units of one Asset and buys units of another Asset. This can happen when an Asset is discontinued by the label. An order of this ordertype can only be created by the giroprovider and not the investors themselves.
  • ReInvest: some Assets yield periodical dividends. This order reinvests those dividends into the portfolio by buying Assets.
  • Refund: when another order fails, this type of order is used to reimburse the failed order’s value.
  • Correction: an ordertype automatically placed in response to an incorrectly executed order. The execution date of this order lies in the past.

orderTrade

An order can perform multiple Asset changes at once. A good example of this are switch-orders. These orders have one orderTrade for the sale of Asset A and one orderTrade for the purchase of Asset B. These are listed as orderTrades under an order.

tradeStatus

Orders are not executed immediately after creation, but rather on a specific MarketDay. A MarketDay is a day on which the label processes all of the orders it aggregated before the cutoff time (usually one or multiple days before the MarketDay).

Because of the nature of orders and the process they go through, all orderTrades have a tradeStatus. This property is used to indicate in what part of the process the order currently resides. GiroPro recognizes 14 different tradeStatuses:

  • Created: the orderTrade was successfully created.
  • ReserveForOrder: Cash or Assets are reserved for execution or the orderTrade.
  • Pending: cutoff time hasn’t passed, changes can be made to this orderTrade.
  • CutoffTimePassed: cutoff time has passed, changes can be made to this orderTrade by the giroprovider.
  • LockedForProcess: cutoff time has passed, changes can’t be made to this orderTrade.
  • WaitingForCostsCalculated: Cash value of orderTrade hasn’t been calculated yet.
  • WaitingForTrade: the trade hasn’t been actualized yet.
  • Processed: the trade has been actualized.
  • Cancelled: the trade was cancelled before the cutoff time had passed.
  • Reversed: this orderTrade negates a previously placed, erroneous orderTrade.
  • WaitingForReconciliation: the reconciliation of the fund administration hasn’t happened yet.
  • ReadyForPayout: [sell only] sale of Assets has been confirmed.
  • WaitingForPortfolio: [sell only] the presence of a sufficient amount of units of the Asset being sold in the portfolio hasn’t been confirmed.
  • NoCashAvailable: [not used] insufficient Cash available for processing the orderTrade.

OrderBaseAggregatedItem

Throughout the GiroPro API, multiple different DTOs are used, both in request bodies and as response types. These DTOs vary slightly from one another based on their specific purpose and use-case. These DTOs all contain, or are a variation of, the OrderBaseAggregatedItem.

The OrderBaseAggregatedItem looks like this:

{
  "return_type": "OrderAggregatedItem", // Name of the derived class
  "orderDiscriminator": "Order", // Options are: Order, AssetMix, Rebalance, ProRata, Correction
  "id": "52572e6f-e611-43aa-aa2b-153aa26bbebe", // ID of the order
  "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357", // ID of the label
  "type": "Buy", // OrderType. Options are: Buy, Sell, IndirectSwitch, Switch, ProRata, ReInvest, Refund, AssetMix, Rebalance, Correction, InternalSwitch, AssetMixSell
  "orderNumber": "202501234567", // Unique number starting with the year, used to identify the order
  "investorAccountId": "488ff6a0-c7b1-4690-b886-23076b7f01e2", // ID of the investorAccount associated with this order
  "investorAccountNumber": "12345678", // Unique number used to identiify the associated investorAccount
  "currency": "EUR",
  "entryTimeStamp": "2025-01-01T07:23:11.8105027Z", // Time stamp of creation of order
  "totalBuyCosts": 0.0, // Combined cost of all buy-orderTrades in this order
  "totalSellCosts": 0.0, // Combined cost of all sell-orderTrades in this order
  "chargedCosts": 0.0, // Costs charged by the Label
  "reasonId": "e0c98ad9-a966-447f-acbc-40590c83653f", // ID of the reason for this order
  "reason": { 
    /* Reason for order. One of a listof reasons defined by the Label. */ 
  }, 
  "description": "", // Description field for the order
  "versionState": "None", // Version the order is in. Options are: None, New, PendingApproval, Future, FuturePendingApproval, UpdateError, Approved, DisApproved
  "isNew": false,
  "isPeriodical": false, // Whether or not this order is executed periodically.
  "tradeItems": [ 
    /* List of OrderTradeItems. Example below this one. */ 
  ],
  "correctionTradeItems": [ 
    /* List of OrderTradeCorrectionItems. */ 
  ],
  "cashLedgerItems": [ 
    /* List of CashLedgerItems. */ 
  ],
  "labelLedgerItems": [ 
    /* List of LabelLedgerItems. */ 
  ],
  "references": [],
  "createSepaRecords": true,
  "paymentReference": "",
  "manualCostInputDtos": [],
  "investorAccount": {
    // InvestorAccount associated with this order
  },
  "investmentAccount": {
    // InvestmentAccount associated with this order
  },
  "bankAccount": {
    // BankAccount associated with this order
  },
  "costPlans": [
    // List of CostPlanAggregatedItems applicable to this order
  ]
}

OrderTradeItem

The OrderBaseAggregatedItem contains a list of orderTrades. These orderTrades represent the actual buying and selling of assets and all have the same base type; OrderTradeBaseItem.

The OrderTradeBaseItem looks like this:

{
  "objectType": "OrderTradeItem", // Name of the derived class.
  "orderTradeDiscriminator": "Trade", // Options are: Trade, Refund, Correction.
  "id": "69000bd0-e605-4d4c-b86c-2026c594a85c", // ID of the OrderTradeBaseItem.
  "orderId": "52572e6f-e611-43aa-aa2b-153aa26bbebe", // ID of the associated Order.
  "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357", // ID of the associated Label.
  "investorAccountId": "488ff6a0-c7b1-4690-b886-23076b7f01e2", // ID of the associated InvestorAccount.
  "investorAccountNumber": "12345678", // Unique number used to identiify the associated investorAccount.
  "currency": "EUR",
  "marketDate": "2025-01-07T00:00:00Z",
  "marketDayId": "a411316c-2634-4c9c-a160-dd799ba5465e", // ID of the associated MarketDay.
  "assetPriceDate": "2025-01-10T00:00:00Z", // Date at which the Asset values are updated to their market value.
  "tradeType": "Buy", // Either Buy or Sell.
  "assetId": "1c092ed5-1bfb-44fa-9748-1af365962298", // ID of the traded Asset.
  "executedJournalEntryItemId": "66062a63-50bf-4545-b246-8d1ea4388b21", // ID of the JournalEntry associated with this orderTrade.
  "assetName": "Stocks World Wide", // Name of traded Asset.
  "unitsOfAsset": 65.32, // Number of traded Asset.
  "assetPrice": 123.45, // AssetPrice of traded Asset.
  "grossOrNet": "Gross", // Either Gross or Net, Net = Gross - TradeCosts
  "valueOfAsset": 8063.754,
  "grossValue": 8063.754,
  "netValue": 8063.754,
  "description": "",
  "executionTimeStamp": "2025-01-10T00:00:00Z",
  "tradeStatus": "Processed", // Options are: Created, ReserveForOrder, NoCashAvailable, Pending, CutoffTimePassed, LockedForProcess, WaitingForCostsCalculated, WaitingForPortfolio, WaitingForTrade, Processed, Cancelled, ReadyForPayout, Reversed, WaitingForReconciliation
  "tradeExecutionType": "ValueBased", // Either UnitBased or ValueBased
  "tradeCosts": {
    // List of OrderTradeCostItems
  },
  allowCutoffTimeNoticedMarketDay: true //  
}

Creating an Order

The creation of Orders is the primary functionality of an investor platform. It allows the user to interact with the assets in their portfolio.

GET /OrderFormDataGetByInvestorAccountId/

The recommended first step in creating an Order is retrieving the appropriate OrderFormDto. This is done through the /OrderFormDataGetByInvestorAccountId/ endpoint. By providing an orderType as a request parameter we get an OrderFormDto which looks like this:

{
  "return_type": "OrderFormDto",
  "investmentAccount": {...}, // the InvestmentAccountAggregatedItem for the provided investorAccountId
  "portfolio": {...}, // the portfolio for the provided investorAccountId
  "cash": {...}, // the CashAggregatedItem for the provided investorAccountId
  "assets": [...], // a list of AssetBaseAggregatedItems the investor can invest in           
  "markets": [...], // list of markets connected to the Assets
  "orderReasons": [...], // list of OrderReasonAggregatedItems for optionial reasons for placing an Order
  "costPlans": [...], // list of CostPlanAggregatedItems relevant to this Order
  "order": {...}, // the OrderBaseAggregatedItem
}

OrderFormDto.order

The OrderBaseAggregatedItem returned as part of the OrderFormDto is prefilled to varying degrees based on the orderType passed in the request.

The OrderBaseAggregatedItem (which may or may not contain (partially) pre-filled OrderTradeItems depending on the provided orderType) can be used as a template for creating an order.

When to use

It is recommended to use the /OrderFormDataGetByInvestorAccountId/ endpoint before creating any order because it provides a (partially) prefilled OrderBaseAggregatedItem and returns a lot of useful data, like the Portfolio and InvestmentAccountAggregatedItem which can be used in Order creation.

PUT /OrderCalculate

Before we create an Order, we want to know whether or not the Order can actually be executed. If we intend to sell units of an asset, that amount of units has to be available in the portfolio so the order can actually be processed. It also shouldn’t exceed the TradeSafetyMargin because that will automatically change the tradeExecutionType to be UnitBased during creation, whilst that might not be what the investor wants to happen.

In order to ‘validate’ our Order we can call the /OrderCalculate endpoint. This endpoint, upon success, returns an OrderBaseAggregatedItem that will pass when used to create an Order.

The minimum required body for a request to the /OrderCalculate endpoint to be successful looks like this:

{
    "objectType": "OrderItem",
    "type": "Sell",
    "id": "00000000-0000-0000-0000-000000000000",
    "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
    "investorAccountId": "4563a12a1-a12a-12ab-a123-a12a1a1ab456",
    "tradeItems": [
        {
            "objectType": "OrderTradeItem",
            "orderTradeDiscriminator": "Trade",
            "tradeType": "Sell",
            "id": "00000000-0000-0000-0000-000000000000",
            "orderId": "00000000-0000-0000-0000-000000000000",
            "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
            "investorAccountId": "4563a12a1-a12a-12ab-a123-a12a1a1ab456",
            "assetId": "5da7c92d-0930-42fb-adf3-c1a5b0f3d0ce",
            "grossOrNet": "Net",
            "netValue": 12.50
        }
    ]
}

The above example is that of a value-based sell order. This is a very bare-bones request body that sends minimal instructions to GiroPro on how to process this order. This results in GiroPro substituting a lot of these instructions based on default values or system-wide configurations.

In this case, the default for the OrderAggregatedItem presets are:

Property Default value
currency EUR
isPeriodical true

And for the OrderTradeItem it contains:

Property Default value
marketDate earliest upcoming MarketDay for which CutoffTime hasn’t passed
tradeExecutionType ValueBased
unitsOfAsset projection of the number of assets to be sold based on the last known assetPrice

When we use the example above as the body of the endpoint, we get a validated, and possibly corrected, OrderAggregatedItem in the response. A correction can be something like capping the sell volume to the value or unit amount that is in the portfolio when the tradeOrder in the request exceeds it.

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/AssetAggregator/OrderCalculate';
const options = {
  method: 'PUT',
  headers: {
    accept: 'application/json', 
    'content-type': 'application/json',
    authorization: 'bearer <token>'
  },
  body: <OrderItem>
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

This returns a PengineResult containing an OrderAggregatedItem:

{
    "success": true,
    "httpStatusCode": 200,
    "errors": [],
    "totalCount": 0,
    "items": [
        {
            "return_type": "OrderAggregatedItem",
            "investorAccount": {
                // The associated investorAccount
            },
            "investmentAccount": {
                // The associated investmentAccount
            },
            "bankAccount": {
                // The associated bankAccount
            },
            "costPlans": [
                // The applicable costPlans
            ],
            "orderDiscriminator": "Order",
            "id": "00000000-0000-0000-0000-000000000000",
            "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
            "type": "Sell",
            "orderNumber": "",
            "investorAccountId": "4563a12a1-a12a-12ab-a123-a12a1a1ab456",
            "investorAccountNumber": "",
            "currency": "",
            "entryTimeStamp": "0001-01-01T00:00:00Z",
            "totalBuyCosts": 0.0,
            "totalSellCosts": 0.0,
            "chargedCosts": 0.0,
            "description": "",
            "versionState": "None",
            "isNew": true,
            "isPeriodical": false,
            "tradeItems": [
                // list of OrderTradeItems
            ],
            "correctionTradeItems": [],
            "cashLedgerItems": [],
            "labelLedgerItems": [],
            "references": [],
            "createSepaRecords": true,
            "paymentReference": "",
            "manualCostInputDtos": [],
            "documents": [],
            "documentsTotalCount": 0,
            "deletedDocumentIds": []
        }
    ]
}

When to use

It is strongly recommended to use the /OrderCalculate endpoint before placing any Orders, for example when validating fields of a form used for Order creation.

/OrderCalculate and Rebalance Orders

Orders of type Rebalance can’t be calculated and will throw an exception when attempting to do so. Instead use the /OrderFormDataGetByInvestorAccountId/, which will return a fully pre-filled OrderBaseAggregatedItem which can be posted directly to the /OrderAdd endpoint.

POST /OrderAdd

The /OrderAdd endpoint creates and returns a new OrderBaseAggregatedItem according to the provided body. We strongly recommend this body to be the OrderAggregatedItem returned by the /OrderCalculate endpoint because this guarantees that correctness of the order.

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/AssetAggregator/OrderAdd';
const options = {
  method: 'PUT',
  headers: {
    accept: 'application/json', 
    'content-type': 'application/json',
    authorization: 'bearer <token>'
  },
  body: <OrderAggregatedItem>
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

This returns a PengineResult containing an OrderAggregatedItem:

{
    "success": true,
    "httpStatusCode": 201,
    "errors": [],
    "totalCount": 0,
    "items": [
        {
            "return_type": "OrderAggregatedItem",
            "orderDiscriminator": "Order",
            "id": "877fc941-afc9-488e-9b77-0d70382bd0c8",
            "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
            "type": "Sell",
            "orderNumber": "202501234567",
            "investorAccountId": "4563a12a1-a12a-12ab-a123-a12a1a1ab456",
            "investorAccountNumber": "12345678",
            "currency": "EUR",
            "entryTimeStamp": "2025-04-16T09:31:41.2863538Z",
            "totalBuyCosts": 0.0,
            "totalSellCosts": 0.0,
            "chargedCosts": 0.0,
            "description": "",
            "versionState": "None",
            "isNew": false,
            "isPeriodical": false,
            "tradeItems": [
                // list of OrderTradeItems
            ],
            "correctionTradeItems": [],
            "cashLedgerItems": [ 
              // List of CashLedgerItems
            ],
            "labelLedgerItems": [ 
              // List of LabelLedgerItems 
            ],
            "references": [],
            "createSepaRecords": true,
            "paymentReference": "",
            "manualCostInputDtos": [],
            "documents": [],
            "documentsTotalCount": 0
        }
    ]
}

isPeriodical

To make an Order periodically repeating we set isPeriodical to true in the request body. This makes the Order a reoccurring one. The interval of periodical Orders as well as other details regarding periodical Orders are defined in the associated Product.

Buy Orders

Buy Order require the InvestorAccount to have the necessary amount of Cash on the Cash Ledger. For testing purposes this can be simulated by configuring credit in the Product.


Retrieving existing Orders

A user might want to retrieve already created Orders, to see what Orders were previously processed, what Orders are being executed in the future or to make changes to existing Orders.

GiroPro offers two endpoints for retrieving Orders.

GET /OrderGetById/

The /OrderGetById/ endpoint retrieves a singular Order in accordance to the provided labelId and orderId.

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/AssetAggregator/OrderGetById/<labelId>/<orderId>';
const options = {
  method: 'GET', 
  headers: {
    accept: 'application/json',
    authorization: 'bearer <token>'
  }
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

This endpoint returns a PengineResult containing a single OrderAggregatedItem.

POST /Orders

The /Orders endpoint retrieves all Orders that match the provided criteria. This means it queries all Orders in GiroPro. To narrow it down to the Orders concerning our Investor we need to provide the relevant investorAccountId as a query parameter.

The minimum required body for the request looks like this:

{
    "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357"
}

This body would return all orders connected to this Label and provided investorAccountId. To further specify the query the following optional properties can be added to the request body:

Property Description Default value
skip items to skip 0
limit maximum amount of returned items 15 when query property exists on the body
query additional queries []
sorting specifies how results are sorted [{
“column”: “OrderNumber”,
“direction”: “Descending”
}]
options property for specifying case-sensitivity and including the total count [{
“caseInsensitive”: true,
“excludeTotalCount”: false
}]

A more specific request body could look like this:

{
    "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
    "skip": 0, // skips no items
    "limit": 5, // returns a maximum of 5 items
    "query": [ // this query only returns Orders that total a value greater than 10,000 EUR
        {
            "column": "Trades.valueOfAsset",
            "value": "10000",
            "type": "Greater"
        }
    ],
    "sorting": [ // sorts items by orderNumber from high to low
        {
            "column": "OrderNumber",
            "direction": "Descending"
        }
    ],
    "options": {
      	"caseInsensitive": true, // ignores case
        "excludeTotalCount": false // returns total number of Orders that match this query
    }
}

A call to the endpoint using the above body would look like this:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/AssetAggregator/Orders?<investorAccountId>';
const options = {
  method: 'POST',
  headers: {
    accept: 'application/json', 
    'content-type': 'application/json'
    authorization: 'bearer <token>'
  },
  body: JSON.stringify({
    "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
    "skip": 0,
    "limit": 5,
    "query": [{"column": "Trades.valueOfAsset","value": "10000","type": "Greater"}],
    "sorting": [{"column": "OrderNumber","direction": "Descending"}],
    "options": {"caseInsensitive": true,"excludeTotalCount": false }
  })
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

The example above returns a PengineResult containing no more than 5 OrderAggregatedItems which had a total trade value greater than 10,000 EUR, sorted by orderNumber from highest to lowest:

{
  "success": true,
  "httpStatusCode": 200,
  "errors": [],
  "totalCount": 14, // number of Orders that match the provided query
  "items": [
    // list of maximum 5 OrderAggregatedItems that match the properties from the request body
  ],
}

When to use

/OrderGetById/ should be used when we want to retrieve a single Order, for example when we want to make changes to said order.

/Orders should be used when we want to display multiple Orders, for example to show upcoming Orders or Orders of a specific period.

Orders =/= Transactions

Within GiroPro Orders do not encompass all mutations made to a Portfolio’s Assets. Mutations like the paying of dividends are excluded from the Orders as those are not explicitly placed by an Investor.

To get all mutations of a Portfolio’s Cash or Assets we use the Ledgers.


Making changes to an Order

An investor might want to make changes to an Order, possibly because it was made in error, it contains an error (wrong type, amount or asset), or because they changed their mind. Only orders with tradeStatus Created or Pending can be changed by the investor, any other status requires assistance from the giroprovider.

GiroPro offers two options for making changes; cancelling and updating.

PUT /OrderCancelById/

To cancel an Order we simply call the /OrderCancelById/ endpoint, providing the relevant labelId and orderId:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/AssetAggregator/OrderCancelById/<labelId>/<orderId>';
const options = {method: 'PUT'};
const options = {
  method: 'PUT',
  headers: {
    authorization: 'bearer <token>'
  }
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

PUT /OrderUpdate

An investor might want to change an Order rather than cancel it. For example, they might want to change the amount they invest when it concerns a periodical Order. To change an Order we use the /OrderUpdate endpoint.

This endpoint accepts a body that contains an OrderBaseAggregatedItem. This OrderBaseAggregatedItem should be an altered version of the Order we want to change. The best way to go about constructing this body is by following these steps:

  1. Call the /OrderGetById/ endpoint with the ID of the order we want to change.
  2. Apply changes to the returned OrderAggregatedItem.
  3. Verify these changes by calling the /OrderCalculate endpoint with the changed OrderAggregatedItem.
  4. Use the OrderAggregatedItem returned by the /OrderCalculate endpoint as the body for the /OrderUpdate endpoint.
const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/AssetAggregator/OrderUpdate';
const options = {
  method: 'PUT',
  headers: {
    accept: 'application/json', 
    'content-type': 'application/json',
    authorization: 'bearer <token>'
  },
  body: <OrderAggregatedItem>
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

This endpoint returns a PengineResult containing the updated OrderAggregatedItem.

When to use

Whether to use the /OrderCancelById/ endpoint or /OrderUpdate endpoint when changing an Order depends on a couple of factors.

It’s best to cancel an Order when:

  • The Order was made in error.
  • The Order contains the wrong amount, or assets and is of orderType:
    • Rebalance
    • IndirectSwitch
    • Buy and doesn’t use direct debit.

It’s best to change an Order when:

  • The Order is executed periodically.
  • The Order contains the wrong amount, or assets and is of orderType:
    • Sell
    • Switch
    • Buy and uses direct debit



Back to top

© 2026 Pengine.