diff -r db0e341384e1 -r 64f48a8c758c advportfolio.scm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/advportfolio.scm Sun Jan 03 20:24:28 2016 -0500 @@ -0,0 +1,1219 @@ +;; -*-scheme-*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; advanced-portfolio.scm +;; by Martijn van Oosterhout (kleptog@svana.org) Feb 2002 +;; modified for GnuCash 1.8 by Herbert Thoma (herbie@hthoma.de) Oct 2002 +;; +;; Heavily based on portfolio.scm +;; by Robert Merkel (rgmerk@mira.net) +;; +;; 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. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, contact: +;; +;; Free Software Foundation Voice: +1-617-542-5942 +;; 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 +;; Boston, MA 02110-1301, USA gnu@gnu.org +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define-module (local my-advanced-portfolio)) + +(use-modules (gnucash main)) ;; FIXME: delete after we finish modularizing. +(use-modules (srfi srfi-1)) +(use-modules (gnucash gnc-module)) +(use-modules (gnucash gettext)) + +(use-modules (gnucash printf)) + +(gnc:module-load "gnucash/report/report-system" 0) + +(define reportname (N_ "My Advanced Portfolio")) + +(define optname-price-source (N_ "Price Source")) +(define optname-shares-digits (N_ "Share decimal places")) +(define optname-zero-shares (N_ "Include accounts with no shares")) +(define optname-show-symbol (N_ "Show ticker symbols")) +(define optname-show-listing (N_ "Show listings")) +(define optname-show-price (N_ "Show prices")) +(define optname-show-shares (N_ "Show number of shares")) +(define optname-basis-method (N_ "Basis calculation method")) +(define optname-prefer-pricelist (N_ "Set preference for price list data")) +(define optname-brokerage-fees (N_ "How to report brokerage fees")) + +;; To avoid overflows in our calculations, define a denominator for prices and unit values +(define price-denom 100000000) +(define units-denom 100000000) + +(define (options-generator) + (let* ((options (gnc:new-options)) + ;; This is just a helper function for making options. + ;; See gnucash/src/scm/options.scm for details. + (add-option + (lambda (new-option) + (gnc:register-option options new-option)))) + + ;; General Tab + ;; date at which to report balance + (gnc:options-add-report-date! + options gnc:pagename-general + (N_ "Date") "a") + + (gnc:options-add-currency! + options gnc:pagename-general (N_ "Report's currency") "c") + + (add-option + (gnc:make-multichoice-option + gnc:pagename-general optname-price-source + "d" (N_ "The source of price information.") 'pricedb-nearest + (list (vector 'pricedb-latest + (N_ "Most recent") + (N_ "The most recent recorded price.")) + (vector 'pricedb-nearest + (N_ "Nearest in time") + (N_ "The price recorded nearest in time to the report date.")) + ))) + + (add-option + (gnc:make-multichoice-option + gnc:pagename-general optname-basis-method + "e" (N_ "Basis calculation method.") 'average-basis + (list (vector 'average-basis + (N_ "Average") + (N_ "Use average cost of all shares for basis.")) + (vector 'fifo-basis + (N_ "FIFO") + (N_ "Use first-in first-out method for basis.")) + (vector 'filo-basis + (N_ "LIFO") + (N_ "Use last-in first-out method for basis.")) + ))) + + (add-option + (gnc:make-simple-boolean-option + gnc:pagename-general optname-prefer-pricelist "f" + (N_ "Prefer use of price editor pricing over transactions, where applicable.") + #t)) + + (add-option + (gnc:make-multichoice-option + gnc:pagename-general optname-brokerage-fees + "g" (N_ "How to report commissions and other brokerage fees.") 'include-in-basis + (list (vector 'include-in-basis + (N_ "Include in basis") + (N_ "Include brokerage fees in the basis for the asset.")) + (vector 'include-in-gain + (N_ "Include in gain") + (N_ "Include brokerage fees in the gain and loss but not in the basis.")) + (vector 'ignore-brokerage + (N_ "Ignore") + (N_ "Ignore brokerage fees entirely.")) + ))) + + (gnc:register-option + options + (gnc:make-simple-boolean-option + gnc:pagename-display optname-show-symbol "a" + (N_ "Display the ticker symbols.") + #t)) + + (gnc:register-option + options + (gnc:make-simple-boolean-option + gnc:pagename-display optname-show-listing "b" + (N_ "Display exchange listings.") + #t)) + + (gnc:register-option + options + (gnc:make-simple-boolean-option + gnc:pagename-display optname-show-shares "c" + (N_ "Display numbers of shares in accounts.") + #t)) + + (add-option + (gnc:make-number-range-option + gnc:pagename-display optname-shares-digits + "d" (N_ "The number of decimal places to use for share numbers.") 2 + 0 6 0 1)) + + (gnc:register-option + options + (gnc:make-simple-boolean-option + gnc:pagename-display optname-show-price "e" + (N_ "Display share prices.") + #t)) + + ;; Account tab + (add-option + (gnc:make-account-list-option + gnc:pagename-accounts (N_ "Accounts") + "b" + (N_ "Stock Accounts to report on.") + (lambda () (filter gnc:account-is-stock? + (gnc-account-get-descendants-sorted + (gnc-get-current-root-account)))) + (lambda (accounts) (list #t + (filter gnc:account-is-stock? accounts))) + #t)) + + (gnc:register-option + options + (gnc:make-simple-boolean-option + gnc:pagename-accounts optname-zero-shares "e" + (N_ "Include accounts that have a zero share balances.") + #f)) + + (gnc:options-set-default-section options gnc:pagename-general) + options)) + +;; This is the rendering function. It accepts a database of options +;; and generates an object of type . See the file +;; report-html.txt for documentation; the file report-html.scm +;; includes all the relevant Scheme code. The option database passed +;; to the function is one created by the options-generator function +;; defined above. + +(define (advanced-portfolio-renderer report-obj) + + (let ((work-done 0) + (work-to-do 0) + (warn-no-price #f) + (warn-price-dirty #f)) + + ;; These are some helper functions for looking up option values. + (define (get-op section name) + (gnc:lookup-option (gnc:report-options report-obj) section name)) + + (define (get-option section name) + (gnc:option-value (get-op section name))) + + (define (split-account-type? split type) + (eq? type (xaccAccountGetType (xaccSplitGetAccount split)))) + + (define (same-split? s1 s2) + (equal? (gncSplitGetGUID s1) (gncSplitGetGUID s2))) + + (define (same-account? a1 a2) + (equal? (gncAccountGetGUID a1) (gncAccountGetGUID a2))) + + (define (same-account-code? a1 a2) + (equal? (xaccAccountGetCode a1) (xaccAccountGetCode a2))) + + ;; sum up the contents of the b-list built by basis-builder below + (define (sum-basis b-list currency-frac) + (if (not (eqv? b-list '())) + (gnc-numeric-add (gnc-numeric-mul (caar b-list) (cdar b-list) currency-frac GNC-RND-ROUND) + (sum-basis (cdr b-list) currency-frac) currency-frac GNC-RND-ROUND) + (gnc-numeric-zero) + ) + ) + + ;; sum up the total number of units in the b-list built by basis-builder below + (define (units-basis b-list) + (if (not (eqv? b-list '())) + (gnc-numeric-add (caar b-list) (units-basis (cdr b-list)) + units-denom GNC-RND-ROUND) + (gnc-numeric-zero) + ) + ) + + ;; apply a ratio to an existing basis-list, useful for splits/mergers and spinoffs + ;; I need to get a brain and use (map) for this. + (define (apply-basis-ratio b-list units-ratio value-ratio) + (if (not (eqv? b-list '())) + (cons (cons (gnc-numeric-mul units-ratio (caar b-list) units-denom GNC-RND-ROUND) + (gnc-numeric-mul value-ratio (cdar b-list) price-denom GNC-RND-ROUND)) + (apply-basis-ratio (cdr b-list) units-ratio value-ratio)) + '() + ) + ) + + ;; this builds a list for basis calculation and handles average, fifo and lifo methods + ;; the list is cons cells of (units-of-stock . price-per-unit)... average method produces only one + ;; cell that mutates to the new average. Need to add a date checker so that we allow for prices + ;; coming in out of order, such as a transfer with a price adjusted to carryover the basis. + (define (basis-builder b-list b-units b-value b-method currency-frac) + (gnc:debug "actually in basis-builder") + (gnc:debug "b-list is " b-list " b-units is " (gnc-numeric-to-string b-units) + " b-value is " (gnc-numeric-to-string b-value) " b-method is " b-method) + + ;; if there is no b-value, then this is a split/merger and needs special handling + (cond + + ;; we have value and positive units, add units to basis + ((and (not (gnc-numeric-zero-p b-value)) + (gnc-numeric-positive-p b-units)) + (case b-method + ((average-basis) + (if (not (eqv? b-list '())) + (list (cons (gnc-numeric-add b-units + (caar b-list) units-denom GNC-RND-ROUND) + (gnc-numeric-div + (gnc-numeric-add b-value + (gnc-numeric-mul (caar b-list) + (cdar b-list) + GNC-DENOM-AUTO GNC-DENOM-REDUCE) + GNC-DENOM-AUTO GNC-DENOM-REDUCE) + (gnc-numeric-add b-units + (caar b-list) GNC-DENOM-AUTO GNC-DENOM-REDUCE) + price-denom GNC-RND-ROUND))) + (append b-list + (list (cons b-units (gnc-numeric-div + b-value b-units price-denom GNC-RND-ROUND)))))) + (else (append b-list + (list (cons b-units (gnc-numeric-div + b-value b-units price-denom GNC-RND-ROUND))))))) + + ;; we have value and negative units, remove units from basis + ((and (not (gnc-numeric-zero-p b-value)) + (gnc-numeric-negative-p b-units)) + (if (not (eqv? b-list '())) + (case b-method + ((fifo-basis) + (case (gnc-numeric-compare (gnc-numeric-abs b-units) (caar b-list)) + ((-1) + ;; Sold less than the first lot, create a new first lot from the remainder + (let ((new-units (gnc-numeric-add b-units (caar b-list) units-denom GNC-RND-ROUND))) + (cons (cons new-units (cdar b-list)) (cdr b-list)))) + ((0) + ;; Sold all of the first lot + (cdr b-list)) + ((1) + ;; Sold more than the first lot, delete it and recurse + (basis-builder (cdr b-list) (gnc-numeric-add b-units (caar b-list) units-denom GNC-RND-ROUND) + b-value ;; Only the sign of b-value matters since the new b-units is negative + b-method currency-frac)))) + ((filo-basis) + (let ((rev-b-list (reverse b-list))) + (case (gnc-numeric-compare (gnc-numeric-abs b-units) (caar rev-b-list)) + ((-1) + ;; Sold less than the last lot + (let ((new-units (gnc-numeric-add b-units (caar rev-b-list) units-denom GNC-RND-ROUND))) + (reverse (cons (cons new-units (cdar rev-b-list)) (cdr rev-b-list))))) + ((0) + ;; Sold all of the last lot + (reverse (cdr rev-b-list)) + ) + ((1) + ;; Sold more than the last lot + (basis-builder (reverse (cdr rev-b-list)) (gnc-numeric-add b-units (caar rev-b-list) units-denom GNC-RND-ROUND) + b-value b-method currency-frac) + )))) + ((average-basis) + (list (cons (gnc-numeric-add + (caar b-list) b-units units-denom GNC-RND-ROUND) + (cdar b-list))))) + '() + )) + + ;; no value, just units, this is a split/merge... + ((and (gnc-numeric-zero-p b-value) + (not (gnc-numeric-zero-p b-units))) + (let* ((current-units (units-basis b-list)) + (units-ratio (gnc-numeric-div (gnc-numeric-add b-units current-units GNC-DENOM-AUTO GNC-DENOM-REDUCE) + current-units GNC-DENOM-AUTO GNC-DENOM-REDUCE)) + ;; If the units ratio is zero the stock is worthless and the value should be zero too + (value-ratio (if (gnc-numeric-zero-p units-ratio) + (gnc-numeric-zero) + (gnc-numeric-div (gnc:make-gnc-numeric 1 1) units-ratio GNC-DENOM-AUTO GNC-DENOM-REDUCE)))) + + (gnc:debug "blist is " b-list " current units is " + (gnc-numeric-to-string current-units) + " value ratio is " (gnc-numeric-to-string value-ratio) + " units ratio is " (gnc-numeric-to-string units-ratio)) + (apply-basis-ratio b-list units-ratio value-ratio) + )) + + ;; If there are no units, just a value, then its a spin-off, + ;; calculate a ratio for the values, but leave the units alone + ;; with a ratio of 1 + ((and (gnc-numeric-zero-p b-units) + (not (gnc-numeric-zero-p b-value))) + (let* ((current-value (sum-basis b-list GNC-DENOM-AUTO)) + (value-ratio (gnc-numeric-div (gnc-numeric-add b-value current-value GNC-DENOM-AUTO GNC-DENOM-REDUCE) + current-value GNC-DENOM-AUTO GNC-DENOM-REDUCE))) + + (gnc:debug "this is a spinoff") + (gnc:debug "blist is " b-list " value ratio is " (gnc-numeric-to-string value-ratio)) + (apply-basis-ratio b-list (gnc:make-gnc-numeric 1 1) value-ratio)) + ) + + ;; when all else fails, just send the b-list back + (else + b-list) + ) + ) + + ;; Given a price list and a currency find the price for that currency on the list. + ;; If there is none for the requested currency, return the first one. + ;; The price list is released but the price returned is ref counted. + (define (find-price price-list currency) + (if (eqv? price-list '()) #f + (let ((price (car price-list))) + (for-each + (lambda (p) + (if (gnc-commodity-equiv currency (gnc-price-get-currency p)) + (set! price p))) + price-list) + (gnc-price-ref price) + (gnc-price-list-destroy price-list) + price))) + + ;; Return true if either account is the parent of the other or they are siblings + (define (parent-or-sibling? a1 a2) + (let ((a2parent (gnc-account-get-parent a2)) + (a1parent (gnc-account-get-parent a1))) + (or (same-account? a2parent a1) + (same-account? a1parent a2) + (same-account? a1parent a2parent)))) + + ;; Test whether the given split is the source of a spin off transaction + ;; This will be a no-units split with only one other split. + ;; xaccSplitGetOtherSplit only returns on a two-split txn. It's not a spinoff + ;; is the other split is in an income or expense account. + (define (spin-off? split current) + (let ((other-split (xaccSplitGetOtherSplit split))) + (and (gnc-numeric-zero-p (xaccSplitGetAmount split)) + (same-account? current (xaccSplitGetAccount split)) + (not (null? other-split)) + (not (split-account-type? other-split ACCT-TYPE-EXPENSE)) + (not (split-account-type? other-split ACCT-TYPE-INCOME))))) + + +(define (table-add-stock-rows table accounts to-date + currency price-fn exchange-fn price-source + include-empty show-symbol show-listing show-shares show-price + basis-method prefer-pricelist handle-brokerage-fees + total-basis total-value + total-moneyin total-moneyout total-income total-gain + total-ugain total-brokerage) + + (let ((share-print-info + (gnc-share-print-info-places + (inexact->exact (get-option gnc:pagename-display + optname-shares-digits))))) + + (define (table-add-stock-rows-internal accounts odd-row?) + (if (null? accounts) total-value + (let* ((row-style (if odd-row? "normal-row" "alternate-row")) + (current (car accounts)) + (rest (cdr accounts)) + ;; commodity is the actual stock/thing we are looking at + (commodity (xaccAccountGetCommodity current)) + (ticker-symbol (gnc-commodity-get-mnemonic commodity)) + (listing (gnc-commodity-get-namespace commodity)) + (unit-collector (gnc:account-get-comm-balance-at-date + current to-date #f)) + (units (cadr (unit-collector 'getpair commodity #f))) + + ;; Counter to keep track of stuff + (brokeragecoll (gnc:make-commodity-collector)) + (dividendcoll (gnc:make-commodity-collector)) + (moneyincoll (gnc:make-commodity-collector)) + (moneyoutcoll (gnc:make-commodity-collector)) + (gaincoll (gnc:make-commodity-collector)) + + + ;; the price of the commodity at the time of the report + (price (price-fn commodity currency to-date)) + ;; the value of the commodity, expressed in terms of + ;; the report's currency. + (value (gnc:make-gnc-monetary currency (gnc-numeric-zero))) ;; Set later + (currency-frac (gnc-commodity-get-fraction currency)) + + (pricing-txn #f) + (use-txn #f) + (basis-list '()) + ;; setup an alist for the splits we've already seen. + (seen_trans '()) + ;; Account used to hold remainders from income reinvestments and + ;; running total of amount moved there + (drp-holding-account #f) + (drp-holding-amount (gnc-numeric-zero)) + ) + + (define (my-exchange-fn fromunits tocurrency) + (if (and (gnc-commodity-equiv currency tocurrency) + (gnc-commodity-equiv (gnc:gnc-monetary-commodity fromunits) commodity)) + ;; Have a price for this commodity, but not necessarily in the report's + ;; currency. Get the value in the commodity's currency and convert it to + ;; report currency. + (exchange-fn + ;; This currency will usually be the same as tocurrency so the + ;; call to exchange-fn below will do nothing + (gnc:make-gnc-monetary + (if use-txn + (gnc:gnc-monetary-commodity price) + (gnc-price-get-currency price)) + (gnc-numeric-mul (gnc:gnc-monetary-amount fromunits) + (if use-txn + (gnc:gnc-monetary-amount price) + (gnc-price-get-value price)) + currency-frac GNC-RND-ROUND)) + tocurrency) + (exchange-fn fromunits tocurrency))) + + (gnc:debug "Starting account " (xaccAccountGetName current) ", initial price: " + (if price + (gnc-commodity-value->string + (list (gnc-price-get-currency price) (gnc-price-get-value price))) + #f)) + + ;; If we have a price that can't be converted to the report currency + ;; don't use it + (if (and price (gnc-numeric-zero-p (gnc:gnc-monetary-amount + (exchange-fn + (gnc:make-gnc-monetary + (gnc-price-get-currency price) + (gnc:make-gnc-numeric 100 1)) + currency)))) + (set! price #f)) + + ;; If we are told to use a pricing transaction, or if we don't have a price + ;; from the price DB, find a good transaction to use. + (if (and (not use-txn) + (or (not price) (not prefer-pricelist))) + (let ((split-list (reverse (gnc:get-match-commodity-splits-sorted + (list current) + (case price-source + ((pricedb-latest) (gnc:get-today)) + ((pricedb-nearest) to-date) + (else (gnc:get-today))) ;; error, but don't crash + #f)))) ;; Any currency + ;; Find the first (most recent) one that can be converted to report currency + (while (and (not use-txn) (not (eqv? split-list '()))) + (let ((split (car split-list))) + (if (and (not (gnc-numeric-zero-p (xaccSplitGetAmount split))) + (not (gnc-numeric-zero-p (xaccSplitGetValue split)))) + (let* ((trans (xaccSplitGetParent split)) + (trans-currency (xaccTransGetCurrency trans)) + (trans-price (exchange-fn (gnc:make-gnc-monetary + trans-currency + (xaccSplitGetSharePrice split)) + currency))) + (if (not (gnc-numeric-zero-p (gnc:gnc-monetary-amount trans-price))) + ;; We can exchange the price from this transaction into the report currency + (begin + (if price (gnc-price-unref price)) + (set! pricing-txn trans) + (set! price trans-price) + (gnc:debug "Transaction price is " (gnc:monetary->string price)) + (set! use-txn #t)) + (set! split-list (cdr split-list)))) + (set! split-list (cdr split-list))) + )))) + + ;; If we still don't have a price, use a price of 1 and complain later + (if (not price) + (begin + (set! price (gnc:make-gnc-monetary currency (gnc:make-gnc-numeric 1 1))) + ;; If use-txn is set, but pricing-txn isn't set, it's a bogus price + (set! use-txn #t) + (set! pricing-txn #f) + ) + ) + + ;; Now that we have a pricing transaction if needed, set the value of the asset + (set! value (my-exchange-fn (gnc:make-gnc-monetary commodity units) currency)) + (gnc:debug "Value " (gnc:monetary->string value) + " from " (gnc-commodity-numeric->string commodity units)) + + (for-each + ;; we're looking at each split we find in the account. these splits + ;; could refer to the same transaction, so we have to examine each + ;; split, determine what kind of split it is and then act accordingly. + (lambda (split) + (set! work-done (+ 1 work-done)) + (gnc:report-percent-done (* 100 (/ work-done work-to-do))) + + (let* ((parent (xaccSplitGetParent split)) + (txn-date (gnc-transaction-get-date-posted parent)) + (commod-currency (xaccTransGetCurrency parent)) + (commod-currency-frac (gnc-commodity-get-fraction commod-currency))) + + (if (and (gnc:timepair-le txn-date to-date) + (not (assoc-ref seen_trans (gncTransGetGUID parent)))) + (let ((trans-income (gnc-numeric-zero)) + (trans-brokerage (gnc-numeric-zero)) + (trans-shares (gnc-numeric-zero)) + (shares-bought (gnc-numeric-zero)) + (trans-sold (gnc-numeric-zero)) + (trans-bought (gnc-numeric-zero)) + (trans-spinoff (gnc-numeric-zero)) + (trans-drp-residual (gnc-numeric-zero)) + (trans-drp-account #f)) + + (gnc:debug "Transaction " (xaccTransGetDescription parent)) + ;; Add this transaction to the list of processed transactions so we don't + ;; do it again if there is another split in it for this account + (set! seen_trans (acons (gncTransGetGUID parent) #t seen_trans)) + + ;; Go through all the splits in the transaction to get an overall idea of + ;; what it does in terms of income, money in or out, shares bought or sold, etc. + (for-each + (lambda (s) + (let ((split-units (xaccSplitGetAmount s)) + (split-value (xaccSplitGetValue s))) + + (cond + ((and + (split-account-type? s ACCT-TYPE-EXPENSE) + (same-account-code? current (xaccSplitGetAccount s))) + ;; Brokerage expense unless a two split transaction with other split + ;; in the stock account in which case it's a stock donation to charity. + (gnc:debug "Pass 1: Expense: split units " (gnc-numeric-to-string split-units) " split-value " + (gnc-numeric-to-string split-value) " commod-currency " + (gnc-commodity-get-printname commod-currency)) + (if (not (same-account? current (xaccSplitGetAccount (xaccSplitGetOtherSplit s)))) + (set! trans-brokerage + (gnc-numeric-add trans-brokerage split-value commod-currency-frac GNC-RND-ROUND)))) + + ((and + (split-account-type? s ACCT-TYPE-INCOME) + (same-account-code? current (xaccSplitGetAccount s))) + (gnc:debug "Pass 1: Income: split units " (gnc-numeric-to-string split-units) " split-value " + (gnc-numeric-to-string split-value) " commod-currency " + (gnc-commodity-get-printname commod-currency)) + (set! trans-income + (gnc-numeric-sub trans-income split-value + commod-currency-frac GNC-RND-ROUND))) + + ((same-account? current (xaccSplitGetAccount s)) + (gnc:debug "Pass 1: Same Account: split units " (gnc-numeric-to-string split-units) " split-value " + (gnc-numeric-to-string split-value) " commod-currency " + (gnc-commodity-get-printname commod-currency)) + (set! trans-shares (gnc-numeric-add trans-shares (gnc-numeric-abs split-units) + units-denom GNC-RND-ROUND)) + (if (gnc-numeric-zero-p split-units) + (if (spin-off? s current) + ;; Count money used in a spin off as money out + (if (gnc-numeric-negative-p split-value) + (set! trans-spinoff (gnc-numeric-sub trans-spinoff split-value + commod-currency-frac GNC-RND-ROUND))) + (if (not (gnc-numeric-zero-p split-value)) + ;; Gain/loss split (amount zero, value non-zero, and not spinoff). There will be + ;; a corresponding income split that will incorrectly be added to trans-income + ;; Fix that by subtracting it here + (set! trans-income (gnc-numeric-sub trans-income split-value + commod-currency-frac GNC-RND-ROUND)))) + ;; Non-zero amount, add the value to the sale or purchase total. + (if (gnc-numeric-positive-p split-value) + (begin + (set! trans-bought + (gnc-numeric-add trans-bought split-value commod-currency-frac GNC-RND-ROUND)) + (set! shares-bought + (gnc-numeric-add shares-bought split-units units-denom GNC-RND-ROUND))) + (set! trans-sold + (gnc-numeric-sub trans-sold split-value commod-currency-frac GNC-RND-ROUND))))) + + ((and + (split-account-type? s ACCT-TYPE-ASSET) + (same-account-code? current (xaccSplitGetAccount s))) + (gnc:debug "Pass 1: Assets: split units " (gnc-numeric-to-string split-units) " split-value " + (gnc-numeric-to-string split-value) " commod-currency " + (gnc-commodity-get-printname commod-currency)) + ;; If all the asset accounts mentioned in the transaction are siblings of each other + ;; keep track of the money transfered to them if it is in the correct currency + (if (not trans-drp-account) + (begin + (set! trans-drp-account (xaccSplitGetAccount s)) + (if (gnc-commodity-equiv commod-currency (xaccAccountGetCommodity trans-drp-account)) + (set! trans-drp-residual split-value) + (set! trans-drp-account 'none))) + (if (not (eq? trans-drp-account 'none)) + (if (parent-or-sibling? trans-drp-account (xaccSplitGetAccount s)) + (set! trans-drp-residual (gnc-numeric-add trans-drp-residual split-value + commod-currency-frac GNC-RND-ROUND)) + (set! trans-drp-account 'none)))))) + )) + (xaccTransGetSplitList parent) + ) + + (gnc:debug "Income: " (gnc-numeric-to-string trans-income) + " Brokerage: " (gnc-numeric-to-string trans-brokerage) + " Shares traded: " (gnc-numeric-to-string trans-shares) + " Shares bought: " (gnc-numeric-to-string shares-bought)) + (gnc:debug " Value sold: " (gnc-numeric-to-string trans-sold) + " Value purchased: " (gnc-numeric-to-string trans-bought) + " Spinoff value " (gnc-numeric-to-string trans-spinoff) + " Trans DRP residual: " (gnc-numeric-to-string trans-drp-residual)) + + ;; We need to calculate several things for this transaction: + ;; 1. Total income: this is already in trans-income + ;; 2. Change in basis: calculated by loop below that looks at every + ;; that acquires or disposes of shares + ;; 3. Realized gain: also calculated below while calculating basis + ;; 4. Money in to the account: this is the value of shares bought + ;; except those purchased with reinvested income + ;; 5. Money out: the money received by disposing of shares. This + ;; is in trans-sold plus trans-spinoff + ;; 6. Brokerage fees: this is in trans-brokerage + + ;; Income + (dividendcoll 'add commod-currency trans-income) + + ;; Brokerage fees. May be either ignored or part of basis, but that + ;; will be dealt with elsewhere. + (brokeragecoll 'add commod-currency trans-brokerage) + + ;; Add brokerage fees to trans-bought if not ignoring them and there are any + (if (and (not (eq? handle-brokerage-fees 'ignore-brokerage)) + (gnc-numeric-positive-p trans-brokerage) + (gnc-numeric-positive-p trans-shares)) + (let* ((fee-frac (gnc-numeric-div shares-bought trans-shares GNC-DENOM-AUTO GNC-DENOM-REDUCE)) + (fees (gnc-numeric-mul trans-brokerage fee-frac commod-currency-frac GNC-RND-ROUND))) + (set! trans-bought (gnc-numeric-add trans-bought fees commod-currency-frac GNC-RND-ROUND)))) + + ;; Update the running total of the money in the DRP residual account. This is relevant + ;; if this is a reinvestment transaction (both income and purchase) and there seems to + ;; asset accounts used to hold excess income. + (if (and trans-drp-account + (not (eq? trans-drp-account 'none)) + (gnc-numeric-positive-p trans-income) + (gnc-numeric-positive-p trans-bought)) + (if (not drp-holding-account) + (begin + (set! drp-holding-account trans-drp-account) + (set! drp-holding-amount trans-drp-residual)) + (if (and (not (eq? drp-holding-account 'none)) + (parent-or-sibling? trans-drp-account drp-holding-account)) + (set! drp-holding-amount (gnc-numeric-add drp-holding-amount trans-drp-residual + commod-currency-frac GNC-RND-ROUND)) + (begin + ;; Wrong account (or no account), assume there isn't a DRP holding account + (set! drp-holding-account 'none) + (set trans-drp-residual (gnc-numeric-zero)) + (set! drp-holding-amount (gnc-numeric-zero)))))) + + ;; Set trans-bought to the amount of money moved in to the account which was used to + ;; purchase more shares. If this is not a DRP transaction then all money used to purchase + ;; shares is money in. + (if (and (gnc-numeric-positive-p trans-income) + (gnc-numeric-positive-p trans-bought)) + (begin + (set! trans-bought (gnc-numeric-sub trans-bought trans-income + commod-currency-frac GNC-RND-ROUND)) + (set! trans-bought (gnc-numeric-add trans-bought trans-drp-residual + commod-currency-frac GNC-RND-ROUND)) + (set! trans-bought (gnc-numeric-sub trans-bought drp-holding-amount + commod-currency-frac GNC-RND-ROUND)) + ;; If the DRP holding account balance is negative, adjust it by the amount + ;; used in this transaction + (if (and (gnc-numeric-negative-p drp-holding-amount) + (gnc-numeric-positive-p trans-bought)) + (set! drp-holding-amount (gnc-numeric-add drp-holding-amount trans-bought + commod-currency-frac GNC-RND-ROUND))) + ;; Money in is never more than amount spent to purchase shares + (if (gnc-numeric-negative-p trans-bought) + (set! trans-bought (gnc-numeric-zero))))) + + (gnc:debug "Adjusted trans-bought " (gnc-numeric-to-string trans-bought) + " DRP holding account " (gnc-numeric-to-string drp-holding-amount)) + + (moneyincoll 'add commod-currency trans-bought) + (moneyoutcoll 'add commod-currency trans-sold) + (moneyoutcoll 'add commod-currency trans-spinoff) + + ;; Look at splits again to handle changes in basis and realized gains + (for-each + (lambda (s) + (let + ;; get the split's units and value + ((split-units (xaccSplitGetAmount s)) + (split-value (xaccSplitGetValue s))) + + (gnc:debug "Pass 2: split units " (gnc-numeric-to-string split-units) " split-value " + (gnc-numeric-to-string split-value) " commod-currency " + (gnc-commodity-get-printname commod-currency)) + + (cond + ((and (not (gnc-numeric-zero-p split-units)) + (same-account? current (xaccSplitGetAccount s))) + ;; Split into subject account with non-zero amount. This is a purchase + ;; or a sale, adjust the basis + (let* ((split-value-currency (gnc:gnc-monetary-amount + (my-exchange-fn (gnc:make-gnc-monetary + commod-currency split-value) currency))) + (orig-basis (sum-basis basis-list currency-frac)) + ;; proportion of the fees attributable to this split + (fee-ratio (gnc-numeric-div (gnc-numeric-abs split-units) trans-shares + GNC-DENOM-AUTO GNC-DENOM-REDUCE)) + ;; Fees for this split in report currency + (fees-currency (gnc:gnc-monetary-amount (my-exchange-fn + (gnc:make-gnc-monetary commod-currency + (gnc-numeric-mul fee-ratio trans-brokerage + commod-currency-frac GNC-RND-ROUND)) + currency))) + (split-value-with-fees (if (eq? handle-brokerage-fees 'include-in-basis) + ;; Include brokerage fees in basis + (gnc-numeric-add split-value-currency fees-currency + currency-frac GNC-RND-ROUND) + split-value-currency))) + (gnc:debug "going in to basis list " basis-list " " (gnc-numeric-to-string split-units) " " + (gnc-numeric-to-string split-value-with-fees)) + + ;; adjust the basis + (set! basis-list (basis-builder basis-list split-units split-value-with-fees + basis-method currency-frac)) + (gnc:debug "coming out of basis list " basis-list) + + ;; If it's a sale or the stock is worthless, calculate the gain + (if (not (gnc-numeric-positive-p split-value)) + ;; Split value is zero or negative. If it's zero it's either a stock split/merge + ;; or the stock has become worthless (which looks like a merge where the number + ;; of shares goes to zero). If the value is negative then it's a disposal of some sort. + (let ((new-basis (sum-basis basis-list currency-frac))) + (if (or (gnc-numeric-zero-p new-basis) + (gnc-numeric-negative-p split-value)) + ;; Split value is negative or new basis is zero (stock is worthless), + ;; Capital gain is money out minus change in basis + (let ((gain (gnc-numeric-sub (gnc-numeric-abs split-value-with-fees) + (gnc-numeric-sub orig-basis new-basis + currency-frac GNC-RND-ROUND) + currency-frac GNC-RND-ROUND))) + (gnc:debug "Old basis=" (gnc-numeric-to-string orig-basis) + " New basis=" (gnc-numeric-to-string new-basis) + " Gain=" (gnc-numeric-to-string gain)) + (gaincoll 'add currency gain))))))) + + ;; here is where we handle a spin-off txn. This will be a no-units + ;; split with only one other split. xaccSplitGetOtherSplit only + ;; returns on a two-split txn. It's not a spinoff is the other split is + ;; in an income or expense account. + ((spin-off? s current) + (gnc:debug "before spin-off basis list " basis-list) + (set! basis-list (basis-builder basis-list split-units (gnc:gnc-monetary-amount + (my-exchange-fn (gnc:make-gnc-monetary + commod-currency split-value) + currency)) + basis-method + currency-frac)) + (gnc:debug "after spin-off basis list " basis-list)) + ) + )) + (xaccTransGetSplitList parent) + ) + ) + ) + ) + ) + (xaccAccountGetSplitList current) + ) + + ;; Look for income and expense transactions that don't have a split in the + ;; the account we're processing. We do this as follow + ;; 1. Make sure the parent account is a currency-valued asset or bank account + ;; 2. If so go through all the splits in that account + ;; 3. If a split is part of a two split transaction where the other split is + ;; to an income or expense account and the leaf name of that account is the + ;; same as the leaf name of the account we're processing, add it to the + ;; income or expense accumulator + ;; + ;; In other words with an account structure like + ;; + ;; Assets (type ASSET) + ;; Broker (type ASSET) + ;; Widget Stock (type STOCK) + ;; Income (type INCOME) + ;; Dividends (type INCOME) + ;; Widget Stock (type INCOME) + ;; + ;; If you are producing a report on "Assets:Broker:Widget Stock" a + ;; transaction that debits the Assets:Broker account and credits the + ;; "Income:Dividends:Widget Stock" account will count as income in + ;; the report even though it doesn't have a split in the account + ;; being reported on. + + (let ((parent-account (gnc-account-get-parent current)) + (account-name (xaccAccountGetName current))) + (if (and (not (null? parent-account)) + (member (xaccAccountGetType parent-account) (list ACCT-TYPE-ASSET ACCT-TYPE-BANK)) + (gnc-commodity-is-currency (xaccAccountGetCommodity parent-account))) + (for-each + (lambda (split) + (let* ((other-split (xaccSplitGetOtherSplit split)) + ;; This is safe because xaccSplitGetAccount returns null for a null split + (other-acct (xaccSplitGetAccount other-split)) + (parent (xaccSplitGetParent split)) + (txn-date (gnc-transaction-get-date-posted parent))) + (if (and (not (null? other-acct)) + (gnc:timepair-le txn-date to-date) + (string=? (xaccAccountGetName other-acct) account-name) + (gnc-commodity-is-currency (xaccAccountGetCommodity other-acct))) + ;; This is a two split transaction where the other split is to an + ;; account with the same name as the current account. If it's an + ;; income or expense account accumulate the value of the transaction + (let ((val (xaccSplitGetValue split)) + (curr (xaccAccountGetCommodity other-acct))) + (cond ((split-account-type? other-split ACCT-TYPE-INCOME) + (gnc:debug "More income " (gnc-numeric-to-string val)) + (dividendcoll 'add curr val)) + ((split-account-type? other-split ACCT-TYPE-EXPENSE) + (gnc:debug "More expense " (gnc-numeric-to-string + (gnc-numeric-neg val))) + (brokeragecoll 'add curr (gnc-numeric-neg val))) + ) + ) + ) + ) + ) + (xaccAccountGetSplitList parent-account) + ) + ) + ) + + (gnc:debug "pricing txn is " pricing-txn) + (gnc:debug "use txn is " use-txn) + (gnc:debug "prefer-pricelist is " prefer-pricelist) + (gnc:debug "price is " price) + + (gnc:debug "basis we're using to build rows is " (gnc-numeric-to-string (sum-basis basis-list + currency-frac))) + (gnc:debug "but the actual basis list is " basis-list) + + (if (eq? handle-brokerage-fees 'include-in-gain) + (gaincoll 'minusmerge brokeragecoll #f)) + + (if (or include-empty (not (gnc-numeric-zero-p units))) + (let* ((moneyin (gnc:sum-collector-commodity moneyincoll currency my-exchange-fn)) + (moneyout (gnc:sum-collector-commodity moneyoutcoll currency my-exchange-fn)) + (brokerage (gnc:sum-collector-commodity brokeragecoll currency my-exchange-fn)) + (income (gnc:sum-collector-commodity dividendcoll currency my-exchange-fn)) + ;; just so you know, gain == realized gain, ugain == un-realized gain, bothgain, well.. + (gain (gnc:sum-collector-commodity gaincoll currency my-exchange-fn)) + (ugain (gnc:make-gnc-monetary currency + (gnc-numeric-sub (gnc:gnc-monetary-amount (my-exchange-fn value currency)) + (sum-basis basis-list (gnc-commodity-get-fraction currency)) + currency-frac GNC-RND-ROUND))) + (bothgain (gnc:make-gnc-monetary currency (gnc-numeric-add (gnc:gnc-monetary-amount gain) + (gnc:gnc-monetary-amount ugain) + currency-frac GNC-RND-ROUND))) + (totalreturn (gnc:make-gnc-monetary currency (gnc-numeric-add (gnc:gnc-monetary-amount bothgain) + (gnc:gnc-monetary-amount income) + currency-frac GNC-RND-ROUND))) + + (activecols (list (gnc:html-account-anchor current))) + ) + + ;; If we're using the txn, warn the user + (if use-txn + (if pricing-txn + (set! warn-price-dirty #t) + (set! warn-no-price #t) + )) + + (total-value 'add (gnc:gnc-monetary-commodity value) (gnc:gnc-monetary-amount value)) + (total-moneyin 'merge moneyincoll #f) + (total-moneyout 'merge moneyoutcoll #f) + (total-brokerage 'merge brokeragecoll #f) + (total-income 'merge dividendcoll #f) + (total-gain 'merge gaincoll #f) + (total-ugain 'add (gnc:gnc-monetary-commodity ugain) (gnc:gnc-monetary-amount ugain)) + (total-basis 'add currency (sum-basis basis-list currency-frac)) + + ;; build a list for the row based on user selections + (if show-symbol (append! activecols (list (gnc:make-html-table-header-cell/markup "text-cell" ticker-symbol)))) + (if show-listing (append! activecols (list (gnc:make-html-table-header-cell/markup "text-cell" listing)))) + (if show-shares (append! activecols (list (gnc:make-html-table-header-cell/markup + "number-cell" (xaccPrintAmount units share-print-info))))) + (if show-price (append! activecols (list (gnc:make-html-table-header-cell/markup + "number-cell" + (if use-txn + (if pricing-txn + (gnc:html-transaction-anchor + pricing-txn + price + ) + price + ) + (gnc:html-price-anchor + price + (gnc:make-gnc-monetary + (gnc-price-get-currency price) + (gnc-price-get-value price))) + ))))) + (append! activecols (list (if use-txn (if pricing-txn "*" "**") " ") + (gnc:make-html-table-header-cell/markup + "number-cell" (gnc:make-gnc-monetary currency (sum-basis basis-list + currency-frac))) + (gnc:make-html-table-header-cell/markup "number-cell" value) + (gnc:make-html-table-header-cell/markup "number-cell" moneyin) + (gnc:make-html-table-header-cell/markup "number-cell" moneyout) + (gnc:make-html-table-header-cell/markup "number-cell" gain) + (gnc:make-html-table-header-cell/markup "number-cell" ugain) + (gnc:make-html-table-header-cell/markup "number-cell" bothgain) + (gnc:make-html-table-header-cell/markup "number-cell" + (let* ((moneyinvalue (gnc-numeric-to-double + (gnc:gnc-monetary-amount moneyin))) + (bothgainvalue (gnc-numeric-to-double + (gnc:gnc-monetary-amount bothgain))) + ) + (if (= 0.0 moneyinvalue) + "" + (sprintf #f "%.2f%%" (* 100 (/ bothgainvalue moneyinvalue))))) + ) + (gnc:make-html-table-header-cell/markup "number-cell" income))) + (if (not (eq? handle-brokerage-fees 'ignore-brokerage)) + (append! activecols (list (gnc:make-html-table-header-cell/markup "number-cell" brokerage)))) + (append! activecols (list (gnc:make-html-table-header-cell/markup "number-cell" totalreturn) + (gnc:make-html-table-header-cell/markup "number-cell" + (let* ((moneyinvalue (gnc-numeric-to-double + (gnc:gnc-monetary-amount moneyin))) + (totalreturnvalue (gnc-numeric-to-double + (gnc:gnc-monetary-amount totalreturn))) + ) + (if (= 0.0 moneyinvalue) + "" + (sprintf #f "%.2f%%" (* 100 (/ totalreturnvalue moneyinvalue)))))) + ) + ) + + (gnc:html-table-append-row/markup! + table + row-style + activecols) + + (if (and (not use-txn) price) (gnc-price-unref price)) + (table-add-stock-rows-internal rest (not odd-row?)) + ) + (begin + (if (and (not use-txn) price) (gnc-price-unref price)) + (table-add-stock-rows-internal rest odd-row?) + ) + ) + ))) + + (set! work-to-do (gnc:accounts-count-splits accounts)) + (table-add-stock-rows-internal accounts #t))) + + ;; Tell the user that we're starting. + (gnc:report-starting reportname) + + ;; The first thing we do is make local variables for all the specific + ;; options in the set of options given to the function. This set will + ;; be generated by the options generator above. + (let ((to-date (gnc:date-option-absolute-time + (get-option gnc:pagename-general "Date"))) + (accounts (get-option gnc:pagename-accounts "Accounts")) + (currency (get-option gnc:pagename-general "Report's currency")) + (price-source (get-option gnc:pagename-general + optname-price-source)) + (report-title (get-option gnc:pagename-general + gnc:optname-reportname)) + (include-empty (get-option gnc:pagename-accounts + optname-zero-shares)) + (show-symbol (get-option gnc:pagename-display + optname-show-symbol)) + (show-listing (get-option gnc:pagename-display + optname-show-listing)) + (show-shares (get-option gnc:pagename-display + optname-show-shares)) + (show-price (get-option gnc:pagename-display + optname-show-price)) + (basis-method (get-option gnc:pagename-general + optname-basis-method)) + (prefer-pricelist (get-option gnc:pagename-general + optname-prefer-pricelist)) + (handle-brokerage-fees (get-option gnc:pagename-general + optname-brokerage-fees)) + + (total-basis (gnc:make-commodity-collector)) + (total-value (gnc:make-commodity-collector)) + (total-moneyin (gnc:make-commodity-collector)) + (total-moneyout (gnc:make-commodity-collector)) + (total-income (gnc:make-commodity-collector)) + (total-gain (gnc:make-commodity-collector)) ;; realized gain + (total-ugain (gnc:make-commodity-collector)) ;; unrealized gain + (total-brokerage (gnc:make-commodity-collector)) + ;;document will be the HTML document that we return. + (table (gnc:make-html-table)) + (document (gnc:make-html-document))) + + (gnc:html-document-set-title! + document (string-append + report-title + (sprintf #f " %s" (gnc-print-date to-date)))) + + (if (not (null? accounts)) + ; at least 1 account selected + (let* ((exchange-fn (gnc:case-exchange-fn price-source currency to-date)) + (pricedb (gnc-pricedb-get-db (gnc-get-current-book))) + (price-fn + (case price-source + ((pricedb-latest) + (lambda (foreign domestic date) + (find-price (gnc-pricedb-lookup-latest-any-currency pricedb foreign) + domestic))) + ((pricedb-nearest) + (lambda (foreign domestic date) + (find-price (gnc-pricedb-lookup-nearest-in-time-any-currency + pricedb foreign (timespecCanonicalDayTime date)) domestic))))) + (headercols (list (_ "Account"))) + (totalscols (list (gnc:make-html-table-cell/markup "total-label-cell" (_ "Total")))) + (sum-total-moneyin (gnc-numeric-zero)) + (sum-total-income (gnc-numeric-zero)) + (sum-total-both-gains (gnc-numeric-zero)) + (sum-total-gain (gnc-numeric-zero)) + (sum-total-ugain (gnc-numeric-zero)) + (sum-total-brokerage (gnc-numeric-zero)) + (sum-total-totalreturn (gnc-numeric-zero))) + + ;;begin building lists for which columns to display + (if show-symbol + (begin (append! headercols (list (_ "Symbol"))) + (append! totalscols (list " ")))) + + (if show-listing + (begin (append! headercols (list (_ "Listing"))) + (append! totalscols (list " ")))) + + (if show-shares + (begin (append! headercols (list (_ "Shares"))) + (append! totalscols (list " ")))) + + (if show-price + (begin (append! headercols (list (_ "Price"))) + (append! totalscols (list " ")))) + + (append! headercols (list " " + (_ "Basis") + (_ "Value") + (_ "Money In") + (_ "Money Out") + (_ "Realized Gain") + (_ "Unrealized Gain") + (_ "Total Gain") + (_ "Rate of Gain") + (_ "Income"))) + + (if (not (eq? handle-brokerage-fees 'ignore-brokerage)) + (append! headercols (list (_ "Brokerage Fees")))) + + (append! headercols (list (_ "Total Return") + (_ "Rate of Return"))) + + (append! totalscols (list " ")) + + (gnc:html-table-set-col-headers! + table + headercols) + + (table-add-stock-rows + table accounts to-date currency price-fn exchange-fn price-source + include-empty show-symbol show-listing show-shares show-price basis-method + prefer-pricelist handle-brokerage-fees + total-basis total-value total-moneyin total-moneyout + total-income total-gain total-ugain total-brokerage) + + + (set! sum-total-moneyin (gnc:sum-collector-commodity total-moneyin currency exchange-fn)) + (set! sum-total-income (gnc:sum-collector-commodity total-income currency exchange-fn)) + (set! sum-total-gain (gnc:sum-collector-commodity total-gain currency exchange-fn)) + (set! sum-total-ugain (gnc:sum-collector-commodity total-ugain currency exchange-fn)) + (set! sum-total-both-gains (gnc:make-gnc-monetary currency (gnc-numeric-add (gnc:gnc-monetary-amount sum-total-gain) + (gnc:gnc-monetary-amount sum-total-ugain) + (gnc-commodity-get-fraction currency) GNC-RND-ROUND))) + (set! sum-total-brokerage (gnc:sum-collector-commodity total-brokerage currency exchange-fn)) + (set! sum-total-totalreturn (gnc:make-gnc-monetary currency (gnc-numeric-add (gnc:gnc-monetary-amount sum-total-both-gains) + (gnc:gnc-monetary-amount sum-total-income) + (gnc-commodity-get-fraction currency) GNC-RND-ROUND))) + + (gnc:html-table-append-row/markup! + table + "grand-total" + (list + (gnc:make-html-table-cell/size + 1 17 (gnc:make-html-text (gnc:html-markup-hr))))) + + ;; finish building the totals columns, now that totals are complete + (append! totalscols (list + (gnc:make-html-table-cell/markup + "total-number-cell" (gnc:sum-collector-commodity total-basis currency exchange-fn)) + (gnc:make-html-table-cell/markup + "total-number-cell" (gnc:sum-collector-commodity total-value currency exchange-fn)) + (gnc:make-html-table-cell/markup + "total-number-cell" sum-total-moneyin) + (gnc:make-html-table-cell/markup + "total-number-cell" (gnc:sum-collector-commodity total-moneyout currency exchange-fn)) + (gnc:make-html-table-cell/markup + "total-number-cell" sum-total-gain) + (gnc:make-html-table-cell/markup + "total-number-cell" sum-total-ugain) + (gnc:make-html-table-cell/markup + "total-number-cell" sum-total-both-gains) + (gnc:make-html-table-cell/markup + "total-number-cell" + (let* ((totalinvalue (gnc-numeric-to-double + (gnc:gnc-monetary-amount sum-total-moneyin))) + (totalgainvalue (gnc-numeric-to-double + (gnc:gnc-monetary-amount sum-total-both-gains))) + ) + (if (= 0.0 totalinvalue) + "" + (sprintf #f "%.2f%%" (* 100 (/ totalgainvalue totalinvalue)))))) + (gnc:make-html-table-cell/markup + "total-number-cell" sum-total-income))) + (if (not (eq? handle-brokerage-fees 'ignore-brokerage)) + (append! totalscols (list + (gnc:make-html-table-cell/markup + "total-number-cell" sum-total-brokerage)))) + (append! totalscols (list + (gnc:make-html-table-cell/markup + "total-number-cell" sum-total-totalreturn) + (gnc:make-html-table-cell/markup + "total-number-cell" + (let* ((totalinvalue (gnc-numeric-to-double + (gnc:gnc-monetary-amount sum-total-moneyin))) + (totalreturnvalue (gnc-numeric-to-double + (gnc:gnc-monetary-amount sum-total-totalreturn))) + ) + (if (= 0.0 totalinvalue) + "" + (sprintf #f "%.2f%%" (* 100 (/ totalreturnvalue totalinvalue)))))) + )) + + + (gnc:html-table-append-row/markup! + table + "grand-total" + totalscols + ) + + (gnc:html-document-add-object! document table) + (if warn-price-dirty + (gnc:html-document-append-objects! document + (list (gnc:make-html-text (_ "* this commodity data was built using transaction pricing instead of the price list.")) + (gnc:make-html-text (gnc:html-markup-br)) + (gnc:make-html-text (_ "If you are in a multi-currency situation, the exchanges may not be correct."))))) + + (if warn-no-price + (gnc:html-document-append-objects! document + (list (gnc:make-html-text (if warn-price-dirty (gnc:html-markup-br) "")) + (gnc:make-html-text (_ "** this commodity has no price and a price of 1 has been used."))))) +) + + ;if no accounts selected. + (gnc:html-document-add-object! + document + (gnc:html-make-no-account-warning + report-title (gnc:report-id report-obj)))) + + (gnc:report-finished) + document))) + +(gnc:define-report + 'version 1 + 'report-guid "2d82e4152af845f2be434f71f8535b85" + 'name reportname + 'menu-path (list gnc:menuname-asset-liability) + 'options-generator options-generator + 'renderer advanced-portfolio-renderer)