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
AssetMixat the ratio of thatAssetMix. - AssetMixSell: sells units of
Assets in a portfolio so the ratio of the remainingAssets more closely resembles the configuredAssetMix. - Rebalance: buys and sells units of
Assets so that the value-distribution of theAssets in the portfolio matches the ratio of theAssetMix. - ProRata: sells units of
Assets following the ratio of distribution of theAssets in the portfolio. - Switch: sells units of one
Assetand buys units of anotherAsseton the sameMarketDay. - IndirectSwitch: sells units of one
Asseton oneMarketDayand buys units of anotherAsseton the subsequentMarketDay. An order of type Switch automatically becomes an IndirectSwitch when it’s value in percentage of the total portfolio value surpasses theTradeSafetyMargin. - InternalSwitch: sells all units of one
Assetand buys units of anotherAsset. This can happen when anAssetis 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 buyingAssets. - 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
orderTradewas successfully created. - ReserveForOrder:
CashorAssets are reserved for execution or theorderTrade. - Pending: cutoff time hasn’t passed, changes can be made to this
orderTrade. - CutoffTimePassed: cutoff time has passed, changes can be made to this
orderTradeby the giroprovider. - LockedForProcess: cutoff time has passed, changes can’t be made to this
orderTrade. - WaitingForCostsCalculated:
Cashvalue oforderTradehasn’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
orderTradenegates a previously placed, erroneousorderTrade. - 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
Assetbeing sold in the portfolio hasn’t been confirmed. - NoCashAvailable: [not used] insufficient
Cashavailable for processing theorderTrade.
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
OrderBaseAggregatedItemreturned as part of theOrderFormDtois prefilled to varying degrees based on theorderTypepassed 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
RebalanceOrders
Orders of typeRebalancecan’t be calculated and will throw an exception when attempting to do so. Instead use the /OrderFormDataGetByInvestorAccountId/, which will return a fully pre-filledOrderBaseAggregatedItemwhich 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
}
]
}
isPeriodicalTo make an
Orderperiodically repeating we setisPeriodicaltotruein the request body. This makes theOrdera reoccurring one. The interval of periodicalOrders as well as other details regarding periodicalOrders are defined in the associatedProduct.
Buy
OrdersBuy
Orderrequire theInvestorAccountto have the necessary amount ofCashon theCashLedger. For testing purposes this can be simulated by configuring credit in theProduct.
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 =/=TransactionsWithin GiroPro
Orders do not encompass all mutations made to aPortfolio’sAssets. Mutations like the paying of dividends are excluded from theOrders as those are not explicitly placed by anInvestor.To get all mutations of a
Portfolio’sCashorAssets we use theLedgers.
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:
- Call the /OrderGetById/ endpoint with the ID of the order we want to change.
- Apply changes to the returned
OrderAggregatedItem. - Verify these changes by calling the /OrderCalculate endpoint with the changed
OrderAggregatedItem. - Use the
OrderAggregatedItemreturned 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
Orderwas made in error. - The
Ordercontains the wrong amount, or assets and is oforderType:RebalanceIndirectSwitchBuyand doesn’t use direct debit.
It’s best to change an Order when:
- The
Orderis executed periodically. - The
Ordercontains the wrong amount, or assets and is oforderType:SellSwitchBuyand uses direct debit