scottrade 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ private/
2
+ .DS_Store
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'money'
4
+
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nolan Brown
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # Scottrade
2
+
3
+ Very basic gem for accessing Scottrade account information including balances and market positions.
4
+
5
+ **This software comes with no warranty and you use it at your own risk.**
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'scottrade'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install scottrade
21
+
22
+ ## Usage
23
+
24
+ To run the tests or example, set the environment key `SCOTTRADE_ACCOUNT` and `SCOTTRADE_PASSWORD` with your account information.
25
+
26
+ Basic usage is:
27
+
28
+ scottrade = Scottrade::Scottrade.new(`ACCOUNT_NUMBER`,`PASSWORD`)
29
+ scottrade.authenticate
30
+ scottrade.brokerage.update_accounts
31
+ scottrade.brokerage.update_positions
32
+
33
+ print scottrade.brokerage.account_balance
34
+
35
+ NOTE: Many variables will return a `Money` object, not a `String`
36
+
37
+ ## To Do
38
+
39
+ - Add additional test coverage
40
+ - Add ability to check quotes
41
+
42
+
43
+ ## Contributing
44
+
45
+ 1. Fork it
46
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
47
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
48
+ 4. Push to the branch (`git push origin my-new-feature`)
49
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,49 @@
1
+
2
+ lib = File.expand_path('../../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'scottrade'
6
+
7
+ scottrade = Scottrade::Scottrade.new(ENV["SCOTTRADE_ACCOUNT"],ENV["SCOTTRADE_PASSWORD"])
8
+ begin
9
+ scottrade.authenticate
10
+ rescue StandardError => e
11
+ puts e
12
+ exit 1
13
+ end
14
+
15
+ begin
16
+ scottrade.brokerage.update_accounts
17
+ scottrade.brokerage.update_positions
18
+
19
+ rescue StandardError => e
20
+ puts e
21
+ exit 1
22
+ end
23
+
24
+ puts scottrade.brokerage.account_balance
25
+ ##
26
+ # The code below will output all available variables for all associated classes
27
+ ##
28
+
29
+ scottrade.brokerage.instance_variables.each{|variable|
30
+ print "#{variable} : "; $stdout.flush
31
+ puts scottrade.brokerage.instance_variable_get(variable)
32
+ }
33
+ print "\n------------------------------------\n"
34
+
35
+ scottrade.brokerage.accounts.each{|account|
36
+ account.instance_variables.each{|variable|
37
+ print "#{variable} : "; $stdout.flush
38
+ puts account.instance_variable_get(variable)
39
+ }
40
+ print "\n***************\n"
41
+ }
42
+
43
+ scottrade.brokerage.positions.each{|position|
44
+ position.instance_variables.each{|variable|
45
+ print "#{variable} : "; $stdout.flush
46
+ puts position.instance_variable_get(variable)
47
+ }
48
+ print "\n***************\n"
49
+ }
@@ -0,0 +1,32 @@
1
+
2
+ module Scottrade
3
+
4
+ class Account
5
+ CASH_ACCOUNT = "Cash"
6
+ MARGIN_ACCOUNT = "Margin"
7
+ SHORT_ACCOUNT = "Short"
8
+ attr_reader :type, :settled_funds, :market_value, :yesterday_market_value, :total_available_for_trading, :deposited_funds, :total_value
9
+
10
+ def initialize(details={})
11
+ @type = details["AccountType"]
12
+ @settled_funds = Money.parse details["SettledFunds"]
13
+ @market_value = Money.parse details["MarketValue"]
14
+ @yesterday_market_value = Money.parse details["YesterdayMarketValue"]
15
+
16
+ @total_available_for_trading = Money.parse details["TotalAccountTypeFundsForTrading"]
17
+ @deposited_funds = Money.parse details["BankDepositProgramForTrading"]
18
+
19
+ @total_value = Money.parse details["TotalAccountTypeValue"]
20
+ end
21
+
22
+ def cash?
23
+ @type == CASH_ACCOUNT
24
+ end
25
+ def margin?
26
+ @type == MARGIN_ACCOUNT
27
+ end
28
+ def short?
29
+ @type == SHORT_ACCOUNT
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,48 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+
5
+ module Scottrade
6
+ class Base
7
+ attr_accessor :session_token, :cookies
8
+ ::API_BASE = "https://mobappfe.scottrade.com"
9
+ ::API_PATH = "/middleware/MWServlet"
10
+
11
+ def post(parameters, cookies = nil)
12
+ uri = URI.parse(API_BASE)
13
+ http = Net::HTTP.new(uri.host, uri.port)
14
+ http.use_ssl = true
15
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
16
+
17
+ request = Net::HTTP::Post.new(API_PATH)
18
+ request.set_form_data(parameters)
19
+
20
+ cookies = @cookies unless cookies
21
+
22
+ request.add_field("Cookie",cookies.join('; ')) if cookies
23
+
24
+ response = http.request(request)
25
+
26
+ return response
27
+ end
28
+
29
+ def get(path, parameters,cookies=nil)
30
+ uri = URI.parse(API_BASE)
31
+ http = Net::HTTP.new(uri.host, uri.port)
32
+ http.use_ssl = true
33
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
34
+
35
+ query_path = "#{path}"
36
+ query_path = "#{path}?#{URI.encode_www_form(parameters)}" if parameters.length > 0
37
+ request = Net::HTTP::Get.new(query_path)
38
+
39
+ cookies = @cookies unless cookies
40
+
41
+ request.add_field("Cookie",cookies.join('; ')) if cookies
42
+
43
+ response = http.request(request)
44
+ return response
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,107 @@
1
+ require 'money'
2
+ require_relative 'account'
3
+ require_relative 'position'
4
+
5
+ module Scottrade
6
+ class Brokerage
7
+
8
+ attr_reader :total_value, :total_cash_balance, :yesterday_total_cash_balance, :available_free_cash_blanace, :account_balance
9
+ attr_reader :total_market_value_with_options, :total_market_value_without_options, :yesterday_total_market_value
10
+ attr_reader :total_settled_funds, :total_unsettled_sells, :funds_available_to_buy_non_marginables, :funds_available_to_buy_marginables
11
+ attr_reader :funds_available_to_buy_options, :funds_available_to_buy_mutual_funds, :funds_available_for_withdraw, :approximate_liquidation_value
12
+ attr_reader :accounts, :positions
13
+ attr_reader :current_market_value, :todays_percent_change, :todays_value_change
14
+
15
+ def initialize(session)
16
+ @session = session
17
+ end
18
+
19
+ # def positions
20
+ #
21
+ # end
22
+ # def accounts
23
+ # end
24
+
25
+ def update_accounts
26
+ params = {}
27
+ params["channel"] = "rc"
28
+ params["appID"] = "Scottrade"
29
+ params["rcid"] = "iPhone"
30
+ params["cacheid"] = ""
31
+ params["platform"] = "iPhone"
32
+ params["appver"] = "1.1.4"
33
+ params["useCachedData"] = "false"
34
+
35
+ params["serviceID"] = "GetFrontEndMoneyBalances"
36
+
37
+ response = @session.post(params)
38
+ parsed_response = JSON.parse(response.body)
39
+ if parsed_response["error"] == "false"
40
+ @total_value = Money.parse parsed_response["TotalAccountValue"]
41
+ @total_cash_balance = Money.parse parsed_response["TotalMoneyBalance"]
42
+ @yesterday_total_cash_balance = Money.parse parsed_response["YesterdayTotalMoneyBalance"]
43
+ @available_free_cash_blanace = Money.parse parsed_response["AvailableFreeCash"]
44
+ @total_market_value_with_options = Money.parse parsed_response["TotalMarketValueWithOptions"]
45
+ @total_market_value_without_options = Money.parse parsed_response["TotalMarketValueNoOptions"]
46
+ @yesterday_total_market_value = Money.parse parsed_response["YesterdayTotalMarketValue"]
47
+ @total_settled_funds = Money.parse parsed_response["TotalSettledFunds"]
48
+ @total_unsettled_sells = Money.parse parsed_response["TotalUnsettledSells"]
49
+ @funds_available_to_buy_non_marginables = Money.parse parsed_response["FundsAvailableToBuyNonMarginables"]
50
+ @funds_available_to_buy_marginables = Money.parse parsed_response["FundsAvailableToBuyMarginables"]
51
+ @funds_available_to_buy_options = Money.parse parsed_response["FundsAvailableToBuyOptions"]
52
+ @funds_available_to_buy_mutual_funds = Money.parse parsed_response["FundsAvailableToBuyMutualFunds"]
53
+ @funds_available_for_withdraw = Money.parse parsed_response["FundsAvailableForWithdraw"]
54
+ @approximate_liquidation_value = Money.parse parsed_response["TodaysChangeApproxLiquidationValNoOptions"]
55
+ @account_balance = Money.parse parsed_response["BrokerageAccountBalance"]
56
+
57
+ @accounts = []
58
+ unparsed_accounts = parsed_response["AccTypeBalances"]
59
+ unparsed_accounts.each{|acct|
60
+ @accounts.push Account.new(acct)
61
+ }
62
+
63
+ elsif parsed_response["msg"]
64
+ raise RequestError, parsed_response["msg"]
65
+ else
66
+ raise RequestError
67
+ end
68
+ end
69
+
70
+ def update_positions
71
+ params = {}
72
+ params["channel"] = "rc"
73
+ params["appID"] = "Scottrade"
74
+ params["rcid"] = "iPhone"
75
+ params["cacheid"] = ""
76
+ params["platform"] = "iPhone"
77
+ params["appver"] = "1.1.4"
78
+
79
+ params["useCachedData"] = "false"
80
+ params["startRow"] = "0"
81
+ params["noOfRows"] = "1000"
82
+ params["returnRealTimeMktValue"] = "true"
83
+
84
+ params["serviceID"] = "GetPositions_v2"
85
+
86
+ response = @session.post(params)
87
+ parsed_response = JSON.parse(response.body)
88
+ if parsed_response["error"] == "false"
89
+
90
+ @current_market_value = parsed_response["totalMktValue"]
91
+ @todays_percent_change = parsed_response["totalPctChange"]
92
+ @todays_value_change = parsed_response["toalPriceChange"]
93
+
94
+ @positions = []
95
+ all_positions = parsed_response["Positions"]
96
+ all_positions.each{|pos|
97
+ @positions.push Position.new(pos)
98
+ }
99
+
100
+ elsif parsed_response["msg"]
101
+ raise RequestError, parsed_response["msg"]
102
+ else
103
+ raise RequestError
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,17 @@
1
+ module Scottrade
2
+ class AuthenticationError < StandardError
3
+ def initialize(msg = "Authentication Error")
4
+ super(msg)
5
+ end
6
+ end
7
+ class UnkownError < StandardError
8
+ def initialize(msg = "Unkown Error")
9
+ super(msg)
10
+ end
11
+ end
12
+ class RequestError < StandardError
13
+ def initialize(msg = "Request Error")
14
+ super(msg)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ module Scottrade
2
+ class Position < Base
3
+
4
+ attr_reader :symbol, :display_symbol, :quantity, :previous_market_close_value, :account_type, :cusip, :security_description
5
+ attr_reader :security_class, :previous_close_price, :realTimePrice, :real_time_price, :real_time_market_value, :price_change
6
+
7
+ def initialize(details)
8
+ @symbol = details["symbol"]
9
+ @display_symbol = details["displaySymbol"]
10
+ @quantity = details["quantity"]
11
+ @previous_market_close_value = Money.parse(details["prevCloseMktValue"])
12
+ @account_type = details["accType"]
13
+ @cusip = details["cusip"]
14
+ @security_description = details["securityDescription"]
15
+ @security_class = details["SecurityClass"]
16
+ @previous_close_price = details["previousClosePrice"]
17
+ @real_time_price = details["realTimePrice"]
18
+ @real_time_market_value = Money.parse(details["RealTimeMktValue"])
19
+ @price_change = Money.parse(details["priceChange"])
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ module Scottrade
2
+ class Quote
3
+ def initialize(session)
4
+ @session = session
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,62 @@
1
+ require 'json'
2
+
3
+ require_relative 'base'
4
+ require_relative 'error'
5
+
6
+ module Scottrade
7
+ class Session < Base
8
+
9
+ attr_reader :encrypted_id, :mask_id
10
+
11
+ def initialize(account, password)
12
+ @account = account
13
+ @password = password
14
+ @cookies = nil
15
+ end
16
+ def authenticated?
17
+ return (@cookies != nil)
18
+ end
19
+ def authenticate
20
+ params = {}
21
+ params["appID"] = "Scottrade"
22
+ params["appName"] = "ScottradeMobileApplication"
23
+ params["rcid"] = "iPhone"
24
+ params["osName"] = "iPhone"
25
+ params["platform"] = "iPhone"
26
+ params["cacheid"] = ""
27
+ params["osVer"] = "6"
28
+ params["appver"] = "1.1.4"
29
+ params["appVer"] = "1.1.4"
30
+ params["isRemAcc"] = "true"
31
+ params["page"] = "LogIn"
32
+ params["serviceID"] = "VerifyLogin"
33
+ params["channel"] = "rc"
34
+ params["langId"] = "English"
35
+
36
+ params["acc"] = @account
37
+ params["pwd"] = @password
38
+ params["isEncrypted"] = "false"
39
+
40
+ response = post(params, nil)
41
+ all_cookies = response.get_fields('set-cookie') # only cookies are set on valid credentials
42
+ parsed_response = JSON.parse(response.body)
43
+ if parsed_response["error"] == "false" and !parsed_response.has_key?("errmsg")
44
+ cookies = []
45
+ all_cookies.each { | cookie |
46
+ cookies.push(cookie.split('; ')[0])
47
+ }
48
+ @cookies = cookies
49
+ @encrypted_dd = parsed_response["encryptedId"]
50
+ @mask_id = parsed_response["maskId"]
51
+
52
+ return self
53
+ elsif parsed_response["msg"]
54
+ raise AuthenticationError, parsed_response["msg"]
55
+ elsif parsed_response["errmsg"]
56
+ raise AuthenticationError, parsed_response["errmsg"]
57
+ else
58
+ raise AuthenticationError
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ module Scottrade
2
+ VERSION = "0.0.2"
3
+ end
data/lib/scottrade.rb ADDED
@@ -0,0 +1,30 @@
1
+ require_relative "scottrade/version"
2
+ require_relative "scottrade/session"
3
+ require_relative "scottrade/brokerage"
4
+
5
+ module Scottrade
6
+ class Scottrade
7
+ attr_reader :session
8
+ def initialize(account, password)
9
+ @session = Session.new(account, password)
10
+ ##
11
+ end
12
+ def authenticate
13
+ response = @session.authenticate
14
+ if @session.authenticated?
15
+ return @session
16
+ else # error
17
+ return response
18
+ end
19
+ end
20
+ def authenticated?
21
+ return @session.authenticated?
22
+ end
23
+
24
+ def brokerage
25
+ return @brokerage if @brokerage
26
+ @brokerage = Brokerage.new(@session)
27
+ end
28
+
29
+ end
30
+ end
data/scottrade.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'scottrade/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "scottrade"
8
+ gem.version = Scottrade::VERSION
9
+ gem.authors = ["Nolan Brown"]
10
+ gem.email = ["nolanbrown@gmail.com"]
11
+ gem.description = %q{Basic gem for accessing a Scottrade account}
12
+ gem.summary = gem.description
13
+ gem.homepage = "https://github.com/nolanbrown/scottrade"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'money'
21
+
22
+ end
@@ -0,0 +1,20 @@
1
+ require 'test/unit'
2
+ require 'rack/test'
3
+ require '../../lib/scottrade'
4
+
5
+ class AuthenticationTest < Test::Unit::TestCase
6
+ include Rack::Test::Methods
7
+
8
+ def test_positive_authentication
9
+ scottrade = Scottrade::Scottrade.new(ENV["SCOTTRADE_ACCOUNT"],ENV["SCOTTRADE_PASSWORD"])
10
+ assert_nothing_raised Scottrade::AuthenticationError do
11
+ scottrade.authenticate
12
+ end
13
+ end
14
+ def test_negative_authentication
15
+ scottrade = Scottrade::Scottrade.new("555555555","password")
16
+ assert_raise Scottrade::AuthenticationError do
17
+ scottrade.authenticate
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ require 'test/unit'
2
+ require 'rack/test'
3
+ require '../../lib/scottrade'
4
+ class AuthenticationTest < Test::Unit::TestCase
5
+ include Rack::Test::Methods
6
+
7
+ def test_brokerage_balances
8
+ scottrade = Scottrade::Scottrade.new(ENV["SCOTTRADE_ACCOUNT"],ENV["SCOTTRADE_PASSWORD"])
9
+ assert_nothing_raised Scottrade::AuthenticationError do
10
+ scottrade.authenticate
11
+ end
12
+ assert_nothing_raised Scottrade::RequestError do
13
+ scottrade.brokerage.update_accounts
14
+ end
15
+ end
16
+ def test_brokerage_positions
17
+ scottrade = Scottrade::Scottrade.new(ENV["SCOTTRADE_ACCOUNT"],ENV["SCOTTRADE_PASSWORD"])
18
+ assert_nothing_raised Scottrade::AuthenticationError do
19
+ scottrade.authenticate
20
+ end
21
+ assert_nothing_raised Scottrade::RequestError do
22
+ scottrade.brokerage.update_positions
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scottrade
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nolan Brown
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: money
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Basic gem for accessing a Scottrade account
31
+ email:
32
+ - nolanbrown@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - examples/basics.rb
43
+ - lib/scottrade.rb
44
+ - lib/scottrade/account.rb
45
+ - lib/scottrade/base.rb
46
+ - lib/scottrade/brokerage.rb
47
+ - lib/scottrade/error.rb
48
+ - lib/scottrade/position.rb
49
+ - lib/scottrade/quote.rb
50
+ - lib/scottrade/session.rb
51
+ - lib/scottrade/version.rb
52
+ - scottrade.gemspec
53
+ - tests/unit/auth.rb
54
+ - tests/unit/brokerage.rb
55
+ homepage: https://github.com/nolanbrown/scottrade
56
+ licenses: []
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 1.8.24
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: Basic gem for accessing a Scottrade account
79
+ test_files: []