UNPKG

@ixily/activ

Version:

Alpha Capture Trade Idea Verification. Blockchain ownership proven trade ideas and strategies.

765 lines (659 loc) 22.6 kB
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, }