Skip to content

danthorpe/FX

Repository files navigation

Build status Coverage Status CocoaPods Compatible Carthage compatible Platform

FX

FX is a Swift framework for iOS, watchOS, tvOS and OS X. It provides support for Foreign Currency Exchange to the Money framework.

Foreign Currency Exchange (FX)

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 custom FX service providers

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.

Bitcoin

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'