FX is a Swift framework for iOS, watchOS, tvOS and OS X. It provides support for Foreign Currency Exchange to the Money framework.
To represent a foreign exchange transaction, i.e. converting USD
to EUR
, use a FX service provider. There is built in support for Yahoo and OpenExchangeRates.org services. But it’s possible for consumers to create their own too.
The following code snippet represents a currency exchange using Yahoo’s currency converter.
Yahoo<USD,EUR>.quote(100) { result in
if let tx = result.value {
print("Exchanged \(tx.base) into \(tx.counter) with a rate of \(tx.rate) and \(tx.commission) commission.")
}
}
Exchanged US$ 100.00 into € 93.09 with a rate of 0.93089 and US$ 0.00 commission.
The result, delivered asynchronously, uses Result
to encapsulate either a FXTransaction
or an FXError
value. Obviously, in real code - you’d need to check for errors ;)
FXTransaction
is a generic type which composes the base and counter monies, the rate of the exchange, and any commission the FX service provider charged in the base currency. Currently FXQuote
only supports percentage based commission.
There is a neat convenience function which just returns the CounterMoney
as its Result
value type.
Yahoo<USD,EUR>.fx(100) { euros in
print("You got \(euros)")
}
You got .Success(€ 93.09)
Creating a custom FX service provider is straightforward. The protocols FXLocalProviderType
and FXRemoteProviderType
define the minimum requirements. The quote
and fx
methods are provided via extensions on the protocols.
For a remote FX service provider, i.e. one which will make a network request to get a rate, we can look at the Yahoo
provider to see how it works.
Firstly, we subclass the generic class FXRemoteProvider
. The generic types are both constrained to MoneyType
. The naming conventions follow those of a currency pair.
public class Yahoo<B: MoneyType, C: MoneyType>: FXRemoteProvider<B, C>, FXRemoteProviderType {
// etc
}
FXRemoteProvider
provides the typealiases for BaseMoney
and CounterMoney
which will be needed to introspect the currency codes.
The protocol requires that we can construct a NSURLRequest
.
public static func request() -> NSURLRequest {
return NSURLRequest(URL: NSURL(string: "https://download.finance.yahoo.com/d/quotes.csv?s=\(BaseMoney.Currency.code)\(CounterMoney.Currency.code)=X&f=nl1")!)
}
The last requirement, is that the network result can be mapped into a Result<FXQuote,FXError>
.
FXQuote
is a struct, which composes the exchange rate and percentage commission to be used. Both properties are BankersDecimal
values (see below on the decimal implementation details).
public static func quoteFromNetworkResult(result: Result<(NSData?, NSURLResponse?), NSError>) -> Result<FXQuote, FXError> {
return result.analysis(
ifSuccess: { data, response in
let rate: BankersDecimal = 1.5 // or whatever
return Result(value: FXQuote(rate: rate))
},
ifFailure: { error in
return Result(error: .NetworkError(error))
}
)
}
Note that the provider doesn’t need to perform any networking itself. It is all done by the framework. This is a deliberate architectural design as it makes it much easier to unit test the adaptor code.
FX has support for using CEX.IO’s trade api to support quotes of Bitcoin currency exchanges. CEX only supports USD
, EUR,
and RUB
fiat currencies.
It’s usage is a little bit different for a regular FX. To represent the purchase of Bitcoins use CEXBuy
like this:
CEXBuy<USD>.quote(100) { result in
if let tx = result.value {
print("\(tx.base) will buy \(tx.counter) at a rate of \(tx.rate) with \(tx.commission)")
}
}
US$ 100.00 will buy Ƀ0.26219275 at a rate of 0.0026272 with US$ 0.20 commission.
To represent the sale of Bitcoins use CEXSell
like this:
CEXSell<EUR>.quote(50) { result in
if let tx = result.value {
print("\(tx.base) will sell for \(tx.counter) at a rate of \(tx.rate) with \(tx.commission) commission.")
}
}
Ƀ50.00 will sell for € 17,541.87 at a rate of 351.5405 with Ƀ0.10 commission.
If trying to buy or sell using a currency not supported by CEX the compiler will prevent your code from compiling.
CEXSell<GBP>.quote(50) { result in
// etc
}
Type 'Currency.GBP' does not conform to protocol 'CEXSupportedFiatCurrencyType'