File indexing completed on 2024-05-12 05:06:40

0001 /*
0002     SPDX-FileCopyrightText: 2007-2010 Alvaro Soliverez <asoliverez@gmail.com>
0003     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "mymoneyforecast.h"
0008 
0009 // ----------------------------------------------------------------------------
0010 // QT Includes
0011 
0012 #include <QString>
0013 #include <QList>
0014 #include <QDebug>
0015 #include <QDate>
0016 
0017 // ----------------------------------------------------------------------------
0018 // KDE Includes
0019 
0020 // ----------------------------------------------------------------------------
0021 // Project Includes
0022 
0023 #include "mymoneyfile.h"
0024 #include "mymoneyaccount.h"
0025 #include "mymoneyaccountloan.h"
0026 #include "mymoneysecurity.h"
0027 #include "mymoneybudget.h"
0028 #include "mymoneyschedule.h"
0029 #include "mymoneyprice.h"
0030 #include "mymoneymoney.h"
0031 #include "mymoneysplit.h"
0032 #include "mymoneytransaction.h"
0033 #include "mymoneytransactionfilter.h"
0034 #include "mymoneyfinancialcalculator.h"
0035 #include "mymoneyexception.h"
0036 #include "mymoneyenums.h"
0037 
0038 enum class eForecastMethod {Scheduled = 0, Historic = 1, };
0039 
0040 /**
0041  * daily balances of an account
0042  */
0043 typedef QMap<QDate, MyMoneyMoney> dailyBalances;
0044 
0045 /**
0046  * map of trends of an account
0047  */
0048 typedef QMap<int, MyMoneyMoney> trendBalances;
0049 
0050 class MyMoneyForecastPrivate
0051 {
0052     Q_DECLARE_PUBLIC(MyMoneyForecast)
0053 
0054 public:
0055     explicit MyMoneyForecastPrivate(MyMoneyForecast *qq) :
0056         q_ptr(qq),
0057         m_accountsCycle(30),
0058         m_forecastCycles(3),
0059         m_forecastDays(90),
0060         m_beginForecastDay(0),
0061         m_forecastMethod(eForecastMethod::Scheduled),
0062         m_historyMethod(1),
0063         m_skipOpeningDate(true),
0064         m_includeUnusedAccounts(false),
0065         m_forecastDone(false),
0066         m_includeFutureTransactions(true),
0067         m_includeScheduledTransactions(true)
0068     {
0069     }
0070 
0071     eForecastMethod forecastMethod() const
0072     {
0073         return m_forecastMethod;
0074     }
0075 
0076     /**
0077      * Returns the list of accounts to create a budget. Only Income and Expenses are returned.
0078      */
0079     QList<MyMoneyAccount> budgetAccountList()
0080     {
0081         auto file = MyMoneyFile::instance();
0082 
0083         QList<MyMoneyAccount> accList;
0084         QStringList emptyStringList;
0085         //Get all accounts from the file and check if they are of the right type to calculate forecast
0086         file->accountList(accList, emptyStringList, false);
0087         QList<MyMoneyAccount>::iterator accList_t = accList.begin();
0088         for (; accList_t != accList.end();) {
0089             auto acc = *accList_t;
0090             if (acc.isClosed()            //check the account is not closed
0091                     || (!acc.isIncomeExpense())) {
0092                 //remove the account if it is not of the correct type
0093                 accList_t = accList.erase(accList_t);
0094             } else {
0095                 ++accList_t;
0096             }
0097         }
0098         return accList;
0099     }
0100 
0101     /**
0102      * calculate daily forecast balance based on historic transactions
0103      */
0104     void calculateHistoricDailyBalances()
0105     {
0106         Q_Q(MyMoneyForecast);
0107         auto file = MyMoneyFile::instance();
0108 
0109         calculateAccountTrendList();
0110 
0111         //Calculate account daily balances
0112         QSet<QString>::ConstIterator it_n;
0113         for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
0114             auto acc = file->account(*it_n);
0115 
0116             //set the starting balance of the account
0117             setStartingBalance(acc);
0118 
0119             switch (q->historyMethod()) {
0120             case 0:
0121             case 1: {
0122                 for (QDate f_day = q->forecastStartDate(); f_day <= q->forecastEndDate();) {
0123                     for (auto t_day = 1; t_day <= q->accountsCycle(); ++t_day) {
0124                         MyMoneyMoney balanceDayBefore = m_accountList[acc.id()][(f_day.addDays(-1))];//balance of the day before
0125                         MyMoneyMoney accountDailyTrend = m_accountTrendList[acc.id()][t_day]; //trend for that day
0126                         //balance of the day is the balance of the day before multiplied by the trend for the day
0127                         m_accountList[acc.id()][f_day] = balanceDayBefore;
0128                         m_accountList[acc.id()][f_day] += accountDailyTrend; //movement trend for that particular day
0129                         m_accountList[acc.id()][f_day] = m_accountList[acc.id()][f_day].convert(acc.fraction());
0130                         //m_accountList[acc.id()][f_day] += m_accountListPast[acc.id()][f_day.addDays(-q->historyDays())];
0131                         f_day = f_day.addDays(1);
0132                     }
0133                 }
0134             }
0135             break;
0136             case 2: {
0137                 QDate baseDate = QDate::currentDate().addDays(-q->accountsCycle());
0138                 for (auto t_day = 1; t_day <= q->accountsCycle(); ++t_day) {
0139                     auto f_day = 1;
0140                     QDate fDate = baseDate.addDays(q->accountsCycle() + 1);
0141                     while (fDate <= q->forecastEndDate()) {
0142 
0143                         //the calculation is based on the balance for the last month, that is then multiplied by the trend
0144                         m_accountList[acc.id()][fDate] = m_accountListPast[acc.id()][baseDate] + (m_accountTrendList[acc.id()][t_day] * MyMoneyMoney(f_day, 1));
0145                         m_accountList[acc.id()][fDate] = m_accountList[acc.id()][fDate].convert(acc.fraction());
0146                         ++f_day;
0147                         fDate = baseDate.addDays(q->accountsCycle() * f_day);
0148                     }
0149                     baseDate = baseDate.addDays(1);
0150                 }
0151             }
0152             }
0153         }
0154     }
0155 
0156     /**
0157      * calculate monthly budget balance based on historic transactions
0158      */
0159     void calculateHistoricMonthlyBalances()
0160     {
0161         Q_Q(MyMoneyForecast);
0162         auto file = MyMoneyFile::instance();
0163 
0164         //Calculate account monthly balances
0165         QSet<QString>::ConstIterator it_n;
0166         for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
0167             auto acc = file->account(*it_n);
0168 
0169             for (QDate f_date = q->forecastStartDate(); f_date <= q->forecastEndDate();) {
0170                 for (auto f_day = 1; f_day <= q->accountsCycle() && f_date <= q->forecastEndDate(); ++f_day) {
0171                     MyMoneyMoney accountDailyTrend = m_accountTrendList[acc.id()][f_day]; //trend for that day
0172                     //check for leap year
0173                     if (f_date.month() == 2 && f_date.day() == 29)
0174                         f_date = f_date.addDays(1); //skip 1 day
0175                     m_accountList[acc.id()][QDate(f_date.year(), f_date.month(), 1)] += accountDailyTrend; //movement trend for that particular day
0176                     f_date = f_date.addDays(1);
0177                 }
0178             }
0179         }
0180     }
0181 
0182     /**
0183      * calculate monthly budget balance based on historic transactions
0184      */
0185     void calculateScheduledMonthlyBalances()
0186     {
0187         Q_Q(MyMoneyForecast);
0188         auto file = MyMoneyFile::instance();
0189 
0190         //Calculate account monthly balances
0191         QSet<QString>::ConstIterator it_n;
0192         for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
0193             auto acc = file->account(*it_n);
0194 
0195             for (QDate f_date = q->forecastStartDate(); f_date <= q->forecastEndDate(); f_date = f_date.addDays(1)) {
0196                 //get the trend for the day
0197                 MyMoneyMoney accountDailyBalance = m_accountList[acc.id()][f_date];
0198 
0199                 //do not add if it is the beginning of the month
0200                 //otherwise we end up with duplicated values as reported by Marko Käning
0201                 if (f_date != QDate(f_date.year(), f_date.month(), 1))
0202                     m_accountList[acc.id()][QDate(f_date.year(), f_date.month(), 1)] += accountDailyBalance;
0203             }
0204         }
0205     }
0206 
0207     /**
0208      * calculate forecast based on future and scheduled transactions
0209      */
0210     void doFutureScheduledForecast()
0211     {
0212         Q_Q(MyMoneyForecast);
0213         auto file = MyMoneyFile::instance();
0214 
0215         if (q->isIncludingFutureTransactions())
0216             addFutureTransactions();
0217 
0218         if (q->isIncludingScheduledTransactions())
0219             addScheduledTransactions();
0220 
0221         //do not show accounts with no transactions
0222         if (!q->isIncludingUnusedAccounts())
0223             purgeForecastAccountsList(m_accountList);
0224 
0225         //adjust value of investments to deep currency
0226         QSet<QString>::ConstIterator it_n;
0227         for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
0228             auto acc = file->account(*it_n);
0229 
0230             if (acc.isInvest()) {
0231                 //get the id of the security for that account
0232                 MyMoneySecurity undersecurity = file->security(acc.currencyId());
0233 
0234                 //only do it if the security is not an actual currency
0235                 if (! undersecurity.isCurrency()) {
0236                     //set the default value
0237                     MyMoneyMoney rate = MyMoneyMoney::ONE;
0238 
0239                     for (QDate it_day = QDate::currentDate(); it_day <= q->forecastEndDate();) {
0240                         //get the price for the tradingCurrency that day
0241                         const MyMoneyPrice &price = file->price(undersecurity.id(), undersecurity.tradingCurrency(), it_day);
0242                         if (price.isValid()) {
0243                             rate = price.rate(undersecurity.tradingCurrency());
0244                         }
0245                         //value is the amount of shares multiplied by the rate of the deep currency
0246                         m_accountList[acc.id()][it_day] = m_accountList[acc.id()][it_day] * rate;
0247                         it_day = it_day.addDays(1);
0248                     }
0249                 }
0250             }
0251         }
0252     }
0253 
0254     /**
0255      * add future transactions to forecast
0256      */
0257     void addFutureTransactions()
0258     {
0259         Q_Q(MyMoneyForecast);
0260         MyMoneyTransactionFilter filter;
0261         auto file = MyMoneyFile::instance();
0262 
0263         // collect and process all transactions that have already been entered but
0264         // are located in the future.
0265         filter.setDateFilter(q->forecastStartDate(), q->forecastEndDate());
0266         filter.setReportAllSplits(false);
0267 
0268         QList<MyMoneyTransaction> list;
0269         file->transactionList(list, filter);
0270         for (const auto& transaction : qAsConst(list)) {
0271             const auto splits = transaction.splits();
0272             for (const auto& split : qAsConst(splits)) {
0273                 if (!split.shares().isZero()) {
0274                     auto acc = file->account(split.accountId());
0275                     if (q->isForecastAccount(acc)) {
0276                         dailyBalances balance;
0277                         balance = m_accountList[acc.id()];
0278                         //if it is income, the balance is stored as negative number
0279                         if (acc.accountType() == eMyMoney::Account::Type::Income) {
0280                             balance[transaction.postDate()] += (split.shares() * MyMoneyMoney::MINUS_ONE);
0281                         } else {
0282                             balance[transaction.postDate()] += split.shares();
0283                         }
0284                         m_accountList[acc.id()] = balance;
0285                     }
0286                 }
0287             }
0288         }
0289 
0290 #if 0
0291         QFile trcFile("forecast.csv");
0292         trcFile.open(QIODevice::WriteOnly);
0293         QTextStream s(&trcFile);
0294 
0295         {
0296             s << "Already present transactions\n";
0297             QMap<QString, dailyBalances>::Iterator it_a;
0298             QSet<QString>::ConstIterator it_n;
0299             for (it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
0300                 auto acc = file->account(*it_n);
0301                 it_a = m_accountList.find(*it_n);
0302                 s << "\"" << acc.name() << "\",";
0303                 for (auto i = 0; i < 90; ++i) {
0304                     s << "\"" << (*it_a)[i].formatMoney("") << "\",";
0305                 }
0306                 s << "\n";
0307             }
0308         }
0309 #endif
0310 
0311     }
0312 
0313     /**
0314      * add scheduled transactions to forecast
0315      */
0316     void addScheduledTransactions()
0317     {
0318         Q_Q(MyMoneyForecast);
0319         auto file = MyMoneyFile::instance();
0320 
0321         // now process all the schedules that may have an impact
0322         QList<MyMoneySchedule> schedule;
0323 
0324         schedule = file->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any,
0325                                       QDate(), q->forecastEndDate(), false);
0326         if (schedule.count() > 0) {
0327             QList<MyMoneySchedule>::Iterator it;
0328             do {
0329                 std::sort(schedule.begin(), schedule.end());
0330                 it = schedule.begin();
0331                 if (it == schedule.end())
0332                     break;
0333 
0334                 if ((*it).isFinished()) {
0335                     schedule.erase(it);
0336                     continue;
0337                 }
0338 
0339                 QDate date = (*it).nextPayment((*it).lastPayment());
0340                 if (!date.isValid()) {
0341                     schedule.erase(it);
0342                     continue;
0343                 }
0344 
0345                 QDate nextDate =
0346                     (*it).adjustedNextPayment((*it).adjustedDate((*it).lastPayment(),
0347                                               (*it).weekendOption()));
0348                 if (nextDate > q->forecastEndDate()) {
0349                     // We're done with this schedule, let's move on to the next
0350                     schedule.erase(it);
0351                     continue;
0352                 }
0353 
0354                 // found the next schedule. process it
0355 
0356                 auto acc = (*it).account();
0357 
0358                 if (!acc.id().isEmpty()) {
0359                     try {
0360                         if (acc.accountType() != eMyMoney::Account::Type::Investment) {
0361                             auto t = (*it).transaction();
0362 
0363                             // only process the entry, if it is still active
0364                             if (!(*it).isFinished() && nextDate != QDate()) {
0365                                 // make sure we have all 'starting balances' so that the autocalc works
0366                                 QMap<QString, MyMoneyMoney> balanceMap;
0367 
0368                                 for (const auto& split : qAsConst(t.splits())) {
0369                                     auto accountFromSplit = file->account(split.accountId());
0370                                     if (q->isForecastAccount(accountFromSplit)) {
0371                                         // collect all overdues on the first day
0372                                         QDate forecastDate = nextDate;
0373                                         if (QDate::currentDate() >= nextDate)
0374                                             forecastDate = QDate::currentDate().addDays(1);
0375 
0376                                         dailyBalances balance;
0377                                         balance = m_accountList[accountFromSplit.id()];
0378                                         for (QDate f_day = QDate::currentDate(); f_day < forecastDate;) {
0379                                             balanceMap[accountFromSplit.id()] += m_accountList[accountFromSplit.id()][f_day];
0380                                             f_day = f_day.addDays(1);
0381                                         }
0382                                     }
0383                                 }
0384 
0385                                 // take care of the autoCalc stuff
0386                                 q->calculateAutoLoan(*it, t, balanceMap);
0387 
0388                                 // now add the splits to the balances
0389                                 for (const auto& split : qAsConst(t.splits())) {
0390                                     auto accountFromSplit = file->account(split.accountId());
0391                                     if (q->isForecastAccount(accountFromSplit)) {
0392                                         dailyBalances balance;
0393                                         balance = m_accountList[accountFromSplit.id()];
0394                                         //auto offset = QDate::currentDate().daysTo(nextDate);
0395                                         //if(offset <= 0) {  // collect all overdues on the first day
0396                                         //  offset = 1;
0397                                         //}
0398                                         // collect all overdues on the first day
0399                                         QDate forecastDate = nextDate;
0400                                         if (QDate::currentDate() >= nextDate)
0401                                             forecastDate = QDate::currentDate().addDays(1);
0402 
0403                                         if (accountFromSplit.accountType() == eMyMoney::Account::Type::Income) {
0404                                             balance[forecastDate] += (split.shares() * MyMoneyMoney::MINUS_ONE);
0405                                         } else {
0406                                             balance[forecastDate] += split.shares();
0407                                         }
0408                                         m_accountList[accountFromSplit.id()] = balance;
0409                                     }
0410                                 }
0411                             }
0412                         }
0413                         (*it).setLastPayment(date);
0414 
0415                     } catch (const MyMoneyException &e) {
0416                         qDebug() << Q_FUNC_INFO << " Schedule " << (*it).id() << " (" << (*it).name() << "): " << e.what();
0417 
0418                         schedule.erase(it);
0419                     }
0420                 } else {
0421                     // remove schedule from list
0422                     schedule.erase(it);
0423                 }
0424             } while (1);
0425         }
0426 
0427 #if 0
0428         {
0429             s << "\n\nAdded scheduled transactions\n";
0430             QMap<QString, dailyBalances>::Iterator it_a;
0431             QSet<QString>::ConstIterator it_n;
0432             for (it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
0433                 auto acc = file->account(*it_n);
0434                 it_a = m_accountList.find(*it_n);
0435                 s << "\"" << acc.name() << "\",";
0436                 for (auto i = 0; i < 90; ++i) {
0437                     s << "\"" << (*it_a)[i].formatMoney("") << "\",";
0438                 }
0439                 s << "\n";
0440             }
0441         }
0442 #endif
0443     }
0444 
0445     /**
0446      * calculate daily forecast balance based on future and scheduled transactions
0447      */
0448     void calculateScheduledDailyBalances()
0449     {
0450         Q_Q(MyMoneyForecast);
0451         auto file = MyMoneyFile::instance();
0452 
0453         //Calculate account daily balances
0454         QSet<QString>::ConstIterator it_n;
0455         for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
0456             auto acc = file->account(*it_n);
0457 
0458             //set the starting balance of the account
0459             setStartingBalance(acc);
0460 
0461             for (QDate f_day = q->forecastStartDate(); f_day <= q->forecastEndDate();) {
0462                 MyMoneyMoney balanceDayBefore = m_accountList[acc.id()][(f_day.addDays(-1))];//balance of the day before
0463                 m_accountList[acc.id()][f_day] += balanceDayBefore; //running sum
0464                 f_day = f_day.addDays(1);
0465             }
0466         }
0467     }
0468 
0469     /**
0470      * set the starting balance for an accounts
0471      */
0472     void setStartingBalance(const MyMoneyAccount& acc)
0473     {
0474         Q_Q(MyMoneyForecast);
0475         auto file = MyMoneyFile::instance();
0476 
0477         //Get current account balance
0478         if (acc.isInvest()) {   //investments require special treatment
0479             //get the security id of that account
0480             MyMoneySecurity undersecurity = file->security(acc.currencyId());
0481 
0482             //only do it if the security is not an actual currency
0483             if (! undersecurity.isCurrency()) {
0484                 //set the default value
0485                 MyMoneyMoney rate = MyMoneyMoney::ONE;
0486                 //get te
0487                 const MyMoneyPrice &price = file->price(undersecurity.id(), undersecurity.tradingCurrency(), QDate::currentDate());
0488                 if (price.isValid()) {
0489                     rate = price.rate(undersecurity.tradingCurrency());
0490                 }
0491                 m_accountList[acc.id()][QDate::currentDate()] = file->balance(acc.id(), QDate::currentDate()) * rate;
0492             }
0493         } else {
0494             m_accountList[acc.id()][QDate::currentDate()] = file->balance(acc.id(), QDate::currentDate());
0495         }
0496 
0497         //if the method is linear regression, we have to add the opening balance to m_accountListPast
0498         if (forecastMethod() == eForecastMethod::Historic && q->historyMethod() == 2) {
0499             //FIXME workaround for stock opening dates
0500             QDate openingDate;
0501             if (acc.accountType() == eMyMoney::Account::Type::Stock) {
0502                 auto parentAccount = file->account(acc.parentAccountId());
0503                 openingDate = parentAccount.openingDate();
0504             } else {
0505                 openingDate = acc.openingDate();
0506             }
0507 
0508             //add opening balance only if it opened after the history start
0509             if (openingDate >= q->historyStartDate()) {
0510 
0511                 MyMoneyMoney openingBalance;
0512 
0513                 openingBalance = file->balance(acc.id(), openingDate);
0514 
0515                 //calculate running sum
0516                 for (QDate it_date = openingDate; it_date <= q->historyEndDate(); it_date = it_date.addDays(1)) {
0517                     //investments require special treatment
0518                     if (acc.isInvest()) {
0519                         //get the security id of that account
0520                         MyMoneySecurity undersecurity = file->security(acc.currencyId());
0521 
0522                         //only do it if the security is not an actual currency
0523                         if (! undersecurity.isCurrency()) {
0524                             //set the default value
0525                             MyMoneyMoney rate = MyMoneyMoney::ONE;
0526 
0527                             //get the rate for that specific date
0528                             const MyMoneyPrice &price = file->price(undersecurity.id(), undersecurity.tradingCurrency(), it_date);
0529                             if (price.isValid()) {
0530                                 rate = price.rate(undersecurity.tradingCurrency());
0531                             }
0532                             m_accountListPast[acc.id()][it_date] += openingBalance * rate;
0533                         }
0534                     } else {
0535                         m_accountListPast[acc.id()][it_date] += openingBalance;
0536                     }
0537                 }
0538             }
0539         }
0540     }
0541 
0542     /**
0543      * Returns the day moving average for the account @a acc based on the daily balances of a given number of @p forecastTerms
0544      * It returns the moving average for a given @p trendDay of the forecastTerm
0545      * With a term of 1 month and 3 terms, it calculates the trend taking the transactions occurred
0546      * at that day and the day before,for the last 3 months
0547      */
0548     MyMoneyMoney accountMovingAverage(const MyMoneyAccount& acc, const qint64 trendDay, const int forecastTerms)
0549     {
0550         Q_Q(MyMoneyForecast);
0551         //Calculate a daily trend for the account based on the accounts of a given number of terms
0552         //With a term of 1 month and 3 terms, it calculates the trend taking the transactions occurred at that day and the day before,
0553         //for the last 3 months
0554         MyMoneyMoney balanceVariation;
0555 
0556         for (auto it_terms = 0; (trendDay + (q->accountsCycle()*it_terms)) <= q->historyDays(); ++it_terms) { //sum for each term
0557             MyMoneyMoney balanceBefore = m_accountListPast[acc.id()][q->historyStartDate().addDays(trendDay+(q->accountsCycle()*it_terms)-2)]; //get balance for the day before
0558             MyMoneyMoney balanceAfter = m_accountListPast[acc.id()][q->historyStartDate().addDays(trendDay+(q->accountsCycle()*it_terms)-1)];
0559             balanceVariation += (balanceAfter - balanceBefore); //add the balance variation between days
0560         }
0561         //calculate average of the variations
0562         return (balanceVariation / MyMoneyMoney(forecastTerms, 1)).convert(10000);
0563     }
0564 
0565     /**
0566      * Returns the weighted moving average for a given @p trendDay
0567      */
0568     MyMoneyMoney accountWeightedMovingAverage(const MyMoneyAccount& acc, const qint64 trendDay, const int totalWeight)
0569     {
0570         Q_Q(MyMoneyForecast);
0571         MyMoneyMoney balanceVariation;
0572 
0573         for (auto it_terms = 0, weight = 1; (trendDay + (q->accountsCycle()*it_terms)) <= q->historyDays(); ++it_terms, ++weight) { //sum for each term multiplied by weight
0574             MyMoneyMoney balanceBefore = m_accountListPast[acc.id()][q->historyStartDate().addDays(trendDay+(q->accountsCycle()*it_terms)-2)]; //get balance for the day before
0575             MyMoneyMoney balanceAfter = m_accountListPast[acc.id()][q->historyStartDate().addDays(trendDay+(q->accountsCycle()*it_terms)-1)];
0576             balanceVariation += ((balanceAfter - balanceBefore) * MyMoneyMoney(weight, 1));   //add the balance variation between days multiplied by its weight
0577         }
0578         //calculate average of the variations
0579         return (balanceVariation / MyMoneyMoney(totalWeight, 1)).convert(10000);
0580     }
0581 
0582     /**
0583      * Returns the linear regression for a given @p trendDay
0584      */
0585     MyMoneyMoney accountLinearRegression(const MyMoneyAccount &acc, const qint64 trendDay, const qint64 actualTerms, const MyMoneyMoney& meanTerms)
0586     {
0587         Q_Q(MyMoneyForecast);
0588         MyMoneyMoney meanBalance, totalBalance, totalTerms;
0589         totalTerms = MyMoneyMoney(actualTerms, 1);
0590 
0591         //calculate mean balance
0592         for (auto it_terms = q->forecastCycles() - actualTerms; (trendDay + (q->accountsCycle()*it_terms)) <= q->historyDays(); ++it_terms) { //sum for each term
0593             totalBalance += m_accountListPast[acc.id()][q->historyStartDate().addDays(trendDay+(q->accountsCycle()*it_terms)-1)];
0594         }
0595         meanBalance = totalBalance / MyMoneyMoney(actualTerms, 1);
0596         meanBalance = meanBalance.convert(10000);
0597 
0598         //calculate b1
0599 
0600         //first calculate x - mean x multiplied by y - mean y
0601         MyMoneyMoney totalXY, totalSqX;
0602         auto term = 1;
0603         for (auto it_terms = q->forecastCycles() - actualTerms; (trendDay + (q->accountsCycle()*it_terms)) <= q->historyDays(); ++it_terms, ++term) { //sum for each term
0604             MyMoneyMoney balance = m_accountListPast[acc.id()][q->historyStartDate().addDays(trendDay+(q->accountsCycle()*it_terms)-1)];
0605 
0606             MyMoneyMoney balMeanBal = balance - meanBalance;
0607             MyMoneyMoney termMeanTerm = (MyMoneyMoney(term, 1) - meanTerms);
0608 
0609             totalXY += (balMeanBal * termMeanTerm).convert(10000);
0610 
0611             totalSqX += (termMeanTerm * termMeanTerm).convert(10000);
0612         }
0613         totalXY = (totalXY / MyMoneyMoney(actualTerms, 1)).convert(10000);
0614         totalSqX = (totalSqX / MyMoneyMoney(actualTerms, 1)).convert(10000);
0615 
0616         //check zero
0617         if (totalSqX.isZero())
0618             return MyMoneyMoney();
0619 
0620         MyMoneyMoney linReg = (totalXY / totalSqX).convert(10000);
0621 
0622         return linReg;
0623     }
0624 
0625     /**
0626      * calculate daily forecast trend based on historic transactions
0627      */
0628     void calculateAccountTrendList()
0629     {
0630         Q_Q(MyMoneyForecast);
0631         auto file = MyMoneyFile::instance();
0632         qint64 auxForecastTerms;
0633         qint64 totalWeight = 0;
0634 
0635         //Calculate account trends
0636         QSet<QString>::ConstIterator it_n;
0637         for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
0638             auto acc = file->account(*it_n);
0639             m_accountTrendList[acc.id()][0] = MyMoneyMoney(); // for today, the trend is 0
0640 
0641             auxForecastTerms = q->forecastCycles();
0642             if (q->skipOpeningDate()) {
0643 
0644                 QDate openingDate;
0645                 if (acc.accountType() == eMyMoney::Account::Type::Stock) {
0646                     auto parentAccount = file->account(acc.parentAccountId());
0647                     openingDate = parentAccount.openingDate();
0648                 } else {
0649                     openingDate = acc.openingDate();
0650                 }
0651 
0652                 if (openingDate > q->historyStartDate()) { //if acc opened after forecast period
0653                     auxForecastTerms = 1 + ((openingDate.daysTo(q->historyEndDate()) + 1) / q->accountsCycle()); // set forecastTerms to a lower value, to calculate only based on how long this account was opened
0654                 }
0655             }
0656 
0657             switch (q->historyMethod()) {
0658             //moving average
0659             case 0: {
0660                 for (auto t_day = 1; t_day <= q->accountsCycle(); ++t_day)
0661                     m_accountTrendList[acc.id()][t_day] = accountMovingAverage(acc, t_day, auxForecastTerms); //moving average
0662                 break;
0663             }
0664             //weighted moving average
0665             case 1: {
0666                 //calculate total weight for moving average
0667                 if (auxForecastTerms == q->forecastCycles()) {
0668                     totalWeight = (auxForecastTerms * (auxForecastTerms + 1)) / 2; //totalWeight is the triangular number of auxForecastTerms
0669                 } else {
0670                     //if only taking a few periods, totalWeight is the sum of the weight for most recent periods
0671                     auto i = 1;
0672                     for (qint64 w = q->forecastCycles(); i <= auxForecastTerms; ++i, --w)
0673                         totalWeight += w;
0674                 }
0675                 for (auto t_day = 1; t_day <= q->accountsCycle(); ++t_day)
0676                     m_accountTrendList[acc.id()][t_day] = accountWeightedMovingAverage(acc, t_day, totalWeight);
0677                 break;
0678             }
0679             case 2: {
0680                 //calculate mean term
0681                 MyMoneyMoney meanTerms = MyMoneyMoney((auxForecastTerms * (auxForecastTerms + 1)) / 2, 1) / MyMoneyMoney(auxForecastTerms, 1);
0682 
0683                 for (auto t_day = 1; t_day <= q->accountsCycle(); ++t_day)
0684                     m_accountTrendList[acc.id()][t_day] = accountLinearRegression(acc, t_day, auxForecastTerms, meanTerms);
0685                 break;
0686             }
0687             default:
0688                 break;
0689             }
0690         }
0691     }
0692 
0693     /**
0694      * set the internal list of accounts to be forecast
0695      */
0696     void setForecastAccountList()
0697     {
0698         Q_Q(MyMoneyForecast);
0699         //get forecast accounts
0700         QList<MyMoneyAccount> accList;
0701         accList = q->forecastAccountList();
0702 
0703         QList<MyMoneyAccount>::const_iterator accList_t = accList.constBegin();
0704         for (; accList_t != accList.constEnd(); ++accList_t) {
0705             m_forecastAccounts.insert((*accList_t).id());
0706         }
0707     }
0708 
0709     /**
0710      * set the internal list of accounts to create a budget
0711      */
0712     void setBudgetAccountList()
0713     {
0714         //get budget accounts
0715         QList<MyMoneyAccount> accList;
0716         accList = budgetAccountList();
0717 
0718         QList<MyMoneyAccount>::const_iterator accList_t = accList.constBegin();
0719         for (; accList_t != accList.constEnd(); ++accList_t) {
0720             m_forecastAccounts.insert((*accList_t).id());
0721         }
0722     }
0723 
0724     /**
0725      * get past transactions for the accounts to be forecast
0726      */
0727     void pastTransactions()
0728     {
0729         Q_Q(MyMoneyForecast);
0730         auto file = MyMoneyFile::instance();
0731         MyMoneyTransactionFilter filter;
0732 
0733         filter.setDateFilter(q->historyStartDate(), q->historyEndDate());
0734         filter.setReportAllSplits(false);
0735 
0736         //Check past transactions
0737         QList<MyMoneyTransaction> list;
0738         file->transactionList(list, filter);
0739         for (const auto& transaction : qAsConst(list)) {
0740             const auto splits = transaction.splits();
0741             for (const auto& split : qAsConst(splits)) {
0742                 if (!split.shares().isZero()) {
0743                     auto acc = file->account(split.accountId());
0744 
0745                     //workaround for stock accounts which have faulty opening dates
0746                     QDate openingDate;
0747                     if (acc.accountType() == eMyMoney::Account::Type::Stock) {
0748                         auto parentAccount = file->account(acc.parentAccountId());
0749                         openingDate = parentAccount.openingDate();
0750                     } else {
0751                         openingDate = acc.openingDate();
0752                     }
0753 
0754                     if (q->isForecastAccount(acc) //If it is one of the accounts we are checking, add the amount of the transaction
0755                             && ((openingDate < transaction.postDate() && q->skipOpeningDate())
0756                                 || !q->skipOpeningDate())) {  //don't take the opening day of the account to calculate balance
0757                         dailyBalances balance;
0758                         //FIXME deal with leap years
0759                         balance = m_accountListPast[acc.id()];
0760                         if (acc.accountType() == eMyMoney::Account::Type::Income) {//if it is income, the balance is stored as negative number
0761                             balance[transaction.postDate()] += (split.shares() * MyMoneyMoney::MINUS_ONE);
0762                         } else {
0763                             balance[transaction.postDate()] += split.shares();
0764                         }
0765                         // check if this is a new account for us
0766                         m_accountListPast[acc.id()] = balance;
0767                     }
0768                 }
0769             }
0770         }
0771 
0772         //purge those accounts with no transactions on the period
0773         if (q->isIncludingUnusedAccounts() == false)
0774             purgeForecastAccountsList(m_accountListPast);
0775 
0776         //calculate running sum
0777         QSet<QString>::ConstIterator it_n;
0778         for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
0779             auto acc = file->account(*it_n);
0780             m_accountListPast[acc.id()][q->historyStartDate().addDays(-1)] = file->balance(acc.id(), q->historyStartDate().addDays(-1));
0781             for (QDate it_date = q->historyStartDate(); it_date <= q->historyEndDate();) {
0782                 m_accountListPast[acc.id()][it_date] += m_accountListPast[acc.id()][it_date.addDays(-1)]; //Running sum
0783                 it_date = it_date.addDays(1);
0784             }
0785         }
0786 
0787         //adjust value of investments to deep currency
0788         for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
0789             auto acc = file->account(*it_n);
0790 
0791             if (acc.isInvest()) {
0792                 //get the id of the security for that account
0793                 MyMoneySecurity undersecurity = file->security(acc.currencyId());
0794                 if (! undersecurity.isCurrency()) { //only do it if the security is not an actual currency
0795                     MyMoneyMoney rate = MyMoneyMoney::ONE;    //set the default value
0796 
0797                     for (QDate it_date = q->historyStartDate().addDays(-1) ; it_date <= q->historyEndDate();) {
0798                         //get the price for the tradingCurrency that day
0799                         const MyMoneyPrice &price = file->price(undersecurity.id(), undersecurity.tradingCurrency(), it_date);
0800                         if (price.isValid()) {
0801                             rate = price.rate(undersecurity.tradingCurrency());
0802                         }
0803                         //value is the amount of shares multiplied by the rate of the deep currency
0804                         m_accountListPast[acc.id()][it_date] = m_accountListPast[acc.id()][it_date] * rate;
0805                         it_date = it_date.addDays(1);
0806                     }
0807                 }
0808             }
0809         }
0810     }
0811 
0812     /**
0813      * calculate the day to start forecast and sets the begin date
0814      * The quantity of forecast days will be counted from this date
0815      * Depends on the values of begin day and accounts cycle
0816      * The rules to calculate begin day are as follows:
0817      * - if beginDay is 0, begin date is current date
0818      * - if the day of the month set by beginDay has not passed, that will be used
0819      * - if adding an account cycle to beginDay, will not go past the beginDay of next month,
0820      *   that date will be used, otherwise it will add account cycle to beginDay until it is past current date
0821      * It returns the total amount of Forecast Days from current date.
0822      */
0823     qint64 calculateBeginForecastDay()
0824     {
0825         Q_Q(MyMoneyForecast);
0826         auto fDays = q->forecastDays();
0827         auto beginDay = q->beginForecastDay();
0828         auto accCycle = q->accountsCycle();
0829         QDate beginDate;
0830 
0831         //if 0, beginDate is current date and forecastDays remains unchanged
0832         if (beginDay == 0) {
0833             q->setBeginForecastDate(QDate::currentDate());
0834             return fDays;
0835         }
0836 
0837         //adjust if beginDay more than days of current month
0838         if (QDate::currentDate().daysInMonth() < beginDay)
0839             beginDay = QDate::currentDate().daysInMonth();
0840 
0841         //if beginDay still to come, calculate and return
0842         if (QDate::currentDate().day() <= beginDay) {
0843             beginDate = QDate(QDate::currentDate().year(), QDate::currentDate().month(), beginDay);
0844             fDays += QDate::currentDate().daysTo(beginDate);
0845             q->setBeginForecastDate(beginDate);
0846             return fDays;
0847         }
0848 
0849         //adjust beginDay for next month
0850         if (QDate::currentDate().addMonths(1).daysInMonth() < beginDay)
0851             beginDay = QDate::currentDate().addMonths(1).daysInMonth();
0852 
0853         //if beginDay of next month comes before 1 interval, use beginDay next month
0854         if (QDate::currentDate().addDays(accCycle) >=
0855                 (QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1).addDays(beginDay - 1))) {
0856             beginDate = QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1).addDays(beginDay - 1);
0857             fDays += QDate::currentDate().daysTo(beginDate);
0858         } else { //add intervals to current beginDay and take the first after current date
0859             beginDay = ((((QDate::currentDate().day() - beginDay) / accCycle) + 1) * accCycle) + beginDay;
0860             beginDate = QDate::currentDate().addDays(beginDay - QDate::currentDate().day());
0861             fDays += QDate::currentDate().daysTo(beginDate);
0862         }
0863 
0864         q->setBeginForecastDate(beginDate);
0865         return fDays;
0866     }
0867 
0868     /**
0869      * remove accounts from the list if the accounts has no transactions in the forecast timeframe.
0870      * Used for scheduled-forecast method.
0871      */
0872     void purgeForecastAccountsList(QMap<QString, dailyBalances>& accountList)
0873     {
0874         const auto keys = accountList.keys();
0875         m_forecastAccounts.intersect(QSet<QString>(keys.constBegin(), keys.constEnd()));
0876     }
0877 
0878     MyMoneyForecast *q_ptr;
0879 
0880     /**
0881      * daily forecast balance of accounts
0882      */
0883     QMap<QString, dailyBalances> m_accountList;
0884 
0885     /**
0886      * daily past balance of accounts
0887      */
0888     QMap<QString, dailyBalances> m_accountListPast;
0889 
0890     /**
0891      * daily forecast trends of accounts
0892      */
0893     QMap<QString, trendBalances> m_accountTrendList;
0894 
0895     /**
0896      * list of forecast account ids.
0897      */
0898     QSet<QString> m_forecastAccounts;
0899 
0900     /**
0901      * cycle of accounts in days
0902      */
0903     qint64 m_accountsCycle;
0904 
0905     /**
0906      * number of cycles to use in forecast
0907      */
0908     qint64 m_forecastCycles;
0909 
0910     /**
0911      * number of days to forecast
0912      */
0913     qint64 m_forecastDays;
0914 
0915     /**
0916      * date to start forecast
0917      */
0918     QDate m_beginForecastDate;
0919 
0920     /**
0921      * day to start forecast
0922      */
0923     qint64 m_beginForecastDay;
0924 
0925     /**
0926      * forecast method
0927      */
0928     eForecastMethod m_forecastMethod;
0929 
0930     /**
0931      * history method
0932      */
0933     int m_historyMethod;
0934 
0935     /**
0936      * start date of history
0937      */
0938     QDate m_historyStartDate;
0939 
0940     /**
0941      * end date of history
0942      */
0943     QDate m_historyEndDate;
0944 
0945     /**
0946      * start date of forecast
0947      */
0948     QDate m_forecastStartDate;
0949 
0950     /**
0951      * end date of forecast
0952      */
0953     QDate m_forecastEndDate;
0954 
0955     /**
0956      * skip opening date when fetching transactions of an account
0957      */
0958     bool m_skipOpeningDate;
0959 
0960     /**
0961      * include accounts with no transactions in the forecast timeframe. default is false.
0962      */
0963     bool m_includeUnusedAccounts;
0964 
0965     /**
0966      * forecast already done
0967      */
0968     bool m_forecastDone;
0969 
0970     /**
0971      * include future transactions when doing a scheduled-based forecast
0972      */
0973     bool m_includeFutureTransactions;
0974 
0975     /**
0976      * include scheduled transactions when doing a scheduled-based forecast
0977      */
0978     bool m_includeScheduledTransactions;
0979 };
0980 
0981 MyMoneyForecast::MyMoneyForecast() :
0982     d_ptr(new MyMoneyForecastPrivate(this))
0983 {
0984     setHistoryStartDate(QDate::currentDate().addDays(-forecastCycles()*accountsCycle()));
0985     setHistoryEndDate(QDate::currentDate().addDays(-1));
0986 }
0987 
0988 MyMoneyForecast::MyMoneyForecast(const MyMoneyForecast& other) :
0989     d_ptr(new MyMoneyForecastPrivate(*other.d_func()))
0990 {
0991     this->d_ptr->q_ptr = this;
0992 }
0993 
0994 void swap(MyMoneyForecast& first, MyMoneyForecast& second)
0995 {
0996     using std::swap;
0997     swap(first.d_ptr, second.d_ptr);
0998     swap(first.d_ptr->q_ptr, second.d_ptr->q_ptr);
0999 }
1000 
1001 MyMoneyForecast::MyMoneyForecast(MyMoneyForecast && other) : MyMoneyForecast()
1002 {
1003     swap(*this, other);
1004 }
1005 
1006 MyMoneyForecast & MyMoneyForecast::operator=(MyMoneyForecast other)
1007 {
1008     swap(*this, other);
1009     return *this;
1010 }
1011 
1012 MyMoneyForecast::~MyMoneyForecast()
1013 {
1014     Q_D(MyMoneyForecast);
1015     delete d;
1016 }
1017 
1018 void MyMoneyForecast::doForecast()
1019 {
1020     Q_D(MyMoneyForecast);
1021     auto fDays = d->calculateBeginForecastDay();
1022     auto fMethod = d->forecastMethod();
1023     auto fAccCycle = accountsCycle();
1024     auto fCycles = forecastCycles();
1025 
1026     //validate settings
1027     if (fAccCycle < 1
1028             || fCycles < 1
1029             || fDays < 1) {
1030         throw MYMONEYEXCEPTION_CSTRING("Illegal settings when calling doForecast. Settings must be higher than 0");
1031     }
1032 
1033     //initialize global variables
1034     setForecastDays(fDays);
1035     setForecastStartDate(QDate::currentDate().addDays(1));
1036     setForecastEndDate(QDate::currentDate().addDays(fDays));
1037     setAccountsCycle(fAccCycle);
1038     setForecastCycles(fCycles);
1039     setHistoryStartDate(forecastCycles() * accountsCycle());
1040     setHistoryEndDate(QDate::currentDate().addDays(-1)); //yesterday
1041 
1042     //clear all data before calculating
1043     d->m_accountListPast.clear();
1044     d->m_accountList.clear();
1045     d->m_accountTrendList.clear();
1046 
1047     //set forecast accounts
1048     d->setForecastAccountList();
1049 
1050     switch (fMethod) {
1051     case eForecastMethod::Scheduled:
1052         d->doFutureScheduledForecast();
1053         d->calculateScheduledDailyBalances();
1054         break;
1055     case eForecastMethod::Historic:
1056         d->pastTransactions();
1057         d->calculateHistoricDailyBalances();
1058         break;
1059     default:
1060         break;
1061     }
1062 
1063     //flag the forecast as done
1064     d->m_forecastDone = true;
1065 }
1066 
1067 bool MyMoneyForecast::isForecastAccount(const MyMoneyAccount& acc)
1068 {
1069     Q_D(MyMoneyForecast);
1070     if (d->m_forecastAccounts.isEmpty()) {
1071         d->setForecastAccountList();
1072     }
1073     return d->m_forecastAccounts.contains(acc.id());
1074 }
1075 
1076 QList<MyMoneyAccount> MyMoneyForecast::accountList()
1077 {
1078     auto file = MyMoneyFile::instance();
1079 
1080     QList<MyMoneyAccount> accList;
1081     QStringList emptyStringList;
1082     //Get all accounts from the file and check if they are present
1083     file->accountList(accList, emptyStringList, false);
1084     QList<MyMoneyAccount>::iterator accList_t = accList.begin();
1085     for (; accList_t != accList.end();) {
1086         auto acc = *accList_t;
1087         if (!isForecastAccount(acc)) {
1088             accList_t = accList.erase(accList_t);    //remove the account
1089         } else {
1090             ++accList_t;
1091         }
1092     }
1093     return accList;
1094 }
1095 
1096 MyMoneyMoney MyMoneyForecast::calculateAccountTrend(const MyMoneyAccount& acc, qint64 trendDays)
1097 {
1098     auto file = MyMoneyFile::instance();
1099     MyMoneyTransactionFilter filter;
1100     MyMoneyMoney netIncome;
1101     QDate startDate;
1102     QDate openingDate = acc.openingDate();
1103 
1104     //validate arguments
1105     if (trendDays < 1) {
1106         throw MYMONEYEXCEPTION_CSTRING("Illegal arguments when calling calculateAccountTrend. trendDays must be higher than 0");
1107     }
1108 
1109     //If it is a new account, we don't take into account the first day
1110     //because it is usually a weird one and it would mess up the trend
1111     if (openingDate.daysTo(QDate::currentDate()) < trendDays) {
1112         startDate = (acc.openingDate()).addDays(1);
1113     } else {
1114         startDate = QDate::currentDate().addDays(-trendDays);
1115     }
1116     //get all transactions for the period
1117     filter.setDateFilter(startDate, QDate::currentDate());
1118     if (acc.accountGroup() == eMyMoney::Account::Type::Income //
1119             || acc.accountGroup() == eMyMoney::Account::Type::Expense) {
1120         filter.addCategory(acc.id());
1121     } else {
1122         filter.addAccount(acc.id());
1123     }
1124 
1125     filter.setReportAllSplits(false);
1126 
1127     //add all transactions for that account
1128     QList<MyMoneyTransaction> list;
1129     file->transactionList(list, filter);
1130     for (const auto& transaction : qAsConst(list)) {
1131         const auto splits = transaction.splits();
1132         for (const auto& split : qAsConst(splits)) {
1133             if (!split.shares().isZero()) {
1134                 if (acc.id() == split.accountId()) netIncome += split.value();
1135             }
1136         }
1137     }
1138 
1139     //calculate trend of the account in the past period
1140     MyMoneyMoney accTrend;
1141 
1142     //don't take into account the first day of the account
1143     if (openingDate.daysTo(QDate::currentDate()) < trendDays) {
1144         accTrend = netIncome / MyMoneyMoney(openingDate.daysTo(QDate::currentDate()) - 1, 1);
1145     } else {
1146         accTrend = netIncome / MyMoneyMoney(trendDays, 1);
1147     }
1148     return accTrend;
1149 }
1150 
1151 MyMoneyMoney MyMoneyForecast::forecastBalance(const MyMoneyAccount& acc, const QDate &forecastDate)
1152 {
1153     Q_D(MyMoneyForecast);
1154     dailyBalances balance;
1155     MyMoneyMoney MM_amount = MyMoneyMoney();
1156 
1157     //Check if acc is not a forecast account, return 0
1158     if (!isForecastAccount(acc)) {
1159         return MM_amount;
1160     }
1161 
1162     if (d->m_accountList.contains(acc.id())) {
1163         balance = d->m_accountList.value(acc.id());
1164     }
1165     if (balance.contains(forecastDate)) { //if the date is not in the forecast, it returns 0
1166         MM_amount = balance.value(forecastDate);
1167     }
1168     return MM_amount;
1169 }
1170 
1171 /**
1172  * Returns the forecast balance trend for account @a acc for offset @p int
1173  * offset is days from current date, inside forecast days.
1174  * Returns 0 if offset not in range of forecast days.
1175  */
1176 MyMoneyMoney MyMoneyForecast::forecastBalance(const MyMoneyAccount& acc, qint64 offset)
1177 {
1178     QDate forecastDate = QDate::currentDate().addDays(offset);
1179     return forecastBalance(acc, forecastDate);
1180 }
1181 
1182 qint64 MyMoneyForecast::daysToMinimumBalance(const MyMoneyAccount& acc)
1183 {
1184     Q_D(MyMoneyForecast);
1185     QString minimumBalance = acc.value("minBalanceAbsolute");
1186     MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance);
1187     dailyBalances balance;
1188 
1189     //Check if acc is not a forecast account, return -1
1190     if (!isForecastAccount(acc)) {
1191         return -1;
1192     }
1193 
1194     balance = d->m_accountList[acc.id()];
1195 
1196     for (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate();) {
1197         if (minBalance > balance[it_day]) {
1198             return QDate::currentDate().daysTo(it_day);
1199         }
1200         it_day = it_day.addDays(1);
1201     }
1202     return -1;
1203 }
1204 
1205 qint64 MyMoneyForecast::daysToZeroBalance(const MyMoneyAccount& acc)
1206 {
1207     Q_D(MyMoneyForecast);
1208     dailyBalances balance;
1209 
1210     //Check if acc is not a forecast account, return -1
1211     if (!isForecastAccount(acc)) {
1212         return -2;
1213     }
1214 
1215     balance = d->m_accountList[acc.id()];
1216 
1217     if (acc.accountGroup() == eMyMoney::Account::Type::Asset) {
1218         for (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate();) {
1219             if (balance[it_day] < MyMoneyMoney()) {
1220                 return QDate::currentDate().daysTo(it_day);
1221             }
1222             it_day = it_day.addDays(1);
1223         }
1224     } else if (acc.accountGroup() == eMyMoney::Account::Type::Liability) {
1225         for (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate();) {
1226             if (balance[it_day] > MyMoneyMoney()) {
1227                 return QDate::currentDate().daysTo(it_day);
1228             }
1229             it_day = it_day.addDays(1);
1230         }
1231     }
1232     return -1;
1233 }
1234 
1235 
1236 MyMoneyMoney MyMoneyForecast::accountCycleVariation(const MyMoneyAccount& acc)
1237 {
1238     Q_D(MyMoneyForecast);
1239     MyMoneyMoney cycleVariation;
1240 
1241     if (d->forecastMethod() == eForecastMethod::Historic) {
1242         switch (historyMethod()) {
1243         case 0:
1244         case 1: {
1245             for (auto t_day = 1; t_day <= accountsCycle() ; ++t_day) {
1246                 cycleVariation += d->m_accountTrendList[acc.id()][t_day];
1247             }
1248         }
1249         break;
1250         case 2: {
1251             cycleVariation = d->m_accountList[acc.id()][QDate::currentDate().addDays(accountsCycle())] - d->m_accountList[acc.id()][QDate::currentDate()];
1252             break;
1253         }
1254         }
1255     }
1256     return cycleVariation;
1257 }
1258 
1259 MyMoneyMoney MyMoneyForecast::accountTotalVariation(const MyMoneyAccount& acc)
1260 {
1261     MyMoneyMoney totalVariation;
1262 
1263     totalVariation = forecastBalance(acc, forecastEndDate()) - forecastBalance(acc, QDate::currentDate());
1264     return totalVariation;
1265 }
1266 
1267 QList<QDate> MyMoneyForecast::accountMinimumBalanceDateList(const MyMoneyAccount& acc)
1268 {
1269     QList<QDate> minBalanceList;
1270     qint64 daysToBeginDay;
1271 
1272     daysToBeginDay = QDate::currentDate().daysTo(beginForecastDate());
1273 
1274     for (auto t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
1275         MyMoneyMoney minBalance = forecastBalance(acc, (t_cycle * accountsCycle() + daysToBeginDay));
1276         QDate minDate = QDate::currentDate().addDays(t_cycle * accountsCycle() + daysToBeginDay);
1277         for (auto t_day = 1; t_day <= accountsCycle() ; ++t_day) {
1278             if (minBalance > forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day)) {
1279                 minBalance = forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day);
1280                 minDate = QDate::currentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay + t_day);
1281             }
1282         }
1283         minBalanceList.append(minDate);
1284     }
1285     return minBalanceList;
1286 }
1287 
1288 QList<QDate> MyMoneyForecast::accountMaximumBalanceDateList(const MyMoneyAccount& acc)
1289 {
1290     QList<QDate> maxBalanceList;
1291     qint64 daysToBeginDay;
1292 
1293     daysToBeginDay = QDate::currentDate().daysTo(beginForecastDate());
1294 
1295     for (auto t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
1296         MyMoneyMoney maxBalance = forecastBalance(acc, ((t_cycle * accountsCycle()) + daysToBeginDay));
1297         QDate maxDate = QDate::currentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay);
1298 
1299         for (auto t_day = 0; t_day < accountsCycle() ; ++t_day) {
1300             if (maxBalance < forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day)) {
1301                 maxBalance = forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day);
1302                 maxDate = QDate::currentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay + t_day);
1303             }
1304         }
1305         maxBalanceList.append(maxDate);
1306     }
1307     return maxBalanceList;
1308 }
1309 
1310 MyMoneyMoney MyMoneyForecast::accountAverageBalance(const MyMoneyAccount& acc)
1311 {
1312     MyMoneyMoney totalBalance;
1313     for (auto f_day = 1; f_day <= forecastDays() ; ++f_day) {
1314         totalBalance += forecastBalance(acc, f_day);
1315     }
1316     return totalBalance / MyMoneyMoney(forecastDays(), 1);
1317 }
1318 
1319 void MyMoneyForecast::createBudget(MyMoneyBudget& budget, QDate historyStart, QDate historyEnd, QDate budgetStart, QDate budgetEnd, const bool returnBudget)
1320 {
1321     Q_D(MyMoneyForecast);
1322     // clear all data except the id and name
1323     QString name = budget.name();
1324     budget = MyMoneyBudget(budget.id(), MyMoneyBudget());
1325     budget.setName(name);
1326 
1327     //check parameters
1328     if (historyStart > historyEnd ||
1329             budgetStart > budgetEnd ||
1330             budgetStart <= historyEnd) {
1331         throw MYMONEYEXCEPTION_CSTRING("Illegal parameters when trying to create budget");
1332     }
1333 
1334     //get forecast method
1335     auto fMethod = d->forecastMethod();
1336 
1337     //set start date to 1st of month and end dates to last day of month, since we deal with full months in budget
1338     historyStart = QDate(historyStart.year(), historyStart.month(), 1);
1339     historyEnd = QDate(historyEnd.year(), historyEnd.month(), historyEnd.daysInMonth());
1340     budgetStart = QDate(budgetStart.year(), budgetStart.month(), 1);
1341     budgetEnd = QDate(budgetEnd.year(), budgetEnd.month(), budgetEnd.daysInMonth());
1342 
1343     //set forecast parameters
1344     setHistoryStartDate(historyStart);
1345     setHistoryEndDate(historyEnd);
1346     setForecastStartDate(budgetStart);
1347     setForecastEndDate(budgetEnd);
1348     setForecastDays(budgetStart.daysTo(budgetEnd) + 1);
1349     if (budgetStart.daysTo(budgetEnd) > historyStart.daysTo(historyEnd)) {         //if history period is shorter than budget, use that one as the trend length
1350         setAccountsCycle(historyStart.daysTo(historyEnd));       //we set the accountsCycle to the base timeframe we will use to calculate the average (eg. 180 days, 365, etc)
1351     } else { //if one timeframe is larger than the other, but not enough to be 1 time larger, we take the lowest value
1352         setAccountsCycle(budgetStart.daysTo(budgetEnd));
1353     }
1354     setForecastCycles((historyStart.daysTo(historyEnd) / accountsCycle()));
1355     if (forecastCycles() == 0)   //the cycles must be at least 1
1356         setForecastCycles(1);
1357 
1358     //do not skip opening date
1359     setSkipOpeningDate(false);
1360 
1361     //clear and set accounts list we are going to use. Categories, in this case
1362     d->m_forecastAccounts.clear();
1363     d->setBudgetAccountList();
1364 
1365     //calculate budget according to forecast method
1366     switch (fMethod) {
1367     case eForecastMethod::Scheduled:
1368         d->doFutureScheduledForecast();
1369         d->calculateScheduledMonthlyBalances();
1370         break;
1371     case eForecastMethod::Historic:
1372         d->pastTransactions(); //get all transactions for history period
1373         d->calculateAccountTrendList();
1374         d->calculateHistoricMonthlyBalances(); //add all balances of each month and put at the 1st day of each month
1375         break;
1376     default:
1377         break;
1378     }
1379 
1380     //flag the forecast as done
1381     d->m_forecastDone = true;
1382 
1383     //only fill the budget if it is going to be used
1384     if (returnBudget) {
1385         //setup the budget itself
1386         auto file = MyMoneyFile::instance();
1387         budget.setBudgetStart(budgetStart);
1388 
1389         //go through all the accounts and add them to budget
1390         for (auto it_nc = d->m_forecastAccounts.constBegin(); it_nc != d->m_forecastAccounts.constEnd(); ++it_nc) {
1391             auto acc = file->account(*it_nc);
1392 
1393             MyMoneyBudget::AccountGroup budgetAcc;
1394             budgetAcc.setId(acc.id());
1395             budgetAcc.setBudgetLevel(eMyMoney::Budget::Level::MonthByMonth);
1396 
1397             for (QDate f_date = forecastStartDate(); f_date <= forecastEndDate();) {
1398                 MyMoneyBudget::PeriodGroup period;
1399 
1400                 //add period to budget account
1401                 period.setStartDate(f_date);
1402                 period.setAmount(forecastBalance(acc, f_date));
1403                 budgetAcc.addPeriod(f_date, period);
1404 
1405                 //next month
1406                 f_date = f_date.addMonths(1);
1407             }
1408             //add budget account to budget
1409             budget.setAccount(budgetAcc, acc.id());
1410         }
1411     }
1412 }
1413 qint64 MyMoneyForecast::historyDays() const
1414 {
1415     Q_D(const MyMoneyForecast);
1416     return (d->m_historyStartDate.daysTo(d->m_historyEndDate) + 1);
1417 }
1418 
1419 void MyMoneyForecast::setAccountsCycle(qint64 accountsCycle)
1420 {
1421     Q_D(MyMoneyForecast);
1422     d->m_accountsCycle = accountsCycle;
1423 }
1424 
1425 void MyMoneyForecast::setForecastCycles(qint64 forecastCycles)
1426 {
1427     Q_D(MyMoneyForecast);
1428     d->m_forecastCycles = forecastCycles;
1429 }
1430 
1431 void MyMoneyForecast::setForecastDays(qint64 forecastDays)
1432 {
1433     Q_D(MyMoneyForecast);
1434     d->m_forecastDays = forecastDays;
1435 }
1436 
1437 void MyMoneyForecast::setBeginForecastDate(const QDate &beginForecastDate)
1438 {
1439     Q_D(MyMoneyForecast);
1440     d->m_beginForecastDate = beginForecastDate;
1441 }
1442 
1443 void MyMoneyForecast::setBeginForecastDay(qint64 beginDay)
1444 {
1445     Q_D(MyMoneyForecast);
1446     d->m_beginForecastDay = beginDay;
1447 }
1448 
1449 void MyMoneyForecast::setForecastMethod(qint64 forecastMethod)
1450 {
1451     Q_D(MyMoneyForecast);
1452     d->m_forecastMethod = static_cast<eForecastMethod>(forecastMethod);
1453 }
1454 
1455 void MyMoneyForecast::setHistoryStartDate(const QDate &historyStartDate)
1456 {
1457     Q_D(MyMoneyForecast);
1458     d->m_historyStartDate = historyStartDate;
1459 }
1460 
1461 void MyMoneyForecast::setHistoryEndDate(const QDate &historyEndDate)
1462 {
1463     Q_D(MyMoneyForecast);
1464     d->m_historyEndDate = historyEndDate;
1465 }
1466 
1467 void MyMoneyForecast::setHistoryStartDate(qint64 daysToStartDate)
1468 {
1469     setHistoryStartDate(QDate::currentDate().addDays(-daysToStartDate));
1470 }
1471 
1472 void MyMoneyForecast::setHistoryEndDate(qint64 daysToEndDate)
1473 {
1474     setHistoryEndDate(QDate::currentDate().addDays(-daysToEndDate));
1475 }
1476 
1477 void MyMoneyForecast::setForecastStartDate(const QDate &_startDate)
1478 {
1479     Q_D(MyMoneyForecast);
1480     d->m_forecastStartDate = _startDate;
1481 }
1482 
1483 void MyMoneyForecast::setForecastEndDate(const QDate &_endDate)
1484 {
1485     Q_D(MyMoneyForecast);
1486     d->m_forecastEndDate = _endDate;
1487 }
1488 
1489 void MyMoneyForecast::setSkipOpeningDate(bool _skip)
1490 {
1491     Q_D(MyMoneyForecast);
1492     d->m_skipOpeningDate = _skip;
1493 }
1494 
1495 void MyMoneyForecast::setHistoryMethod(int historyMethod)
1496 {
1497     Q_D(MyMoneyForecast);
1498     d->m_historyMethod = historyMethod;
1499 }
1500 
1501 void MyMoneyForecast::setIncludeUnusedAccounts(bool _bool)
1502 {
1503     Q_D(MyMoneyForecast);
1504     d->m_includeUnusedAccounts = _bool;
1505 }
1506 
1507 void MyMoneyForecast::setForecastDone(bool _bool)
1508 {
1509     Q_D(MyMoneyForecast);
1510     d->m_forecastDone = _bool;
1511 }
1512 
1513 void MyMoneyForecast::setIncludeFutureTransactions(bool _bool)
1514 {
1515     Q_D(MyMoneyForecast);
1516     d->m_includeFutureTransactions = _bool;
1517 }
1518 
1519 void MyMoneyForecast::setIncludeScheduledTransactions(bool _bool)
1520 {
1521     Q_D(MyMoneyForecast);
1522     d->m_includeScheduledTransactions = _bool;
1523 }
1524 
1525 qint64 MyMoneyForecast::accountsCycle() const
1526 {
1527     Q_D(const MyMoneyForecast);
1528     return d->m_accountsCycle;
1529 }
1530 
1531 qint64 MyMoneyForecast::forecastCycles() const
1532 {
1533     Q_D(const MyMoneyForecast);
1534     return d->m_forecastCycles;
1535 }
1536 
1537 qint64 MyMoneyForecast::forecastDays() const
1538 {
1539     Q_D(const MyMoneyForecast);
1540     return d->m_forecastDays;
1541 }
1542 
1543 QDate MyMoneyForecast::beginForecastDate() const
1544 {
1545     Q_D(const MyMoneyForecast);
1546     return d->m_beginForecastDate;
1547 }
1548 
1549 qint64 MyMoneyForecast::beginForecastDay() const
1550 {
1551     Q_D(const MyMoneyForecast);
1552     return d->m_beginForecastDay;
1553 }
1554 
1555 QDate MyMoneyForecast::historyStartDate() const
1556 {
1557     Q_D(const MyMoneyForecast);
1558     return d->m_historyStartDate;
1559 }
1560 
1561 QDate MyMoneyForecast::historyEndDate() const
1562 {
1563     Q_D(const MyMoneyForecast);
1564     return d->m_historyEndDate;
1565 }
1566 
1567 QDate MyMoneyForecast::forecastStartDate() const
1568 {
1569     Q_D(const MyMoneyForecast);
1570     return d->m_forecastStartDate;
1571 }
1572 
1573 QDate MyMoneyForecast::forecastEndDate() const
1574 {
1575     Q_D(const MyMoneyForecast);
1576     return d->m_forecastEndDate;
1577 }
1578 
1579 bool MyMoneyForecast::skipOpeningDate() const
1580 {
1581     Q_D(const MyMoneyForecast);
1582     return d->m_skipOpeningDate;
1583 }
1584 
1585 int MyMoneyForecast::historyMethod() const
1586 {
1587     Q_D(const MyMoneyForecast);
1588     return d->m_historyMethod;
1589 }
1590 
1591 bool MyMoneyForecast::isIncludingUnusedAccounts() const
1592 {
1593     Q_D(const MyMoneyForecast);
1594     return d->m_includeUnusedAccounts;
1595 }
1596 
1597 bool MyMoneyForecast::isForecastDone() const
1598 {
1599     Q_D(const MyMoneyForecast);
1600     return d->m_forecastDone;
1601 }
1602 
1603 bool MyMoneyForecast::isIncludingFutureTransactions() const
1604 {
1605     Q_D(const MyMoneyForecast);
1606     return d->m_includeFutureTransactions;
1607 }
1608 
1609 bool MyMoneyForecast::isIncludingScheduledTransactions() const
1610 {
1611     Q_D(const MyMoneyForecast);
1612     return d->m_includeScheduledTransactions;
1613 }
1614 
1615 void MyMoneyForecast::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances)
1616 {
1617     if (schedule.type() == eMyMoney::Schedule::Type::LoanPayment) {
1618 
1619         //get amortization and interest autoCalc splits
1620         MyMoneySplit amortizationSplit = transaction.amortizationSplit();
1621         MyMoneySplit interestSplit = transaction.interestSplit();
1622         const bool interestSplitValid = !interestSplit.id().isEmpty();
1623 
1624         if (!amortizationSplit.id().isEmpty()) {
1625             MyMoneyAccountLoan acc(MyMoneyFile::instance()->account(amortizationSplit.accountId()));
1626             MyMoneyFinancialCalculator calc;
1627             QDate dueDate;
1628 
1629             // FIXME: setup dueDate according to when the interest should be calculated
1630             // current implementation: take the date of the next payment according to
1631             // the schedule. If the calculation is based on the payment reception, and
1632             // the payment is overdue then take the current date
1633             dueDate = schedule.nextDueDate();
1634             if (acc.interestCalculation() == MyMoneyAccountLoan::paymentReceived) {
1635                 if (dueDate < QDate::currentDate())
1636                     dueDate = QDate::currentDate();
1637             }
1638 
1639             // we need to calculate the balance at the time the payment is due
1640 
1641             MyMoneyMoney balance;
1642             if (balances.count() == 0)
1643                 balance = MyMoneyFile::instance()->balance(acc.id(), dueDate.addDays(-1));
1644             else
1645                 balance = balances[acc.id()];
1646 
1647             // FIXME: for now, we only support interest calculation at the end of the period
1648             calc.setBep();
1649             // FIXME: for now, we only support periodic compounding
1650             calc.setDisc();
1651 
1652             calc.setPF(MyMoneySchedule::eventsPerYear(schedule.baseOccurrence()));
1653             eMyMoney::Schedule::Occurrence compoundingOccurrence = static_cast<eMyMoney::Schedule::Occurrence>(acc.interestCompounding());
1654             if (compoundingOccurrence == eMyMoney::Schedule::Occurrence::Any)
1655                 compoundingOccurrence = schedule.baseOccurrence();
1656 
1657             calc.setCF(MyMoneySchedule::eventsPerYear(compoundingOccurrence));
1658 
1659             calc.setPv(balance.toDouble());
1660             calc.setIr(acc.interestRate(dueDate).abs().toDouble());
1661             calc.setPmt(acc.periodicPayment().toDouble());
1662 
1663             MyMoneyMoney interest(calc.interestDue(), 100), amortization;
1664             interest = interest.abs();    // make sure it's positive for now
1665             amortization = acc.periodicPayment() - interest;
1666 
1667             if (acc.accountType() == eMyMoney::Account::Type::AssetLoan) {
1668                 interest = -interest;
1669                 amortization = -amortization;
1670             }
1671 
1672             amortizationSplit.setShares(amortization);
1673             if (interestSplitValid)
1674                 interestSplit.setShares(interest);
1675 
1676             // FIXME: for now we only assume loans to be in the currency of the transaction
1677             amortizationSplit.setValue(amortization);
1678             if (interestSplitValid)
1679                 interestSplit.setValue(interest);
1680 
1681             transaction.modifySplit(amortizationSplit);
1682             if (interestSplitValid)
1683                 transaction.modifySplit(interestSplit);
1684         }
1685     }
1686 }
1687 
1688 QList<MyMoneyAccount> MyMoneyForecast::forecastAccountList()
1689 {
1690     auto file = MyMoneyFile::instance();
1691 
1692     QList<MyMoneyAccount> accList;
1693     //Get all accounts from the file and check if they are of the right type to calculate forecast
1694     file->accountList(accList);
1695     QList<MyMoneyAccount>::iterator accList_t = accList.begin();
1696     for (; accList_t != accList.end();) {
1697         auto acc = *accList_t;
1698         if (acc.isClosed()            //check the account is not closed
1699                 || (!acc.isAssetLiability())) {
1700             //|| (acc.accountType() == eMyMoney::Account::Type::Investment) ) {//check that it is not an Investment account and only include Stock accounts
1701             //remove the account if it is not of the correct type
1702             accList_t = accList.erase(accList_t);
1703         } else {
1704             ++accList_t;
1705         }
1706     }
1707     return accList;
1708 }