You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1341 lines
48 KiB
1341 lines
48 KiB
/***************************************************************************
|
|
mymoneyforecast.cpp
|
|
-------------------
|
|
begin : Wed May 30 2007
|
|
copyright : (C) 2007 by Alvaro Soliverez
|
|
email : asoliverez@gmail.com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// QT Includes
|
|
|
|
#include <tqstring.h>
|
|
#include <tqdatetime.h>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// KDE Includes
|
|
#include <kdebug.h>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Project Includes
|
|
|
|
#include "mymoneyforecast.h"
|
|
#include "../kmymoneyglobalsettings.h"
|
|
#include "mymoneyfile.h"
|
|
#include "mymoneytransactionfilter.h"
|
|
#include "mymoneyfinancialcalculator.h"
|
|
|
|
MyMoneyForecast::MyMoneyForecast() :
|
|
m_forecastMethod(0),
|
|
m_historyMethod(0),
|
|
m_skipOpeningDate(true),
|
|
m_includeUnusedAccounts(false),
|
|
m_forecastDone(false)
|
|
{
|
|
setForecastCycles(KMyMoneyGlobalSettings::forecastCycles());
|
|
setAccountsCycle(KMyMoneyGlobalSettings::forecastAccountCycle());
|
|
setHistoryStartDate(TQDate::tqcurrentDate().addDays(-forecastCycles()*accountsCycle()));
|
|
setHistoryEndDate(TQDate::tqcurrentDate().addDays(-1));
|
|
setForecastDays(KMyMoneyGlobalSettings::forecastDays());
|
|
setBeginForecastDay(KMyMoneyGlobalSettings::beginForecastDay());
|
|
setForecastMethod(KMyMoneyGlobalSettings::forecastMethod());
|
|
setHistoryMethod(KMyMoneyGlobalSettings::historyMethod());
|
|
setIncludeFutureTransactions(KMyMoneyGlobalSettings::includeFutureTransactions());
|
|
setIncludeScheduledTransactions(KMyMoneyGlobalSettings::includeScheduledTransactions());
|
|
}
|
|
|
|
|
|
void MyMoneyForecast::doForecast()
|
|
{
|
|
int fDays = calculateBeginForecastDay();
|
|
int fMethod = forecastMethod();
|
|
int fAccCycle = accountsCycle();
|
|
int fCycles = forecastCycles();
|
|
|
|
//validate settings
|
|
if(fAccCycle < 1
|
|
|| fCycles < 1
|
|
|| fDays < 1)
|
|
{
|
|
throw new MYMONEYEXCEPTION("Illegal settings when calling doForecast. Settings must be higher than 0");
|
|
}
|
|
|
|
//initialize global variables
|
|
setForecastDays(fDays);
|
|
setForecastStartDate(TQDate::tqcurrentDate().addDays(1));
|
|
setForecastEndDate(TQDate::tqcurrentDate().addDays(fDays));
|
|
setAccountsCycle(fAccCycle);
|
|
setForecastCycles(fCycles);
|
|
setHistoryStartDate(forecastCycles() * accountsCycle());
|
|
setHistoryEndDate(TQDate::tqcurrentDate().addDays(-1)); //yesterday
|
|
|
|
//clear all data before calculating
|
|
m_accountListPast.clear();
|
|
m_accountList.clear();
|
|
m_accountTrendList.clear();
|
|
|
|
//set forecast accounts
|
|
setForecastAccountList();
|
|
|
|
switch(fMethod)
|
|
{
|
|
case eScheduled:
|
|
doFutureScheduledForecast();
|
|
calculateScheduledDailyBalances();
|
|
break;
|
|
case eHistoric:
|
|
pastTransactions();
|
|
calculateHistoricDailyBalances();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//flag the forecast as done
|
|
m_forecastDone = true;
|
|
}
|
|
|
|
MyMoneyForecast::~MyMoneyForecast()
|
|
{
|
|
}
|
|
|
|
void MyMoneyForecast::pastTransactions()
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
MyMoneyTransactionFilter filter;
|
|
|
|
filter.setDateFilter(historyStartDate(), historyEndDate());
|
|
filter.setReportAllSplits(false);
|
|
|
|
TQValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
|
|
TQValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
|
|
|
|
//Check past transactions
|
|
for(; it_t != transactions.end(); ++it_t ) {
|
|
const TQValueList<MyMoneySplit>& splits = (*it_t).splits();
|
|
TQValueList<MyMoneySplit>::const_iterator it_s = splits.begin();
|
|
for(; it_s != splits.end(); ++it_s ) {
|
|
if(!(*it_s).shares().isZero()) {
|
|
MyMoneyAccount acc = file->account((*it_s).accountId());
|
|
|
|
//workaround for stock accounts which have faulty opening dates
|
|
TQDate openingDate;
|
|
if(acc.accountType() == MyMoneyAccount::Stock) {
|
|
MyMoneyAccount parentAccount = file->account(acc.parentAccountId());
|
|
openingDate = parentAccount.openingDate();
|
|
} else {
|
|
openingDate = acc.openingDate();
|
|
}
|
|
|
|
if(isForecastAccount(acc) //If it is one of the accounts we are checking, add the amount of the transaction
|
|
&& ( (openingDate < (*it_t).postDate() && skipOpeningDate())
|
|
|| !skipOpeningDate() ) ){ //don't take the opening day of the account to calculate balance
|
|
dailyBalances balance;
|
|
//FIXME deal with leap years
|
|
balance = m_accountListPast[acc.id()];
|
|
if(acc.accountType() == MyMoneyAccount::Income) {//if it is income, the balance is stored as negative number
|
|
balance[(*it_t).postDate()] += ((*it_s).shares() * MyMoneyMoney(-1, 1));
|
|
} else {
|
|
balance[(*it_t).postDate()] += (*it_s).shares();
|
|
}
|
|
// check if this is a new account for us
|
|
m_accountListPast[acc.id()] = balance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//purge those accounts with no transactions on the period
|
|
if(isIncludingUnusedAccounts() == false)
|
|
purgeForecastAccountsList(m_accountListPast);
|
|
|
|
//calculate running sum
|
|
TQMap<TQString, TQString>::Iterator it_n;
|
|
for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
|
|
MyMoneyAccount acc = file->account(*it_n);
|
|
m_accountListPast[acc.id()][historyStartDate().addDays(-1)] = file->balance(acc.id(), historyStartDate().addDays(-1));
|
|
for(TQDate it_date = historyStartDate(); it_date <= historyEndDate(); ) {
|
|
m_accountListPast[acc.id()][it_date] += m_accountListPast[acc.id()][it_date.addDays(-1)]; //Running sum
|
|
it_date = it_date.addDays(1);
|
|
}
|
|
}
|
|
|
|
//adjust value of investments to deep currency
|
|
for ( it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n ) {
|
|
MyMoneyAccount acc = file->account ( *it_n );
|
|
|
|
if ( acc.isInvest() ) {
|
|
//get the id of the security for that account
|
|
MyMoneySecurity undersecurity = file->security ( acc.currencyId() );
|
|
if ( ! undersecurity.isCurrency() ) //only do it if the security is not an actual currency
|
|
{
|
|
MyMoneyMoney rate = MyMoneyMoney ( 1, 1 ); //set the default value
|
|
MyMoneyPrice price;
|
|
|
|
for ( TQDate it_date = historyStartDate().addDays(-1) ; it_date <= historyEndDate();) {
|
|
//get the price for the tradingCurrency that day
|
|
price = file->price ( undersecurity.id(), undersecurity.tradingCurrency(), it_date );
|
|
if ( price.isValid() )
|
|
{
|
|
rate = price.rate ( undersecurity.tradingCurrency() );
|
|
}
|
|
//value is the amount of shares multiplied by the rate of the deep currency
|
|
m_accountListPast[acc.id() ][it_date] = m_accountListPast[acc.id() ][it_date] * rate;
|
|
it_date = it_date.addDays(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MyMoneyForecast::isForecastAccount(const MyMoneyAccount& acc)
|
|
{
|
|
if(m_nameIdx.isEmpty())
|
|
{
|
|
setForecastAccountList();
|
|
}
|
|
TQMap<TQString, TQString>::Iterator it_n;
|
|
for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
|
|
if(*it_n == acc.id())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MyMoneyForecast::calculateAccountTrendList()
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
int auxForecastTerms;
|
|
int totalWeight = 0;
|
|
|
|
//Calculate account trends
|
|
TQMap<TQString, TQString>::Iterator it_n;
|
|
for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
|
|
MyMoneyAccount acc = file->account(*it_n);
|
|
m_accountTrendList[acc.id()][0] = MyMoneyMoney(0,1); // for today, the trend is 0
|
|
|
|
auxForecastTerms = forecastCycles();
|
|
if(skipOpeningDate()) {
|
|
|
|
TQDate openingDate;
|
|
if(acc.accountType() == MyMoneyAccount::Stock) {
|
|
MyMoneyAccount parentAccount = file->account(acc.parentAccountId());
|
|
openingDate = parentAccount.openingDate();
|
|
} else {
|
|
openingDate = acc.openingDate();
|
|
}
|
|
|
|
if(openingDate > historyStartDate() ) { //if acc opened after forecast period
|
|
auxForecastTerms = 1 + ((openingDate.daysTo(historyEndDate()) + 1)/ accountsCycle()); // set forecastTerms to a lower value, to calculate only based on how long this account was opened
|
|
}
|
|
}
|
|
|
|
switch (historyMethod())
|
|
{
|
|
//moving average
|
|
case 0:
|
|
{
|
|
for(int t_day = 1; t_day <= accountsCycle(); t_day++)
|
|
m_accountTrendList[acc.id()][t_day] = accountMovingAverage(acc, t_day, auxForecastTerms); //moving average
|
|
break;
|
|
}
|
|
//weighted moving average
|
|
case 1:
|
|
{
|
|
//calculate total weight for moving average
|
|
if(auxForecastTerms == forecastCycles()) {
|
|
totalWeight = (auxForecastTerms * (auxForecastTerms + 1))/2; //totalWeight is the triangular number of auxForecastTerms
|
|
} else {
|
|
//if only taking a few periods, totalWeight is the sum of the weight for most recent periods
|
|
for(int i = 1, w = forecastCycles(); i <= auxForecastTerms; ++i, --w)
|
|
totalWeight += w;
|
|
}
|
|
for(int t_day = 1; t_day <= accountsCycle(); t_day++)
|
|
m_accountTrendList[acc.id()][t_day] = accountWeightedMovingAverage(acc, t_day, totalWeight);
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
//calculate mean term
|
|
MyMoneyMoney meanTerms = MyMoneyMoney((auxForecastTerms * (auxForecastTerms + 1))/2, 1) / MyMoneyMoney(auxForecastTerms, 1);
|
|
|
|
for(int t_day = 1; t_day <= accountsCycle(); t_day++)
|
|
m_accountTrendList[acc.id()][t_day] = accountLinearRegression(acc, t_day, auxForecastTerms, meanTerms);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TQValueList<MyMoneyAccount> MyMoneyForecast::forecastAccountList(void)
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
TQValueList<MyMoneyAccount> accList;
|
|
//Get all accounts from the file and check if they are of the right type to calculate forecast
|
|
file->accountList(accList);
|
|
TQValueList<MyMoneyAccount>::iterator accList_t = accList.begin();
|
|
for(; accList_t != accList.end(); ) {
|
|
MyMoneyAccount acc = *accList_t;
|
|
if(acc.isClosed() //check the account is not closed
|
|
|| (!acc.isAssetLiability()) ) {
|
|
//|| (acc.accountType() == MyMoneyAccount::Investment) ) {//check that it is not an Investment account and only include Stock accounts
|
|
accList.remove(accList_t); //remove the account if it is not of the correct type
|
|
accList_t = accList.begin();
|
|
} else {
|
|
++accList_t;
|
|
}
|
|
}
|
|
return accList;
|
|
}
|
|
|
|
TQValueList<MyMoneyAccount> MyMoneyForecast::accountList(void)
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
TQValueList<MyMoneyAccount> accList;
|
|
TQStringList emptyStringList;
|
|
//Get all accounts from the file and check if they are present
|
|
file->accountList(accList, emptyStringList, false);
|
|
TQValueList<MyMoneyAccount>::iterator accList_t = accList.begin();
|
|
for(; accList_t != accList.end(); ) {
|
|
MyMoneyAccount acc = *accList_t;
|
|
if(!isForecastAccount( acc ) ) {
|
|
accList.remove(accList_t); //remove the account
|
|
accList_t = accList.begin();
|
|
} else {
|
|
++accList_t;
|
|
}
|
|
}
|
|
return accList;
|
|
}
|
|
|
|
MyMoneyMoney MyMoneyForecast::calculateAccountTrend(const MyMoneyAccount& acc, int trendDays)
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
MyMoneyTransactionFilter filter;
|
|
MyMoneyMoney netIncome;
|
|
TQDate startDate;
|
|
TQDate openingDate = acc.openingDate();
|
|
|
|
//validate arguments
|
|
if(trendDays < 1)
|
|
{
|
|
throw new MYMONEYEXCEPTION("Illegal arguments when calling calculateAccountTrend. trendDays must be higher than 0");
|
|
}
|
|
|
|
//If it is a new account, we don't take into account the first day
|
|
//because it is usually a weird one and it would mess up the trend
|
|
if(openingDate.daysTo(TQDate::tqcurrentDate())<trendDays){
|
|
startDate = (acc.openingDate()).addDays(1);
|
|
}
|
|
else {
|
|
startDate = TQDate::tqcurrentDate().addDays(-trendDays);
|
|
}
|
|
//get all transactions for the period
|
|
filter.setDateFilter(startDate, TQDate::tqcurrentDate());
|
|
if(acc.accountGroup() == MyMoneyAccount::Income
|
|
|| acc.accountGroup() == MyMoneyAccount::Expense) {
|
|
filter.addCategory(acc.id());
|
|
} else {
|
|
filter.addAccount(acc.id());
|
|
}
|
|
|
|
filter.setReportAllSplits(false);
|
|
TQValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
|
|
TQValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
|
|
|
|
//add all transactions for that account
|
|
for(; it_t != transactions.end(); ++it_t ) {
|
|
const TQValueList<MyMoneySplit>& splits = (*it_t).splits();
|
|
TQValueList<MyMoneySplit>::const_iterator it_s = splits.begin();
|
|
for(; it_s != splits.end(); ++it_s ) {
|
|
if(!(*it_s).shares().isZero()) {
|
|
if(acc.id()==(*it_s).accountId()) netIncome += (*it_s).value();
|
|
}
|
|
}
|
|
}
|
|
|
|
//calculate trend of the account in the past period
|
|
MyMoneyMoney accTrend;
|
|
|
|
//don't take into account the first day of the account
|
|
if(openingDate.daysTo(TQDate::tqcurrentDate())<trendDays) {
|
|
accTrend = netIncome/MyMoneyMoney(openingDate.daysTo(TQDate::tqcurrentDate())-1,1);
|
|
} else {
|
|
accTrend = netIncome/MyMoneyMoney(trendDays,1);
|
|
}
|
|
return accTrend;
|
|
}
|
|
|
|
MyMoneyMoney MyMoneyForecast::accountMovingAverage(const MyMoneyAccount &acc, const int trendDay, const int forecastTerms)
|
|
{
|
|
//Calculate a daily trend for the account based on the accounts of a given number of terms
|
|
//With a term of 1 month and 3 terms, it calculates the trend taking the transactions occurred at that day and the day before,
|
|
//for the last 3 months
|
|
MyMoneyMoney balanceVariation;
|
|
|
|
for(int it_terms = 0; (trendDay+(accountsCycle()*it_terms)) <= historyDays(); ++it_terms) //sum for each term
|
|
{
|
|
MyMoneyMoney balanceBefore = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-2)]; //get balance for the day before
|
|
MyMoneyMoney balanceAfter = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
|
|
balanceVariation += (balanceAfter - balanceBefore); //add the balance variation between days
|
|
}
|
|
//calculate average of the variations
|
|
return (balanceVariation / MyMoneyMoney(forecastTerms,1)).convert(10000);
|
|
}
|
|
|
|
MyMoneyMoney MyMoneyForecast::accountWeightedMovingAverage(const MyMoneyAccount &acc, const int trendDay, const int totalWeight)
|
|
{
|
|
MyMoneyMoney balanceVariation;
|
|
|
|
for(int it_terms = 0, weight = 1; (trendDay+(accountsCycle()*it_terms)) <= historyDays(); ++it_terms, ++weight) //sum for each term multiplied by weight
|
|
{
|
|
MyMoneyMoney balanceBefore = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-2)]; //get balance for the day before
|
|
MyMoneyMoney balanceAfter = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
|
|
balanceVariation += ( (balanceAfter - balanceBefore) * MyMoneyMoney(weight, 1) ); //add the balance variation between days multiplied by its weight
|
|
}
|
|
//calculate average of the variations
|
|
return (balanceVariation / MyMoneyMoney(totalWeight, 1)).convert(10000);
|
|
}
|
|
|
|
MyMoneyMoney MyMoneyForecast::accountLinearRegression(const MyMoneyAccount &acc, const int trendDay, const int actualTerms, const MyMoneyMoney meanTerms)
|
|
{
|
|
MyMoneyMoney meanBalance, totalBalance, totalTerms;
|
|
totalTerms = MyMoneyMoney(actualTerms, 1);
|
|
|
|
//calculate mean balance
|
|
for(int it_terms = forecastCycles() - actualTerms; (trendDay+(accountsCycle()*it_terms)) <= historyDays(); ++it_terms) //sum for each term
|
|
{
|
|
totalBalance += m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
|
|
}
|
|
meanBalance = totalBalance / MyMoneyMoney(actualTerms,1);
|
|
meanBalance = meanBalance.convert(10000);
|
|
|
|
//calculate b1
|
|
|
|
//first calculate x - mean x multiplied by y - mean y
|
|
MyMoneyMoney totalXY, totalSqX;
|
|
for(int it_terms = forecastCycles() - actualTerms, term = 1; (trendDay+(accountsCycle()*it_terms)) <= historyDays(); ++it_terms, ++term) //sum for each term
|
|
{
|
|
MyMoneyMoney balance = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
|
|
|
|
MyMoneyMoney balMeanBal = balance - meanBalance;
|
|
MyMoneyMoney termMeanTerm = (MyMoneyMoney(term, 1) - meanTerms);
|
|
|
|
totalXY += (balMeanBal * termMeanTerm).convert(10000);
|
|
|
|
totalSqX += (termMeanTerm * termMeanTerm).convert(10000);
|
|
}
|
|
totalXY = (totalXY / MyMoneyMoney(actualTerms,1)).convert(10000);
|
|
totalSqX = (totalSqX / MyMoneyMoney(actualTerms,1)).convert(10000);
|
|
|
|
//check zero
|
|
if(totalSqX.isZero())
|
|
return MyMoneyMoney(0,1);
|
|
|
|
MyMoneyMoney linReg = (totalXY/totalSqX).convert(10000);
|
|
|
|
return linReg;
|
|
}
|
|
|
|
void MyMoneyForecast::calculateHistoricDailyBalances()
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
calculateAccountTrendList();
|
|
|
|
//Calculate account daily balances
|
|
TQMap<TQString, TQString>::ConstIterator it_n;
|
|
for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
|
|
MyMoneyAccount acc = file->account(*it_n);
|
|
|
|
//set the starting balance of the account
|
|
setStartingBalance(acc);
|
|
|
|
switch(historyMethod()) {
|
|
case 0:
|
|
case 1:
|
|
{
|
|
for(TQDate f_day = forecastStartDate(); f_day <= forecastEndDate(); ) {
|
|
for(int t_day = 1; t_day <= accountsCycle(); ++t_day) {
|
|
MyMoneyMoney balanceDayBefore = m_accountList[acc.id()][(f_day.addDays(-1))];//balance of the day before
|
|
MyMoneyMoney accountDailyTrend = m_accountTrendList[acc.id()][t_day]; //trend for that day
|
|
//balance of the day is the balance of the day before multiplied by the trend for the day
|
|
m_accountList[acc.id()][f_day] = balanceDayBefore;
|
|
m_accountList[acc.id()][f_day] += accountDailyTrend; //movement trend for that particular day
|
|
m_accountList[acc.id()][f_day] = m_accountList[acc.id()][f_day].convert(acc.fraction());
|
|
//m_accountList[acc.id()][f_day] += m_accountListPast[acc.id()][f_day.addDays(-historyDays())];
|
|
f_day = f_day.addDays(1);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
TQDate baseDate = TQDate::tqcurrentDate().addDays(-accountsCycle());
|
|
for(int t_day = 1; t_day <= accountsCycle(); ++t_day) {
|
|
int f_day = 1;
|
|
TQDate fDate = baseDate.addDays(accountsCycle()+1);
|
|
while (fDate <= forecastEndDate()) {
|
|
|
|
//the calculation is based on the balance for the last month, that is then multiplied by the trend
|
|
m_accountList[acc.id()][fDate] = m_accountListPast[acc.id()][baseDate] + (m_accountTrendList[acc.id()][t_day] * MyMoneyMoney(f_day,1));
|
|
m_accountList[acc.id()][fDate] = m_accountList[acc.id()][fDate].convert(acc.fraction());
|
|
++f_day;
|
|
fDate = baseDate.addDays(accountsCycle() * f_day);
|
|
}
|
|
baseDate = baseDate.addDays(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MyMoneyMoney MyMoneyForecast::forecastBalance(const MyMoneyAccount& acc, TQDate forecastDate)
|
|
{
|
|
|
|
dailyBalances balance;
|
|
MyMoneyMoney MM_amount = MyMoneyMoney(0,1);
|
|
|
|
//Check if acc is not a forecast account, return 0
|
|
if ( !isForecastAccount ( acc ) )
|
|
{
|
|
return MM_amount;
|
|
}
|
|
|
|
balance = m_accountList[acc.id() ];
|
|
if ( balance.contains ( forecastDate ) )
|
|
{ //if the date is not in the forecast, it returns 0
|
|
MM_amount = m_accountList[acc.id() ][forecastDate];
|
|
}
|
|
return MM_amount;
|
|
}
|
|
|
|
/**
|
|
* Returns the forecast balance trend for account @a acc for offset @p int
|
|
* offset is days from current date, inside forecast days.
|
|
* Returns 0 if offset not in range of forecast days.
|
|
*/
|
|
MyMoneyMoney MyMoneyForecast::forecastBalance (const MyMoneyAccount& acc, int offset )
|
|
{
|
|
TQDate forecastDate = TQDate::tqcurrentDate().addDays(offset);
|
|
return forecastBalance(acc, forecastDate);
|
|
}
|
|
|
|
void MyMoneyForecast::doFutureScheduledForecast(void)
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
if(isIncludingFutureTransactions())
|
|
addFutureTransactions();
|
|
|
|
if(isIncludingScheduledTransactions())
|
|
addScheduledTransactions();
|
|
|
|
//do not show accounts with no transactions
|
|
if(!isIncludingUnusedAccounts())
|
|
purgeForecastAccountsList(m_accountList);
|
|
|
|
//adjust value of investments to deep currency
|
|
TQMap<TQString, TQString>::ConstIterator it_n;
|
|
for ( it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n ) {
|
|
MyMoneyAccount acc = file->account ( *it_n );
|
|
|
|
if ( acc.isInvest() ) {
|
|
//get the id of the security for that account
|
|
MyMoneySecurity undersecurity = file->security ( acc.currencyId() );
|
|
|
|
//only do it if the security is not an actual currency
|
|
if ( ! undersecurity.isCurrency() )
|
|
{
|
|
//set the default value
|
|
MyMoneyMoney rate = MyMoneyMoney ( 1, 1 );
|
|
MyMoneyPrice price;
|
|
|
|
for (TQDate it_day = TQDate::tqcurrentDate(); it_day <= forecastEndDate(); ) {
|
|
//get the price for the tradingCurrency that day
|
|
price = file->price ( undersecurity.id(), undersecurity.tradingCurrency(), it_day );
|
|
if ( price.isValid() )
|
|
{
|
|
rate = price.rate ( undersecurity.tradingCurrency() );
|
|
}
|
|
//value is the amount of shares multiplied by the rate of the deep currency
|
|
m_accountList[acc.id() ][it_day] = m_accountList[acc.id() ][it_day] * rate;
|
|
it_day = it_day.addDays(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MyMoneyForecast::addFutureTransactions(void)
|
|
{
|
|
MyMoneyTransactionFilter filter;
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
// collect and process all transactions that have already been entered but
|
|
// are located in the future.
|
|
filter.setDateFilter(forecastStartDate(), forecastEndDate());
|
|
filter.setReportAllSplits(false);
|
|
|
|
TQValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
|
|
TQValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
|
|
|
|
for(; it_t != transactions.end(); ++it_t ) {
|
|
const TQValueList<MyMoneySplit>& splits = (*it_t).splits();
|
|
TQValueList<MyMoneySplit>::const_iterator it_s = splits.begin();
|
|
for(; it_s != splits.end(); ++it_s ) {
|
|
if(!(*it_s).shares().isZero()) {
|
|
MyMoneyAccount acc = file->account((*it_s).accountId());
|
|
if(isForecastAccount(acc)) {
|
|
dailyBalances balance;
|
|
balance = m_accountList[acc.id()];
|
|
//if it is income, the balance is stored as negative number
|
|
if(acc.accountType() == MyMoneyAccount::Income) {
|
|
balance[(*it_t).postDate()] += ((*it_s).shares() * MyMoneyMoney(-1, 1));
|
|
} else {
|
|
balance[(*it_t).postDate()] += (*it_s).shares();
|
|
}
|
|
m_accountList[acc.id()] = balance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
TQFile trcFile("forecast.csv");
|
|
trcFile.open(IO_WriteOnly);
|
|
TQTextStream s(&trcFile);
|
|
|
|
{
|
|
s << "Already present transactions\n";
|
|
TQMap<TQString, dailyBalances>::Iterator it_a;
|
|
TQMap<TQString, TQString>::ConstIterator it_n;
|
|
for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
|
|
MyMoneyAccount acc = file->account(*it_n);
|
|
it_a = m_accountList.find(*it_n);
|
|
s << "\"" << acc.name() << "\",";
|
|
for(int i = 0; i < 90; ++i) {
|
|
s << "\"" << (*it_a)[i].formatMoney("") << "\",";
|
|
}
|
|
s << "\n";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
void MyMoneyForecast::addScheduledTransactions (void)
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
// now process all the schedules that may have an impact
|
|
TQValueList<MyMoneySchedule> schedule;
|
|
|
|
schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY,
|
|
TQDate::tqcurrentDate(), forecastEndDate());
|
|
if(schedule.count() > 0) {
|
|
TQValueList<MyMoneySchedule>::Iterator it;
|
|
do {
|
|
qBubbleSort(schedule);
|
|
it = schedule.begin();
|
|
if(it == schedule.end())
|
|
break;
|
|
|
|
if((*it).isFinished()) {
|
|
schedule.erase(it);
|
|
continue;
|
|
}
|
|
|
|
TQDate date = (*it).nextPayment((*it).lastPayment());
|
|
if(!date.isValid()) {
|
|
schedule.remove(it);
|
|
continue;
|
|
}
|
|
|
|
TQDate nextDate = (*it).adjustedNextPayment((*it).lastPayment());
|
|
if (nextDate > forecastEndDate()) {
|
|
// We're done with this schedule, let's move on to the next
|
|
schedule.remove(it);
|
|
continue;
|
|
}
|
|
|
|
// found the next schedule. process it
|
|
|
|
MyMoneyAccount acc = (*it).account();
|
|
|
|
if(!acc.id().isEmpty()) {
|
|
try {
|
|
if(acc.accountType() != MyMoneyAccount::Investment) {
|
|
MyMoneyTransaction t = (*it).transaction();
|
|
|
|
// only process the entry, if it is still active
|
|
if(!(*it).isFinished() && nextDate != TQDate()) {
|
|
// make sure we have all 'starting balances' so that the autocalc works
|
|
TQValueList<MyMoneySplit>::const_iterator it_s;
|
|
TQMap<TQString, MyMoneyMoney> balanceMap;
|
|
|
|
for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s ) {
|
|
MyMoneyAccount acc = file->account((*it_s).accountId());
|
|
if(isForecastAccount(acc)) {
|
|
// collect all overdues on the first day
|
|
TQDate forecastDate = nextDate;
|
|
if(TQDate::tqcurrentDate() >= nextDate)
|
|
forecastDate = TQDate::tqcurrentDate().addDays(1);
|
|
|
|
dailyBalances balance;
|
|
balance = m_accountList[acc.id()];
|
|
for(TQDate f_day = TQDate::tqcurrentDate(); f_day < forecastDate; ) {
|
|
balanceMap[acc.id()] += m_accountList[acc.id()][f_day];
|
|
f_day = f_day.addDays(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// take care of the autoCalc stuff
|
|
calculateAutoLoan(*it, t, balanceMap);
|
|
|
|
// now add the splits to the balances
|
|
for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s ) {
|
|
MyMoneyAccount acc = file->account((*it_s).accountId());
|
|
if(isForecastAccount(acc)) {
|
|
dailyBalances balance;
|
|
balance = m_accountList[acc.id()];
|
|
//int offset = TQDate::tqcurrentDate().daysTo(nextDate);
|
|
//if(offset <= 0) { // collect all overdues on the first day
|
|
// offset = 1;
|
|
//}
|
|
// collect all overdues on the first day
|
|
TQDate forecastDate = nextDate;
|
|
if(TQDate::tqcurrentDate() >= nextDate)
|
|
forecastDate = TQDate::tqcurrentDate().addDays(1);
|
|
|
|
if(acc.accountType() == MyMoneyAccount::Income) {
|
|
balance[forecastDate] += ((*it_s).shares() * MyMoneyMoney(-1, 1));
|
|
} else {
|
|
balance[forecastDate] += (*it_s).shares();
|
|
}
|
|
m_accountList[acc.id()] = balance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
(*it).setLastPayment(date);
|
|
|
|
} catch(MyMoneyException* e) {
|
|
kdDebug(2) << __func__ << " Schedule " << (*it).id() << " (" << (*it).name() << "): " << e->what() << endl;
|
|
|
|
schedule.remove(it);
|
|
delete e;
|
|
}
|
|
} else {
|
|
// remove schedule from list
|
|
schedule.remove(it);
|
|
}
|
|
}
|
|
while(1);
|
|
}
|
|
|
|
#if 0
|
|
{
|
|
s << "\n\nAdded scheduled transactions\n";
|
|
TQMap<TQString, dailyBalances>::Iterator it_a;
|
|
TQMap<TQString, TQString>::ConstIterator it_n;
|
|
for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
|
|
MyMoneyAccount acc = file->account(*it_n);
|
|
it_a = m_accountList.find(*it_n);
|
|
s << "\"" << acc.name() << "\",";
|
|
for(int i = 0; i < 90; ++i) {
|
|
s << "\"" << (*it_a)[i].formatMoney("") << "\",";
|
|
}
|
|
s << "\n";
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void MyMoneyForecast::calculateScheduledDailyBalances (void)
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
//Calculate account daily balances
|
|
TQMap<TQString, TQString>::ConstIterator it_n;
|
|
for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
|
|
MyMoneyAccount acc = file->account(*it_n);
|
|
|
|
//set the starting balance of the account
|
|
setStartingBalance(acc);
|
|
|
|
for(TQDate f_day = forecastStartDate(); f_day <= forecastEndDate(); ) {
|
|
MyMoneyMoney balanceDayBefore = m_accountList[acc.id()][(f_day.addDays(-1))];//balance of the day before
|
|
m_accountList[acc.id()][f_day] += balanceDayBefore; //running sum
|
|
f_day = f_day.addDays(1);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
int MyMoneyForecast::daysToMinimumBalance(const MyMoneyAccount& acc)
|
|
{
|
|
TQString minimumBalance = acc.value("minBalanceAbsolute");
|
|
MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance);
|
|
dailyBalances balance;
|
|
|
|
//Check if acc is not a forecast account, return -1
|
|
if(!isForecastAccount(acc)) {
|
|
return -1;
|
|
}
|
|
|
|
balance = m_accountList[acc.id()];
|
|
|
|
for(TQDate it_day = TQDate::tqcurrentDate() ; it_day <= forecastEndDate(); ) {
|
|
if(minBalance > balance[it_day]) {
|
|
return TQDate::tqcurrentDate().daysTo(it_day);
|
|
}
|
|
it_day = it_day.addDays(1);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int MyMoneyForecast::daysToZeroBalance(const MyMoneyAccount& acc)
|
|
{
|
|
dailyBalances balance;
|
|
|
|
//Check if acc is not a forecast account, return -1
|
|
if(!isForecastAccount(acc)) {
|
|
return -2;
|
|
}
|
|
|
|
balance = m_accountList[acc.id()];
|
|
|
|
if (acc.accountGroup() == MyMoneyAccount::Asset) {
|
|
for (TQDate it_day = TQDate::tqcurrentDate() ; it_day <= forecastEndDate(); )
|
|
{
|
|
if ( balance[it_day] < MyMoneyMoney ( 0, 1 ) )
|
|
{
|
|
return TQDate::tqcurrentDate().daysTo(it_day);
|
|
}
|
|
it_day = it_day.addDays(1);
|
|
}
|
|
} else if (acc.accountGroup() == MyMoneyAccount::Liability) {
|
|
for (TQDate it_day = TQDate::tqcurrentDate() ; it_day <= forecastEndDate(); )
|
|
{
|
|
if ( balance[it_day] > MyMoneyMoney ( 0, 1 ) )
|
|
{
|
|
return TQDate::tqcurrentDate().daysTo(it_day);
|
|
}
|
|
it_day = it_day.addDays(1);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void MyMoneyForecast::setForecastAccountList(void)
|
|
{
|
|
|
|
//get forecast accounts
|
|
TQValueList<MyMoneyAccount> accList;
|
|
accList = forecastAccountList();
|
|
|
|
TQValueList<MyMoneyAccount>::const_iterator accList_t = accList.begin();
|
|
for(; accList_t != accList.end(); ++accList_t ) {
|
|
MyMoneyAccount acc = *accList_t;
|
|
// check if this is a new account for us
|
|
if(m_nameIdx[acc.id()] != acc.id()) {
|
|
m_nameIdx[acc.id()] = acc.id();
|
|
}
|
|
}
|
|
}
|
|
|
|
MyMoneyMoney MyMoneyForecast::accountCycleVariation(const MyMoneyAccount& acc)
|
|
{
|
|
MyMoneyMoney cycleVariation;
|
|
|
|
if (forecastMethod() == eHistoric) {
|
|
for(int t_day = 1; t_day <= accountsCycle() ; ++t_day) {
|
|
cycleVariation += m_accountTrendList[acc.id()][t_day];
|
|
}
|
|
}
|
|
return cycleVariation;
|
|
}
|
|
|
|
MyMoneyMoney MyMoneyForecast::accountTotalVariation(const MyMoneyAccount& acc)
|
|
{
|
|
MyMoneyMoney totalVariation;
|
|
|
|
totalVariation = forecastBalance(acc, forecastEndDate()) - forecastBalance(acc, TQDate::tqcurrentDate());
|
|
return totalVariation;
|
|
}
|
|
|
|
TQValueList<TQDate> MyMoneyForecast::accountMinimumBalanceDateList(const MyMoneyAccount& acc)
|
|
{
|
|
TQValueList<TQDate> minBalanceList;
|
|
int daysToBeginDay;
|
|
|
|
daysToBeginDay = TQDate::tqcurrentDate().daysTo(beginForecastDate());
|
|
|
|
for(int t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
|
|
MyMoneyMoney minBalance = forecastBalance(acc, (t_cycle * accountsCycle() + daysToBeginDay));
|
|
TQDate minDate = TQDate::tqcurrentDate().addDays(t_cycle * accountsCycle() + daysToBeginDay);
|
|
for(int t_day = 1; t_day <= accountsCycle() ; ++t_day) {
|
|
if( minBalance > forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day) ) {
|
|
minBalance = forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day );
|
|
minDate = TQDate::tqcurrentDate().addDays( (t_cycle * accountsCycle()) + daysToBeginDay + t_day);
|
|
}
|
|
}
|
|
minBalanceList.append(minDate);
|
|
}
|
|
return minBalanceList;
|
|
}
|
|
|
|
TQValueList<TQDate> MyMoneyForecast::accountMaximumBalanceDateList(const MyMoneyAccount& acc)
|
|
{
|
|
TQValueList<TQDate> maxBalanceList;
|
|
int daysToBeginDay;
|
|
|
|
daysToBeginDay = TQDate::tqcurrentDate().daysTo(beginForecastDate());
|
|
|
|
for(int t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
|
|
MyMoneyMoney maxBalance = forecastBalance(acc, ((t_cycle * accountsCycle()) + daysToBeginDay));
|
|
TQDate maxDate = TQDate::tqcurrentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay);
|
|
|
|
for(int t_day = 0; t_day < accountsCycle() ; ++t_day) {
|
|
if( maxBalance < forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day) ) {
|
|
maxBalance = forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day );
|
|
maxDate = TQDate::tqcurrentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay + t_day);
|
|
}
|
|
}
|
|
maxBalanceList.append(maxDate);
|
|
}
|
|
return maxBalanceList;
|
|
}
|
|
|
|
MyMoneyMoney MyMoneyForecast::accountAverageBalance(const MyMoneyAccount& acc)
|
|
{
|
|
MyMoneyMoney totalBalance;
|
|
for(int f_day = 1; f_day <= forecastDays() ; ++f_day) {
|
|
totalBalance += forecastBalance(acc, f_day);
|
|
}
|
|
return totalBalance / MyMoneyMoney( forecastDays(), 1);
|
|
}
|
|
|
|
int MyMoneyForecast::calculateBeginForecastDay()
|
|
{
|
|
int fDays = forecastDays();
|
|
int beginDay = beginForecastDay();
|
|
int accCycle = accountsCycle();
|
|
TQDate beginDate;
|
|
|
|
//if 0, beginDate is current date and forecastDays remains unchanged
|
|
if(beginDay == 0) {
|
|
setBeginForecastDate(TQDate::tqcurrentDate());
|
|
return fDays;
|
|
}
|
|
|
|
//adjust if beginDay more than days of current month
|
|
if(TQDate::tqcurrentDate().daysInMonth() < beginDay)
|
|
beginDay = TQDate::tqcurrentDate().daysInMonth();
|
|
|
|
//if beginDay still to come, calculate and return
|
|
if(TQDate::tqcurrentDate().day() <= beginDay) {
|
|
beginDate = TQDate( TQDate::tqcurrentDate().year(), TQDate::tqcurrentDate().month(), beginDay);
|
|
fDays += TQDate::tqcurrentDate().daysTo(beginDate);
|
|
setBeginForecastDate(beginDate);
|
|
return fDays;
|
|
}
|
|
|
|
//adjust beginDay for next month
|
|
if(TQDate::tqcurrentDate().addMonths(1).daysInMonth() < beginDay)
|
|
beginDay = TQDate::tqcurrentDate().addMonths(1).daysInMonth();
|
|
|
|
//if beginDay of next month comes before 1 interval, use beginDay next month
|
|
if(TQDate::tqcurrentDate().addDays(accCycle) >=
|
|
(TQDate(TQDate::tqcurrentDate().addMonths(1).year(), TQDate::tqcurrentDate().addMonths(1).month(), 1).addDays(beginDay-1) ) )
|
|
{
|
|
beginDate = TQDate(TQDate::tqcurrentDate().addMonths(1).year(), TQDate::tqcurrentDate().addMonths(1).month(), 1).addDays(beginDay-1);
|
|
fDays += TQDate::tqcurrentDate().daysTo(beginDate);
|
|
}
|
|
else //add intervals to current beginDay and take the first after current date
|
|
{
|
|
beginDay = ((((TQDate::tqcurrentDate().day()-beginDay)/accCycle) + 1) * accCycle) + beginDay;
|
|
beginDate = TQDate::tqcurrentDate().addDays(beginDay - TQDate::tqcurrentDate().day());
|
|
fDays += TQDate::tqcurrentDate().daysTo(beginDate);
|
|
}
|
|
|
|
setBeginForecastDate(beginDate);
|
|
return fDays;
|
|
}
|
|
|
|
void MyMoneyForecast::purgeForecastAccountsList(TQMap<TQString, dailyBalances>& accountList)
|
|
{
|
|
TQMap<TQString, TQString>::Iterator it_n;
|
|
for ( it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ) {
|
|
if(!accountList.contains(*it_n)) {
|
|
m_nameIdx.remove(it_n);
|
|
it_n = m_nameIdx.begin();
|
|
} else
|
|
++it_n;
|
|
}
|
|
}
|
|
|
|
void MyMoneyForecast::createBudget ( MyMoneyBudget& budget, TQDate historyStart, TQDate historyEnd, TQDate budgetStart, TQDate budgetEnd, const bool returnBudget )
|
|
{
|
|
// clear all data except the id and name
|
|
TQString name = budget.name();
|
|
budget = MyMoneyBudget(budget.id(), MyMoneyBudget());
|
|
budget.setName(name);
|
|
|
|
//check parameters
|
|
if ( historyStart > historyEnd ||
|
|
budgetStart > budgetEnd ||
|
|
budgetStart <= historyEnd )
|
|
{
|
|
throw new MYMONEYEXCEPTION ( "Illegal parameters when trying to create budget" );
|
|
}
|
|
|
|
//get forecast method
|
|
int fMethod = forecastMethod();
|
|
|
|
//set start date to 1st of month and end dates to last day of month, since we deal with full months in budget
|
|
historyStart = TQDate ( historyStart.year(), historyStart.month(), 1 );
|
|
historyEnd = TQDate ( historyEnd.year(), historyEnd.month(), historyEnd.daysInMonth() );
|
|
budgetStart = TQDate ( budgetStart.year(), budgetStart.month(), 1 );
|
|
budgetEnd = TQDate ( budgetEnd.year(), budgetEnd.month(), budgetEnd.daysInMonth() );
|
|
|
|
//set forecast parameters
|
|
setHistoryStartDate ( historyStart );
|
|
setHistoryEndDate ( historyEnd );
|
|
setForecastStartDate ( budgetStart );
|
|
setForecastEndDate ( budgetEnd );
|
|
setForecastDays ( budgetStart.daysTo ( budgetEnd ) + 1 );
|
|
if ( budgetStart.daysTo ( budgetEnd ) > historyStart.daysTo ( historyEnd ) ) { //if history period is shorter than budget, use that one as the trend length
|
|
setAccountsCycle ( historyStart.daysTo ( historyEnd ) ); //we set the accountsCycle to the base timeframe we will use to calculate the average (eg. 180 days, 365, etc)
|
|
} else { //if one timeframe is larger than the other, but not enough to be 1 time larger, we take the lowest value
|
|
setAccountsCycle ( budgetStart.daysTo ( budgetEnd ) );
|
|
}
|
|
setForecastCycles ( ( historyStart.daysTo ( historyEnd ) / accountsCycle() ) );
|
|
if ( forecastCycles() == 0 ) //the cycles must be at least 1
|
|
setForecastCycles ( 1 );
|
|
|
|
//do not skip opening date
|
|
setSkipOpeningDate ( false );
|
|
|
|
//clear and set accounts list we are going to use. Categories, in this case
|
|
m_nameIdx.clear();
|
|
setBudgetAccountList();
|
|
|
|
//calculate budget according to forecast method
|
|
switch(fMethod)
|
|
{
|
|
case eScheduled:
|
|
doFutureScheduledForecast();
|
|
calculateScheduledMonthlyBalances();
|
|
break;
|
|
case eHistoric:
|
|
pastTransactions(); //get all transactions for history period
|
|
calculateAccountTrendList();
|
|
calculateHistoricMonthlyBalances(); //add all balances of each month and put at the 1st day of each month
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//flag the forecast as done
|
|
m_forecastDone = true;
|
|
|
|
//only fill the budget if it is going to be used
|
|
if ( returnBudget ) {
|
|
//setup the budget itself
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
budget.setBudgetStart ( budgetStart );
|
|
|
|
//go through all the accounts and add them to budget
|
|
TQMap<TQString, TQString>::ConstIterator it_nc;
|
|
for ( it_nc = m_nameIdx.begin(); it_nc != m_nameIdx.end(); ++it_nc ) {
|
|
MyMoneyAccount acc = file->account ( *it_nc );
|
|
|
|
MyMoneyBudget::AccountGroup budgetAcc;
|
|
budgetAcc.setId ( acc.id() );
|
|
budgetAcc.setBudgetLevel ( MyMoneyBudget::AccountGroup::eMonthByMonth );
|
|
|
|
for ( TQDate f_date = forecastStartDate(); f_date <= forecastEndDate(); ) {
|
|
MyMoneyBudget::PeriodGroup period;
|
|
|
|
//add period to budget account
|
|
period.setStartDate ( f_date );
|
|
period.setAmount ( forecastBalance ( acc, f_date ) );
|
|
budgetAcc.addPeriod ( f_date, period );
|
|
|
|
//next month
|
|
f_date = f_date.addMonths ( 1 );
|
|
}
|
|
//add budget account to budget
|
|
budget.setAccount ( budgetAcc, acc.id() );
|
|
}
|
|
}
|
|
}
|
|
|
|
void MyMoneyForecast::setBudgetAccountList(void)
|
|
{
|
|
//get budget accounts
|
|
TQValueList<MyMoneyAccount> accList;
|
|
accList = budgetAccountList();
|
|
|
|
TQValueList<MyMoneyAccount>::const_iterator accList_t = accList.begin();
|
|
for(; accList_t != accList.end(); ++accList_t ) {
|
|
MyMoneyAccount acc = *accList_t;
|
|
// check if this is a new account for us
|
|
if(m_nameIdx[acc.id()] != acc.id()) {
|
|
m_nameIdx[acc.id()] = acc.id();
|
|
}
|
|
}
|
|
}
|
|
|
|
TQValueList<MyMoneyAccount> MyMoneyForecast::budgetAccountList(void)
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
TQValueList<MyMoneyAccount> accList;
|
|
TQStringList emptyStringList;
|
|
//Get all accounts from the file and check if they are of the right type to calculate forecast
|
|
file->accountList(accList, emptyStringList, false);
|
|
TQValueList<MyMoneyAccount>::iterator accList_t = accList.begin();
|
|
for(; accList_t != accList.end(); ) {
|
|
MyMoneyAccount acc = *accList_t;
|
|
if(acc.isClosed() //check the account is not closed
|
|
|| (!acc.isIncomeExpense()) ) {
|
|
accList.remove(accList_t); //remove the account if it is not of the correct type
|
|
accList_t = accList.begin();
|
|
} else {
|
|
++accList_t;
|
|
}
|
|
}
|
|
return accList;
|
|
}
|
|
|
|
void MyMoneyForecast::calculateHistoricMonthlyBalances()
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
//Calculate account monthly balances
|
|
TQMap<TQString, TQString>::ConstIterator it_n;
|
|
for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
|
|
MyMoneyAccount acc = file->account(*it_n);
|
|
|
|
for( TQDate f_date = forecastStartDate(); f_date <= forecastEndDate(); ) {
|
|
for(int f_day = 1; f_day <= accountsCycle() && f_date <= forecastEndDate(); ++f_day) {
|
|
MyMoneyMoney accountDailyTrend = m_accountTrendList[acc.id()][f_day]; //trend for that day
|
|
//check for leap year
|
|
if(f_date.month() == 2 && f_date.day() == 29)
|
|
f_date = f_date.addDays(1); //skip 1 day
|
|
m_accountList[acc.id()][TQDate(f_date.year(), f_date.month(), 1)] += accountDailyTrend; //movement trend for that particular day
|
|
f_date = f_date.addDays(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MyMoneyForecast::calculateScheduledMonthlyBalances()
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
//Calculate account monthly balances
|
|
TQMap<TQString, TQString>::ConstIterator it_n;
|
|
for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
|
|
MyMoneyAccount acc = file->account(*it_n);
|
|
|
|
for( TQDate f_date = forecastStartDate(); f_date <= forecastEndDate(); f_date = f_date.addDays(1) ) {
|
|
//get the trend for the day
|
|
MyMoneyMoney accountDailyBalance = m_accountList[acc.id()][f_date];
|
|
|
|
//do not add if it is the beginning of the month
|
|
//otherwise we end up with duplicated values as reported by Marko Käning
|
|
if(f_date != TQDate(f_date.year(), f_date.month(), 1) )
|
|
m_accountList[acc.id()][TQDate(f_date.year(), f_date.month(), 1)] += accountDailyBalance;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MyMoneyForecast::setStartingBalance(const MyMoneyAccount &acc)
|
|
{
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
//Get current account balance
|
|
if ( acc.isInvest() ) { //investments require special treatment
|
|
//get the security id of that account
|
|
MyMoneySecurity undersecurity = file->security ( acc.currencyId() );
|
|
|
|
//only do it if the security is not an actual currency
|
|
if ( ! undersecurity.isCurrency() )
|
|
{
|
|
//set the default value
|
|
MyMoneyMoney rate = MyMoneyMoney ( 1, 1 );
|
|
//get te
|
|
MyMoneyPrice price = file->price ( undersecurity.id(), undersecurity.tradingCurrency(), TQDate::tqcurrentDate() );
|
|
if ( price.isValid() )
|
|
{
|
|
rate = price.rate ( undersecurity.tradingCurrency() );
|
|
}
|
|
m_accountList[acc.id()][TQDate::tqcurrentDate()] = file->balance(acc.id(), TQDate::tqcurrentDate()) * rate;
|
|
}
|
|
} else {
|
|
m_accountList[acc.id()][TQDate::tqcurrentDate()] = file->balance(acc.id(), TQDate::tqcurrentDate());
|
|
}
|
|
|
|
//if the method is linear regression, we have to add the opening balance to m_accountListPast
|
|
if(forecastMethod() == eHistoric && historyMethod() == 2) {
|
|
//FIXME workaround for stock opening dates
|
|
TQDate openingDate;
|
|
if(acc.accountType() == MyMoneyAccount::Stock) {
|
|
MyMoneyAccount parentAccount = file->account(acc.parentAccountId());
|
|
openingDate = parentAccount.openingDate();
|
|
} else {
|
|
openingDate = acc.openingDate();
|
|
}
|
|
|
|
//add opening balance only if it opened after the history start
|
|
if(openingDate >= historyStartDate()) {
|
|
|
|
MyMoneyMoney openingBalance;
|
|
|
|
openingBalance = file->balance(acc.id(), openingDate);
|
|
|
|
//calculate running sum
|
|
for(TQDate it_date = openingDate; it_date <= historyEndDate(); it_date = it_date.addDays(1) ) {
|
|
//investments require special treatment
|
|
if ( acc.isInvest() ) {
|
|
//get the security id of that account
|
|
MyMoneySecurity undersecurity = file->security ( acc.currencyId() );
|
|
|
|
//only do it if the security is not an actual currency
|
|
if ( ! undersecurity.isCurrency() )
|
|
{
|
|
//set the default value
|
|
MyMoneyMoney rate = MyMoneyMoney ( 1, 1 );
|
|
|
|
//get the rate for that specific date
|
|
MyMoneyPrice price = file->price ( undersecurity.id(), undersecurity.tradingCurrency(), it_date );
|
|
if ( price.isValid() )
|
|
{
|
|
rate = price.rate ( undersecurity.tradingCurrency() );
|
|
}
|
|
m_accountListPast[acc.id()][it_date] += openingBalance * rate;
|
|
}
|
|
} else {
|
|
m_accountListPast[acc.id()][it_date] += openingBalance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MyMoneyForecast::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const TQMap<TQString, MyMoneyMoney>& balances)
|
|
{
|
|
if (schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) {
|
|
|
|
//get amortization and interest autoCalc splits
|
|
MyMoneySplit amortizationSplit = transaction.amortizationSplit();
|
|
MyMoneySplit interestSplit = transaction.interestSplit();
|
|
|
|
if(!amortizationSplit.id().isEmpty() && !interestSplit.id().isEmpty()) {
|
|
MyMoneyAccountLoan acc(MyMoneyFile::instance()->account(amortizationSplit.accountId()));
|
|
MyMoneyFinancialCalculator calc;
|
|
TQDate dueDate;
|
|
|
|
// FIXME: setup dueDate according to when the interest should be calculated
|
|
// current implementation: take the date of the next payment according to
|
|
// the schedule. If the calculation is based on the payment reception, and
|
|
// the payment is overdue then take the current date
|
|
dueDate = schedule.nextDueDate();
|
|
if(acc.interestCalculation() == MyMoneyAccountLoan::paymentReceived) {
|
|
if(dueDate < TQDate::tqcurrentDate())
|
|
dueDate = TQDate::tqcurrentDate();
|
|
}
|
|
|
|
// we need to calculate the balance at the time the payment is due
|
|
|
|
MyMoneyMoney balance;
|
|
if(balances.count() == 0)
|
|
balance = MyMoneyFile::instance()->balance(acc.id(), dueDate.addDays(-1));
|
|
else
|
|
balance = balances[acc.id()];
|
|
|
|
/*
|
|
TQValueList<MyMoneyTransaction> list;
|
|
TQValueList<MyMoneyTransaction>::ConstIterator it;
|
|
MyMoneySplit split;
|
|
MyMoneyTransactionFilter filter(acc.id());
|
|
|
|
filter.setDateFilter(TQDate(), dueDate.addDays(-1));
|
|
list = MyMoneyFile::instance()->transactionList(filter);
|
|
|
|
for(it = list.begin(); it != list.end(); ++it) {
|
|
try {
|
|
split = (*it).splitByAccount(acc.id());
|
|
balance += split.value();
|
|
|
|
} catch(MyMoneyException *e) {
|
|
// account is not referenced within this transaction
|
|
delete e;
|
|
}
|
|
}
|
|
*/
|
|
|
|
// FIXME: for now, we only support interest calculation at the end of the period
|
|
calc.setBep();
|
|
// FIXME: for now, we only support periodic compounding
|
|
calc.setDisc();
|
|
|
|
calc.setPF(MyMoneySchedule::eventsPerYear(schedule.occurence()));
|
|
MyMoneySchedule::occurenceE compoundingOccurence = static_cast<MyMoneySchedule::occurenceE>(acc.interestCompounding());
|
|
if(compoundingOccurence == MyMoneySchedule::OCCUR_ANY)
|
|
compoundingOccurence = schedule.occurence();
|
|
|
|
calc.setCF(MyMoneySchedule::eventsPerYear(compoundingOccurence));
|
|
|
|
calc.setPv(balance.toDouble());
|
|
calc.setIr(static_cast<FCALC_DOUBLE> (acc.interestRate(dueDate).abs().toDouble()));
|
|
calc.setPmt(acc.periodicPayment().toDouble());
|
|
|
|
MyMoneyMoney interest(calc.interestDue()), amortization;
|
|
interest = interest.abs(); // make sure it's positive for now
|
|
amortization = acc.periodicPayment() - interest;
|
|
|
|
if(acc.accountType() == MyMoneyAccount::AssetLoan) {
|
|
interest = -interest;
|
|
amortization = -amortization;
|
|
}
|
|
|
|
amortizationSplit.setShares(amortization);
|
|
interestSplit.setShares(interest);
|
|
|
|
// FIXME: for now we only assume loans to be in the currency of the transaction
|
|
amortizationSplit.setValue(amortization);
|
|
interestSplit.setValue(interest);
|
|
|
|
transaction.modifySplit(amortizationSplit);
|
|
transaction.modifySplit(interestSplit);
|
|
}
|
|
}
|
|
}
|
|
|