This Gist was automatically created by Carbide, a free online programming environment.
Last active
December 21, 2016 06:19
-
-
Save weberste/40780a0ae89991a2f4b0c661180acaeb to your computer and use it in GitHub Desktop.
Perf Calc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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