@ixily/activ
Version:
Alpha Capture Trade Idea Verification. Blockchain ownership proven trade ideas and strategies.
765 lines (659 loc) • 22.6 kB
text/typescript
import { CONTRACT_INTERFACES } from '../../'
interface ITradeIdeaResultCalculationStepPriorTranch {
qty: number
price: number
action: 'buy' | 'sell'
}
interface ITradeIdeaResultCalculationStepPriorReturn {
iValue: number
buys: number
sells: number
cash: number
}
interface ITradeIdeaResultCalculationStepPrior {
qty: number
price: number
direction: 'long' | 'short'
description: string
tranches: ITradeIdeaResultCalculationStepPriorTranch[]
return: ITradeIdeaResultCalculationStepPriorReturn
}
const getIdeaPerformance = (
tradeIdeas: CONTRACT_INTERFACES.ITradeIdea[],
): CONTRACT_INTERFACES.ITradeIdeaResult => {
const openRecommendation = tradeIdeas[0]
const adjustRecommendations = tradeIdeas.slice(1, -1)
let closeRecommendation: CONTRACT_INTERFACES.ITradeIdea | undefined =
tradeIdeas[tradeIdeas.length - 1]
if (
(closeRecommendation.idea! as CONTRACT_INTERFACES.ITradeIdeaIdea)
.kind !== 'close'
) {
adjustRecommendations.push(closeRecommendation)
closeRecommendation = undefined
}
const calc = calculateResultProcess(
openRecommendation,
adjustRecommendations,
closeRecommendation,
)
// console.log('post calculateResultProcess');
// log('getCalculateResult (calc)', calc);
// const userReference = data.userReference;
// const assetId = data.assetId;
// const ticker = data.ticker;
// await calculateAssetProfit(userReference, assetId, ticker, dbConnectionString);
// const calculationStr = calculation.toString();
// encrich the calcs
const enrichedCals = enrichTrades(calc.calculation, adjustRecommendations)
let enrichedCalsHandlered = enrichedCals
// if (!storeResult) {
// enrichedCalsHandlered = enrichedCals?.filter(
// (res) => res.note !== 'Position closed',
// )
// }
const result: CONTRACT_INTERFACES.ITradeIdeaResult = {
isRebalance: false,
summary: calc.profit,
calculation: enrichedCalsHandlered,
}
return result
}
export const calculateResultProcess = (
openRecommendation: CONTRACT_INTERFACES.ITradeIdea,
adjustRecommendations: CONTRACT_INTERFACES.ITradeIdea[],
closeRecommendation: CONTRACT_INTERFACES.ITradeIdea | undefined,
): {
calculation: ITradeIdeaResultCalculationStepPrior[]
profit: CONTRACT_INTERFACES.ITradeIdeaResultProfit
} => {
const calculation: ITradeIdeaResultCalculationStepPrior[] = []
// to start we get the price from the open
// console.log('openRecommendation')
// console.log(openRecommendation)
// console.log('openRecommendation?.idea')
// console.log(openRecommendation?.idea)
const openIdea =
openRecommendation.idea as CONTRACT_INTERFACES.ITradeIdeaIdea
if (openIdea.kind !== 'open') {
throw new Error(
'Calculate Performance Error: openIdea.kind !== open: Did your idea contains the correct strategy reference and strategy have an open idea on this ticker?',
)
}
// console.log('openIdea.trade')
// console.log(openIdea.trade)
let closeIdea: CONTRACT_INTERFACES.ITradeIdeaIdea
if (closeRecommendation?.idea !== undefined) {
closeIdea =
closeRecommendation?.idea as CONTRACT_INTERFACES.ITradeIdeaIdea
} else {
closeIdea = {
...(adjustRecommendations[adjustRecommendations.length - 1]
.idea as CONTRACT_INTERFACES.ITradeIdeaIdea),
}
closeIdea.kind = 'close'
delete closeIdea.adjustment
}
const direction = openIdea.trade!.direction
const openAsk =
openIdea.priceInfo!.price!.ask || openIdea.priceInfo!.price!.globalPrice
const openBid =
openIdea.priceInfo!.price!.bid || openIdea.priceInfo!.price!.globalPrice
const rightOpenPrice = direction === 'long' ? openAsk : openBid
const openPrice = Number(rightOpenPrice || 0)
// console.log('openRecommendation', openRecommendation);
// console.log('direction', direction);
let tradeType: 'buy' | 'sell' = direction == 'long' ? 'buy' : 'sell'
// lets include the conviction
const openObj: ITradeIdeaResultCalculationStepPrior = {
qty: 1,
price: openPrice,
direction,
description: 'init',
tranches: [
{
qty: 1,
price: openPrice,
action: tradeType,
},
],
return: {
iValue: 1,
buys: 0,
sells: 0,
cash: 1,
},
}
if (direction === 'long') {
openObj.return.buys = 1
} else {
openObj.return.sells = 1
}
// add this to our calculation
calculation.push(openObj)
// update any
if (adjustRecommendations.length > 0) {
for (const i in adjustRecommendations) {
if (i) {
// get the size of the existing position
const currentPosition =
calculation[calculation.length - 1].qty || 0
const currentDirection =
calculation[calculation.length - 1].direction
// adjustment info
// console.log(
// 'adjustRecommendations[i].idea!.idea as ITradeIdeaIdea:',
// )
// console.log(
// adjustRecommendations[i].idea!.idea as ITradeIdeaIdea,
// )
const adjustment = adjustRecommendations[i]
.idea as CONTRACT_INTERFACES.ITradeIdeaIdea
const adjustmentType = adjustment.adjustment!.kind
const adjustmentPercentage = adjustment.adjustment!.percentage
const adjustmentAsk =
adjustment.priceInfo!.price!.ask ||
adjustment.priceInfo!.price!.globalPrice
const adjustmentBid =
adjustment.priceInfo!.price!.bid ||
adjustment.priceInfo!.price!.globalPrice
const adjustmentPrice =
direction === 'long'
? adjustmentType === 'increase'
? adjustmentAsk
: adjustmentBid
: adjustmentType === 'increase'
? adjustmentBid
: adjustmentAsk
// oneIndexedCala
// we need to work out the percentage change
const previousPrice = calculation[calculation.length - 1].price
// console.log('1 Indx: previousPrice', previousPrice);
const currentPrice = adjustmentPrice
// console.log('1 Indx: currentPrice', currentPrice);
const priceChange =
(currentPrice - previousPrice) / previousPrice
// console.log('1 Indx: priceChange', priceChange);
const newOneIndexedValue =
calculation[calculation.length - 1].return.iValue +
calculation[calculation.length - 1].return.iValue *
priceChange
// console.log('1 Indx: newOneIndexedValue', newOneIndexedValue);
switch (adjustmentType) {
case 'increase':
// we're increasing the size of the position
// console.log('current position', currentPosition);
// console.log('adjustmentPercentage', adjustmentPercentage);
const increasedAmt =
(adjustmentPercentage / 100) * currentPosition
// console.log('increasedAmt', increasedAmt);
// const val = increasedAmt * currentPosition;
const increaseOneIndexedValue =
(adjustmentPercentage / 100) * newOneIndexedValue
// console.log('1 Indx: nincreaseOneIndexedValue', increaseOneIndexedValue);
// what do we format the qty to?
const newQty = currentPosition + increasedAmt
// const newVal = newQty * adjustmentPrice;
tradeType = direction === 'long' ? 'buy' : 'sell'
// our actual trade object
const tradeObj = {
qty: increasedAmt,
price: adjustmentPrice,
action: tradeType,
}
// add our trade object to the tranches
// console.log('push into trances', tradeObj);
const currentTranchesIncrease = [
...calculation[calculation.length - 1].tranches,
]
currentTranchesIncrease.push(tradeObj)
// now adjust our current oneIndexedValue for the price change
// const currentOneIndexedValue = (priceChange *
// oneIndexedValue = (Number(tranches[t].price - tranches[count - 1].value) / tranches[count - 1].value;
// // now this value has been increased or decreased
// if (tranches[t].AdjustmentType === 'increase') {
// oneIndexedValue *
// }
// oneIndexedCala
// we need to work out the percentage change
// currentValue - previousValue
// const previousPrice = tranches[count - 1].price;
// const currentPrice = tranches[t].price;
// const priceChange = (currentPrice - previousPrice) / previousPrice;
// the extra 'cash' a user puts in is the % adjustment of our current position
const newCash =
(Number(adjustmentPercentage) / 100) *
Number(increaseOneIndexedValue)
// console.log('new cash', newCash);
const totalCash =
Number(newCash) +
Number(
calculation[calculation.length - 1].return.cash,
)
// console.log('Number(calculation[calculation.length - 1].return.buys)', Number(calculation[calculation.length - 1].return.buys));
const totalBuys =
Number(
calculation[calculation.length - 1].return.buys,
) + Number(newCash)
// console.log('totalBuys', totalBuys);
const newReturn = {
iValue: increaseOneIndexedValue,
buys: totalBuys,
sells: Number(
calculation[calculation.length - 1].return
.sells,
),
cash: totalCash,
}
// console.log('return in increase', newReturn);
// put it all together
const recObj: ITradeIdeaResultCalculationStepPrior = {
qty: newQty,
price: adjustmentPrice,
direction: currentDirection,
description: 'increase',
tranches: currentTranchesIncrease,
return: newReturn,
}
calculation.push(recObj)
break
case 'decrease':
// we're increasing the size of the position
const decreaseAmt =
(adjustmentPercentage / 100) * currentPosition
const decreaseOneIndexedValue =
(adjustmentPercentage / 100) * newOneIndexedValue
// console.log('1 Indx: decreaseOneIndexedValue', decreaseOneIndexedValue);
// what do we format the qty to?
const newQtyD = currentPosition - decreaseAmt
tradeType = direction === 'long' ? 'buy' : 'sell'
const reverseTradeType =
tradeType === 'buy' ? 'sell' : 'buy'
// our actual trade object
const tradeObjD: ITradeIdeaResultCalculationStepPriorTranch =
{
qty: decreaseAmt,
price: adjustmentPrice,
action: reverseTradeType,
}
// add our trade object to the tranches
// console.log('push into trances', tradeObj);
const currentTranchesDecrease = [
...calculation[calculation.length - 1].tranches,
]
currentTranchesDecrease.push(tradeObjD)
const totalSells =
Number(
calculation[calculation.length - 1].return
.sells,
) + Number(decreaseOneIndexedValue)
// note that we don't deduct the cash withdrawn as the user has already 'invested' a certain amount of cash
const updatedReturn: ITradeIdeaResultCalculationStepPriorReturn =
{
iValue: decreaseOneIndexedValue,
buys: Number(
calculation[calculation.length - 1].return
.buys,
),
sells: totalSells,
cash: calculation[calculation.length - 1].return
.cash,
}
// console.log('return in decrease', updatedReturn);
// put it all together
const recObjD = {
qty: newQtyD,
price: adjustmentPrice,
direction: currentDirection,
description: 'decrease',
tranches: currentTranchesDecrease,
return: updatedReturn,
}
calculation.push(recObjD)
break
}
}
}
}
// console.log('calculations object final', calculation);
// console.log('closeRecommendation', closeRecommendation);
// if (closeRecommendation) { // <= always true
// console.log('closeRecommendation', closeRecommendation);
// we get the close qty from the previous row qty
const totalPosition = calculation[calculation.length - 1].qty || 0
const closeAsk =
closeIdea.priceInfo!.price!.ask ||
closeIdea.priceInfo!.price!.globalPrice
const closeBid =
closeIdea.priceInfo!.price!.ask ||
closeIdea.priceInfo!.price!.globalPrice
const closePrice = (direction === 'long' ? closeBid : closeAsk) || 0
const existingTranches = [...calculation[calculation.length - 1].tranches]
// this gets the reverse direction
// console.log('initial trade idea open direction', direction);
// console.log('closeTradeType', closeTradeType);
const reverseCloseTradeType = direction === 'long' ? 'sell' : 'buy'
// console.log('reverseCloseTradeType', reverseCloseTradeType);
// oneIndexedCala
// we need to work out the percentage change
const previousPrice = calculation[calculation.length - 1].price
const currentPrice = closePrice
const priceChange = ((currentPrice - previousPrice) / previousPrice) * 100
// console.log('previousPrice', previousPrice);
// console.log('currentPrice', currentPrice);
// console.log('priceChange', priceChange);
// console.log('calculation.length', calculation.length);
// console.log('calculation[calculation.length - 1]', calculation[calculation.length - 1]);
// console.log('Number(calculation[calculation.length - 1].iValue)', Number(calculation[calculation.length - 1].return.iValue));
// this is 0
// const newOneIndexedValue = 0;
const newOneIndexedValue =
Number(calculation[calculation.length - 1].return.iValue) +
Number(calculation[calculation.length - 1].return.iValue) *
Number(priceChange)
// console.log('newOneIndexedValue', newOneIndexedValue);
const tradeObjClose: ITradeIdeaResultCalculationStepPriorTranch = {
qty: totalPosition,
price: closePrice,
action: reverseCloseTradeType,
}
// console.log('tradeObjClose', tradeObjClose);
existingTranches.push(tradeObjClose)
// console.log('existingTranches', existingTranches);
// if this is a long order, then we are selling
let closeBuys = 0
let closeSells = 0
if (direction === 'long') {
closeBuys = Number(calculation[calculation.length - 1].return.buys)
closeSells =
Number(calculation[calculation.length - 1].return.sells) +
Number(newOneIndexedValue)
} else if (direction === 'short') {
closeBuys =
Number(calculation[calculation.length - 1].return.buys) +
Number(newOneIndexedValue)
closeSells = Number(calculation[calculation.length - 1].return.sells)
}
// note that we don't deduct the cash withdrawn as the user has already 'invested' a certain amount of cash
const updatedReturn = {
iValue: newOneIndexedValue,
buys: closeBuys,
sells: closeSells,
cash: calculation[calculation.length - 1].return.cash,
}
const closeObj = {
qty: 0,
price: closePrice,
direction,
description: 'close',
tranches: existingTranches,
return: updatedReturn,
}
// update our main array
calculation.push(closeObj)
// update the tranches with the profit
const profit = calculateProfitTranches(
existingTranches,
closeObj.return,
calculation,
)
// } else {
// // console.log('No close recommendation');
// }
// update our calculation with a little helpful info
// console.log('final calculation', calculation);
const response = {
calculation,
profit,
}
return response
}
export const calculateProfitTranches = (
tranches: ITradeIdeaResultCalculationStepPriorTranch[],
data: ITradeIdeaResultCalculationStepPriorReturn,
calculationArray: ITradeIdeaResultCalculationStepPrior[],
): CONTRACT_INTERFACES.ITradeIdeaResultProfit => {
// let profit = 0;
let value = 0
let qty = 0
let iValue = 0
let totalBuys = 0
let totalSells = 0
let count = 0
// loop through the tranches
for (const t in tranches) {
if (t) {
count++
// if this isn't the open, then we have a previous price
if (tranches.length === 1) {
// profit is the same as the price of the current tranche
// tranches[t].profit = 0
qty = tranches[t].qty
value = tranches[t].qty * tranches[t].price
iValue = 1 // always starts with 1
} else {
// console.log('tranche[t]', tranches[t]);
const jsDecimalFix = 1000000000
// if a buy
if (tranches[t].action?.toLowerCase() === 'buy') {
// console.log('Buy count', count);
// console.log('Buy qty', tranches[t].qty);
// value at the current price
qty = qty + tranches[t].qty
// console.log('buy updated qty', qty);
value =
Number(value) +
((Number(tranches[t].qty) * jsDecimalFix) /
jsDecimalFix) *
(Number(tranches[t].price * jsDecimalFix) /
jsDecimalFix)
// console.log('buy updated value', value);
// iValue = Number(value) / Number(tranches[t].price);
totalBuys = value
} else if (tranches[t].action?.toLowerCase() === 'sell') {
// console.log('Sell count', count);
// console.log('Sell qty', tranches[t].qty);
// value at the current price
qty = qty - tranches[t].qty
// console.log('Sell updated qty', tranches[t].qty);
// console.log('Sell updated price', tranches[t].price);
const sellValue =
(Number(tranches[t].qty) *
jsDecimalFix *
Number(tranches[t].price) *
jsDecimalFix) /
jsDecimalFix /
jsDecimalFix
// const sellValue = Number(tranches[t].qty) * Number(tranches[t].price);
// console.log('sellValue', sellValue);
// console.log('Sell updated value', value);
value = Number(sellValue) - Number(value)
totalSells = value
}
}
}
}
// console.log('totalQty', qty);
// console.log('totalValue', value);
// now we work out the percentage change
let profit = false
let capital = 0
const startAction = tranches[0].action?.toLowerCase()
// const endAction = tranches[tranches.length - 1].action?.toLowerCase()
// if the initial position was short, we need a negative number to profit
// console.log('tranches[0].action', tranches[0].action);
// console.log('value', value);
if (startAction === 'sell') {
if (Number(value) < 0) {
profit = true
}
// max capital used
capital = totalSells
} else if (startAction === 'buy') {
if (Number(value) > 0) {
profit = true
}
// max capital used
capital = totalBuys
}
let percentage = 0
if (tranches.length > 0) {
percentage = value / capital || 0
}
// console.log('value', value);
// console.log('capital', capital);
// this is our fina;
// get our start price
// get our end price
let startPrice = 0
let endPrice = 0
let priceChangePercentage = 0
let priceChangeSuccess = false
let priceChangeDirection: 'up' | 'down' = 'up'
startPrice = tranches[0].price
endPrice = tranches[tranches.length - 1].price
if (Number(startPrice) > 0 && Number(endPrice)) {
const diff = endPrice - startPrice
priceChangePercentage = diff / startPrice
// log('calculateProfitTranches (diff)', diff);
// log('calculateProfitTranches (priceChangePercentage)', priceChangePercentage);
if (startAction === 'sell') {
if (diff < 0) {
priceChangeSuccess = true
priceChangeDirection = 'down'
} else {
priceChangeDirection = 'up'
}
} else if (startAction === 'buy') {
if (diff > 0) {
priceChangeSuccess = true
priceChangeDirection = 'up'
} else {
priceChangeDirection = 'down'
}
}
}
const response = {
profit,
capital,
percentage,
price: {
percentage: priceChangePercentage,
direction: priceChangeDirection,
success: priceChangeSuccess,
},
}
// console.log('calculateProfitTranches (calculationArray)', calculationArray)
// console.log('calculateProfitTranches (tranches)', tranches)
/* JB CHANGES */
const lastChangesStatus = true
if (lastChangesStatus) {
const isClosed = calculationArray?.find(
(res) => res?.description === 'close',
)
const shortCheck = isClosed !== undefined && startAction === 'sell'
if (shortCheck) {
const diff = startPrice - endPrice
const priceChangePercentage = diff / startPrice
response.percentage = priceChangePercentage
response.price.percentage = priceChangePercentage
if (diff > 0) {
response.profit = true
response.price.direction = 'up'
}
if (diff < 0) {
response.profit = false
response.price.direction = 'down'
}
}
}
/* JB CHANGES */
// log('calculateProfitTranches (response)', response);
return response
}
/**
* Adds a little more detail to our data
*
* @export
*/
export const enrichTrades = (
calculations: ITradeIdeaResultCalculationStepPrior[],
adjustsMetas: CONTRACT_INTERFACES.ITradeIdea[],
): CONTRACT_INTERFACES.ITradeIdeaResultCalculationStep[] => {
const adjusts = adjustsMetas.map(
(each) => each.idea as CONTRACT_INTERFACES.ITradeIdeaIdea,
)
// console.log('calculations', calculations);
const data: CONTRACT_INTERFACES.ITradeIdeaResultCalculationStep[] = []
// get the last of the tranches
const tranches = calculations[calculations.length - 1].tranches
tranches.forEach((item, index) => {
let note = ''
if (index === 0) {
note = 'Position initiated'
} else if (index === calculations.length - 1) {
note = 'Position closed'
} else if (adjusts.length > 0) {
if (adjusts[index - 1].adjustment!.kind === 'increase') {
note =
'Increase by ' +
adjusts[index - 1].adjustment!.percentage.toFixed(0) +
'%'
} else if (adjusts[index - 1].adjustment!.kind === 'decrease') {
note =
'Decrease by ' +
adjusts[index - 1].adjustment!.percentage.toFixed(0) +
'%'
}
}
const row: CONTRACT_INTERFACES.ITradeIdeaResultCalculationStep = {
note,
action: item.action,
qty: item.qty,
price: item.price,
trade: item.qty * item.price,
positionQty: 0,
positionDirection: 'long',
positionValue: 0,
}
data.push(row)
})
data.forEach((row, i) => {
row.trade = Number(row.qty) * Number(row.price)
const action = row.action.toLowerCase() || ''
// console.log('i', i);
if (i === 0) {
row.positionQty = row.qty
// row.positionValue = row.qty * row.price;
if (action === 'buy') {
row.positionDirection = 'long'
} else if (action === 'sell') {
row.positionDirection = 'short'
}
row.positionValue = row.qty * row.price
} else if (action === 'buy') {
if (data[0].positionDirection === 'long') {
row.positionQty = data[i - 1].positionQty + row.qty
row.positionValue = row.positionQty * row.price
} else if (data[0].positionDirection === 'short') {
row.positionQty = data[i - 1].positionQty - row.qty
row.positionValue = row.positionQty * row.price
}
} else if (action === 'sell') {
if (data[0].positionDirection === 'long') {
row.positionQty = data[i - 1].positionQty - row.qty
row.positionValue = row.positionQty * row.price
} else if (data[0].positionDirection === 'short') {
row.positionQty = data[i - 1].positionQty + row.qty
row.positionValue = row.positionQty * row.price
}
}
if (row.positionQty === 0) {
row.positionDirection = 'closed'
}
})
return data
}
export const IdeaPerformanceModule = {
getIdeaPerformance,
}