forex 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +7 -1
- data/README.md +43 -10
- data/features/step_definitions/tabular_rates_steps.rb +42 -13
- data/features/support/env.rb +1 -0
- data/features/tabular_rates.feature +9 -6
- data/forex.gemspec +2 -0
- data/lib/forex.rb +2 -1
- data/lib/forex/tabular_rates.rb +30 -31
- data/lib/forex/traders/jm/bns.rb +1 -1
- data/lib/forex/traders/jm/cok.rb +24 -0
- data/lib/forex/traders/jm/fgb.rb +1 -1
- data/lib/forex/traders/jm/fxtraders.rb +8 -3
- data/lib/forex/traders/jm/jmmb.rb +7 -2
- data/lib/forex/traders/jm/jnbs.rb +1 -2
- data/lib/forex/traders/jm/ncb.rb +1 -1
- data/lib/forex/traders/jm/sagicor.rb +3 -3
- data/lib/forex/version.rb +1 -1
- data/spec/rates/cok.yml +20 -0
- data/spec/rates/fxtraders.yml +26 -0
- data/spec/vcr/traders/cok.yml +681 -0
- data/spec/vcr/traders/{sagicore.yml → sagicor.yml} +1 -1
- metadata +39 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0805a3b1ecebdfe37fb068a85dc133dd9e500007
|
4
|
+
data.tar.gz: 735001cf921eeeda36f4c1235adce750aff8000b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cca23e919fe8b9745119b06430c644e7c33d4c5867bd9e844db861984f9a8d405661a0d3a3c2f2d560a19917a88198ac025e5e8b7e23c30fd6ae12c0cd2b23f
|
7
|
+
data.tar.gz: 5e7980edf74dd90a048f833ee74c1c3c280c26bcb223f6137fe45333aa1e6d83df87e5e8324e5c8b99daa6485c9f2b61d307282b05983b429fa72e26e35b3570
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
# Forex
|
2
2
|
|
3
|
+
[![Code Climate](https://codeclimate.com/github/mcmorgan/forex.png)](https://codeclimate.com/github/mcmorgan/forex)
|
4
|
+
[![Build Status](https://api.travis-ci.org/mcmorgan/forex.png)](https://travis-ci.org/mcmorgan/forex)
|
5
|
+
[![Dependency Status](https://gemnasium.com/mcmorgan/forex.png)](https://gemnasium.com/mcmorgan/forex)
|
6
|
+
[![Gem Version](https://badge.fury.io/rb/forex.png)](http://rubygems.org/gems/forex)
|
7
|
+
|
3
8
|
Provides a simple DSL for managing Foreign Exchange rates (forex) for various
|
4
|
-
traders.
|
9
|
+
traders (Inspired by [CurrrencyJA](https://github.com/kenrick/currencyja)).
|
5
10
|
|
6
11
|
## Installation
|
7
12
|
|
@@ -30,7 +35,7 @@ Forex::Trader.define "NCB" do |t|
|
|
30
35
|
t.name = "National Commercial Bank"
|
31
36
|
t.endpoint = "http://www.jncb.com/rates/foreignexchangerates"
|
32
37
|
|
33
|
-
t.rates_parser =
|
38
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
34
39
|
# process the doc and return rates hash in the following format
|
35
40
|
|
36
41
|
# {
|
@@ -46,18 +51,46 @@ end
|
|
46
51
|
|
47
52
|
# Rates may be fetched for a specific trader
|
48
53
|
Forex::Trader.all['BNS'].fetch
|
54
|
+
```
|
49
55
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
56
|
+
### TabularRates
|
57
|
+
|
58
|
+
`TabularRates` allows you to simplify the process of parsing a table of rates.
|
59
|
+
Simply find the table and pass it along with options of what values are in which
|
60
|
+
columns (zero indexed).
|
61
|
+
|
62
|
+
For example:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
#...
|
66
|
+
|
67
|
+
translations = {
|
68
|
+
'US$' => 'USD',
|
69
|
+
'TT$' => 'TTD',
|
70
|
+
}
|
71
|
+
|
72
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
73
|
+
|
74
|
+
options = {
|
75
|
+
currency_code: 1,
|
76
|
+
sell_cash: 2,
|
77
|
+
buy_cash: 4,
|
78
|
+
buy_draft: 3,
|
79
|
+
}
|
80
|
+
|
81
|
+
table = doc.css(".rates table").first
|
82
|
+
|
83
|
+
# translations is optional
|
84
|
+
Forex::TabularRates.new(table, options).parse_rates(translations)
|
85
|
+
end
|
86
|
+
|
87
|
+
#...
|
54
88
|
```
|
55
89
|
|
56
|
-
##
|
90
|
+
## TODO
|
57
91
|
|
58
|
-
*
|
59
|
-
*
|
60
|
-
* [![Dependency Status](https://gemnasium.com/mcmorgan/forex.png)](https://gemnasium.com/mcmorgan/forex)
|
92
|
+
* Make it possible to have the same trader in multiple countries
|
93
|
+
* Add other traders in Jamaica
|
61
94
|
|
62
95
|
## Contributing
|
63
96
|
|
@@ -7,7 +7,7 @@ When(/^the column options exist for the tabular rates parser:$/) do |options_tab
|
|
7
7
|
end
|
8
8
|
|
9
9
|
Then(/^parsing the table should return the following rates:$/) do |table|
|
10
|
-
|
10
|
+
assert_rates_are_equal_to table
|
11
11
|
end
|
12
12
|
|
13
13
|
Then(/^parsing the table should raise an exception with the message:$/) do |table|
|
@@ -15,20 +15,49 @@ Then(/^parsing the table should raise an exception with the message:$/) do |tabl
|
|
15
15
|
expect { parse_rates }.to raise_error(message)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
When(/^the currency translations:$/) do |table|
|
19
|
+
@translations = table.raw.each_with_object({}) do |translation, h|
|
20
|
+
key, value = translation
|
21
|
+
h[key] = value
|
22
|
+
end
|
20
23
|
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
+
module KnowsTabularRates
|
26
|
+
include Forex
|
27
|
+
|
28
|
+
def translations
|
29
|
+
@translations || {}
|
30
|
+
end
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
currency, rate = *currency_rate
|
29
|
-
rates[currency] = rate.to_f == 0 ? nil : rate.to_f
|
30
|
-
end
|
32
|
+
def parse_rates
|
33
|
+
TabularRates.new(@table, @options).parse_rates(translations)
|
31
34
|
end
|
32
35
|
|
33
|
-
|
34
|
-
|
36
|
+
def assert_rates_are_equal_to(table)
|
37
|
+
table = table.dup # because we modify it below
|
38
|
+
|
39
|
+
convert_rates! table
|
40
|
+
|
41
|
+
formatted_rates = table.hashes.each_with_object({}) do |table_hash, currencies|
|
42
|
+
table_hash.symbolize_keys!
|
43
|
+
|
44
|
+
currencies[table_hash.delete(:currency_code)] =
|
45
|
+
table_hash.each_with_object({}) do |currency_rate, rates|
|
46
|
+
currency, rate = *currency_rate
|
47
|
+
rates[currency] = rate
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
parse_rates.should == formatted_rates
|
52
|
+
end
|
53
|
+
|
54
|
+
def convert_rates!(table)
|
55
|
+
zero_to_nil = ->(value) { value == 0 ? nil : value }
|
56
|
+
|
57
|
+
TabularRates::COLUMN_LABELS.each do |column_label|
|
58
|
+
table.map_column!(column_label) { |value| zero_to_nil.(value.to_f) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
World(KnowsTabularRates)
|
data/features/support/env.rb
CHANGED
@@ -9,12 +9,12 @@ Feature: Tabular Rates
|
|
9
9
|
Given the tabular rates table:
|
10
10
|
"""
|
11
11
|
<table>
|
12
|
-
<tr><td>
|
13
|
-
<tr><td>
|
14
|
-
<tr><td>
|
15
|
-
<tr><td>GBP</td> <td>166.20</td> <td>169.63</td> <td>173.30</td> </tr>
|
16
|
-
<tr><td>CAD</td> <td> 94.51</td> <td> 96.92</td> <td> 99.72</td> </tr>
|
17
|
-
<tr><td>
|
12
|
+
<tr><td> </td> <td>Cash </td> <td>Cheque</td> <td>Cash & Cheque</td></tr>
|
13
|
+
<tr><td> </td> <td>BUY </td> <td>BUY </td> <td>SELL </td> </tr>
|
14
|
+
<tr><td>US$ </td> <td>101.30</td> <td>103.30</td> <td>105.00</td> </tr>
|
15
|
+
<tr><td>GBP </td> <td>166.20</td> <td>169.63</td> <td>173.30</td> </tr>
|
16
|
+
<tr><td>CAD </td> <td> 94.51</td> <td> 96.92</td> <td> 99.72</td> </tr>
|
17
|
+
<tr><td>EURO</td> <td> </td> <td>137.42</td> <td>143.16</td> </tr>
|
18
18
|
</table>
|
19
19
|
"""
|
20
20
|
|
@@ -22,6 +22,9 @@ Feature: Tabular Rates
|
|
22
22
|
When the column options exist for the tabular rates parser:
|
23
23
|
| currency_code | buy_cash | buy_draft | sell_cash | sell_draft |
|
24
24
|
| 0 | 1 | 2 | 3 | 3 |
|
25
|
+
And the currency translations:
|
26
|
+
| US$ | USD |
|
27
|
+
| EURO | EUR |
|
25
28
|
Then parsing the table should return the following rates:
|
26
29
|
| currency_code | buy_cash | buy_draft | sell_cash | sell_draft |
|
27
30
|
| USD | 101.30 | 103.30 | 105.00 | 105.00 |
|
data/forex.gemspec
CHANGED
@@ -26,6 +26,8 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "cucumber"
|
27
27
|
spec.add_development_dependency "vcr"
|
28
28
|
spec.add_development_dependency "webmock"
|
29
|
+
spec.add_development_dependency "pry"
|
30
|
+
spec.add_development_dependency "fivemat"
|
29
31
|
|
30
32
|
spec.add_development_dependency "bundler", "~> 1.3"
|
31
33
|
spec.add_development_dependency "rake"
|
data/lib/forex.rb
CHANGED
@@ -7,4 +7,5 @@ require "forex/tabular_rates"
|
|
7
7
|
require "forex/trader"
|
8
8
|
|
9
9
|
# Traders are automatically loaded
|
10
|
-
|
10
|
+
traders_path = File.join(File.dirname(__FILE__), 'forex', 'traders', '**', '*.rb')
|
11
|
+
Dir[traders_path].each { |file| require file }
|
data/lib/forex/tabular_rates.rb
CHANGED
@@ -4,31 +4,34 @@ module Forex
|
|
4
4
|
|
5
5
|
attr_accessor :table, :options
|
6
6
|
|
7
|
+
COLUMN_LABELS = [
|
8
|
+
:buy_cash,
|
9
|
+
:buy_draft,
|
10
|
+
:sell_cash,
|
11
|
+
:sell_draft
|
12
|
+
]
|
13
|
+
|
7
14
|
DEFAULT_OPTIONS = {
|
8
15
|
currency_code: 0,
|
9
|
-
|
10
|
-
buy_draft: 2,
|
11
|
-
sell_cash: 3,
|
12
|
-
sell_draft: 4,
|
13
|
-
}
|
16
|
+
}.merge(Hash[(COLUMN_LABELS).zip(1..COLUMN_LABELS.size)])
|
14
17
|
|
15
18
|
def initialize(table, options = DEFAULT_OPTIONS)
|
16
19
|
@table = table
|
17
20
|
@options = options.symbolize_keys
|
18
21
|
end
|
19
22
|
|
20
|
-
def parse_rates
|
23
|
+
def parse_rates(translations = {})
|
21
24
|
currency = options.delete(:currency_code) || 0
|
22
25
|
|
23
26
|
table.css('tr').each_with_object({}) do |tr, currencies|
|
24
27
|
cells = tr.css('td')
|
25
28
|
next if cells.empty?
|
26
29
|
|
27
|
-
currency_code = CurrencyCode.new(cells[currency.to_i].content)
|
30
|
+
currency_code = CurrencyCode.new(cells[currency.to_i].content, translations)
|
28
31
|
|
29
32
|
next if currencies.has_key?(currency_code.to_s) || currency_code.invalid?
|
30
33
|
|
31
|
-
currencies[currency_code.to_s] =
|
34
|
+
currencies[currency_code.to_s] = COLUMN_LABELS.each_with_object({}) do |column_label, rates|
|
32
35
|
next unless rate_column = options[column_label]
|
33
36
|
|
34
37
|
rate_node = cells[rate_column.to_i]
|
@@ -39,35 +42,28 @@ module Forex
|
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
42
|
-
def column_labels
|
43
|
-
[:buy_cash, :buy_draft, :sell_cash, :sell_draft]
|
44
|
-
end
|
45
|
-
|
46
45
|
end
|
47
46
|
|
48
|
-
class Currency
|
49
|
-
|
50
|
-
def initialize(string)
|
51
|
-
@string = string
|
52
|
-
end
|
47
|
+
class Currency < Struct.new(:string)
|
53
48
|
|
54
49
|
# converts the currency to it's storage representation
|
55
50
|
def value
|
56
|
-
|
57
|
-
value == 0.0 ? nil : value
|
51
|
+
converted == 0.0 ? nil : converted
|
58
52
|
end
|
59
53
|
|
60
|
-
|
54
|
+
private
|
61
55
|
|
62
|
-
|
63
|
-
|
64
|
-
@string = string
|
56
|
+
def converted
|
57
|
+
@converted ||= string.strip.to_f
|
65
58
|
end
|
66
59
|
|
60
|
+
end
|
61
|
+
|
62
|
+
class CurrencyCode < Struct.new(:string, :translations)
|
63
|
+
|
67
64
|
# TODO validate the currency codes via http://www.xe.com/iso4217.php
|
68
65
|
def valid?
|
69
|
-
|
70
|
-
!@string.blank? && @string.length == 3
|
66
|
+
!translated.blank? && translated.length == 3
|
71
67
|
end
|
72
68
|
|
73
69
|
def invalid?
|
@@ -75,14 +71,17 @@ module Forex
|
|
75
71
|
end
|
76
72
|
|
77
73
|
def to_s
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
74
|
+
translated
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
82
78
|
|
83
|
-
|
84
|
-
|
79
|
+
def translated
|
80
|
+
@translated ||= translations[converted] || converted
|
81
|
+
end
|
85
82
|
|
83
|
+
def converted
|
84
|
+
@converted ||= string.strip
|
86
85
|
end
|
87
86
|
end
|
88
87
|
end
|
data/lib/forex/traders/jm/bns.rb
CHANGED
@@ -3,7 +3,7 @@ Forex::Trader.define "BNS" do |t|
|
|
3
3
|
t.name = "Bank of Nova Scotia"
|
4
4
|
t.endpoint = "http://www4.scotiabank.com/cgi-bin/ratesTool/depdisplay.cgi?pid=56"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
|
8
8
|
options = {
|
9
9
|
currency_code: 0,
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Forex::Trader.define "COK" do |t|
|
2
|
+
t.base_currency = "JMD"
|
3
|
+
t.name = "City Of Kingston Credit Union"
|
4
|
+
t.endpoint = "http://www.cokcu.com/otherservices/cambio/"
|
5
|
+
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
|
+
|
8
|
+
options = {
|
9
|
+
currency_code: 0,
|
10
|
+
buy_cash: 3,
|
11
|
+
buy_draft: 2,
|
12
|
+
sell_cash: 1,
|
13
|
+
}
|
14
|
+
|
15
|
+
table = doc.css(".table-bordered").first
|
16
|
+
|
17
|
+
translations = {
|
18
|
+
'CI' => 'KYD',
|
19
|
+
'Euro' => 'EUR',
|
20
|
+
}
|
21
|
+
|
22
|
+
Forex::TabularRates.new(table, options).parse_rates(translations)
|
23
|
+
end
|
24
|
+
end
|
data/lib/forex/traders/jm/fgb.rb
CHANGED
@@ -3,7 +3,7 @@ Forex::Trader.define "FGB" do |t|
|
|
3
3
|
t.name = "First Global Bank"
|
4
4
|
t.endpoint = "http://www.firstglobal-bank.com/"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
options = {
|
8
8
|
currency_code: 0,
|
9
9
|
buy_cash: 1,
|
@@ -3,7 +3,7 @@ Forex::Trader.define "FXTRADERS" do |t|
|
|
3
3
|
t.name = "FX Traders"
|
4
4
|
t.endpoint = "http://www.fxtrader.gkmsonline.com/rates"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
|
8
8
|
content_for = ->(type, n, part = nil) do
|
9
9
|
doc.css(
|
@@ -11,10 +11,15 @@ Forex::Trader.define "FXTRADERS" do |t|
|
|
11
11
|
).first.content
|
12
12
|
end
|
13
13
|
|
14
|
+
translations = {
|
15
|
+
'EURO' => 'EUR',
|
16
|
+
}
|
17
|
+
|
14
18
|
(1..5).each_with_object({}) do |n, currencies|
|
15
|
-
|
19
|
+
currency_code = content_for.(:currency, n)
|
20
|
+
currency_code = translations[currency_code] || currency_code
|
16
21
|
|
17
|
-
currencies[
|
22
|
+
currencies[currency_code] = {
|
18
23
|
buy_cash: content_for.(:buying, n).to_f,
|
19
24
|
buy_draft: content_for.(:buying, n, :b).to_f,
|
20
25
|
sell_cash: content_for.(:selling, n).to_f,
|
@@ -3,7 +3,7 @@ Forex::Trader.define "JMMB" do |t|
|
|
3
3
|
t.name = "Jamaica Money Market Brokers"
|
4
4
|
t.endpoint = "http://www.jmmb.com/full_rates.php"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
|
8
8
|
options = {
|
9
9
|
currency_code: 0,
|
@@ -13,12 +13,17 @@ Forex::Trader.define "JMMB" do |t|
|
|
13
13
|
sell_draft: 4
|
14
14
|
}
|
15
15
|
|
16
|
+
translations = {
|
17
|
+
'US$' => 'USD',
|
18
|
+
'TT$' => 'TTD',
|
19
|
+
}
|
20
|
+
|
16
21
|
table =
|
17
22
|
doc.search("[text()*='FX Trading Rates']").first. # Section with rates
|
18
23
|
ancestors('table').first. # Root table for section
|
19
24
|
css("table").first # Rates table
|
20
25
|
|
21
|
-
Forex::TabularRates.new(table, options).parse_rates
|
26
|
+
Forex::TabularRates.new(table, options).parse_rates(translations)
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
@@ -3,10 +3,9 @@ Forex::Trader.define "JNBS" do |t|
|
|
3
3
|
t.name = "Jamaica National Building Society"
|
4
4
|
t.endpoint = "http://www.jnbs.com/fx-rates-2"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
|
8
8
|
options = {
|
9
|
-
currency_code: 0,
|
10
9
|
sell_cash: 4,
|
11
10
|
buy_cash: 2,
|
12
11
|
buy_draft: 1,
|