You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmymoney/kmymoney2/mymoney/mymoneyforecast.cpp

1341 lines
48 KiB

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