Automating complex corporate actions for stock trading firms

October 6, 2022. Olof Almqvist.

Corporate actions are relatively common occurrences among public companies that, in turn, may require some sort of input from both investors/shareholders and brokers. While they are fairly straightforward for investors, they can be a real headache for brokers to deal with, especially if they’re more complex.

What is a corporate action?

A corporate action is any action taken by a company that brings material change to the securities issued by that company. As an investor, a corporate action will mean some change to the stocks or bonds you own, such as the value or type of share. As a result, brokers need to ensure that their customers’ accounts and assets are appropriately managed to reflect the changes.

Common corporate actions like dividends and stock splits are relatively easy to manage and many brokers and asset managers have moved to automate these. On the other hand, there are complex corporate actions which are less easy to process and, according to some reports, are only getting more complex.

Can brokers automate complex corporate actions?

Yes and no. As we explained in a previous article, complex corporate actions can be managed with automation, but this will likely require a wizard-like tool to be created first. Here at Bricknode, our brokerage aficionados have created several such tools, which save our customers days of operations admin. Our customers can develop such tools as well, which connect via API to our back-office brokerage system.

Case study: automating a complex corporate action

We came up against an unusual event recently when the company Lundin Energy AB decided to sell their gas and oil business to the Norwegian company Aker BP ASA. They had decided to go in a different direction and only keep their assets that produce electricity through wind and solar. As part of this transformation, the company also changed its name to Orrön Energy AB.

Aker BP ASA acquired Lundin Energy’s oil platforms in the Norwegian sea and paid the previous shareholders with both cash and stocks. For every share owned in Lundin Energy AB, a shareholder got a cash distribution of 7.76 USD and 0.95098 shares in Aker BP.

Bricknode uses the bank SEB as custodian for this customer and Euroclear keeps track of how many shares all end users of the house own. So, the shares in Aker BP ASA and the cash distribution were paid as two large quantities to two different bank accounts that Bricknode manages at SEB. Bricknode Broker is the software that keeps track of how the ownership in large custody accounts like this should be distributed to individual end customers and their accounts.

Figure 1. Simplified overview of the different entities involved in the corporate action and how Bricknode Broker mirrors real financial resources at a custodian (SEB in this case).

Although splits and mergers are common occurrences, this type of event has not happened before in our system, so we needed to create a tool to manage it. The fact that a fractional share in Aker BP was part of the payment made the solution a bit more complex since fractional shares don’t trade on the open market.

Breaking down the required steps

  1. For every share in Lundin Energy AB, pay a cash dividend of 7.76 USD
  2. For every share in Lundin Energy AB, transfer 0.95098 shares in Aker BP. But only transfer Aker BP shares to customers if the sum of shares is equal to or larger than one, and only transfer whole numbers of shares.
  3. Sell all fractional shares of Aker BP that could not be distributed to customers and transfer the resulting cash to the customers, proportional to the amount of fractional shares they are owed.

There were also five types of accounts affected by these transactions: end customer accounts, the NOK custody-, USD custody- and the instrument custody accounts of the house and the corporate actions account.

When writing the code for this we decided to implement a script by using the Bricknode API. Methods that were used extensively were CreateBusinessTransactions, GetPositions, GetAccounts, GetPersons, CreateInstruments and GetInstruments.

Step 1 – Distributing the Lundin Energy Merger shares into house accounts

In the first step we created the instrument that we called “Lundin Energy Merger”. We also used GetPositions to find out how many shares end customers owned in Lundin Energy AB at the date of the corporate event. For each share in Lundin Energy AB, we distributed one share in Lundin Energy Merger.

We used this information to create transactions in the trade and settlement dimensions into the Corporate Actions account as well as the Instrument Custody account. For this we used the transaction types “Default_Payment_Trade_Instrument_Dividend” and “Default_Payment_Settle_Instrument_Dividend”.

 
// Create the instrument
var response = (await _bfsAssetService.CreateInstrumentsAsync(new Instrument[]
{
   new Instrument()
   {
      Name = "Lundin Energy Merger",
      InstrumentType = InstrumentTypes.Shares,
      CurrencyCode = BaseCurrencyCodes.SEK,
      DisplayDecimalsPrice = 3,
      InstrumentStatus = InstrumentStatuses.OpenAdmin
   }
}, bfsInstanceKey));

Figure 2. We created a new temporary instrument and created positions in it on the Corporate Actions account and the Instrument Custody account.

var transactionDate = new DateTime(2022, 06, 27);

var numShares = LundinEnergyPositionsOnCustomerAccountsAtMergerDate.Sum(d => d.Amount);

var transactionReference = $"Payment of {numShares} Lundin Energy Merger private shares based on " +
                            $"{numShares} held by customers in Lundin Energy AB at merger date {MergerDate.ToShortDateString()}.";

var superTransaction = new SuperTransaction()
{
    BusinessTransactions = new[]
        {
            new BusinessTransaction()
            {
                Account = CorporateActionsAccount.BrickId,
                BusinessTransactionType = BusinessTransactionTypes.DefaultPaymentTradeInstrumentDividend,
                AmountAsset2 = numShares,
                Asset2 = lundinEnergyMergerInstrument.BrickId,
                CreatedDate = DateTime.UtcNow,
                TradeDate = transactionDate,
                TransactionReference = transactionReference
            },
            new BusinessTransaction()
            {
                Account = CorporateActionsAccount.BrickId,
                BusinessTransactionType = BusinessTransactionTypes.DefaultPaymentSettleInstrumentDividend,
                AmountAsset2 = numShares,
                Asset2 = lundinEnergyMergerInstrument.BrickId,
                CreatedDate = DateTime.UtcNow,
                SettlementDate = transactionDate,
                ValueDate = transactionDate,
                TransactionReference = transactionReference
            },

            // Trade & Settle Custody Account
            new BusinessTransaction()
            {
                Account = InstrumentCustodyAccount.BrickId,
                BusinessTransactionType = BusinessTransactionTypes.DefaultPaymentTradeInstrumentDividend,
                AmountAsset2 = numShares,
                Asset2 = lundinEnergyMergerInstrument.BrickId,
                CreatedDate = DateTime.UtcNow,
                TradeDate = transactionDate
            },
            new BusinessTransaction()
            {
                Account = InstrumentCustodyAccount.BrickId,
                BusinessTransactionType = BusinessTransactionTypes.DefaultPaymentSettleInstrumentDividend,
                AmountAsset2 = numShares,
                Asset2 = lundinEnergyMergerInstrument.BrickId,
                CreatedDate = DateTime.UtcNow,
                SettlementDate = transactionDate,
                ValueDate = transactionDate
            }
        }
};

var transactionsResponse = await _bfsTransactionService.CreateBusinessTransactionsAsync(new [] { superTransaction }, bfsInstanceKey);

Figure 3. How we created transactions to generate positions in the Lundin Energy Merger instrument.

Step 2 – Converting Lundin Energy Merger real shares

Each share of Lundin Energy Merger was converted into 0.95098 shares of the temporary instrument “Aker BP SDR”. These were then converted to Aker BP ASA shares which are the real instrument traded on different stock exchanges. These two actions were carried out in a duplicated fashion on both the Corporate Actions and the Instrument Custody account.

Step 3 – Deposit cash transactions into house accounts

Next, we created a large one-off deposit transaction of the total cash dividend in USD that we registered on our bank account at SEB. This was 7.76 USD per share held in Lundin Energy AB. Transactions were created on both the Corporate Actions and the USD Custody account.

Step 4 – Move cash and instruments to end customers

The newly arrived USD position on the Corporate Actions account was subsequently distributed to the end customers. Also, the shares in Aker BP ASA were distributed to the end customers but only if they were owed equal to or more than one share and only in whole numbers, no fractional shares.

We calculated the acquisition value of the new shares by multiplying the acquisition value on the Lundin Energy share that distributed the Aker BP ASA share by the split ratio (0.95098) and the price decline in the stock on the merger date (~-98%).

var instrumentSuperTransactions = new List<SuperTransaction>();
foreach (var customerAccountId in customerAccountIds)
{
    var numLundinEnergyShares = LundinEnergyPositionsOnCustomerAccountsAtMergerDate
        .Where(d => d.Account == customerAccountId)
        .Sum(c => c.Amount);

    if (numLundinEnergyShares < 2)
        continue;

    var oldAcquisitionValueLundinEnergy = LundinEnergyPositionsOnCustomerAccountsAtMergerDate
        .Single(c => c.Account == customerAccountId).AcquisitionValue;

    var newAcquisitionValueAkerBpAsaSek = oldAcquisitionValueLundinEnergy
                                        * Convert.ToDouble(LundinEnergyAkerBpRatio)
                                        * Convert.ToDouble(PriceDeclineLundinEnergyOnMergerDay);

    var newAcquisitionValueAkerBpAsaNok = Convert.ToDouble(newAcquisitionValueAkerBpAsaSek) * sekToNok.Value;

    var numAkerBpSharesToReceiveFractional = numLundinEnergyShares * LundinEnergyAkerBpRatio;
    var numAkerBpSharesToReceiveInt = (int)numAkerBpSharesToReceiveFractional;

    var superTransaction = new SuperTransaction()
    {
        BusinessTransactions = new[]
        {
            // Corporate Account

            // Settle
            new BusinessTransaction()
            {
                Account = CorporateActionsAccount.BrickId,
                BusinessTransactionType =
                    BusinessTransactionTypes.DefaultTransferSettleInstrument,
                CreatedDate = DateTime.UtcNow,
                SettlementDate = transactionDate,
                ValueDate = transactionDate,
                Asset2 = akerbpShareAsset.BrickId,
                AmountAsset2 = - numAkerBpSharesToReceiveInt,
                AcquisitionValue = newAcquisitionValueAkerBpAsaNok,
                AcquisitionValueAccountCurrency = newAcquisitionValueAkerBpAsaSek
            },

            // Trade
            new BusinessTransaction()
            {
                Account = CorporateActionsAccount.BrickId,
                BusinessTransactionType =
                    BusinessTransactionTypes.DefaultTransferTradeInstrument,
                CreatedDate = DateTime.UtcNow,
                TradeDate = transactionDate,
                Asset2 = akerbpShareAsset.BrickId,
                AmountAsset2 = -numAkerBpSharesToReceiveInt,
                AcquisitionValue = newAcquisitionValueAkerBpAsaNok,
                AcquisitionValueAccountCurrency = newAcquisitionValueAkerBpAsaSek
            },

            // Customer Transactions

            // Settle
            new BusinessTransaction()
            {
                Account = customerAccountId,
                BusinessTransactionType =
                    BusinessTransactionTypes.DefaultTransferSettleInstrument,
                CreatedDate = DateTime.UtcNow,
                SettlementDate = transactionDate,
                ValueDate = transactionDate,
                Asset2 = akerbpShareAsset.BrickId,
                AmountAsset2 = numAkerBpSharesToReceiveInt,
                AcquisitionValue = newAcquisitionValueAkerBpAsaNok,
                AcquisitionValueAccountCurrency = newAcquisitionValueAkerBpAsaSek
            },

            // Trade
            new BusinessTransaction()
            {
                Account = customerAccountId,
                BusinessTransactionType =
                    BusinessTransactionTypes.DefaultTransferTradeInstrument,
                CreatedDate = DateTime.UtcNow,
                TradeDate = transactionDate,
                Asset2 = akerbpShareAsset.BrickId,
                AmountAsset2 = numAkerBpSharesToReceiveInt,
                AcquisitionValue = newAcquisitionValueAkerBpAsaNok,
                AcquisitionValueAccountCurrency = newAcquisitionValueAkerBpAsaSek
            }
        },
        CustomFields = new CustomField[]
        {
            new CustomField()
            {
                FieldName = CustomFieldNameForTransferOfAkerBpFromCorporateAccountToCustomerAccount,
                Value = "Stock transfered as part of merger event between Lundin Energy and Aker BP"
            }
        }
    };

    instrumentSuperTransactions.Add(superTransaction);
}

var transactionResponse = await _bfsTransactionService.CreateBusinessTransactionsAsync(instrumentSuperTransactions.ToArray(), bfsInstanceKey);

Figure 4. We looped through each customer that had one or more shares in Lundin Energy AB at the merger date and distributed Aker BP ASA shares to them if they had 2 or more shares in Lundin Energy and if it could be evenly distributed.

Step 5 – Sell and distribute fractional shares

We sold all shares in Aker BP ASA that could not be evenly distributed to customers. This was done manually by back office staff through the SEB intermediary. The date and selling price when doing this was retrieved from them and specified in the code.

To understand how much money out of the total each customer should receive we wrote a section of code that populated the class “FractionalShareCashDistributeDto” with how many shares of Aker BP ASA each customer should have gotten (including decimals), how many they did receive and how many fractional shares they were still owed.

var fractionalSharesToDistributeInCashDtos = new List<FractionalShareCashDistributeDto>();
foreach (var customerAccountId in customerAccountIds)
{
    var fractionalCashToDistributeDto = new FractionalShareCashDistributeDto
    {
        AccountId = customerAccountId,
        NumAkerBpSharesCorporateActionsAccount = corporateActionsNumAkerBpShares,
        NumAkerBpSharesCustodyAccount = custodyNumAkerBpShares
    };

    var numSharesLundinEnergy = LundinEnergyPositionsOnCustomerAccountsAtMergerDate
        .Where(c => c.Account == customerAccountId)
        .Sum(d => d.Amount);

    fractionalCashToDistributeDto.NumSharesLundinEnergy = (int)numSharesLundinEnergy;

    var numAkerBpShares = 0.0m;
    var akerBpShares = akerBpNumSharesByCustomerAccount.SingleOrDefault(d => d.AccountId == customerAccountId);

    if (akerBpShares != default)
        numAkerBpShares = akerBpShares.NumShares;

    fractionalCashToDistributeDto.NumReceivedSharesAkerBp = (int)numAkerBpShares;

    fractionalCashToDistributeDto.FractionalRightsToSharesAkerBp =
        numSharesLundinEnergy * LundinEnergyAkerBpRatio;

    fractionalCashToDistributeDto.FractionalRightsToSharesAkerBpMinusReceived =
        fractionalCashToDistributeDto.FractionalRightsToSharesAkerBp -
        fractionalCashToDistributeDto.NumReceivedSharesAkerBp;

    fractionalSharesToDistributeInCashDtos.Add(fractionalCashToDistributeDto);
}

Figure 5. We sold all shares that could not be evenly distributed and calculated how many fractional shares each customer was still owed.

Then we ran a second loop where we calculated the proportion of total each customer should receive by dividing the customer specific number of fractional shares owed by the total (sumOfShareRightsMinusReceived).

This percentage was lastly multiplied by the cash that we got from selling all the Aker BP ASA shares behind the scenes and voila – the cash the specific end customer should get into their account.

// Second loop to find proportional right to cash distribution when selling fractional shares
var sumOfShareRightsMinusReceived =
    fractionalSharesToDistributeInCashDtos.Sum(c => c.FractionalRightsToSharesAkerBpMinusReceived);

foreach (var fractionalSharesToDistributeDto in fractionalSharesToDistributeInCashDtos)
{
    // Percentage of the proportion of total cash that this customer should receive
    fractionalSharesToDistributeDto.ProportionOfTotalCashToReceive =
        fractionalSharesToDistributeDto.FractionalRightsToSharesAkerBpMinusReceived /
        sumOfShareRightsMinusReceived;

    fractionalSharesToDistributeDto.SumCashToReceive =
        fractionalSharesToDistributeDto.ProportionOfTotalCashToReceive * Convert.ToDecimal(totalAmountSold);
}

Figure 6. Lastly, we populated a new property, ProportionOfTotalCashToReceive by dividing the fractional shares owed to each customer by the sum of fractional shares owed by all customers. This figure was multiplied by the cash we got from selling all fractions.

If your operations team is losing time and money managing complex corporate actions, get in touch to find out how Bricknode is helping companies like yours to automate the process.