Skip to content
This repository has been archived by the owner on Jul 2, 2018. It is now read-only.

Commit

Permalink
[MNY-18]: Fixes tests
Browse files Browse the repository at this point in the history
  • Loading branch information
danthorpe committed Nov 8, 2015
1 parent 074ca0b commit 4bc2ad2
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 39 deletions.
26 changes: 18 additions & 8 deletions Money/FX/Bitcoin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,21 @@ public typealias BTC = _Money<Currency.BTC>

// MARK - cex.io FX

public protocol CEXSupportedFiatCurrencyType: CurrencyType { }
public protocol CEXSupportedFiatCurrencyType: CurrencyType {
static var cex_commissionPercentage: BankersDecimal { get }
}

extension Currency.USD: CEXSupportedFiatCurrencyType {
public static let cex_commissionPercentage: BankersDecimal = 0.2
}

extension Currency.USD: CEXSupportedFiatCurrencyType { }
extension Currency.EUR: CEXSupportedFiatCurrencyType { }
extension Currency.RUB: CEXSupportedFiatCurrencyType { }
extension Currency.EUR: CEXSupportedFiatCurrencyType {
public static let cex_commissionPercentage: BankersDecimal = 0.2
}

extension Currency.RUB: CEXSupportedFiatCurrencyType {
public static let cex_commissionPercentage: BankersDecimal = 0
}

struct _CEXBuy<Base: MoneyType where Base.Currency: CEXSupportedFiatCurrencyType>: CryptoCurrencyMarketTransactionType {
typealias BaseMoney = Base
Expand All @@ -96,14 +106,14 @@ struct _CEXSell<Counter: MoneyType where Counter.Currency: CEXSupportedFiatCurre
static var transactionKind: CurrencyMarketTransactionKind { return .Sell }
}

class _CEX<Transaction: CryptoCurrencyMarketTransactionType where Transaction.FiatCurrency: CEXSupportedFiatCurrencyType>: FXRemoteProvider<Transaction.BaseMoney, Transaction.CounterMoney>, FXRemoteProviderType {
class _CEX<T: CryptoCurrencyMarketTransactionType where T.FiatCurrency: CEXSupportedFiatCurrencyType>: FXRemoteProvider<T.BaseMoney, T.CounterMoney>, FXRemoteProviderType {

static func name() -> String {
return "CEX.IO \(BaseMoney.Currency.code)\(CounterMoney.Currency.code)"
}

static func request() -> NSURLRequest {
let url = NSURL(string: "https://cex.io/api/convert/\(BTC.Currency.code)/\(Transaction.FiatCurrency.code)")
let url = NSURL(string: "https://cex.io/api/convert/\(BTC.Currency.code)/\(T.FiatCurrency.code)")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
Expand Down Expand Up @@ -133,14 +143,14 @@ class _CEX<Transaction: CryptoCurrencyMarketTransactionType where Transaction.Fi

let rate: BankersDecimal

switch Transaction.transactionKind {
switch T.transactionKind {
case .Buy:
rate = BankersDecimal(floatLiteral: rateLiteral).reciprocal
case .Sell:
rate = BankersDecimal(floatLiteral: rateLiteral)
}

return Result(value: FXQuote(rate: rate, percentage: 0.2))
return Result(value: FXQuote(rate: rate, percentage: T.FiatCurrency.cex_commissionPercentage))
},

ifFailure: { error in
Expand Down
20 changes: 12 additions & 8 deletions Money/FX/FX.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,14 @@ public struct FXTransaction<Base, Counter where
public let rate: BankersDecimal
public let counter: CounterMoney

init(base: BaseMoney, commission: BaseMoney, rate: BankersDecimal, counter: CounterMoney) {
internal init(base: BaseMoney, commission: BaseMoney, rate: BankersDecimal, counter: CounterMoney) {
self.base = base
self.commission = commission
self.rate = rate
self.counter = counter
}

init(base: BaseMoney, quote: FXQuote) {
public init(base: BaseMoney, quote: FXQuote) {
self.base = base
self.commission = quote.commission(base)
self.rate = quote.rate
Expand Down Expand Up @@ -218,11 +218,13 @@ extension FXLocalProviderType where
CounterMoney.Coder.ValueType == CounterMoney,
CounterMoney.DecimalStorageType == BankersDecimal.DecimalStorageType {

public typealias Transaction = FXTransaction<BaseMoney, CounterMoney>

/**
This is the primary API used to determine for Foreign Exchange transactions.
*/
public static func fx(base: BaseMoney) -> FXTransaction<BaseMoney, CounterMoney> {
return FXTransaction(base: base, quote: quote())
public static func fx(base: BaseMoney) -> Transaction {
return Transaction(base: base, quote: quote())
}
}

Expand Down Expand Up @@ -294,8 +296,10 @@ extension FXRemoteProviderType where
CounterMoney.Coder.ValueType == CounterMoney,
CounterMoney.DecimalStorageType == BankersDecimal.DecimalStorageType {

internal static func fxFromQuoteWithBase(base: BaseMoney) -> FXQuote -> FXTransaction<BaseMoney, CounterMoney> {
return { FXTransaction(base: base, quote: $0) }
public typealias Transaction = FXTransaction<BaseMoney, CounterMoney>

internal static func fxFromQuoteWithBase(base: BaseMoney) -> FXQuote -> Transaction {
return { Transaction(base: base, quote: $0) }
}

/**
Expand All @@ -317,7 +321,7 @@ extension FXRemoteProviderType where
base money, the quote, and the counter money, or `(BaseMoney, FXQuote, CounterMoney)`.
- returns: an `NSURLSessionDataTask`.
*/
public static func quote(base: BaseMoney, completion: Result<FXTransaction<BaseMoney, CounterMoney>, FXError> -> Void) -> NSURLSessionDataTask {
public static func quote(base: BaseMoney, completion: Result<Transaction, FXError> -> Void) -> NSURLSessionDataTask {
let client = FXServiceProviderNetworkClient(session: session())
let fxFromQuote = fxFromQuoteWithBase(base)
return client.get(request(), adaptor: quoteFromNetworkResult) { completion($0.map(fxFromQuote)) }
Expand Down Expand Up @@ -399,7 +403,7 @@ public final class FXQuoteCoder: NSObject, NSCoding, CodingType {
}
}

extension FXTransaction {
extension FXTransaction: ValueCoding {
public typealias Coder = FXTransactionCoder<BaseMoney, CounterMoney>
}

Expand Down
36 changes: 15 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ The following code snippet represent a currency exchange using Yahoo’s currenc
```swift
Yahoo<USD,EUR>.quote(100) { result in
if let (dollars, quote, euros) = result.value {
print("Exchanged \(dollars) into \(euros) with a rate of \(quote.rate)")
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 € 92.15 with a rate of 0.9215
> Exchanged US$ 100.00 into € 93.09 with a rate of 0.93089 and US$ 0.00 commission.
The result, delivered asynchronously, uses [`Result`](http://github.com/antitypical/Result) to encapsulate either a tuple value `(BaseMoney, FXQuote, CounterMoney)` or an `FXError` value. Obviously, in real code - you’d need to check for errors ;)
The result, delivered asynchronously, uses [`Result`](http://github.com/antitypical/Result) to encapsulate either a `FXTransaction` or an `FXError` value. Obviously, in real code - you’d need to check for errors ;)
There is a neat convenience function which just returns the `CounterMoney` as its `Result` value type.
Expand All @@ -88,7 +88,7 @@ Yahoo<USD,EUR>.fx(100) { euros in
}
```
> You got .Success(€ 92.15)
> You got .Success(€ 93.09)
### Creating custom FX service providers
Expand Down Expand Up @@ -134,13 +134,9 @@ public static func quoteFromNetworkResult(result: Result<(NSData?, NSURLResponse

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.

Additionally FX APIs will be added shortly,
1. To calculate the reverse exchange, i.e. how many dollars would I need to get so many euros.
2. For the two (forward & reverse) exchanges, I’ll also add a `quote` function, which will return the `FXQuote` object. This might be useful if your app needs to persist the quote used for an exchange.

# Bitcoin Support

As of version 1.2, Money has support for Bitcoin. Bitcoin has two type, the popular `BTC` and the unofficial ISO 4217 currency code `XBT`.
Money has support for Bitcoin types, the popular `BTC` and the unofficial ISO 4217 currency code `XBT`.

In [November 2015](http://www.coindesk.com/bitcoin-unicode-symbol-approval/), the Unicode consortium accepted U+20BF as the Bitcoin symbol. However, right now that means it is not available in Foundation. Therefore, currently the Bitcoin currency type(s) use Ƀ, which is also a popular symbol and available already within Unicode.

Expand All @@ -154,37 +150,35 @@ print(“You have \(bitcoin)”)

## CEX.IO

Money has support for using [CEX.IO](https://cex.io)’s [trade api] to support quotes of Bitcoin currency exchanges. Note that CEX only support `USD`, `EUR,` and `RUB` fiat currencies. It’s usage is a little bit different for a regular FX.
Money has support for using [CEX.IO](https://cex.io)’s [trade api] to support quotes of Bitcoin currency exchanges. Note that CEX only support `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:

```swift
CEXBuy<USD>.quote(100) { result in
if let (dollars, quote, bitcoins) = result.value {
print("\(dollars) will buy you \(bitcoins) at a rate of \(quote.rate)")
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 you Ƀ0.25773196 at a rate of 0.00257731958762886597938144329896907216
> 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:

```swift
CEXSell<EUR>.quote(100) { result in
if let (bitcoins, quote, euros) = result.value {
print("\(bitcoins) will sell for \(euros) with a rate of \(quote.rate)")
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.")
}
}
```
> Ƀ100.00 will sell for € 35,748.99 with a rate of 357.4898999999999488
> Ƀ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 [fiat currency](https://en.wikipedia.org/wiki/Fiat_money) not supported by CEX the compiler will prevent your code from compiling.

```swift
CEXSell<GBP>.quote(50) { result in
if let (bitcoins, quote, euros) = result.value {
print("\(bitcoins) will sell for \(euros) with a rate of \(quote.rate)")
}
// etc
}
```
> Type 'Currency.GBP' does not conform to protocol 'CEXSupportedFiatCurrencyType'
Expand Down
15 changes: 15 additions & 0 deletions Tests/BitcoinTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ class BitcoinCurrencyTests: XCTestCase {
}
}

class CEXTests: XCTestCase {

func test__usd_commission_percentage() {
XCTAssertEqual(Currency.USD.cex_commissionPercentage, 0.2)
}

func test__eur_commission_percentage() {
XCTAssertEqual(Currency.EUR.cex_commissionPercentage, 0.2)
}

func test__rub_commission_percentage() {
XCTAssertEqual(Currency.RUB.cex_commissionPercentage, 0)
}
}

class FXCEXBuyTests: FXProviderTests {

typealias Provider = CEXBuy<USD>
Expand Down
27 changes: 25 additions & 2 deletions Tests/FXTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ class FaultyFXRemoteProvider<Provider: FXRemoteProviderType>: FXRemoteProviderTy


class FakeLocalFX<B: MoneyType, C: MoneyType where
B.Coder: NSCoding,
B.Coder.ValueType == B,
B.DecimalStorageType == BankersDecimal.DecimalStorageType,
C.Coder: NSCoding,
C.Coder.ValueType == C,
C.DecimalStorageType == BankersDecimal.DecimalStorageType>: FXLocalProviderType {

typealias BaseMoney = B
Expand Down Expand Up @@ -123,8 +127,7 @@ class FXProviderTests: XCTestCase {
class FXLocalProviderTests: XCTestCase {

func test_fx() {
let money: Money = 10
XCTAssertEqual(FakeLocalFX<Money, USD>.fx(money).counter, 11)
XCTAssertEqual(FakeLocalFX<Money, USD>.fx(100).counter, 110)
}
}

Expand All @@ -145,3 +148,23 @@ class FXQuoteTests: XCTestCase {
XCTAssertEqual(unarchive(archiveEncodedQuote())!.rate, quote.rate)
}
}

class FXTransactionTests: XCTestCase {

typealias Transaction = FXTransaction<USD, GBP>

var transaction: Transaction!

func archiveEncodedTransaction() -> NSData {
return NSKeyedArchiver.archivedDataWithRootObject(transaction.encoded)
}

func unarchive(archive: NSData) -> Transaction? {
return Transaction.decode(NSKeyedUnarchiver.unarchiveObjectWithData(archive))
}

func test__transaction_encodes() {
transaction = Transaction(base: 100, quote: FXQuote(rate: 1.2))
XCTAssertEqual(unarchive(archiveEncodedTransaction())!.base, 100)
}
}

0 comments on commit 4bc2ad2

Please sign in to comment.