Skip to content

Instantly share code, notes, and snippets.

@weberste
Last active December 21, 2016 06:19
Show Gist options
  • Save weberste/40780a0ae89991a2f4b0c661180acaeb to your computer and use it in GitHub Desktop.
Save weberste/40780a0ae89991a2f4b0c661180acaeb to your computer and use it in GitHub Desktop.
Perf Calc
import R from 'ramda'
export const balance = (txLegs) => {
var getQuantity = (txLeg) => txLeg.quantity;
var quantities = R.map(getQuantity, txLegs);
return R.sum(quantities);
};
// txLegs are ordered by increasing trx date (oldest transactions first)
export const averagePrice = (txLegs, averagePriceCcy, fxFunc) => {
const updateAp = (agg, txLeg) => {
const newQuantity = agg.quantity + txLeg.quantity;
const txPriceInApCcy = txLeg.price.value * fxFunc(txLeg.price.currency, averagePriceCcy, txLeg.tx.timestamp);
var newAp;
// if the sign of the txLeg and the balance before the tx are
// equal we need to update the average price (except for the special case
// where newQuantity is 0--which can happen if oldQuantity & txLeg.quantity
// are also 0)
if (Math.sign(txLeg.quantity) === Math.sign(agg.quantity) && newQuantity !== 0) {
newAp = (agg.quantity * agg.ap + txLeg.quantity * txPriceInApCcy) / newQuantity;
// if new quantity is 0 set the avg price to 0
} else if (newQuantity === 0) {
newAp = 0;
// if the sign of the balance before and after the tx change, the new
// average price is equal to the price on the transaction
} else if (Math.sign(newQuantity) !== Math.sign(agg.quantity)) {
newAp = txPriceInApCcy;
// in any other case, the average price does not change
} else {
newAp = agg.ap;
}
return {
ap: newAp,
quantity: newQuantity
}
}
const res = R.reduce(updateAp, { ap: 0, quantity: 0 }, txLegs);
return {
value: res.ap,
currency: averagePriceCcy
};
};
// salesPrice and costBasis have to be in same ccy
const pnl = (quantity, salesPrice, costBasis) => {
if (salesPrice.currency !== costBasis.currency) {
throw "Currencies of sale and costBasis need to match but are: " + salesPrice.currency + ", " + costBasis.currency;
}
var res = {
value: quantity * (salesPrice.value - costBasis.value),
currency: salesPrice.currency
};
return res;
};
export const realisedPnl = (txLegs, costBasisFunc, reportingCcy, fxFunc) => {
const range = R.range(1, txLegs.length + 1);
const sublists = R.map((numTxs) => txLegs.slice(0, numTxs), range);
const realisedPnlWithLastTx = (txs) => {
var last = R.last(txs);
const txsLastDropped = R.dropLast(1, txs);
const oldBalance = balance(txsLastDropped);
if (Math.sign(last.quantity) !== Math.sign(oldBalance) && oldBalance !== 0) {
const costBasis = averagePrice(txsLastDropped, reportingCcy, fxFunc);
const lastPrice = {
value: last.price.value * fxFunc(last.price.currency, reportingCcy, last.tx.timestamp),
currency: reportingCcy
};
return pnl(
- Math.sign(last.quantity) * Math.min(Math.abs(last.quantity), Math.abs(oldBalance)),
lastPrice,
costBasis);
} else {
return {
value: 0,
currency: reportingCcy
};
}
};
const realisedPnls = R.map(realisedPnlWithLastTx, sublists);
const sumOfRealisedPnls = R.sum(R.map(R.prop('value'), realisedPnls));
return {
value: sumOfRealisedPnls,
currency: reportingCcy
};
};
export const unrealisedPnl = (txLegs, costBasisFunc, currentPrice, reportingCcy, fxFunc) => {
const currentBalance = balance(txLegs);
const costBasis = costBasisFunc(txLegs, reportingCcy, fxFunc);
return pnl(currentBalance, currentPrice, costBasis);
};
export const netIncome = (txLegs) => {
const values = R.map((txLeg) => txLeg.quantity * txLeg.price.value, txLegs);
return R.sum(values);
};
// date is currently ignored for simplicity
export const fxRateLookup = (fxRates, baseCcy, quotationCcy, date) => {
if (baseCcy === quotationCcy) {
return 1.0;
}
const baseCcyRates = fxRates[baseCcy]
if (baseCcyRates) {
const quotationCcyRate = baseCcyRates[quotationCcy];
if (quotationCcyRate) {;
return quotationCcyRate;
}
}
// rate not found, try the inverse
const invBaseCcyRates = fxRates[quotationCcy];
if (invBaseCcyRates) {
const invQuotationCcyRate = invBaseCcyRates[baseCcy];
if (invQuotationCcyRate) {
return 1.0 / invQuotationCcyRate;
}
}
throw "no rate found for " + baseCcy + quotationCcy;
};
import R from 'ramda';
import {fxRateLookup, balance, averagePrice, unrealisedPnl, realisedPnl} from './cell1';
// legs have to be in chronological order (timestamp is ignored)
const item = {
txLegs: [ ///Transactions that make up the position
{
price: {
value: 45.63,
currency: 'EUR'
},
quantity: 5000,
tx: {
timestamp: '2016-01-01T00:00:00.000Z'
}
},
{
price: {
value: 50,
currency: 'EUR'
},
quantity: -1000,
tx: {
timestamp: '2016-01-02T00:00:00.000Z'
}
}
]
};
const fxRates = { ///FX rates that are used
// base currency
EUR: {
// quotation currency
CHF: 1.08
}
};
const reportingCcy = 'CHF'; ///Reporting currency
const currentPrice = {
value: 45.9,
currency: 'EUR'
}; ///Current price and its currency
const fxFunc = R.curry(fxRateLookup)(fxRates); ///CALCULATIONS START HERE
const currentPriceInRepCcy = { ///Translates the price into reporting ccy
value: currentPrice.value * fxFunc(currentPrice.currency, reportingCcy, undefined),
currency: reportingCcy
};
balance(item.txLegs); ///Current balance
averagePrice(item.txLegs, reportingCcy, fxFunc); ///Average price
unrealisedPnl(item.txLegs, averagePrice, currentPriceInRepCcy, reportingCcy, fxFunc); ///Unrealised PnL
realisedPnl(item.txLegs, averagePrice, reportingCcy, fxFunc); ///Realised PnL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment