On Wed, 2007-11-28 at 22:46 -0500, Morrison J. Chang wrote: > > I'll post what I've got shortly.
Okay here is the full file. This one also needs to be cleaned up for the log statements and comments. I called it advanced-portfolio-test.scm so that I could run it with the old one. If anything else this might be instructive to anyone looking to play around with scheme. Notes: In this version I created two new lists for holding the sold-value and sold-basis-value. I apply stock splits against the basis in basis-modify. Comments appreciated. -Morrison
;; -*-scheme-*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; advanced-portfolio.scm ;; by Martijn van Oosterhout ([EMAIL PROTECTED]) Feb 2002 ;; modified for GnuCash 1.8 by Herbert Thoma ([EMAIL PROTECTED]) Oct 2002 ;; ;; Heavily based on portfolio.scm ;; by Robert Merkel ([EMAIL PROTECTED]) ;; ;; 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 [EMAIL PROTECTED] ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-module (gnucash report advanced-portfolio-test)) (use-modules (gnucash main)) ;; FIXME: delete after we finish modularizing. (use-modules (srfi srfi-1)) (use-modules (ice-9 slib)) (use-modules (gnucash gnc-module)) (require 'printf) (gnc:module-load "gnucash/report/report-system" 0) (define reportname (N_ "Advanced Portfolio TEST")) (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-include-gains (N_ "Include gains and losses")) (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 (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 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")) (vector 'pricedb-latest-before (N_ "Most recent to report") (N_ "The most recent recorded price before 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_ "FILO") (N_ "Use first-in last-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)) (gnc:register-option options (gnc:make-simple-boolean-option gnc:pagename-general optname-include-gains "g" (N_ "Include splits with no shares for calculating money-in and money-out") #f)) (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 <html-document>. 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-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) (string=? (gncSplitGetGUID s1) (gncSplitGetGUID s2))) (define (same-account? a1 a2) (string=? (gncAccountGetGUID a1) (gncAccountGetGUID a2))) ;; this builds a list for basis calculation and handles average, fifo and filo 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) (gnc:msg "in basis-builder:" b-list ":::" b-units ":::" b-value ":::" b-method) ;; (if (not (gnc-numeric-zero-p b-units)) (if (gnc-numeric-positive-p b-units) (case b-method ((average-basis) (cond ((not (eqv? b-list '())) (list (cons (gnc-numeric-add b-units (caar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (gnc-numeric-div (gnc-numeric-add b-value (gnc-numeric-mul (caar b-list) (cdar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (gnc-numeric-add b-units (caar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)))) ;; (gnc:msg " test math " b-units ":::" (caar b-list) ) ) (else (list (cons b-units (gnc-numeric-div b-value b-units GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER))))))) (else (append b-list (list (cons b-units (gnc-numeric-div b-value b-units GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER))))))) (if (not (eqv? b-list '())) (case b-method ((fifo-basis) (if (not (= -1 (gnc-numeric-compare (gnc-numeric-abs b-units) (caar b-list)))) (basis-builder (cdr b-list) (gnc-numeric-add b-units (caar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) b-value b-method) (append (list (cons (gnc-numeric-add b-units (caar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (cdar b-list))) (cdr b-list)))) ((filo-basis) (if (not (= -1 (gnc-numeric-compare (gnc-numeric-abs b-units) (caar (reverse b-list))))) (basis-builder (reverse (cdr (reverse b-list))) (gnc-numeric-add b-units (caar (reverse b-list)) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) b-value b-method) (append (cdr (reverse b-list)) (list (cons (gnc-numeric-add b-units (caar (reverse b-list)) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (cdar (reverse b-list))))))) ((average-basis) (list (cons (gnc-numeric-add (caar b-list) b-units GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (cdar b-list)))) ) '() ) ) ) ;; sum up the contents of the b-list built by basis-builder above (define (sum-basis b-list) (if (not (eqv? b-list '())) ;; (gnc:msg "in sum-basis " b-list) (gnc-numeric-add (gnc-numeric-mul (caar b-list) (cdar b-list) 100 GNC-RND-ROUND) (sum-basis (cdr b-list)) 100 GNC-RND-ROUND) (gnc-numeric-zero) ) ) ;; sum up the total number of units in the b-list built by basis-builder above (define (sum-units b-list) (if (not (eqv? b-list '())) (gnc-numeric-add (caar b-list) (sum-units (cdr b-list)) 100 GNC-RND-ROUND) (gnc-numeric-zero) ) ) ;; return the basis-value of units sold ;; b-list is the basis-list in left to right time order ;; units-amt is the negative number of units sold (define (get-basis-value b-list units-amt) (gnc:msg "in get-basis-value " b-list "::" units-amt) (if (not (eqv? b-list '())) (if (<= (gnc-numeric-compare (gnc-numeric-abs units-amt) (caar b-list)) 0) ;; units-amt is smaller than or equal to purchase, return purchase basis (gnc-numeric-mul (gnc-numeric-abs units-amt) (cdar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) ;; units-amt is larger than purchase so add units * price to remaining b-list (gnc-numeric-add (gnc-numeric-mul (caar b-list) (cdar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (get-basis-value (cdr b-list) (gnc-numeric-add units-amt (gnc-numeric-mul (caar b-list) (cdar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER))) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) ) ;; empty b-list so return zero since you can't sell more than you have (gnc-numeric-zero) ) ) ;; modify the basis-list to handle equity stock split scenario ;; change in shares to be applied to b-list per b-method ;; in the case of average basis just add to units and apply factor to price ;; in the case of fifo and filo ;; apply a factor to the contents of b-list (needed for handling equity stock splits/reverse splits) ;; factor should be (original sum of units + additional split units) / original sum of units ;; so a 2 for 1 split of 100 shares would be (100+100)/(100) = 2 ;; conversely a 1 for 4 reverse split of 100 shares would be (100 - 75)/(100) = 0.25 ;; the factor has to be applied to both parts of the cell (units-of-stock . price-per-unit) to maintain value ;; new-units-of-stock = units-of-stock * factor ;; new-price-per-unit = price-per-unit / factor (define (basis-modify b-list b-units b-method) (if (not (eqv? b-list '())) (case b-method ((average-basis) (list (cons (gnc-numeric-add b-units (caar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (gnc-numeric-div (cdar b-list) (gnc-numeric-div (gnc-numeric-add b-units (caar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (caar b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)))) ) ((fifo-basis filo-basis) (let ((factor (gnc-numeric-div (gnc-numeric-add (sum-units b-list) b-units GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (sum-units b-list) GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)))) (map (lambda (cell) (cons (gnc-numeric-mul (car cell) factor GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)) (gnc-numeric-div (cdr cell) factor GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER)))) b-list)) ) ) '() )) (define (table-add-stock-rows table accounts to-date currency price-fn exchange-fn include-empty include-gains show-symbol show-listing show-shares show-price basis-method prefer-pricelist total-basis total-value total-moneyin total-moneyout total-gain total-ugain) (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)) (name (xaccAccountGetName current)) (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))) ;; (totalunits 0.0) ;; these two items do nothing, but are in a debug below, ;; (totalunityears 0.0);; so I'm leaving it. asw ;; Counter to keep track of stuff (unitscoll (gnc:make-commodity-collector)) (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)) (soldbasiscoll (gnc:make-commodity-collector)) (soldvaluecoll (gnc:make-commodity-collector)) (price-list (price-fn commodity to-date)) (price (if (> (length price-list) 0) (car price-list) #f)) ;; if there is no price, set a sane commod-currency for those zero-share ;; accounts. if its a no price account with shares, we'll get a currency later. (commod-currency (if price (gnc-price-get-currency price) currency)) (value (exchange-fn (gnc:make-gnc-monetary commodity units) currency)) (txn-value (gnc-numeric-zero)) (txn-date to-date) (pricing-txn #f) (use-txn #f) (basis-list '()) (txn-units (gnc-numeric-zero)) (sold-basis-value-list '()) (sold-value-list '()) ) (gnc:msg "---" name "---") (for-each (lambda (split) (set! work-done (+ 1 work-done)) (gnc:msg "work-done " work-done) (gnc:report-percent-done (* 100 (/ work-done work-to-do))) (let ((parent (xaccSplitGetParent split))) (if (gnc:timepair-le (gnc-transaction-get-date-posted parent) to-date) (begin (set! txn-value (gnc-numeric-zero)) (set! txn-units (gnc-numeric-zero)) ;; Iterate through split transaction ;; Appropriately set txn-value and txn-units for basis calculation (for-each (lambda (s) ;; only for non zero amount (shares) set txn-value and txn-units (cond ((and (same-account? current (xaccSplitGetAccount s)) (not (gnc-numeric-zero-p (xaccSplitGetAmount s)))) (if (not price) (set! commod-currency (xaccAccountGetCommodity (xaccSplitGetAccount s))) ) ;; add up the non-zero values in both txn-value and txn-amount for this asset account in this ledger-split (set! txn-value (gnc-numeric-add (xaccSplitGetValue s) txn-value GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER))) (set! txn-units (gnc-numeric-add (xaccSplitGetAmount s) txn-units GNC-DENOM-AUTO (logior GNC-DENOM-REDUCE GNC-RND-NEVER))) (set! txn-date (gnc-transaction-get-date-posted parent)) (set! pricing-txn parent) (gnc:msg "Ledger split " txn-value "///" txn-units) ) ) ) (xaccTransGetSplitList parent)) ;; go build the basis-list ;; the use of exchange-fn here is an attempt to get the basis list into one ;; currency to help accomodate stock transfers and other things. might not work. (gnc:msg "create basis-list" basis-list "::" txn-units "::" txn-value) ;; if this is a sale (both txn-units and txn-value are negative) ;; create/update sold-value-list and sold-basis-value-list (if (and (gnc-numeric-negative-p txn-units) (gnc-numeric-negative-p txn-value)) (begin (soldbasiscoll 'add commod-currency (gnc-numeric-abs txn-value)) (set! sold-value-list (append sold-value-list (list (gnc-numeric-abs txn-value)))) (case basis-method ((fifo-basis average-basis) (soldvaluecoll 'add commod-currency (get-basis-value basis-list txn-units)) (set! sold-basis-value-list (append sold-basis-value-list (list (get-basis-value basis-list txn-units)))) ) ((filo-basis) (soldvaluecoll 'add commod-currency (get-basis-value (reverse basis-list) txn-units)) (set! sold-basis-value-list (append sold-basis-value-list (list (get-basis-value (reverse basis-list) txn-units)))) ) ) ) ) (if (gnc-numeric-zero-p txn-value) ;; zero txn-value implies an equity stock split/reverse split (set! basis-list (basis-modify basis-list txn-units basis-method)) ;; otherwise usual basis-builder action for postive and negative amounts (set! basis-list (basis-builder basis-list txn-units (gnc:gnc-monetary-amount (exchange-fn (gnc:make-gnc-monetary commod-currency txn-value) currency)) basis-method)) ) (gnc:msg "basis-list is:" basis-list) (gnc:msg "sold-basis-value-list is:" sold-basis-value-list) (gnc:msg "sold-value-list is: "sold-value-list) (for-each (lambda (s) (cond ((same-split? s split) (gnc:msg "amount " (gnc-numeric-to-double (xaccSplitGetAmount s)) " acct " (xaccAccountGetName (xaccSplitGetAccount s)) ) (gnc:msg "value " (gnc-numeric-to-double (xaccSplitGetValue s)) " in " (gnc-commodity-get-printname commod-currency) " from " (xaccTransGetDescription (xaccSplitGetParent s))) ;; now process each split entry for money in, money out, expenses (cond ((or include-gains (not (gnc-numeric-zero-p (xaccSplitGetAmount s)))) (unitscoll 'add commodity (xaccSplitGetAmount s)) ;; Is the stock transaction? ;; these lines do nothing, but are in a debug so I'm leaving it, just in case. asw. ;; (if (< 0 (gnc-numeric-to-double ;; (xaccSplitGetAmount s))) ;; (set! totalunits ;; (+ totalunits ;; (gnc-numeric-to-double (xaccSplitGetAmount s)))) ;; ) ;; (set! totalunityears ;; (+ totalunityears ;; (* (gnc-numeric-to-double (xaccSplitGetAmount s)) ;; (gnc:date-year-delta ;; (car (gnc-transaction-get-date-posted parent)) ;; (current-time))))) (cond ((gnc-numeric-negative-p (xaccSplitGetValue s)) (moneyoutcoll 'add commod-currency (gnc-numeric-neg (xaccSplitGetValue s))) (gnc:msg "money out coll: " moneyoutcoll "///" (xaccSplitGetValue s)) ) (else (moneyincoll 'add commod-currency (gnc-numeric-neg (xaccSplitGetValue s)))))))) ((split-account-type? s ACCT-TYPE-EXPENSE) (brokeragecoll 'add commod-currency (xaccSplitGetValue s)) (gnc:msg "brokeragecoll:" brokeragecoll "///" (xaccSplitGetValue s)) ) ;; all dividends and Cap-Gain will be picked up here ((split-account-type? s ACCT-TYPE-INCOME) (dividendcoll 'add commod-currency (xaccSplitGetValue s)) (gnc:msg "dividendcoll:" dividendcoll "///" (xaccSplitGetValue s)) ) ) ) (xaccTransGetSplitList parent) ) ) ) ) ) (xaccAccountGetSplitList current) ) ;; (gnc:debug "totalunits" totalunits) ;; (gnc:debug "totalunityears" totalunityears) ;; now we determine which price data to use, the pricelist or the txn ;; and if we have a choice, use whichever is newest. (set! use-txn (if (not price) #t (if prefer-pricelist #f (if (not (gnc:timepair-le txn-date (gnc-price-get-time price))) #t #f)))) ;; okay we're using the txn, so make a new price, value etc. and warn the user (if use-txn (begin (set! price (if (not (gnc-numeric-zero-p txn-units)) (gnc:make-gnc-monetary commod-currency (gnc-numeric-div txn-value (gnc-numeric-abs txn-units) 100 GNC-RND-ROUND)) (gnc:make-gnc-monetary commod-currency (gnc-numeric-zero)))) (set! value (if price (gnc:make-gnc-monetary commod-currency (gnc-numeric-mul units (gnc:gnc-monetary-amount price) 100 GNC-RND-ROUND)) (gnc:make-gnc-monetary commod-currency (gnc-numeric-zero)))) (set! warn-price-dirty #t) ) ) ;; what this means is gain = moneyout - moneyin + basis-of-current-shares, and ;; adjust for brokers and dividends. (gnc:msg "Other sum-basis -- basis-list " basis-list) (gaincoll 'add currency (sum-basis basis-list)) ;;(moneyincoll 'minusmerge dividendcoll #f) ;;(moneyoutcoll 'minusmerge brokeragecoll #f) (gaincoll 'merge moneyoutcoll #f) (gaincoll 'merge moneyincoll #f) (gnc:msg "moneyincoll is " (gnc:sum-collector-commodity moneyincoll currency exchange-fn)) (gnc:msg "moneyoutcoll is " (gnc:sum-collector-commodity moneyoutcoll currency exchange-fn)) (gnc:msg "gaincoll is " (gnc:sum-collector-commodity gaincoll currency exchange-fn)) (gnc:msg "soldbasiscoll is " (gnc:sum-collector-commodity soldbasiscoll currency exchange-fn)) (gnc:msg "soldvaluecoll is " (gnc:sum-collector-commodity soldvaluecoll currency exchange-fn)) (if (or include-empty (not (gnc-numeric-zero-p units))) (let* ( (moneyin (gnc:monetary-neg (gnc:sum-collector-commodity moneyincoll currency exchange-fn))) (moneyout (gnc:sum-collector-commodity moneyoutcoll currency exchange-fn)) ;; just so you know, gain == realized gain, ugain == un-realized gain, bothgain, well.. (gain (gnc:sum-collector-commodity gaincoll currency exchange-fn)) (ugain (gnc:make-gnc-monetary currency (gnc-numeric-sub (gnc:gnc-monetary-amount (exchange-fn value currency)) (sum-basis basis-list) 100 GNC-RND-ROUND))) (bothgain (gnc:make-gnc-monetary currency (gnc-numeric-add (gnc:gnc-monetary-amount gain) (gnc:gnc-monetary-amount ugain) 100 GNC-RND-ROUND))) (activecols (list (gnc:html-account-anchor current))) ) (total-value 'add (gnc:gnc-monetary-commodity value) (gnc:gnc-monetary-amount value)) (total-moneyin 'merge moneyincoll #f) (total-moneyout 'merge moneyoutcoll #f) (total-gain 'merge gaincoll #f) (total-ugain 'add (gnc:gnc-monetary-commodity ugain) (gnc:gnc-monetary-amount ugain)) (gnc:msg "call sum-basis " basis-list) (total-basis 'add currency (sum-basis basis-list)) (gnc:msg "total-basis is " total-basis) (gnc:msg "total-basis is " (gnc:sum-collector-commodity total-basis currency exchange-fn)) ;; build a list for the row based on user selections (if show-symbol (append! activecols (list ticker-symbol))) (if show-listing (append! activecols (list 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 (gnc:html-transaction-anchor pricing-txn 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 "*" " ") (gnc:make-html-table-header-cell/markup "number-cell" (gnc:make-gnc-monetary currency (sum-basis basis-list))) (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)))) (if (= 0.0 moneyinvalue) (sprintf #f "%.2f%%" moneyinvalue) (sprintf #f "%.2f%%" (* 100 (/ (gnc-numeric-to-double (gnc:gnc-monetary-amount bothgain)) moneyinvalue)))))) ) ) (gnc:html-table-append-row/markup! table row-style activecols) (table-add-stock-rows-internal rest (not odd-row?)) ) (table-add-stock-rows-internal rest odd-row?) ) (gnc-price-list-destroy price-list) ))) (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 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)) (include-gains (get-option gnc:pagename-general optname-include-gains)) (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)) (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-gain (gnc:make-commodity-collector)) ;; realized gain (total-ugain (gnc:make-commodity-collector)) ;; unrealized gain ;;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 date) (gnc-pricedb-lookup-latest-any-currency pricedb foreign))) ((pricedb-nearest) (lambda (foreign date) (gnc-pricedb-lookup-nearest-in-time-any-currency pricedb foreign (timespecCanonicalDayTime date)))) ((pricedb-latest-before) (lambda (foreign date) (gnc-pricedb-lookup-latest-before-any-currency pricedb foreign (timespecCanonicalDayTime date)))))) (headercols (list (_ "Account"))) (totalscols (list (gnc:make-html-table-cell/markup "total-label-cell" (_ "Total")))) (sum-total-both-gains (gnc-numeric-zero)) (sum-total-gain (gnc-numeric-zero)) (sum-total-ugain (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") (_ "Total 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 include-empty include-gains show-symbol show-listing show-shares show-price basis-method prefer-pricelist total-basis total-value total-moneyin total-moneyout total-gain total-ugain) (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) 100 GNC-RND-ROUND))) (gnc:html-table-append-row/markup! table "grand-total" (list (gnc:make-html-table-cell/size 1 14 (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" (gnc:monetary-neg (gnc:sum-collector-commodity total-moneyin currency exchange-fn))) (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 (gnc:monetary-neg (gnc:sum-collector-commodity total-moneyin currency exchange-fn)))))) (if (= 0.0 totalinvalue) (sprintf #f "%.2f%%" totalinvalue) (sprintf #f "%.2f%%" (* 100 (/ (gnc-numeric-to-double (gnc:gnc-monetary-amount sum-total-both-gains)) 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 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 'name reportname 'menu-path (list gnc:menuname-asset-liability) 'options-generator options-generator 'renderer advanced-portfolio-renderer)
_______________________________________________ gnucash-devel mailing list gnucash-devel@gnucash.org https://lists.gnucash.org/mailman/listinfo/gnucash-devel