em-nordnet 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ module Em
2
+ module Nordnet
3
+ class Config < Struct.new :url, :user, :password, :pem, :service
4
+ def credentials
5
+ @credentials ||= generate_credentials
6
+ end
7
+
8
+ private
9
+ def generate_credentials
10
+ auth = [user, password, Time.now.to_i*1000]
11
+ encoded = auth.map { |val| Base64.encode64 val.to_s.strip }.join(':')
12
+ Base64.encode64 OpenSSL::PKey::RSA.new(pem).public_encrypt encoded
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,53 @@
1
+ module Em
2
+ module Nordnet
3
+ module Feed
4
+ module Connection
5
+ attr_reader :session, :callbacks
6
+
7
+ def initialize(session)
8
+ @session = session
9
+ @callbacks = []
10
+ end
11
+
12
+ def on_tick &block
13
+ @callbacks << block
14
+ end
15
+
16
+ def subscribe args
17
+ send_data generate(:subscribe, args)
18
+ end
19
+
20
+ def connection_completed
21
+ start_tls
22
+ end
23
+
24
+ def ssl_handshake_completed
25
+ params = { session_key: session, service: Nordnet.config.service }
26
+ send_data generate(:login, params)
27
+ end
28
+
29
+ def receive_data json
30
+ callbacks.each { |callback| callback.call parse(json) }
31
+ end
32
+
33
+ def unbind
34
+ EventMachine::stop_event_loop
35
+ end
36
+
37
+ def send_data data
38
+ super "%s\n" % data # All writes must end with linefeed
39
+ end
40
+
41
+ private
42
+
43
+ def generate command, args ={}
44
+ MultiJson.dump cmd: command.to_s, args: args
45
+ end
46
+
47
+ def parse json
48
+ MultiJson.load(json, symbolize_keys: true)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,56 @@
1
+ module Em
2
+ module Nordnet
3
+ module Feed
4
+ class DSL
5
+ attr_reader :socket, :session, :callbacks
6
+
7
+ def initialize
8
+ @callbacks = initialize_callbacks
9
+
10
+ socket.on_tick { |data| run_callback data }
11
+ end
12
+
13
+ def socket
14
+ @socket = EventMachine::connect hostname, port, Connection, session
15
+ end
16
+
17
+ def session
18
+ @session ||= Nordnet.api.session
19
+ end
20
+
21
+ def on_heartbeat &block
22
+ add_callback :heartbeat, block
23
+ end
24
+
25
+ def add_callback type, block
26
+ @callbacks[type] << block
27
+ end
28
+
29
+ def types
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def hostname
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def port
38
+ raise NotImplementedError
39
+ end
40
+
41
+ private
42
+
43
+ def initialize_callbacks
44
+ types.inject heartbeat: [] do |callbacks, type|
45
+ callbacks[type] = []
46
+ end
47
+ end
48
+
49
+ def run_callback data
50
+ type, args = data[:cmd], data[:args]
51
+ callbacks[type].each { |callback| callback.call args }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ module Em
2
+ module Nordnet
3
+ module Feed
4
+ class Private < Em::Nordnet::Feed::DSL
5
+ def types
6
+ [:trade, :order]
7
+ end
8
+
9
+ def hostname
10
+ Nordnet.api.private_feed[:hostname]
11
+ end
12
+
13
+ def port
14
+ Nordnet.api.private_feed[:port]
15
+ end
16
+
17
+ def on_trade &block
18
+ add_callback :trade, block
19
+ end
20
+
21
+ def on_order &block
22
+ add_callback :order, block
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,43 @@
1
+ module Em
2
+ module Nordnet
3
+ module Feed
4
+ class Public < Em::Nordnet::Feed::DSL
5
+ def types
6
+ [:price, :depth, :trade, :index, :news, :trading_status]
7
+ end
8
+
9
+ def hostname
10
+ Nordnet.api.public_feed[:hostname]
11
+ end
12
+
13
+ def port
14
+ Nordnet.api.public_feed[:port]
15
+ end
16
+
17
+ def on_price &block
18
+ add_callback :price, block
19
+ end
20
+
21
+ def on_trade &block
22
+ add_callback :trade, block
23
+ end
24
+
25
+ def on_depth &block
26
+ add_callback :depth, block
27
+ end
28
+
29
+ def on_index &block
30
+ add_callback :index, block
31
+ end
32
+
33
+ def on_news &block
34
+ add_callback :news, block
35
+ end
36
+
37
+ def on_trading_status &block
38
+ add_callback :trading_status, block
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ module Em::Nordnet::Goliath
2
+ class FeedPlugin
3
+ @@connection = nil
4
+
5
+ class << self
6
+ def connection
7
+ @@connection
8
+ end
9
+ end
10
+
11
+ def initialize port, config, status, logger
12
+ @port, @config, @status, @logger = port, config, status, logger
13
+ end
14
+
15
+ def run
16
+ @@connection = feed_connection
17
+ end
18
+
19
+ private
20
+
21
+ def feed_connection
22
+ raise NotImplementedError
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ module Em::Nordnet::Goliath
2
+ class PrivateFeed < FeedPlugin
3
+ private
4
+ def feed_connection
5
+ Nordnet::Feed::Private.new
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module Em::Nordnet::Goliath
2
+ class PublicFeed < FeedPlugin
3
+ class << self
4
+ def subscribe type, instrument, &block
5
+ instrument.subscribe_params(type).tap do |args|
6
+ connection.add_callback type, &block
7
+ connection.subscribe args
8
+ end
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def feed_connection
15
+ Nordnet::Feed::Public.new
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ module Em
2
+ module Nordnet
3
+ class Instrument
4
+ attr_accessor :id, :market_id, :currency, :shortname, :is_in_code, :longname, :ticksize_id
5
+
6
+ def self.search symbol, options={}
7
+ search_options = {query: symbol, type: 'A', country: ['SE','US']}.merge options
8
+ results = Nordnet.api.instrument_search(search_options)
9
+ results.map { |attributes| new attributes }
10
+ end
11
+
12
+ def initialize attributes={}
13
+ self.id = attributes[:id] || attributes[:identifier]
14
+ self.market_id = attributes[:market_id] || attributes[:marketID]
15
+ self.currency = attributes[:currency]
16
+ self.shortname = attributes[:shortname]
17
+ self.longname = attributes[:longname]
18
+ self.is_in_code = attributes[:is_in_code] || attributes[:isinCode]
19
+ self.ticksize_id = attributes[:ticksize_id] || attributes[:ticksizeID]
20
+ end
21
+
22
+ def subscribe_params type
23
+ { 't' => type.to_s, 'i' => id, 'm' => market_id }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,48 @@
1
+ module Em
2
+ module Nordnet
3
+ class Order
4
+ BUY = 'BUY'.freeze
5
+ SELL = 'SELL'.freeze
6
+
7
+ attr_accessor :id, :account, :instrument, :price, :volume, :status
8
+
9
+ class << self
10
+ def all account_id
11
+ orders = Nordnet.api.orders(account_id)
12
+ orders.map { |attributes| new attributes }
13
+ end
14
+
15
+ def buy attributes={}
16
+ symbol, price, volume = attributes[:symbol], attributes[:price], attributes[:volume]
17
+
18
+ instrument = Instrument.search(symbol).first
19
+ account = attributes[:account] || Account.default
20
+
21
+ order = Order.new account: account, instrument: instrument, price: price, volume: volume
22
+ order.buy!
23
+ end
24
+ end
25
+
26
+ def initialize attributes={}
27
+ self.id = attributes[:id] || attributes[:orderID]
28
+ self.account = attributes[:account]
29
+ self.instrument = attributes[:instrument]
30
+ self.price = attributes[:price]
31
+ self.volume = attributes[:volume]
32
+ self.status = attributes[:status]
33
+ end
34
+
35
+ def buy!
36
+ self.status = Nordnet.api.create_order account.id,
37
+ identifier: instrument.id,
38
+ marketID: instrument.market_id,
39
+ price: price,
40
+ volume: volume,
41
+ side: BUY,
42
+ currency: instrument.currency
43
+
44
+ status
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ module Em
2
+ module Nordnet
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Em::Nordnet::Account do
4
+ let(:attributes) { { id: 123, alias: 'alias', default: true } }
5
+ before { Em::Nordnet.stub(:api).and_return stub(accounts: [attributes]) }
6
+
7
+ subject { Em::Nordnet::Account.new attributes }
8
+
9
+ describe 'initialization' do
10
+ its(:id) { should == 123 }
11
+ its(:alias) { should == 'alias' }
12
+ its(:default) { should be_true }
13
+ end
14
+
15
+ describe '.all' do
16
+ it 'should returns a collection of accounts fetched with the API' do
17
+ Em::Nordnet::Account.all.first.should be_instance_of(Em::Nordnet::Account)
18
+ end
19
+ it 'should be memoized' do
20
+ Em::Nordnet::Account.all.should be Em::Nordnet::Account.all
21
+ end
22
+ end
23
+
24
+ it '.default', 'should return the account that is set to default' do
25
+ account = Em::Nordnet::Account.default
26
+ account.should be_instance_of Em::Nordnet::Account
27
+ account.default.should be_true
28
+ end
29
+
30
+ it '#orders', 'should return a collection of orders connected to the account' do
31
+ Em::Nordnet::Order.should_receive(:all).with(123)
32
+ subject.orders
33
+ end
34
+ end
@@ -0,0 +1,179 @@
1
+ require 'spec_helper'
2
+
3
+ describe Em::Nordnet::Api, type: 'api authenticated' do
4
+ let(:api) { Em::Nordnet::Api.new }
5
+ subject { api }
6
+
7
+ let(:account_id) { 4711 }
8
+
9
+ describe 'initialization' do
10
+ its(:credentials) { should_not be_nil }
11
+ its(:session) { should_not be_nil }
12
+ its(:expires_in) { should_not be_nil }
13
+ end
14
+
15
+ describe '#login' do
16
+ its(:session) { should == 'cf147c690f65fe1068f40ef23f23648ebf8c7f02' }
17
+ its(:expires_in) { should == 300 }
18
+ it { api.login.should == login_result }
19
+ end
20
+
21
+ describe '#touch' do
22
+ before { stub_touch(api.session) }
23
+ it { api.touch.should == touch_result }
24
+ end
25
+
26
+ describe '#realtime_access' do
27
+ before { stub_realtime_access }
28
+ it { api.realtime_access.should == realtime_access_result }
29
+ end
30
+
31
+ describe '#news_sources' do
32
+ before { stub_news_sources }
33
+ it { api.news_sources.should == news_sources_result }
34
+ end
35
+
36
+ describe '#news_items' do
37
+ let(:params) { {query: 'ERIC'} }
38
+ before { stub_news_items params }
39
+ it { api.news_items.should == news_items_result }
40
+ end
41
+
42
+ describe '#news_item' do
43
+ let(:news_item_id) { 213 }
44
+ before { stub_news_item news_item_id }
45
+ it { api.news_item(news_item_id).should == news_item_result }
46
+ end
47
+
48
+ describe '#accounts' do
49
+ before { stub_accounts }
50
+ it { api.accounts.should == accounts_result }
51
+ end
52
+
53
+ describe '#account' do
54
+ before { stub_account account_id }
55
+ it { api.account(account_id).should == account_result }
56
+ end
57
+
58
+ describe '#ledgers' do
59
+ before { stub_ledgers account_id }
60
+ it { api.ledgers(account_id).should == ledgers_result }
61
+ end
62
+
63
+ describe '#positions' do
64
+ before { stub_positions account_id }
65
+ it { api.positions(account_id).should == positions_result }
66
+ end
67
+
68
+ describe '#orders' do
69
+ before { stub_orders account_id }
70
+ it { api.orders(account_id).should == orders_result }
71
+ end
72
+
73
+ describe '#trades' do
74
+ before { stub_trades account_id }
75
+ it { api.trades(account_id).should == trades_result }
76
+ end
77
+
78
+ describe '#instrument_search' do
79
+ let(:params) { {query: 'abb', type: 'A', country: 'SE'} }
80
+ before { stub_instrument_search params }
81
+ it { api.instrument_search(params).should == instrument_search_result }
82
+ end
83
+
84
+ describe '#instrument_lookup' do
85
+ let(:params) { {identifier: 101, marketID: 11} }
86
+ before { stub_instrument_lookup params }
87
+ it { api.instrument_lookup(params).should == instrument_lookup_result }
88
+ end
89
+
90
+ describe '#multiple_instrument_lookup' do
91
+ let(:list) { "101,11;102,11;" }
92
+ before { stub_multiple_instrument_lookup list }
93
+ it { api.multiple_instrument_lookup(list).should == multiple_instrument_lookup_result }
94
+ end
95
+
96
+ describe '#chart_data' do
97
+ let(:params) { {identifier: 101, marketID: 11} }
98
+ before { stub_chart_data params }
99
+ it { api.chart_data(params).should == chart_data_result }
100
+ end
101
+
102
+ describe '#lists' do
103
+ before { stub_lists }
104
+ it { api.lists.should == lists_result }
105
+ end
106
+
107
+ describe '#list_items' do
108
+ let(:list_id) { 123 }
109
+ before { stub_list_items list_id }
110
+ it { api.list_items.should == list_items_result }
111
+ end
112
+
113
+ describe '#markets' do
114
+ before { stub_markets }
115
+ it { api.markets.should == markets_result }
116
+ end
117
+
118
+ describe '#trading_days' do
119
+ let(:market_id) { 32 }
120
+ before { stub_trading_days market_id }
121
+ it { api.trading_days(market_id).should == trading_days_result }
122
+ end
123
+
124
+ describe '#indices' do
125
+ before { stub_indices }
126
+ it { api.indices.should == indices_result }
127
+ end
128
+
129
+ describe '#ticksizes' do
130
+ let(:instrument_id) { 11002 }
131
+ before { stub_ticksizes instrument_id }
132
+ it { api.ticksizes(instrument_id).should == ticksizes_result }
133
+ end
134
+
135
+ describe '#countries' do
136
+ let(:derivative_type) { 'WNT' }
137
+ before { stub_countries derivative_type }
138
+ it { api.countries(derivative_type).should == countries_result }
139
+ end
140
+
141
+ describe '#underlyings' do
142
+ let(:derivative_type) { 'WNT' }
143
+ let(:country) { 'SE' }
144
+ before { stub_underlyings derivative_type, country }
145
+ it { api.underlyings(derivative_type, country).should == underlyings_result }
146
+ end
147
+
148
+ describe '#derivatives' do
149
+ let(:derivative_type) { 'WNT' }
150
+ let(:params) { {identifier: 101, marketID: 11} }
151
+ before { stub_derivatives derivative_type, params }
152
+ it { api.derivatives(derivative_type, params).should == derivatives_result }
153
+ end
154
+
155
+ describe '#related_markets' do
156
+ let(:params) { {identifier: 101, marketID: 11} }
157
+ before { stub_related_markets params }
158
+ it { api.related_markets(params).should == related_markets_result }
159
+ end
160
+
161
+ describe '#create_order' do
162
+ let(:params) { { identifier: 101, marketID: 11, price: 60, volume: 200, side: 'BUY', currency: 'SEK' } }
163
+ before { stub_create_order account_id, params }
164
+ it { api.create_order(account_id, params).should == create_order_result }
165
+ end
166
+
167
+ describe '#modify_order' do
168
+ let(:order_id) { 6670 }
169
+ let(:params) { { identifier: 101, marketID: 11, price: 100, volume: 200, side: 'BUY', currency: 'SEK' } }
170
+ before { stub_modify_order account_id, order_id, params }
171
+ it { api.modify_order(account_id, order_id, params).should == modify_order_result }
172
+ end
173
+
174
+ describe '#destroy_order' do
175
+ let(:order_id) { 6670 }
176
+ before { stub_destroy_order account_id, order_id }
177
+ it { api.destroy_order(account_id, order_id).should == destroy_order_result }
178
+ end
179
+ end