I've been trying to clean up some of the securities transactions in my
data file and in the process have discovered some problems in the
handling of lots and capital gains and fixed a few of them. I wanted
to create some proper bug reports for these, but I don't think I'm
going to have time to do that soon so I thought I would at least send
the patch here in case it is useful to anyone else. I realize that
this area is somewhat a work in progress, but perhaps this will help a
little.
There are several bugs fixed in this patch, and some other changes
which are more speculative. The most important fixes, it seems to me,
are the ones to gnc_lot_get_earliest_split and gnc_lot_get_latest_split
and the similar changes to xaccAccountFindEarliestOpenLot and
xaccAccountFindLatestOpenLot. Most of them initialize the starting
time in such a way that they fail to work correctly, at least in some
environments. xaccAccountFindEarliestOpenLot computes a large 64 bit
integer as a starting time, but at least on gcc 4.0.1 on MacOSX the
computation overflows and gives a small integer instead. This may be a
compiler bug, but it seems better to use the predefined constants for
this anyway.
The "latest" variants don't work since they use a negative starting
time but the time field is an unsigned 64 bit integer so it looks like
a very big starting value.
The most significant other change I made was to
xaccSplitComputeCapGains which now figures the basis for the sale based
on all the prior splits in the lot instead of just the opening split.
This works better if you have stock splits, previous partial sales, or
transactions (like spin-offs) which change the basis without changing
the number of shares. Of course it is still difficult to get such
transactions into the lot without editing the file by hand, but if you
manage to do so, this computes the capital gain more nearly correctly.
The other changes are mostly minor. I did change some of the existing
scrub to scrub lots too. This may not be a good idea given the current
state of lot scrubbing.
The patch is attached, for whatever it's worth. It seems to work ok
for me, but I don't use the business features which I think depend on
the lot infrastructure so I may have broken something I don't know
about.
--
Mike Alexander [EMAIL PROTECTED]
Ann Arbor, MI PGP key ID: BEA343A6
Index: src/engine/gnc-lot.c
===================================================================
--- src/engine/gnc-lot.c (revision 13771)
+++ src/engine/gnc-lot.c (working copy)
@@ -226,6 +226,40 @@
/* ============================================================= */
void
+gnc_lot_get_balance_before (GNCLot *lot, Split *split,
+ gnc_numeric *amount, gnc_numeric *value)
+{
+ GList *node;
+ gnc_numeric zero = gnc_numeric_zero();
+ gnc_numeric amt = zero;
+ gnc_numeric val = zero;
+
+ if (lot && lot->splits)
+ {
+ for (node = lot->splits; node; node = node->next)
+ {
+ Split *s = node->data;
+ Transaction *ta, *tb;
+ ta = xaccSplitGetParent (s);
+ tb = xaccSplitGetParent (split);
+ if ((ta == tb && s != split) ||
+ xaccTransOrder (ta, tb) < 0)
+ {
+ gnc_numeric tmpval = xaccSplitGetAmount (s);
+ amt = gnc_numeric_add_fixed (amt, tmpval);
+ tmpval = xaccSplitGetValue (s);
+ val = gnc_numeric_add_fixed (val, tmpval);
+ }
+ }
+ }
+
+ *amount = amt;
+ *value = val;
+}
+
+/* ============================================================= */
+
+void
gnc_lot_add_split (GNCLot *lot, Split *split)
{
Account * acc;
@@ -294,7 +328,7 @@
Timespec ts;
Split *earliest = NULL;
- ts.tv_sec = ((long long) LONG_MAX);
+ ts.tv_sec = ((long long) ULONG_MAX);
ts.tv_nsec = 0;
if (!lot) return NULL;
@@ -323,7 +357,7 @@
Timespec ts;
Split *latest = NULL;
- ts.tv_sec = -((long long) LONG_MAX);
+ ts.tv_sec = 0;
ts.tv_nsec = 0;
if (!lot) return NULL;
Index: src/engine/gnc-lot.h
===================================================================
--- src/engine/gnc-lot.h (revision 13771)
+++ src/engine/gnc-lot.h (working copy)
@@ -97,6 +97,13 @@
* of the account. */
gnc_numeric gnc_lot_get_balance (GNCLot *);
+/** The gnc_lot_get_balance_before routines computes both the balance and
+ * value in the lot considering only splits in transactions prior to the
+ * one containing the given split or other splits in the same transaction.
+ * The first return value is the amount and the second is the value. */
+void gnc_lot_get_balance_before (GNCLot *, Split *,
+ gnc_numeric *, gnc_numeric *);
+
/** The gnc_lot_is_closed() routine returns a boolean flag: is this
* lot closed? A lot is closed if its balance is zero. This
* routine is faster than using gnc_lot_get_balance() because
Index: src/engine/Transaction.c
===================================================================
--- src/engine/Transaction.c (revision 13771)
+++ src/engine/Transaction.c (working copy)
@@ -1014,6 +1014,8 @@
*/
if (!(trans->inst.do_free) && scrub_data &&
!qof_book_shutting_down(xaccTransGetBook(trans))) {
+ /* If scrubbing gains recurses through here, don't call it again. */
+ scrub_data = 0;
/* The total value of the transaction should sum to zero.
* Call the trans scrub routine to fix it. Indirectly, this
* routine also performs a number of other transaction fixes too.
@@ -1021,6 +1023,8 @@
xaccTransScrubImbalance (trans, NULL, NULL);
/* Get the cap gains into a consistent state as well. */
xaccTransScrubGains (trans, NULL);
+ /* Allow scrubbing in transaction commit again */
+ scrub_data = 1;
}
/* Record the time of last modification */
Index: src/engine/Scrub2.c
===================================================================
--- src/engine/Scrub2.c (revision 13771)
+++ src/engine/Scrub2.c (working copy)
@@ -413,6 +413,7 @@
Split *s = node->data;
if (xaccSplitGetLot (s) != lot) continue;
if (s == split) continue;
+ if (s->inst.do_free) continue;
/* OK, this split is in the same lot (and thus same account)
* as the indicated split. It must be a subsplit (although
Index: src/engine/cap-gains.c
===================================================================
--- src/engine/cap-gains.c (revision 13771)
+++ src/engine/cap-gains.c (working copy)
@@ -86,6 +86,9 @@
if (!acc) return FALSE;
+ if (xaccAccountIsPriced (acc))
+ return TRUE;
+
acc_comm = acc->commodity;
for (node=acc->splits; node; node=node->next)
@@ -158,7 +161,7 @@
static inline GNCLot *
xaccAccountFindOpenLot (Account *acc, gnc_numeric sign,
gnc_commodity *currency,
- gint64 guess,
+ guint64 guess,
gboolean (*date_pred)(Timespec, Timespec))
{
struct find_lot_s es;
@@ -184,7 +187,7 @@
ENTER (" sign=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, sign.num,
sign.denom);
lot = xaccAccountFindOpenLot (acc, sign, currency,
- G_GINT64_CONSTANT(2^31) * G_GINT64_CONSTANT(2^31),
earliest_pred);
+ G_MAXUINT64, earliest_pred);
LEAVE ("found lot=%p %s baln=%s", lot, gnc_lot_get_title (lot),
gnc_num_dbg_to_string(gnc_lot_get_balance(lot)));
return lot;
@@ -199,8 +202,7 @@
sign.num, sign.denom);
lot = xaccAccountFindOpenLot (acc, sign, currency,
- G_GINT64_CONSTANT(-2^31) * G_GINT64_CONSTANT(2^31),
- latest_pred);
+ 0, latest_pred);
LEAVE ("found lot=%p %s", lot, gnc_lot_get_title (lot));
return lot;
}
@@ -588,12 +590,17 @@
GNCPolicy *pcy;
if (!split) return FALSE;
+
+ /* If this split already belongs to a lot or the account doesn't
+ * have lots, we are done.
+ */
+ if (split->lot) return FALSE;
+ acc = split->acc;
+ if (!xaccAccountHasTrades (acc))
+ return FALSE;
ENTER ("(split=%p)", split);
- /* If this split already belongs to a lot, we are done. */
- if (split->lot) return FALSE;
- acc = split->acc;
pcy = acc->policy;
xaccAccountBeginEdit (acc);
@@ -657,6 +664,7 @@
gnc_numeric value = zero;
gnc_numeric frac;
gnc_numeric opening_amount, opening_value;
+ gnc_numeric lot_amount, lot_value;
gnc_commodity *opening_currency;
if (!split) return;
@@ -705,9 +713,15 @@
return;
}
+ if (safe_strcmp ("stock-split", xaccSplitGetType (split)) == 0)
+ {
+ LEAVE ("Stock split split, returning.");
+ return;
+ }
+
if (GAINS_STATUS_GAINS & split->gains)
{
- Split *s;
+ Split *s;
PINFO ("split is a gains recording split, switch over");
/* If this is the split that records the gains, then work with
* the split that generates the gains.
@@ -730,7 +744,7 @@
xaccTransDestroy (trans);
#endif
}
- split = s;
+ split = s;
}
/* Note: if the value of the 'opening' split(s) has changed,
@@ -826,19 +840,23 @@
return;
}
- /* The cap gains is the difference between the value of the
- * opening split, and the current split, pro-rated for an equal
+ /* The cap gains is the difference between the basis prior to the
+ * current split, and the current split, pro-rated for an equal
* amount of shares.
- * i.e. purchase_price = opening_value / opening_amount
- * cost_basis = purchase_price * current_amount
- * cap_gain = current_value - cost_basis
+ * i.e. purchase_price = lot_value / lot_amount
+ * cost_basis = purchase_price * current_split_amount
+ * cap_gain = current_split_value - cost_basis
*/
- frac = gnc_numeric_div (split->amount, opening_amount,
+ gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
+ /* Fraction of the lot that this split represents: */
+ frac = gnc_numeric_div (split->amount, lot_amount,
GNC_DENOM_AUTO,
GNC_HOW_DENOM_REDUCE);
- value = gnc_numeric_mul (frac, opening_value,
+ /* Basis for this split: */
+ value = gnc_numeric_mul (frac, lot_value,
gnc_numeric_denom(opening_value),
GNC_HOW_DENOM_EXACT|GNC_HOW_RND_ROUND);
+ /* Capital gain for this split: */
value = gnc_numeric_sub (value, split->value,
GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
PINFO ("Open amt=%s val=%s; split amt=%s val=%s; gains=%s\n",
@@ -1124,7 +1142,10 @@
{
gboolean altered = FALSE;
split->gains |= ~GAINS_STATUS_ADIRTY;
- if (split->lot) altered = xaccScrubLot (split->lot);
+ if (split->lot)
+ altered = xaccScrubLot (split->lot);
+ else
+ altered = xaccSplitAssign (split);
if (altered) goto restart;
}
}
Index: src/gnome/window-reconcile.c
===================================================================
--- src/gnome/window-reconcile.c (revision 13771)
+++ src/gnome/window-reconcile.c (working copy)
@@ -37,6 +37,7 @@
#include <glib/gi18n.h>
#include "Scrub.h"
+#include "Scrub3.h"
#include "dialog-account.h"
#include "dialog-transfer.h"
#include "dialog-utils.h"
@@ -1230,6 +1231,8 @@
xaccAccountTreeScrubOrphans (account);
xaccAccountTreeScrubImbalance (account);
+
+ xaccAccountTreeScrubLots (account);
gnc_resume_gui_refresh ();
}
Index: src/gnome/gnc-plugin-page-account-tree.c
===================================================================
--- src/gnome/gnc-plugin-page-account-tree.c (revision 13771)
+++ src/gnome/gnc-plugin-page-account-tree.c (working copy)
@@ -43,6 +43,7 @@
#include "gnc-plugin-page-register.h"
#include "Scrub.h"
+#include "Scrub3.h"
#include "Transaction.h"
#include "dialog-account.h"
#include "dialog-transfer.h"
@@ -1191,6 +1192,8 @@
xaccAccountScrubOrphans (account);
xaccAccountScrubImbalance (account);
+
+ xaccAccountScrubLots (account);
gnc_resume_gui_refresh ();
}
@@ -1206,6 +1209,8 @@
xaccAccountTreeScrubOrphans (account);
xaccAccountTreeScrubImbalance (account);
+
+ xaccAccountTreeScrubLots (account);
gnc_resume_gui_refresh ();
}
@@ -1219,6 +1224,10 @@
xaccGroupScrubOrphans (group);
xaccGroupScrubImbalance (group);
+
+ xaccGroupScrubLots (group);
+
+ gnc_resume_gui_refresh ();
}
/** @} */
_______________________________________________
gnucash-devel mailing list
gnucash-devel@gnucash.org
https://lists.gnucash.org/mailman/listinfo/gnucash-devel