Capital gains and lots fixes

Mike Alexander mta at umich.edu
Thu Apr 13 00:39:43 EDT 2006


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           mta at umich.edu
Ann Arbor, MI            PGP key ID: BEA343A6
-------------- next part --------------
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 ();
 }
 
 /** @} */


More information about the gnucash-devel mailing list