In a brokerage application I worked on a few years ago, I believe we made a good start at defining a set of useful business classes. Not surprisingly, the first type of unit we addressed was money, which is not only the main preoccupation of much of business programming, but the fuel that makes much of it go. Money, as defined in our application, has a magnitude and a currency. Since currencies are so important to our society, we do actually have 3-letter ISO codes for the different currencies - our package supported about 200 of them. In our package, a monetary value was always represented by the ISO code followed (with no intervening blanks) by an amount, whether for input or for output. Thus we might have CAD205.10, or USD3012.55. Each currency has a preferred number of decimals for amounts (although this does not apply to rates), except for some oddball "currencies" (such as Euro-Gold) that allow any number of decimals. In our application we loaded the currency information at start of session from a database.
Note that rates have different precision requirements from monetary amounts. The general rule for currency is that, at the end of a computation, they must be rounded to whatever the smallest denomination is in that currency, whereas rates can have any number of decimal places. In the case of (US and Canadian) currency, the smallest unit is really the cent, but instead of 12345 cents we usually write $123.45. This can be handled in most cases by forcing rounding when the currency amount is turned into displayable format, e.g. by using some method built on the Java toString() method. In addition, in our application we provided a round() method which did the same thing to non-display currency amounts. It has been suggested that we should really distinguish between "non-final" and "final" currency values, but that would involve a vast increase in the number of methods over what we already have, so I think the above technique is adequate.
In turn, the numeric value in our Monetary class was implemented
using the new Java BigDecimal class which is now available, as of Java
1.5,
replacing the previous one. Even though they have explicit
precision, floating point numbers are not appropriate for monetary
amounts,
although they work quite well for rates (another difference between
rates and monetary amounts) or physical units such as distances.
We chose the new BigDecimal, as its arithmetic has been consciously
designed to give the same results that you would get yourself when
doing
decimal arithmetic by hand. For instance, 3.82 + 2.18
becomes
6.00, not 6.
Before getting into the specific classes and methods, I would like
to point out that money was really rather an easy class to
define. There is a much more complex issue revolving around the
question of Quantity. We have tentatively added a
class called Quantity to the project, but the real question is
"Quantity of what?".
Quantity
does not extend BigDecimal, as there are many
things you can do with BigDecimal that you do not want to automatically
be able to do with
Quantity - i.e. you cannot normally multiply two Quantities together
(unless it is two lengths to form an area),
and conversely, a Quantity should almost always be a quantity of
something. Users
can extend Quantity for their own purposes - this will usually entail
adding the thing that it is a
quantity of - what Denis Garneau (the very talented IBM architect who did most of the design work)
calls its "quality". You
should not be able to add weight to length -
or number of eggs to number of chickens! Denis recently ran into
a mysterious programming bug, which eventually turned out to be caused
by someone adding a temperature to a weight! Alternatively,
Quantity could be an interface, rather than a class. So you see,
we are
starting to stray into the area talked about in Physical Units.
Denis
and I would like people to explore this area, especially in
different problem domains, and write papers about it!
Specifically, a) should Quantity be a class or an interface? b) what
extensions/implementations did you need to add to it for your
particular domain of interest?
As of Nov. 4, 2009, the source code
for these classes can be found
in SourceForge. A non-executable jar
file can be downloaded from SourceForge - it is also available as a jar file.
The constructors for our money class, called "Monetary", are as
follows:
Constructors |
---|
Monetary(BigDecimal,
Currency) Monetary constructor with value initialized from BigDecimal |
Monetary (String) Monetary constructor using currency + value in String, eg CAD25.74 |
BigDecimal objects are (immutable) decimal numbers with explicit precision, while Currency objects represent the actual currencies themselves - thus one Currency object is US dollars, another is Canadian dollars, while yet another might be pounds sterling . The first constructor is needed to build Monetary objects that are the result of a computation; the second to build them from the String representation. So we needed the converse operation, which was a method called either toString() or serialize().
The complete set of methods we provided for our Monetary class are
as follows:
returns | Methods |
---|---|
Monetary | add
(Monetary) add value of Monetary object to this Monetary object, creating another one |
Monetary | convert(ExchRate) Convert this Monetary amount using an Exchange Rate, giving another Monetary amount of the target currency type. |
Monetary | divide(BigDecimal) Divide this Monetary amount by a BigDecimal quantity, creating a new Monetary |
BigDecimal | divide(Monetary) Divide value of this Monetary object by Monetary object, creating BigDecimal |
boolean | eq(Monetary) Compare to see if monetary amount is equal to this one |
boolean | ge(Monetary) Compare to see if this monetary amount is greater than or equal to specified one |
Currency | getCurrency() Get currency from Monetary value |
boolean | gt(Monetary) Compare to see if this monetary amount is greater than specified one |
boolean | isPositive() Check if value is positive (> 0) |
boolean | le(Monetary) Compare to see if this monetary amount is less than or equal to specified one |
boolean | lt(Monetary) Compare to see if this monetary amount is less than specified one |
Monetary | multiply(BigDecimal) Multiply this Monetary amount by a BigDecimal, giving another Monetary amount |
Monetary | multiply(PCPrice) Multiply this Monetary amount by a Percent Price, returning a Monetary amount |
boolean | ne(Monetary) Compare to see if a monetary amount is unequal to this one |
void | round() Method to adjust precision of Monetary amount to that specified in Currency object, rounding as determined by active MathContext. |
String | serialize() Generate a 'preference neutral' string from Monetary value. |
Monetary | setCurrency(Currency) Create new Monetary with new currency - can only be done if currency is unknown (== null) |
Monetary | setCurrency(String) Create new Monetary with new currency - can only be done if currency is unknown (== null) |
Monetary | subtract(Monetary) subtract value of Monetary object from this Monetary object, creating another one |
String | toString() Create a String from this object |
This list is to give you a flavour for the kinds of things we felt would be needed in this class. As requirements for new methods came up in our application we added them, so if you don't see something in this list that you would expect, it's probably because the need didn't come up in our application. You will see that a number of other classes (apart from the standard Java ones) are mentioned in the list given above and we will be describing them in what follows - although perhaps not to such a fine level of detail.
Monetary amounts cannot be multiplied together, but they can be added, subtracted, compared against each other, and so on. In all these cases, the currency must be the same; if the two currencies involved are not identical, an error condition is raised. In our implementation, this was a run-time test, as opposed to a compile-time test. In our view it wouldn't make sense to make every one of our 200 currencies a different class.
Monetary amounts can however be multiplied or divided by instances of the classes PCPrice and BigDecimal (more about PCPrice later). These latter are both essentially dimensionless, and in general we can multiply or divide dimensioned values by dimensionless ones, giving a result that has the same dimension as the dimensioned quantity.
Note that we allowed a Monetary object to have an undefined currency. In this state you cannot do anything with it except set the currency. I am not sure if this function was ever used.
This web page will be talking about the classes we actually used in our application, and what their capabilities were. Although we used BigDecimal for MPrice (in conjunction with Currency), we allowed an MPrice object to have more decimal places than monetary amounts. I believe one of the types of floating point number might also work well for MPrice, but we went with BigDecimal..
For now, we are going to expand on the Monetary Price (MPrice) class. This could be entered as a decimal value, but also as a fraction of the form "3 + 5 / 16" (usually referred to as a "vulgar fraction"), where the last integer was always a power of 2. Supposedly, US stock exchanges are switching to decimal prices soon (or may already have done so), but at the time we built this system, they had not yet made the switchover.
We held the three integer fields, provided in a constructor, in the MPrice object, as well as the decimal value, which was computed if the price was entered in vulgar fraction form. If the price was entered as a decimal, the denominator item of the integer fields was set to zero, and the other two integer fields were ignored. I managed to figure out a way to calculate the vulgar fraction from the decimal, and we provided a constructor to do this. Again I'm not sure if it was ever used.
Here are the constructors for Monetary Prices:
Constructors |
---|
MPrice(BigDecimal, Currency) MPrice constructor with value initialized from BigDecimal value and Currency object |
MPrice(BigDecimal, Currency,
boolean) MPrice constructor with value initialized from BigDecimal value, Currency object, and boolean indicating that it is to be held as a vulgar fraction |
MPrice(int[], Currency) MPrice constructor with value initialized from int[3] array and Currency object |
MPrice(String) MPrice constructor using currency + value in String. |
MPrice(String, String) MPrice constructor with value initialized from 2 Strings - value (vulgar or decimal), and currency abbreviation. |
Here are some typical methods for this class:
returns | Methods |
---|---|
MPrice |
add
(MPrice) Add this Monetary Price to another, giving a Monetary Price. |
MPrice |
divPrice (BigDecimal) Divide this MPrice amount by a BigDecimal quantity, creating a new MPrice |
boolean |
isMultipleOf (MPrice) Determine if this MPrice is a multiple of the parameter MPrice - return true if it is. |
MPrice |
multPrice (BigDecimal) Multiply this MPrice amount by a BigDecimal quantity, creating a new MPrice |
String |
serialize () Generate a 'preference neutral' string from Monetary Price. |
MPrice |
setPriceCurrency (Currency) Create new MPrice with new currency - can only be done if currency is unknown (== null) |
MPrice |
setPriceCurrency (String) Create new MPrice with new currency - can only be done if currency is unknown (== null) |
MPrice |
subtract (MPrice) Subtract a Monetary Price from this one, giving a Monetary Price. |
String |
toString () Create a String from this object |
Our brokerage application also required a different kind of price, which we called "PCPrice". This stands for PerCentPrice and is basically a dimensionless quantity that is entered or displayed as a percentage, i.e. 20PCT represents the value .2. PCPrice also supports "discount" and "premium", coded as DSC and PRM. 20DSC represents a value of .8, while 20PRM represents 1.2. Although PCPrice has interesting ways of being displayed, it is dimensionless, so it is equivalent to a pure value. Because of this, it can extend BigDecimal, and its methods are basically whatever it inherits from BigDecimal, plus a few quirks due to the PCT/DSC/PRM convention, so I will not go into it further.
Since we are dealing with multiple currencies, we need a way of exchanging money. You can't add money of different currencies, so of course we need an ExchRate class - instances of this class can be applied to a Monetary value to convert it to Monetary of another currency.
Its constructors are as follows:
Constructors |
---|
ExchRate(BigDecimal, Currency,
Currency, TimeStamp) Exchange Rate constructor with value initialized from BigDecimal |
ExchRate (String) Exchange Rate constructor using single String, containing value, source currency, target currency, optional timestamp, separated by semicolons. |
ExchRate(String, String, String,
String) Exchange Rate constructor with value initialized from 4 Strings (TimeStamp will use serialized form) |
By the way, our TimeStamp class extends the Java Date class, which goes
down to the millisecond, in spite of its name. The Java SQL
TimeStamp class goes down to the nanosecond, but we didn't consider
that
necessary for our application, although we had methods to convert our
time stamps from and to SQL time stamps. We held all time stamps in
UTC format (Universal Time Code) as our application not only dealt in
many currencies, but in many countries and time zones. More about that
later.
The methods of ExchRate are as follows:
returns | Methods |
---|---|
Monetary |
convert(Monetary) Convert a Monetary amount using this Exchange Rate, giving another Monetary amount of the target currency type. This will only work if the currency of the Monetary amount matches the source currency of the Exchange Rate |
boolean |
eq(ExchRate) Compare to see if this exchange rate is equal to specified one |
boolean |
ge(ExchRate) Compare to see if this exchange rate is greater than or equal to specified one |
Currency |
getSourceCurrency () Get Source Currency |
Currency |
getTargetCurrency () Get Target Currency |
TimeStamp |
getTimeStamp () Get TimeStamp |
boolean |
gt(ExchRate) Compare to see if this exchange rate is greater than specified one |
ExchRate |
invert() This method inverts an Exchange Rate - a new Exchange Rate object is created with the currencies interchanged, and with a new value which is the reciprocal of this one |
boolean |
le(ExchRate) Compare to see if this exchange rate is less than or equal to specified one |
boolean |
lt(ExchRate) Compare to see if this exchange rate is less than specified one |
boolean |
ne(ExchRate) Compare to see if this exchange rate is not equal to specified one |
ExchRate |
propagate(ExchRate) This method combines this Exchange Rate with another Exchange Rate, resulting in a new Exchange Rate object. |
String |
serialize() Generate a 'preference neutral' string from Exchange Rate. |
String |
toString() Create a String from this object |
Of particular interest are invert() and propagate():
the
former creates an exchange rate that is the inverse of the given
one, while the latter combines two exchange rates to create a single
resultant exchange rate. In the first case, the inverse of the
exchange rate
from | to | rate |
---|---|---|
USD (US Dollars) | CDN (Canadian) | 1.6 |
would be
from | to | rate |
---|---|---|
CDN | USD | 1 / 1.6 (=.625) |
In the second case, combining
from | to | rate |
---|---|---|
USD | CDN | 1.6 |
and
from | to | rate |
---|---|---|
CDN | GBP (Great Britain Pounds) | .4 |
would result in
from | to | rate |
---|---|---|
USD | GBP | 1.6 x .4 (= .64) |
You may perhaps have noticed that we didn't integrate the TimeStamp values into our Exchange Rate methods - this was put in because clearly exchange rates only apply at a particular point in time. Ideally, the convert() methods should have been tagged with a TimeStamp, and therefore most other methods dealing with the ExchRate class. Now most computer applications apply at some moment in time - they could be called "synchronic", whereas in real life things change across time ("diachronic"). So it was reasonable to leave the time stamp off the exchange rate. We left it in, however, as a pointer to a wider scope that we could implement at some time in the future. In fact, in my experience, the time dimension is always the hardest aspect of computer applications to implement.
This also illustrates a problem with the "big bang" approach to system development: if you don't have time to "do it right the first time" (you never do), you must build in enough flexibility so that you can move there incrementally, preferably without destroying your working system.
This seems like a good time to talk about dates, timestamps and time zones. Our application had to deal with time zones right around the world, as we had to support someone in Tokyo buying a stock that was listed on the Toronto Stock Exchange on the other side of the planet. Perhaps in hindsight we should have given our classes different names, as Java has some of these classes, both in the Java.util and Java.sql packages, but in practice this didn't cause confusion as we hid the Java.util classes behind our own. We couldn't hide the Java.sql classes, but the spelling difference (see below) helped!
We did have to do a lot of thinking about what times and dates really mean, as this is something that the designers of most business applications never have to think about. We decided to hold times in UTC (Coordinated Universal Time), which is based on Greenwich, UK, and does not change with Daylight Savings Time (DST). So we had to know which time zones particular stock exchanges were in, and which time zones our users would be in. When you are dealing with a 7/24, world-wide application, the only notation that tells you whether two events are simultaneous is UTC.
A time zone not only determines how many hours ahead of or behind
UTC the location is, but also when they switch from and to DST - if in
fact they do (Japan does not switch nor does the province of
Saskatchewan in Canada). It turned out that the version of Java
we were using had a very incomplete set of time zones, but an excellent
mechanism
for building new ones! A major part of the problem, as Java has
found out, is that there is no generally agreed on standard for time
zone
names. Java (from 1.2 on) seems to use a convention of
"area/city",
but one of the areas is America, so there are zones with names like
America/Winnipeg, which is not really acceptable in a Canadian
application! Not to
mention America/Manaus, America/Tortola and America/St_Kitts, but for
some
reason Atlantic/Bermuda! And Java certainly does not seem to
know
much about Newfoundland, which is famous (at least up here) for being
1/2
hour out of step with the next time zone (honest!) - it is actually 3.5
hours behind UTC (not counting DST) - Java has it as 4 hours behind,
but
it does say Canada/Newfoundland (I think because it treats it as a
zone,
rather than a city). We therefore used the commonly used Canadian
abbreviations where possible, but these are only really unique within
Canada
and I do not know whether they are encoded in any standards
document.
The problem of coming up with unique, worldwide zone designations
needs to be addressed, but I don't really feel that the Java approach
is
adequate. However, we should give them credit for trying!
In
case you were wondering, it is not enough to just use the offset in
hours
from UTC, as different time zones may have the same offset but
different
rules for beginning and ending DST.
The Java Date class includes time, but ours did not, which I feel is more logical. Our TimeStamp class was more similar to the Java Date class, except that the Java Date class has a default time zone, which we felt would be confusing. It's better to always have to state the time zone explicitly... when it matters.... Since all timestamps were held in UTC, we only needed time zones when handling input from, or output to, humans. Our time zone class is actually a simple one: we just used the Java SimpleTimeZone class to build a list of time zones, identified by a 3-character abbreviation, "CST", "SST", etc., and then used the abbreviation as an optional part of a time stamp (default was UTC), e.g. 20021021T13:10:40678!CST. Note that this can only be converted into a UTC time stamp if the date portion is present.
It actually took a little effort for old-guard programmers to start thinking in terms of time zones. For example, the date does depend on the time zone: it can be Tuesday in Tokyo while it's Monday in Toronto, so you can convert from date plus time (UTC) to the local date, if you have the time zone, but you can't go from a date by itself to UTC unless you know the time and time zone. Actually there is an hour during the year, in time zones with DST, when the same local time repeats itself.
To make things even more interesting, the Java.sql package has Date and Timestamp (note the lower case 's'): the SQL Date is very similar to ours, while Timestamp goes down to nanoseconds, which we really didn't need. We provided conversion methods between our classes and the corresponding Java.sql ones, so that we could store our data in relational databases.
Just to get this class out of the way, I will give a brief description of this class. We decided we needed a Bool class mostly because we built a rules-driven decision tool which specified its rules in XML. A patent is pending on this work: "Rules-Based Engine for Validating Financial Transactions". I hope you can read legalese, as I can't, and I helped to write the thing!
A couple of interesting attributes of this class are:
The equals() method returns true if and only if the argument is not null and is a Boolean object that contains the same boolean value as this object. For obvious reasons, we defined two public static final objects called TRUE and FALSE.
We are now getting into an area which is less standardized (or
standardizable). The lack of standardization in the industry means that
we cannot specify
classes that will be usable world-wide. However, hopefully the
following
remarks will give some of the flavour of what we felt was needed for
our
particular application..
In our case, the account ID object was comprised of three parts:
- identifier (required)
- optional firm
- optional system ID
It was basically used as an identifier.
A Market object was identified by its Reuters code, and had attributes
such as time zone, open and close times, list of holidays, country code
and quote currency. The open and close times were reloaded at
the
start of every day, as occasionally a particular market would be closed
unexpectedly,
for varying amounts of time, e.g. an hour or a half-day. In
addition
the open and close times could be different for different financial
instruments,
so this information had to be stored, and reflected in the methods we
provided.
InstrumentId was constructed out of a schema (a number) and a financial
instrument ID. The schema numbers indicate what coding scheme is used
for the financial instrument ID. The schema numbers are as follows:
1 | CUSIP |
2 | SEDOL |
3 | QUIK |
4 | ISIN number |
5 | RIC code |
6 | ISO Currency code |
7 | ISO Country code |
111 | IBM security number |
112 | ADP security number |
113 | SVC |
114 | Canadian Mutual Funds |
999 | Unknown |
A Symbol object identified something that is traded in a market.
It was constructed out of a market ID, symbol, and country code.
This
reflects the fact that a symbol is only unique within a market.
Either
market or country will usually be present - if both are missing, we
used
the default country.