 • Search in book...
• Toggle Font Controls

# Chapter 8. Evaluating a Portfolio

Money itself isn’t lost or made, it’s simply transferred from one perception to another. Like magic.

Gordon Gekko, Wall Street (the movie)

We’ve dallied around the question of how to convert the several `Money` s in a `Portfolio` into a single `currency`. Let’s dally no longer!

The next feature on our list is the one dealing with mixed currencies:

 5 USD x 2 = 10 USD 10 EUR x 2 = 20 EUR 4002 KRW / 4 = 1000.5 KRW 5 USD + 10 USD = 15 USD Separate test code from production code Remove redundant tests 5 USD + 10 EUR = 17 USD 1 USD + 1100 KRW = 2200 KRW

# Mixing money

A heterogeneous combination of currencies demands that we create a new abstraction in our code: the conversion of money from one currency to another. This requires establishing some ground rules about currency conversions, drawn from our problem domain:

1. Conversion always relates a pair of currencies. This is important because we want all conversions to be independent. It does happen in reality that multiple currencies are “pegged” to one single currency — which means that a particular exchange rate is fixed de jure. 1 Even in such cases, it’s important to treat each pegged relationship as a distinct pair.

2. Conversion is from one currency to another with a well defined exchange rate. The exchange rate — the number of units of the “to” currency we get for one unit of the “from” currency — is a key component of currency conversion. The exchange rate is represented by a fractional number.

3. The two exchange rates between a pair currencies may or may not be arithmetical reciprocals of each other. For example: the exchange rate from EUR to USD may or may not be the mathematical reciprocal (i.e. 1⁠/⁠x) of the exchange rate from USD to EUR.

4. It is possible for a currency to have no defined exchange rate to another currency. This could be because one of the two currencies is an inconvertible currency. 2

Given that currency conversion involves all the above considerations, how should we implement it? The answer is: one test-driven scenario at a time!

We’ll start by test-driving the scenario listed in the next item on our feature list: the conversion from EUR to USD. This will help us set up the scaffolding for the “convert” method and a single exchange rate from EUR to USD. Because exchange rates are unidirectional, we’ll represent this particular one as “EUR→USD”.

Starting with this one scenario means it’s likely that we’ll add more items to our feature list. That’s all right — making controlled progress at a measured pace isn’t a bad deal!

## Go

Let’s write our new test in `money_test.go` to represent addition of Dollars and Euros:

````func`` ``TestAdditionOfDollarsAndEuros``(``t`` ``*``testing``.``T``)`` ``{````
````    ``var`` ``portfolio`` ``s``.``Portfolio````
``````
````    ``fiveDollars`` ``:=`` ``s``.``NewMoney``(``5``,`` ``"USD"``)````
````    ``tenEuros`` ``:=`` ``s``.``NewMoney``(``10``,`` ``"EUR"``)````
``````
````    ``portfolio`` ``=`` ``portfolio``.``Add``(``fiveDollars``)````
````    ``portfolio`` ``=`` ``portfolio``.``Add``(``tenEuros``)````
``````
````    ``expectedValue`` ``:=`` ``s``.``NewMoney``(``17``,`` ``"USD"``)`` ` ```
````actualValue`` ``:=`` ``portfolio``.``Evaluate``(``"USD"``)````
``````
````    ``assertEqual``(``t``,`` ``expectedValue``,`` ``actualValue``)````
````}```` The expected value of 17 USD assumes that we get 1.2 dollars for every euro we convert

The test creates two `Money` `struct`s representing 5 USD and 10 EUR, respectively. They are added to a newly created `Portfolio` `struct`. The `actualValue` from evaluating the Portfolio in dollars is compared with the `expectedValue` `struct` of 17 USD.

The test fails as expected:

``...` `Expected`  `[{``amount``:``17` `currency``:``USD``}]` `Got``:` `[{``amount``:``15` `currency``:``USD``}]``

This validates what we know: the `evaluate` method simply adds the amounts of all `Money` `struct`s (5 and 10 in our test) to get the result, regardless of the currencies involved (USD and EUR, respectively, in our test).

What we need is to first convert the amount of each Money into the target currency and then add it.

````    ``for`` ``_``,`` ``m`` ``:=`` ``range`` ``p`` ``{`` ` ```
````total`` ``=`` ``total`` ``+`` ``convert``(``m``,`` ``currency``)````
````    ``}```` In `Evaluate` method

How should we write the `convert` method? The simplest thing that works is to return the `amount` when the currencies match and to arbitrarily multiply by the conversion rate required by our test otherwise:

````func`` ``convert``(``money`` ``Money``,`` ``currency`` ``string``)`` ``float64`` ``{`` ` ```
````if`` ``money``.``currency`` ``==`` ``currency`` ``{````
````        ``return`` ``money``.``amount````
````    ``}````
````    ``return`` ``money``.``amount`` ``*`` ``1.2`` ` ```
````}```` New function in `portfolio.go` file Hard-coded exchange rate

The test turns green, but something doesn’t seem right about our code! Specifically:

1. The exchange rate is hard-coded. It should be declared as a variable.

2. The exchange rate isn’t dependent on the currency. It should be looked up based on the two currencies involved.

3. The exchange rate should be modifiable.

Let’s address the first of these and add the remaining two to our feature list. We define a variable named `eurToUsd` in our `convert` method and use it:

````func`` ``convert``(``money`` ``Money``,`` ``currency`` ``string``)`` ``float64`` ``{````
````    ``eurToUsd`` ``:=`` ``1.2`` ` ```
````if`` ``money``.``currency`` ``==`` ``currency`` ``{````
````        ``return`` ``money``.``amount````
````    ``}````
````    ``return`` ``money``.``amount`` ``*`` ``eurToUsd`` ` ```
````}```` Exchange rate is defined as an appropriately named variable The exchange rate variable is used to convert currency

The test is still green.

## JavaScript

Let’s start by adding a new test in `MoneyTest` to test the addition of Dollars and Euros:

````  ``testAdditionOfDollarsAndEuros``(``)`` ``{````
````let`` ``fiveDollars`` ``=`` ``new`` ``Money``(``5``,`` ``"USD"``)``;````
````let`` ``tenEuros`` ``=`` ``new`` ``Money``(``10``,`` ``"EUR"``)``;````
````let`` ``portfolio`` ``=`` ``new`` ``Portfolio``(``)``;````
````portfolio``.``add``(``fiveDollars``,`` ``tenEuros``)``;````
````let`` ``expectedValue`` ``=`` ``new`` ``Money``(``17``,`` ``"USD"``)``;`` ` ```
````assert``.``deepStrictEqual``(``portfolio``.``evaluate``(``"USD"``)``,`` ``expectedValue``)``;````
````}```` The expected value of 17 USD assumes that we get 1.2 dollars for every euro we convert

The test creates two `Money` objects representing 5 USD and 10 EUR each. These are added to a `Portfolio` object. The value from evaluating the `Portfolio` in USD is compared with a `Money` object representing 17 USD.

The test fails as expected:

````AssertionError` `[``ERR_ASSERTION``]``:` `Expected` `values` `to` `be` `strictly` `deep``-``equal``:`
`+` `actual` `-` `expected`

`Money` `{`
`+`   `amount``:` `15``,`
`-`   `amount``:` `17``,`
`currency``:` `'USD'`
`}````

We expect this failure because the current implementation of `evaluate` method simply adds the `amount` attribute of all `Money` objects, regardless of their currencies.

We need to first convert the amount of each Money into the target currency and then sum it.

```    `evaluate``(``currency``)` `{`
`let` `total` `=` `this``.``moneys``.``reduce``((``sum``,` `money``)` `=>` `{`
`return` `sum` `+` `this``.``convert``(``money``,` `currency``);`
`},` `0``);`
`return` `new` `Money``(``total``,` `currency``);`
`}````

How should the `convert` method work? For now, the simplest implementation that works is one that returns `amount` when the currencies match and otherwise multiplies the amount by the conversion rate require by our test:

````    ``convert``(``money``,`` ``currency``)`` ``{`` ` ```
````if`` ``(``money``.``currency`` ``===`` ``currency``)`` ``{````
````return`` ``money``.``amount``;````
````}````
````return`` ``money``.``amount`` ``*`` ``1.2``;`` ` ```
````}```` New method in `Portfolio` class Hard-coded exchange rate

The test is now green. That’s progress, but not everything is hunky-dory. In particular:

1. The exchange rate is hard-coded. It should be declared as a variable.

2. The exchange rate isn’t dependent on the currency. It should be looked up based on the two currencies involved.

3. The exchange rate should be modifiable.

Let’s address the first of these right away and add the remaining to our feature list.

We define a variable named `eurToUsd` and use it in our `convert` method:

````    ``convert``(``money``,`` ``currency``)`` ``{````
````let`` ``eurToUsd`` ``=`` ``1.2``;`` ` ```
````if`` ``(``money``.``currency`` ``===`` ``currency``)`` ``{````
````return`` ``money``.``amount``;````
````}````
````return`` ``money``.``amount`` ``*`` ``eurToUsd``;`` ` ```
````}```` Exchange rate is defined as an appropriately named variable The exchange rate variable is used to convert currency

All tests are green.

## Python

Let’s write a new test in `test_money.py` that validates adding Dollars to Euros:

````    ``def`` ``testAdditionOfDollarsAndEuros``(``self``)``:````
````        ``fiveDollars`` ``=`` ``Money``(``5``,`` ``"``USD``"``)````
````        ``tenEuros`` ``=`` ``Money``(``10``,`` ``"``EUR``"``)````
````        ``portfolio`` ``=`` ``Portfolio``(``)````
````        ``portfolio``.``add``(``fiveDollars``,`` ``tenEuros``)````
````        ``expectedValue`` ``=`` ``Money``(``17``,`` ``"``USD``"``)`` ` ```
````        ``actualValue`` ``=`` ``portfolio``.``evaluate``(``"``USD``"``)````
````        ``self``.``assertEqual``(``expectedValue``,`` ``actualValue``)```` The expected value of 17 USD assumes that we get 1.2 dollars for every euro we convert

The test creates two `Money` objects representing 5 USD and 10 EUR, respectively. They are added to a pristine `Portfolio` object. The `actualValue` from evaluating the `Portfolio` in dollars is compared with a newly minted `expectedValue` of 17 USD.

We expect the test to fail, of course, because we are in the RED phase of our of RGR cycle. However, the error message from the assertion failure is rather cryptic:

``AssertionError``:` `<``money``.``Money` `object` `at` `0x10f3c3280``>` `!=` `<``money``.``Money` `object` `at` `0x10f3c33a0``>``

Who on earth knows what mysterious goblins reside at those obscure memory addresses!

This is one of those times where we must slow down and write a better failing test before we attempt to get to GREEN. Can we make the assertion statement print a more helpful error message?

The `assertEqual` method — like most other assertion methods in the `unittest` package — takes an optional third parameter, which is a custom error message. Let’s provide a formatted string showing the stringified representation of `expectedValue` and `actualValue`:

````self``.``assertEqual``(``expectedValue``,`` ``actualValue``,````
````                ``"``%s`` != ``%s``"``%``(``expectedValue``,`` ``actualValue``)``)`` ` ``` Last line of `testAdditionOfDollarsAndEuros` test method

Nope! That simply prints the obscure memory addresses twice:

````AssertionError``:`
`<``money``.``Money` `object` `at` `0x1081111f0``>` `!=` `<``money``.``Money` `object` `at` `0x108111310``>` `:`
`<``money``.``Money` `object` `at` `0x1081111f0``>` `!=` `<``money``.``Money` `object` `at` `0x108111310``>````

What we need to do is to override the `__str__` method in the `Money` class and make it return a more human-readable representation. Something like “USD 17.00”.

````    ``def`` ``__str__``(``self``)``:`` ` ```
````        ``return`` ``f``"``{self.currency} {self.amount:0.2f}``"```` In `Money` class

We format `Money`’s `currency` and `amount` fields, printing the latter up to two decimal places.

After adding the `__str__` method, let’s run our test suite again:

``AssertionError``:` `...` `USD` `17.00` `!=` `USD` `15.00``

Ah, much better! Seventeen dollars certainly aren’t the same thing as 15 dollars!

###### Tip

Python’s F-strings interpolation provides a succinct and neat way to format strings with a mixture of fixed text and variables. F-strings were defined in [PEP-498]https://www.python.org/dev/peps/pep-0498/ and have been a part of Python since version 3.6.

This validates our belief that the `evaluate` method, as currently implemented, mindlessly adds the amounts of all `Money` objects (5 and 10 in our test) to get the result, with no regard to the currencies (USD and EUR, respectively, in our test).

A closer examination of the `evaluate` method shows that the mindlessness is in the lambda expression. It maps every `Money` object to its `amount`, regardless of its currency. These amounts are then added up by the `reduce` function using the `add` operator.

What if the lambda expression mapped every `Money` object to its converted value? The target currency for the conversion would be the currency in which the `Portfolio` is being evaluated.

````        ``total`` ``=`` ``functools``.``reduce``(``operator``.``add``,````
````          ``map``(``lambda`` ``m``:`` ``self``.``__convert``(``m``,`` ``currency``)``,`` ``self``.``moneys``)``,`` ``0``)`` ` ``` In `Money` class

###### Important

Python doesn’t have truly “Private” scope for variables or functions. The naming convention and something called “name mangling” ensure that names with two leading underscores are treated as private.

How should we implement the `__convert` method? Converting to the same `currency` as that of the `Money` is trivial: the `Money`’s amount doesn’t change in this case. When converting to a different currency, we’ll multiply `Money`’s amount with the (for now) hard-coded exchange rate between USD and EUR:

````    ``def`` ``__convert``(``self``,`` ``aMoney``,`` ``aCurrency``)``:`` ` ```
````        ``if`` ``aMoney``.``currency`` ``==`` ``aCurrency``:````
````            ``return`` ``aMoney``.``amount````
````        ``else``:````
````            ``return`` ``aMoney``.``amount`` ``*`` ``1.2`` ` ``` New method in `Portfolio` class Hard-coded exchange rate

The test is green. Yay … and hmm! We should do the refactoring to remove the ugliness of this code. Here are some problems with it:

1. The exchange rate is hard-coded. It should be declared as a variable.

2. The exchange rate isn’t dependent on the currency. It should be looked up based on the two currencies involved.

3. The exchange rate should be modifiable.

Let’s address the first of these three items in the REFACTOR phase and add the remaining two to our feature list.

We define a private variable named `_eur_to_usd` in the `__init__` method and use it instead of the hard-coded value in the `__convert` method:

````class`` ``Portfolio``:````
````    ``def`` ``__init__``(``self``)``:````
````        ``self``.``moneys`` ``=`` ``[``]````
````        ``self``.``_eur_to_usd`` ``=`` ``1.2`` ` ```
````.``.``.````
````    ``def`` ``__convert``(``self``,`` ``aMoney``,`` ``aCurrency``)``:````
````        ``if`` ``aMoney``.``currency`` ``==`` ``aCurrency``:````
````            ``return`` ``aMoney``.``amount````
````        ``else``:````
````            ``return`` ``aMoney``.``amount`` ``*`` ``self``.``_eur_to_usd`` ` ``` Exchange rate is defined as an appropriately named variable The exchange rate variable is used to convert currency

All tests are green.

# Committing our changes

We have our first implementation of converting money between two different currencies, specifically USD → EUR. Let’s commit our changes to our local Git repository:

```git add .
git commit -m `"feat: convert from EUR to USD"````

# Where We Are

We have solved the conversion of `Money` s in different currencies for the scenario of converting USD to EUR. However, we cut a few corners while doing so. The conversion only works for one specific case (USD → EUR). Furthermore, there is no way to add or modify exchange rates.

Let’s update our feature list to cross out the accomplished items and add the new ones.

 5 USD x 2 = 10 USD 10 EUR x 2 = 20 EUR 4002 KRW / 4 = 1000.5 KRW 5 USD + 10 USD = 15 USD Separate test code from production code Remove redundant tests 5 USD + 10 EUR = 17 USD 1 USD + 1100 KRW = 2200 KRW Determine exchange rate based on the currencies involved (from → to) Allow exchange rates to be modified

1 For an economic discussion of currency pegging, see https://www.investopedia.com/terms/c/currency-peg.asp

2 Currencies can be inconvertible for a variety of reasons: political, economic, or even military. https://www.investopedia.com/terms/i/inconvertible_currency.asp

• No Comment
..................Content has been hidden....................