summaryrefslogtreecommitdiffstats
path: root/kmymoney2/mymoney/mymoneyforecast.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2011-07-04 22:38:03 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2011-07-04 22:38:03 +0000
commitdadc34655c3ab961b0b0b94a10eaaba710f0b5e8 (patch)
tree99e72842fe687baea16376a147619b6048d7e441 /kmymoney2/mymoney/mymoneyforecast.cpp
downloadkmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.tar.gz
kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.zip
Added kmymoney
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kmymoney@1239792 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kmymoney2/mymoney/mymoneyforecast.cpp')
-rw-r--r--kmymoney2/mymoney/mymoneyforecast.cpp1340
1 files changed, 1340 insertions, 0 deletions
diff --git a/kmymoney2/mymoney/mymoneyforecast.cpp b/kmymoney2/mymoney/mymoneyforecast.cpp
new file mode 100644
index 0000000..56125ea
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyforecast.cpp
@@ -0,0 +1,1340 @@
+/***************************************************************************
+ 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 <qstring.h>
+#include <qdatetime.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(QDate::currentDate().addDays(-forecastCycles()*accountsCycle()));
+ setHistoryEndDate(QDate::currentDate().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(QDate::currentDate().addDays(1));
+ setForecastEndDate(QDate::currentDate().addDays(fDays));
+ setAccountsCycle(fAccCycle);
+ setForecastCycles(fCycles);
+ setHistoryStartDate(forecastCycles() * accountsCycle());
+ setHistoryEndDate(QDate::currentDate().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);
+
+ QValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+
+ //Check past transactions
+ for(; it_t != transactions.end(); ++it_t ) {
+ const QValueList<MyMoneySplit>& splits = (*it_t).splits();
+ QValueList<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
+ QDate 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
+ QMap<QString, QString>::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(QDate 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 ( QDate 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();
+ }
+ QMap<QString, QString>::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
+ QMap<QString, QString>::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()) {
+
+ QDate 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;
+ }
+ }
+}
+
+QValueList<MyMoneyAccount> MyMoneyForecast::forecastAccountList(void)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ QValueList<MyMoneyAccount> accList;
+ //Get all accounts from the file and check if they are of the right type to calculate forecast
+ file->accountList(accList);
+ QValueList<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;
+}
+
+QValueList<MyMoneyAccount> MyMoneyForecast::accountList(void)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ QValueList<MyMoneyAccount> accList;
+ QStringList emptyStringList;
+ //Get all accounts from the file and check if they are present
+ file->accountList(accList, emptyStringList, false);
+ QValueList<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;
+ QDate startDate;
+ QDate 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(QDate::currentDate())<trendDays){
+ startDate = (acc.openingDate()).addDays(1);
+ }
+ else {
+ startDate = QDate::currentDate().addDays(-trendDays);
+ }
+ //get all transactions for the period
+ filter.setDateFilter(startDate, QDate::currentDate());
+ if(acc.accountGroup() == MyMoneyAccount::Income
+ || acc.accountGroup() == MyMoneyAccount::Expense) {
+ filter.addCategory(acc.id());
+ } else {
+ filter.addAccount(acc.id());
+ }
+
+ filter.setReportAllSplits(false);
+ QValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+
+ //add all transactions for that account
+ for(; it_t != transactions.end(); ++it_t ) {
+ const QValueList<MyMoneySplit>& splits = (*it_t).splits();
+ QValueList<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(QDate::currentDate())<trendDays) {
+ accTrend = netIncome/MyMoneyMoney(openingDate.daysTo(QDate::currentDate())-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
+ QMap<QString, QString>::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(QDate 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:
+ {
+ QDate baseDate = QDate::currentDate().addDays(-accountsCycle());
+ for(int t_day = 1; t_day <= accountsCycle(); ++t_day) {
+ int f_day = 1;
+ QDate 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, QDate 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 )
+{
+ QDate forecastDate = QDate::currentDate().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
+ QMap<QString, QString>::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 (QDate it_day = QDate::currentDate(); 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);
+
+ QValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+
+ for(; it_t != transactions.end(); ++it_t ) {
+ const QValueList<MyMoneySplit>& splits = (*it_t).splits();
+ QValueList<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
+ QFile trcFile("forecast.csv");
+ trcFile.open(IO_WriteOnly);
+ QTextStream s(&trcFile);
+
+ {
+ s << "Already present transactions\n";
+ QMap<QString, dailyBalances>::Iterator it_a;
+ QMap<QString, QString>::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
+ QValueList<MyMoneySchedule> schedule;
+
+ schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY,
+ QDate::currentDate(), forecastEndDate());
+ if(schedule.count() > 0) {
+ QValueList<MyMoneySchedule>::Iterator it;
+ do {
+ qBubbleSort(schedule);
+ it = schedule.begin();
+ if(it == schedule.end())
+ break;
+
+ if((*it).isFinished()) {
+ schedule.erase(it);
+ continue;
+ }
+
+ QDate date = (*it).nextPayment((*it).lastPayment());
+ if(!date.isValid()) {
+ schedule.remove(it);
+ continue;
+ }
+
+ QDate 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 != QDate()) {
+ // make sure we have all 'starting balances' so that the autocalc works
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ QMap<QString, 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
+ QDate forecastDate = nextDate;
+ if(QDate::currentDate() >= nextDate)
+ forecastDate = QDate::currentDate().addDays(1);
+
+ dailyBalances balance;
+ balance = m_accountList[acc.id()];
+ for(QDate f_day = QDate::currentDate(); 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 = QDate::currentDate().daysTo(nextDate);
+ //if(offset <= 0) { // collect all overdues on the first day
+ // offset = 1;
+ //}
+ // collect all overdues on the first day
+ QDate forecastDate = nextDate;
+ if(QDate::currentDate() >= nextDate)
+ forecastDate = QDate::currentDate().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";
+ QMap<QString, dailyBalances>::Iterator it_a;
+ QMap<QString, QString>::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
+ QMap<QString, QString>::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(QDate 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)
+{
+ QString 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(QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate(); ) {
+ if(minBalance > balance[it_day]) {
+ return QDate::currentDate().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 (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate(); )
+ {
+ if ( balance[it_day] < MyMoneyMoney ( 0, 1 ) )
+ {
+ return QDate::currentDate().daysTo(it_day);
+ }
+ it_day = it_day.addDays(1);
+ }
+ } else if (acc.accountGroup() == MyMoneyAccount::Liability) {
+ for (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate(); )
+ {
+ if ( balance[it_day] > MyMoneyMoney ( 0, 1 ) )
+ {
+ return QDate::currentDate().daysTo(it_day);
+ }
+ it_day = it_day.addDays(1);
+ }
+ }
+ return -1;
+}
+
+void MyMoneyForecast::setForecastAccountList(void)
+{
+
+ //get forecast accounts
+ QValueList<MyMoneyAccount> accList;
+ accList = forecastAccountList();
+
+ QValueList<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, QDate::currentDate());
+ return totalVariation;
+}
+
+QValueList<QDate> MyMoneyForecast::accountMinimumBalanceDateList(const MyMoneyAccount& acc)
+{
+ QValueList<QDate> minBalanceList;
+ int daysToBeginDay;
+
+ daysToBeginDay = QDate::currentDate().daysTo(beginForecastDate());
+
+ for(int t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
+ MyMoneyMoney minBalance = forecastBalance(acc, (t_cycle * accountsCycle() + daysToBeginDay));
+ QDate minDate = QDate::currentDate().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 = QDate::currentDate().addDays( (t_cycle * accountsCycle()) + daysToBeginDay + t_day);
+ }
+ }
+ minBalanceList.append(minDate);
+ }
+ return minBalanceList;
+}
+
+QValueList<QDate> MyMoneyForecast::accountMaximumBalanceDateList(const MyMoneyAccount& acc)
+{
+ QValueList<QDate> maxBalanceList;
+ int daysToBeginDay;
+
+ daysToBeginDay = QDate::currentDate().daysTo(beginForecastDate());
+
+ for(int t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
+ MyMoneyMoney maxBalance = forecastBalance(acc, ((t_cycle * accountsCycle()) + daysToBeginDay));
+ QDate maxDate = QDate::currentDate().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 = QDate::currentDate().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();
+ QDate beginDate;
+
+ //if 0, beginDate is current date and forecastDays remains unchanged
+ if(beginDay == 0) {
+ setBeginForecastDate(QDate::currentDate());
+ return fDays;
+ }
+
+ //adjust if beginDay more than days of current month
+ if(QDate::currentDate().daysInMonth() < beginDay)
+ beginDay = QDate::currentDate().daysInMonth();
+
+ //if beginDay still to come, calculate and return
+ if(QDate::currentDate().day() <= beginDay) {
+ beginDate = QDate( QDate::currentDate().year(), QDate::currentDate().month(), beginDay);
+ fDays += QDate::currentDate().daysTo(beginDate);
+ setBeginForecastDate(beginDate);
+ return fDays;
+ }
+
+ //adjust beginDay for next month
+ if(QDate::currentDate().addMonths(1).daysInMonth() < beginDay)
+ beginDay = QDate::currentDate().addMonths(1).daysInMonth();
+
+ //if beginDay of next month comes before 1 interval, use beginDay next month
+ if(QDate::currentDate().addDays(accCycle) >=
+ (QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1).addDays(beginDay-1) ) )
+ {
+ beginDate = QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1).addDays(beginDay-1);
+ fDays += QDate::currentDate().daysTo(beginDate);
+ }
+ else //add intervals to current beginDay and take the first after current date
+ {
+ beginDay = ((((QDate::currentDate().day()-beginDay)/accCycle) + 1) * accCycle) + beginDay;
+ beginDate = QDate::currentDate().addDays(beginDay - QDate::currentDate().day());
+ fDays += QDate::currentDate().daysTo(beginDate);
+ }
+
+ setBeginForecastDate(beginDate);
+ return fDays;
+}
+
+void MyMoneyForecast::purgeForecastAccountsList(QMap<QString, dailyBalances>& accountList)
+{
+ QMap<QString, QString>::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, QDate historyStart, QDate historyEnd, QDate budgetStart, QDate budgetEnd, const bool returnBudget )
+{
+ // clear all data except the id and name
+ QString 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 = QDate ( historyStart.year(), historyStart.month(), 1 );
+ historyEnd = QDate ( historyEnd.year(), historyEnd.month(), historyEnd.daysInMonth() );
+ budgetStart = QDate ( budgetStart.year(), budgetStart.month(), 1 );
+ budgetEnd = QDate ( 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
+ QMap<QString, QString>::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 ( QDate 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
+ QValueList<MyMoneyAccount> accList;
+ accList = budgetAccountList();
+
+ QValueList<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();
+ }
+ }
+}
+
+QValueList<MyMoneyAccount> MyMoneyForecast::budgetAccountList(void)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ QValueList<MyMoneyAccount> accList;
+ QStringList emptyStringList;
+ //Get all accounts from the file and check if they are of the right type to calculate forecast
+ file->accountList(accList, emptyStringList, false);
+ QValueList<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
+ QMap<QString, QString>::ConstIterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+
+ for( QDate 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()][QDate(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
+ QMap<QString, QString>::ConstIterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+
+ for( QDate 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 != QDate(f_date.year(), f_date.month(), 1) )
+ m_accountList[acc.id()][QDate(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(), QDate::currentDate() );
+ if ( price.isValid() )
+ {
+ rate = price.rate ( undersecurity.tradingCurrency() );
+ }
+ m_accountList[acc.id()][QDate::currentDate()] = file->balance(acc.id(), QDate::currentDate()) * rate;
+ }
+ } else {
+ m_accountList[acc.id()][QDate::currentDate()] = file->balance(acc.id(), QDate::currentDate());
+ }
+
+ //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
+ QDate 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(QDate 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 QMap<QString, 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;
+ QDate 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 < QDate::currentDate())
+ dueDate = QDate::currentDate();
+ }
+
+ // 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()];
+
+ /*
+ QValueList<MyMoneyTransaction> list;
+ QValueList<MyMoneyTransaction>::ConstIterator it;
+ MyMoneySplit split;
+ MyMoneyTransactionFilter filter(acc.id());
+
+ filter.setDateFilter(QDate(), 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);
+ }
+ }
+}
+