Last active
January 1, 2022 11:54
-
-
Save szxmsu/7676f14a489c89683d31b57d079298ca to your computer and use it in GitHub Desktop.
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
const fs = require('fs'); | |
const path = require('path'); | |
const utility = require('./utility'); | |
// region Constants. | |
const DATA_FILE_PATH = path.join(__dirname, '..', 'fund-data'); | |
const DATE_RANGES = { | |
SIX_MONTHS: 'SIX_MONTHS', | |
ONE_YEAR: 'ONE_YEAR', | |
TWO_YEARS: 'TWO_YEARS', | |
THREE_YEARS: 'THREE_YEARS', | |
}; | |
const ACTIONS = { | |
ANALYZE: 'analyze', | |
CALCULATE: 'calculate', | |
}; | |
const FORMATTER = new Intl.NumberFormat('en-US', { | |
minimumFractionDigits: 2, | |
maximumFractionDigits: 2, | |
}); | |
// endregion | |
// region Functions. | |
const getCurrentFundsFilePath = (year) => { | |
const fileName = `funds-${year}-07.json`; | |
return path.join(DATA_FILE_PATH, fileName); | |
}; | |
const getFutureFundsFilePath = (year, dateRange) => { | |
let fileName = ''; | |
switch (dateRange) { | |
case DATE_RANGES.SIX_MONTHS: | |
fileName = `funds-${+year + 1}-01.json`; | |
break; | |
case DATE_RANGES.ONE_YEAR: | |
fileName = `funds-${+year + 1}-07.json`; | |
break; | |
case DATE_RANGES.TWO_YEARS: | |
fileName = `funds-${+year + 2}-07.json`; | |
break; | |
case DATE_RANGES.THREE_YEARS: | |
fileName = `funds-${+year + 3}-07.json`; | |
break; | |
default: | |
throw new Error(`Unknown date range: ${dateRange}.`); | |
} | |
return path.join(DATA_FILE_PATH, fileName); | |
}; | |
const readFileSync = (filePath) => { | |
let resultString = null; | |
if (fs.existsSync(filePath)) { | |
resultString = fs.readFileSync(filePath, { encoding: 'utf8' }); | |
} | |
return JSON.parse(resultString); | |
}; | |
const writeFileSync = (filePath, data) => { | |
fs.writeFileSync(filePath, JSON.stringify(data), { encoding: 'utf8' }); | |
}; | |
const analyzeFunds = (initialFunds, year) => { | |
const fundStruct = { | |
code: null, | |
name: null, | |
type: null, | |
sixMonths: null, | |
oneYear: null, | |
twoYears: null, | |
threeYears: null, | |
}; | |
const averageStruct = { | |
sixMonths: null, | |
oneYear: null, | |
twoYears: null, | |
threeYears: null, | |
}; | |
const analyzedFunds = utility.analyzeFunds(initialFunds, 'strict'); | |
const analyticalResult = { | |
year, | |
month: '07', | |
mode: 'strict', | |
allowedTypes: null, | |
analyzedFunds: null, | |
initialAverages: null, | |
analyzedAverages: null, | |
initialQuantities: null, | |
analyzedQuantities: null, | |
}; | |
analyticalResult.allowedTypes = utility.getAllowedTypes(); | |
analyticalResult.analyzedFunds = utility.initializeFundResults(analyzedFunds, fundStruct); | |
analyticalResult.initialAverages = utility.initializeAverageResults(initialFunds, averageStruct); | |
analyticalResult.analyzedAverages = utility.initializeAverageResults(analyzedFunds, averageStruct); | |
analyticalResult.initialQuantities = utility.convertToQuantitiesWithType(initialFunds); | |
analyticalResult.analyzedQuantities = utility.convertToQuantitiesWithType(analyzedFunds); | |
return analyticalResult; | |
}; | |
const calculateFunds = (currentFunds, futureFunds, analyticalResult, options) => { | |
const flattedFutureFunds = Object.values(futureFunds).flat(); | |
analyticalResult.allowedTypes.forEach((type) => { | |
analyticalResult.analyzedFunds[type].forEach((analyzedFund) => { | |
const index = flattedFutureFunds | |
.findIndex((futureFund) => futureFund.code === analyzedFund.code); | |
if (index !== -1) { | |
// eslint-disable-next-line no-param-reassign | |
analyzedFund[options.analyticalField] = flattedFutureFunds[index][options.initialField]; | |
} | |
}); | |
let incomeBeforeAnalyzing = 0; | |
let quantityBeforeAnalyzing = 0; | |
flattedFutureFunds.forEach((futureFund) => { | |
const index = currentFunds[type] | |
.findIndex((currentFund) => currentFund.code === futureFund.code); | |
if (index !== -1) { | |
incomeBeforeAnalyzing += futureFund[options.initialField]; | |
quantityBeforeAnalyzing += 1; | |
} | |
}); | |
let incomeAfterAnalyzing = 0; | |
let quantityAfterAnalyzing = 0; | |
flattedFutureFunds.forEach((futureFund) => { | |
const index = analyticalResult.analyzedFunds[type] | |
.findIndex((analyzedFund) => analyzedFund.code === futureFund.code); | |
if (index !== -1) { | |
incomeAfterAnalyzing += futureFund[options.initialField]; | |
quantityAfterAnalyzing += 1; | |
} | |
}); | |
const averageBeforeAnalyzing = +FORMATTER.format( | |
incomeBeforeAnalyzing / quantityBeforeAnalyzing, | |
); | |
const averageAfterAnalyzing = +FORMATTER.format( | |
incomeAfterAnalyzing / quantityAfterAnalyzing, | |
); | |
// eslint-disable-next-line no-param-reassign | |
analyticalResult.initialAverages[type][options.analyticalField] = averageBeforeAnalyzing; | |
// eslint-disable-next-line no-param-reassign | |
analyticalResult.analyzedAverages[type][options.analyticalField] = averageAfterAnalyzing; | |
}); | |
}; | |
// endregion | |
// region Main. | |
const main = () => { | |
const args = process.argv.slice(2); | |
const [action, year] = args; | |
if (action === undefined || year === undefined) { | |
console.error('Usage: node calculate-funds.js <action> <year>.'); | |
console.error(' action: analyze | calculate'); | |
console.error(' year: the year to analyze or calculate'); | |
process.exit(1); | |
} | |
const currentFunds = readFileSync(getCurrentFundsFilePath(year)); | |
if (currentFunds === null) { | |
console.error(`Could not read file: ${getCurrentFundsFilePath(year)}.`); | |
process.exit(1); | |
} | |
const analyticalResultsFileName = 'analytical-results.json'; | |
const analyticalResultsFilePath = path.join(DATA_FILE_PATH, analyticalResultsFileName); | |
if (action === ACTIONS.ANALYZE) { | |
const analyticalResults = readFileSync(analyticalResultsFilePath) || []; | |
const analyticalResult = analyzeFunds(currentFunds, year); | |
const index = analyticalResults.findIndex((result) => result.year === year); | |
if (index !== -1) { | |
analyticalResults.splice(index, 1, analyticalResult); | |
} else { | |
analyticalResults.push(analyticalResult); | |
} | |
analyticalResults.sort((x, y) => +y.year - +x.year); | |
writeFileSync(analyticalResultsFilePath, analyticalResults); | |
} | |
if (action === ACTIONS.CALCULATE) { | |
const futureFundsArray = [ | |
readFileSync(getFutureFundsFilePath(year, DATE_RANGES.SIX_MONTHS)), | |
readFileSync(getFutureFundsFilePath(year, DATE_RANGES.ONE_YEAR)), | |
readFileSync(getFutureFundsFilePath(year, DATE_RANGES.TWO_YEARS)), | |
readFileSync(getFutureFundsFilePath(year, DATE_RANGES.THREE_YEARS)), | |
]; | |
const optionsArray = [{ | |
initialField: 'lastSixMonths', | |
analyticalField: 'sixMonths', | |
}, { | |
initialField: 'lastOneYear', | |
analyticalField: 'oneYear', | |
}, { | |
initialField: 'lastTwoYears', | |
analyticalField: 'twoYears', | |
}, { | |
initialField: 'lastThreeYears', | |
analyticalField: 'threeYears', | |
}]; | |
const analyticalResults = readFileSync(analyticalResultsFilePath) || []; | |
const analyticalResult = analyticalResults.find((result) => result.year === year); | |
futureFundsArray.forEach((futureFunds, index) => { | |
const options = optionsArray[index]; | |
if (futureFunds !== null) { | |
calculateFunds(currentFunds, futureFunds, analyticalResult, options); | |
} | |
}); | |
writeFileSync(analyticalResultsFilePath, analyticalResults); | |
} | |
}; | |
// endregion | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment