qc 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ef59d64a4ce89a1850fbd7b90923c78340d925b7
4
+ data.tar.gz: 6e0aa75773f5e068567e2637f3ce2791a229d1ae
5
+ SHA512:
6
+ metadata.gz: 856d682bb7fc6fcc23b060a157ec53168a263bd16f536babf35639fa8afa8f03c446d127505d05cc1f3bc1318cdeb5da54f317e691d7459d0dda0de5642730dd
7
+ data.tar.gz: 6be35bcf5140b01050323135e193b2c8a766c3af18baac921c61eeb55d4b23d7eb1a50921cea829af063d75a3f2b38154e690cfb7a90ff603cba3616312d5991
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
@@ -0,0 +1 @@
1
+ 2.4.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in qc.gemspec
6
+ gemspec
@@ -0,0 +1,54 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ qc (0.5.1)
5
+ rest-client (~> 2.0.2)
6
+ vcr (~> 3.0.3)
7
+ webmock (~> 3.1.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.5.2)
13
+ public_suffix (>= 2.0.2, < 4.0)
14
+ awesome_print (1.8.0)
15
+ crack (0.4.3)
16
+ safe_yaml (~> 1.0.0)
17
+ domain_name (0.5.20170404)
18
+ unf (>= 0.0.5, < 1.0.0)
19
+ hashdiff (0.3.7)
20
+ http-cookie (1.0.3)
21
+ domain_name (~> 0.5)
22
+ mime-types (3.1)
23
+ mime-types-data (~> 3.2015)
24
+ mime-types-data (3.2016.0521)
25
+ minitest (5.10.3)
26
+ netrc (0.11.0)
27
+ public_suffix (3.0.0)
28
+ rake (10.5.0)
29
+ rest-client (2.0.2)
30
+ http-cookie (>= 1.0.2, < 2.0)
31
+ mime-types (>= 1.16, < 4.0)
32
+ netrc (~> 0.8)
33
+ safe_yaml (1.0.4)
34
+ unf (0.1.4)
35
+ unf_ext
36
+ unf_ext (0.0.7.4)
37
+ vcr (3.0.3)
38
+ webmock (3.1.0)
39
+ addressable (>= 2.3.6)
40
+ crack (>= 0.3.2)
41
+ hashdiff
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ awesome_print
48
+ bundler (~> 1.15)
49
+ minitest (~> 5.0)
50
+ qc!
51
+ rake (~> 10.0)
52
+
53
+ BUNDLED WITH
54
+ 1.15.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Jorge Manrubia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,56 @@
1
+ # Qc
2
+
3
+ Qc is a commandline tool that lets you sync and run your [QuantConnect](https://www.quantconnect.com) backtests from the command line.
4
+
5
+ ## Installation
6
+
7
+ Qc is distributed as a ruby gem. You can install it with:
8
+
9
+ ```
10
+ gem install qc
11
+ ```
12
+
13
+ To update it run:
14
+
15
+ ```
16
+ gem update qc
17
+ ```
18
+
19
+ ## Workflow
20
+
21
+ 1. Run `qc login` for introducing your QuantConnect credentials
22
+ 2. Run `qc init` on the directory that contains your QuantConnect algorithm
23
+ 3. Execute `qc` to sync and backtest your algorithm in QuantConnect
24
+
25
+ ## Usage
26
+
27
+ ```
28
+ qc [command]
29
+ ```
30
+
31
+ The supported commands are:
32
+
33
+ | Command| description|
34
+ | -- | -- |
35
+ | `qc login`| It will ask for the api credentials you can find in [your QuantConnect account page](https://www.quantconnect.com/account). They will be stored in `~/.qc`. You only need to login once. |
36
+ | `qc logout`| Logout from QuantConnect clearing the credentials stored locally. |
37
+ | `qc init`| Initialize the directory of the algo project you are working on. It will ask for a QuantConnect project to link your algo with. You need to run this once for every project you want to sync with QuantConnect. |
38
+ | `qc push` | Send your local files to QuantConnect. It will only send the files that changed since the last time you run the command |
39
+ | `qc compile` | Compile your project in QuantConnect |
40
+ | `qc backtest` | Run the backtest of your algorithm in QuantConnect |
41
+
42
+ When no command is provided, it will execute `push`, `compile` and `backtest` in sequence.
43
+
44
+ ## Development
45
+
46
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
47
+
48
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
49
+
50
+ ## Contributing
51
+
52
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/qc.
53
+
54
+ ## License
55
+
56
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "qc"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/qc ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require 'qc'
5
+
6
+ status = Qc::Console.new.run(ARGV) ? 0 : 1
7
+ exit status
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,12 @@
1
+ require 'fileutils'
2
+ require 'yaml'
3
+ require 'rest-client'
4
+ require 'json'
5
+ require 'awesome_print'
6
+ require 'optparse'
7
+
8
+ Dir[File.join(__dir__, "qc/**/*.rb")].each { |f| require f }
9
+
10
+ module Qc
11
+ # Your code goes here...
12
+ end
@@ -0,0 +1,14 @@
1
+ module Qc
2
+ class Backtest < Struct.new(:id, :name, :completed, :progress, :result, :success)
3
+ alias success? success
4
+ alias completed? completed
5
+
6
+ def error?
7
+ !success?
8
+ end
9
+
10
+ def progress_in_percentage
11
+ progress * 100
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,232 @@
1
+ module Qc
2
+ class CommandRunner
3
+ DEFAULT_FILE_EXTENSIONS = 'cs,py'
4
+
5
+ SUPPORTED_COMMANDS =%i(login logout init push compile backtest)
6
+ COMPILE_POLLING_DELAY_IN_SECONDS = 2
7
+ BACKTEST_DELAY_IN_SECONDS = 2
8
+
9
+ attr_reader :quant_connect_proxy
10
+ attr_accessor :project_settings
11
+
12
+ def initialize(quant_connect_proxy)
13
+ @quant_connect_proxy = quant_connect_proxy
14
+ @project_settings = read_project_settings
15
+ end
16
+
17
+ def run(command)
18
+ if command == :login
19
+ do_run(command)
20
+ else
21
+ require_login do
22
+ do_run(command)
23
+ end
24
+ end
25
+ end
26
+
27
+ def run_default
28
+ run(:default)
29
+ end
30
+
31
+ private
32
+
33
+ def credentials
34
+ quant_connect_proxy&.credentials
35
+ end
36
+
37
+ def credentials=(new_credentials)
38
+ quant_connect_proxy.credentials = new_credentials
39
+ end
40
+
41
+ def read_project_settings
42
+ if ::File.exist?(project_settings_file)
43
+ YAML.load(::File.open(project_settings_file))
44
+ else
45
+ Qc::ProjectSettings.new
46
+ end
47
+ end
48
+
49
+ def logged_in?
50
+ !!credentials
51
+ end
52
+
53
+ def require_login
54
+ if credentials
55
+ yield
56
+ else
57
+ puts "Please sign in by executing 'qc login' first"
58
+ false
59
+ end
60
+ end
61
+
62
+ def do_run(command)
63
+ case command
64
+ when :default
65
+ do_run_default
66
+ when *SUPPORTED_COMMANDS
67
+ send "run_#{command}"
68
+ else
69
+ raise "Unknonw command '#{command}'. Supported commands: #{SUPPORTED_COMMANDS.collect(&:to_s).join(', ')}"
70
+ end
71
+ end
72
+
73
+ def run_login
74
+ puts "Please introduce your QuantConnect API credentials. You can find them in your preferences in https://www.quantconnect.com/account."
75
+ user_id = ask_for_value 'User id:'
76
+ access_token = ask_for_value 'Access token:'
77
+
78
+ self.credentials = Qc::Credentials.new(user_id, access_token)
79
+
80
+ if valid_login?
81
+ credentials.save_to_home
82
+ true
83
+ else
84
+ puts "Invalid credentials"
85
+ false
86
+ end
87
+ end
88
+
89
+ def run_init
90
+ FileUtils.mkdir_p(Qc::Util.project_dir)
91
+ project = ask_for_project
92
+ self.project_settings.project_id = project.id
93
+ self.project_settings.file_extensions = ask_for_extensions
94
+ save_project_settings
95
+ true
96
+ end
97
+
98
+ def run_logout
99
+ credentials.destroy
100
+ puts "Logged out successfully"
101
+ true
102
+ end
103
+
104
+ def run_push
105
+ sync_changed_files
106
+ save_current_timestamp
107
+ true
108
+ end
109
+
110
+ def run_compile
111
+ compile = quant_connect_proxy.create_compile project_settings.project_id
112
+ puts "Compile request sent to the queue with id #{compile.id}"
113
+
114
+ begin
115
+ puts "Waiting for compilation result..."
116
+ compile = quant_connect_proxy.read_compile project_settings.project_id, compile.id
117
+ sleep COMPILE_POLLING_DELAY_IN_SECONDS if compile.in_queue?
118
+ end while compile.in_queue?
119
+
120
+ puts "Compile success" if compile.success?
121
+ puts "Compile failed" if compile.error?
122
+
123
+ project_settings.last_compile_id = compile.id
124
+ save_project_settings
125
+
126
+ compile.success?
127
+ end
128
+
129
+ def run_backtest
130
+ unless project_settings.last_compile_id
131
+ puts "Project not compiled. Please run 'qc compile'"
132
+ return false
133
+ end
134
+
135
+ do_run_backtest
136
+
137
+ true
138
+ end
139
+
140
+ def do_run_default
141
+ failed = %i(push compile backtest).find do |command|
142
+ !run(command)
143
+ end
144
+
145
+ !failed
146
+ end
147
+
148
+ def do_run_backtest
149
+ backtest = quant_connect_proxy.create_backtest project_settings.project_id, project_settings.last_compile_id, "backtest-#{project_settings.last_compile_id}"
150
+ puts "Backtest for compile #{project_settings.last_compile_id} sent to the queue with id #{backtest.id}"
151
+
152
+ begin
153
+ puts "Waiting for backtest to finish (#{backtest.progress_in_percentage}\% completed)..."
154
+ backtest = quant_connect_proxy.read_backtest project_settings.project_id, backtest.id
155
+ sleep BACKTEST_DELAY_IN_SECONDS if backtest.completed?
156
+ end while !backtest.completed?
157
+
158
+ puts "RESULT: #{backtest.result}"
159
+
160
+ puts "Bactest finished" if backtest.success?
161
+ puts "Backtest failed" if backtest.error?
162
+
163
+ project_settings.last_backtest_id = backtest.id
164
+ save_project_settings
165
+ end
166
+
167
+ def valid_login?
168
+ quant_connect_proxy.valid_login?
169
+ end
170
+
171
+ def save_project_settings
172
+ ::File.open(project_settings_file, 'w') {|file| file.write self.project_settings.to_yaml}
173
+ end
174
+
175
+ def project_settings_file
176
+ ::File.join(Qc::Util.project_dir, 'settings.yml')
177
+ end
178
+
179
+ def ask_for_value(question)
180
+ puts question
181
+ v = STDIN.gets
182
+ v.chomp
183
+ end
184
+
185
+ def ask_for_project
186
+ puts "Fetching projets from Quantconnect..."
187
+ projects = quant_connect_proxy.list_projects
188
+ puts "Select the project you want to associate with this directory"
189
+ projects.each.with_index do |project, index|
190
+ puts "[#{index+1}] - #{project.name}"
191
+ end
192
+ index = ask_for_value "Project number?"
193
+ index = index.to_i
194
+ if index >=1 && index < projects.length + 1
195
+ projects[index-1]
196
+ else
197
+ puts "Invalid value (please type a number between #{1} and #{projects.length})"
198
+ ask_for_project
199
+ end
200
+ end
201
+
202
+ def ask_for_extensions
203
+ file_extensions = ask_for_value "Introduce the file extensions you want to send to QuantConnect as a comma separated list. ENTER to default '#{DEFAULT_FILE_EXTENSIONS}'"
204
+ file_extensions = DEFAULT_FILE_EXTENSIONS if file_extensions.empty?
205
+ file_extensions
206
+ end
207
+
208
+
209
+ def sync_changed_files
210
+ changed_files.each do |file|
211
+ puts "uploading #{file}..."
212
+ content = ::File.read file
213
+ quant_connect_proxy.put_file project_settings.project_id, file, content
214
+ end
215
+ end
216
+
217
+ def changed_files
218
+ all_files = Dir["*.{#{project_settings.file_extensions}}"]
219
+
220
+ return all_files unless project_settings.last_sync_at
221
+
222
+ all_files.find_all do |file|
223
+ ::File.mtime(file) > project_settings.last_sync_at
224
+ end
225
+ end
226
+
227
+ def save_current_timestamp
228
+ project_settings.last_sync_at = Time.now
229
+ save_project_settings
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,15 @@
1
+ module Qc
2
+ class Compile < Struct.new(:id, :state)
3
+ def in_queue?
4
+ state == 'InQueue'
5
+ end
6
+
7
+ def success?
8
+ state == 'BuildSuccess'
9
+ end
10
+
11
+ def error?
12
+ state == 'BuildError'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,59 @@
1
+ module Qc
2
+ class Console
3
+ def initialize
4
+ end
5
+
6
+ def run(argv)
7
+ options = parse_options(argv)
8
+
9
+ command = argv[0]
10
+ client = Qc::CommandRunner.new(quant_connect_proxy)
11
+ success = begin
12
+ if command
13
+ client.run(command.to_sym)
14
+ else
15
+ client.run_default
16
+ end
17
+ rescue StandardError => error
18
+ puts "Error: #{error}"
19
+ false
20
+ end
21
+
22
+ success
23
+ end
24
+
25
+ private
26
+
27
+ def quant_connect_proxy
28
+ @quant_connect_proxy ||= Qc::QuantConnectProxy.new(Qc::Credentials.read_from_home)
29
+ end
30
+
31
+ def parse_options(args)
32
+ options = OpenStruct.new
33
+
34
+ opt_parser = OptionParser.new do |opts|
35
+ opts.banner = "Usage: qc [command] [options]"
36
+
37
+ opts.separator ""
38
+ opts.separator "Supported commands: #{Qc::CommandRunner::SUPPORTED_COMMANDS.join(', ')}"
39
+ opts.separator "When no command provided it will execute: 'push compile backtest' in sequence"
40
+
41
+ opts.separator ""
42
+ opts.separator "Common options:"
43
+
44
+ opts.on("-h", "--help", "Show this message") do
45
+ puts opts
46
+ exit 0
47
+ end
48
+
49
+ opts.on("--version", "Show version") do
50
+ puts Qc::VERSION
51
+ exit 0
52
+ end
53
+ end
54
+
55
+ opt_parser.parse!(args)
56
+ options
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ module Qc
2
+ class Credentials < Struct.new(:user_id, :access_token)
3
+ FILE_NAME = 'credentials.yml'
4
+
5
+ def self.read_from_home
6
+ return nil unless ::File.exists?(credentials_file)
7
+ YAML.load_file credentials_file
8
+ end
9
+
10
+ def save_to_home
11
+ FileUtils.mkdir_p(Qc::Util.home_dir)
12
+ ::File.open(self.class.credentials_file, 'w') do |file|
13
+ file.write self.to_yaml
14
+ end
15
+ end
16
+
17
+ def destroy
18
+ FileUtils.remove(self.class.credentials_file)
19
+ end
20
+
21
+ def self.credentials_file
22
+ ::File.join(Qc::Util.home_dir, FILE_NAME)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ module Qc
2
+ class File < Struct.new(:name, :content)
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Qc
2
+ class Project < Struct.new(:id, :name)
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Qc
2
+ class ProjectSettings< Struct.new(:project_id, :file_extensions, :last_sync_at, :last_compile_id, :last_backtest_id)
3
+
4
+ end
5
+ end
@@ -0,0 +1,120 @@
1
+ module Qc
2
+ class QuantConnectProxy
3
+ class RequestError < StandardError;
4
+ end
5
+
6
+ attr_accessor :credentials
7
+
8
+ BASE_URL = "https://www.quantconnect.com/api/v2"
9
+
10
+ def initialize(credentials)
11
+ @credentials = credentials
12
+ end
13
+
14
+ def valid_login?
15
+ response = perform_request :get, '/authenticate'
16
+ response.success
17
+ end
18
+
19
+ def list_projects
20
+ response = perform_request :get, '/projects/read'
21
+ validate_response! response
22
+ response.projects.collect do |project_json|
23
+ Qc::Project.new(project_json['projectId'], project_json['name'])
24
+ end
25
+ end
26
+
27
+ def delete_project(project_id)
28
+ response = perform_request :post, '/projects/delete', payload: {projectId: project_id}
29
+ validate_response! response
30
+ project_id
31
+ end
32
+
33
+ def create_project(project_name, language: 'C#')
34
+ response = perform_request :post, '/projects/create', payload: {name: project_name, language: language}
35
+ validate_response! response
36
+ project = response.projects[0]
37
+ Qc::Project.new(project['project_id'], project['name'])
38
+ end
39
+
40
+ def put_file(project_id, file_name, file_content)
41
+ payload = {projectId: project_id, name: file_name, content: file_content}
42
+ response = perform_request :post, '/files/update', payload: payload
43
+ if missing_file_error?(response)
44
+ response = perform_request :post, '/files/create', payload: payload
45
+ end
46
+ validate_response! response
47
+ create_file_from_json_response(response.files[0])
48
+ end
49
+
50
+ def read_file(project_id, file_name)
51
+ response = perform_request :post, '/files/read', payload: {projectId: project_id, name: file_name}
52
+ validate_response! response
53
+ create_file_from_json_response(response.files[0])
54
+ end
55
+
56
+ def create_compile(project_id)
57
+ response = perform_request :post, '/compile/create', payload: {projectId: project_id}
58
+ validate_response! response
59
+ create_compile_from_json_response(response)
60
+ end
61
+
62
+ def read_compile(project_id, compile_id)
63
+ response = perform_request :get, '/compile/read', params: {projectId: project_id, compileId: compile_id}
64
+ validate_response! response
65
+ create_compile_from_json_response(response)
66
+ end
67
+
68
+ def create_backtest(project_id, compile_id, backtest_name)
69
+ response = perform_request :post, '/backtests/create', payload: {projectId: project_id, compileId: compile_id, backtestName: backtest_name}
70
+ validate_response! response
71
+ create_backtest_from_json_response(response)
72
+ end
73
+
74
+ def read_backtest(project_id, backtest_id)
75
+ response = perform_request :get, '/backtests/read', params: {projectId: project_id, compileId: backtest_id}
76
+ validate_response! response
77
+ create_backtest_from_json_response(OpenStruct.new({success: response.success}.merge(response.backtests[0])))
78
+ end
79
+
80
+ private
81
+
82
+ def create_backtest_from_json_response(response)
83
+ Qc::Backtest.new(response.backtestId, response.name, response.completed, response.progres.to_f, response.result, response.success)
84
+ end
85
+
86
+ def create_compile_from_json_response(response)
87
+ Qc::Compile.new(response.compileId, response.state)
88
+ end
89
+
90
+ def missing_file_error?(response)
91
+ !response.success && (response.errors.join("\n") =~ /file not found/i)
92
+ end
93
+
94
+ def create_file_from_json_response(file_json)
95
+ Qc::File.new(file_json['name'], file_json['content'])
96
+ end
97
+
98
+ def perform_request(method, path, payload: {}, params: {})
99
+ timestamp = Time.now.utc.to_time.to_i
100
+ password_hash = Digest::SHA256.hexdigest "#{credentials.access_token}:#{timestamp}"
101
+ headers = {Timestamp: timestamp}.merge(params: params)
102
+ response = RestClient::Request.execute method: method,
103
+ url: "#{BASE_URL}#{path}",
104
+ headers: headers,
105
+ user: credentials.user_id,
106
+ content_type: :json,
107
+ accept: :json,
108
+ password: password_hash,
109
+ payload: payload
110
+ body = response.body.empty? ? '{"success": false}' : response.body
111
+ OpenStruct.new JSON.parse(body)
112
+ end
113
+
114
+ def validate_response!(response)
115
+ unless response.success
116
+ raise RequestError, response.inspect
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,12 @@
1
+ module Qc
2
+ class Util
3
+ def self.home_dir
4
+ # Not using `Dir.home` because aruba won't let you mock it
5
+ ::File.join(ENV['HOME'], '.qc')
6
+ end
7
+
8
+ def self.project_dir
9
+ ::File.join('.', '.qc')
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Qc
2
+ VERSION = "0.5.1"
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "qc/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "qc"
8
+ spec.version = Qc::VERSION
9
+ spec.authors = ["Jorge Manrubia"]
10
+ spec.email = ["jorge.manrubia@gmail.com"]
11
+
12
+ spec.summary = %q{Sync and run your QuantConnect backtests from the command line}
13
+ spec.homepage = "https://github.com/jorgemanrubia/qc"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_runtime_dependency "rest-client", "~> 2.0.2"
24
+ spec.add_runtime_dependency "vcr", "~> 3.0.3"
25
+ spec.add_runtime_dependency "webmock", "~> 3.1.0"
26
+ spec.add_development_dependency "awesome_print"
27
+ spec.add_development_dependency "bundler", "~> 1.15"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "minitest", "~> 5.0"
30
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.1
5
+ platform: ruby
6
+ authors:
7
+ - Jorge Manrubia
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-11-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: vcr
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.1.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.1.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: awesome_print
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.15'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.15'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '5.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '5.0'
111
+ description:
112
+ email:
113
+ - jorge.manrubia@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".ruby-version"
120
+ - Gemfile
121
+ - Gemfile.lock
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/console
126
+ - bin/qc
127
+ - bin/setup
128
+ - lib/qc.rb
129
+ - lib/qc/backtest.rb
130
+ - lib/qc/command_runner.rb
131
+ - lib/qc/compile.rb
132
+ - lib/qc/console.rb
133
+ - lib/qc/credentials.rb
134
+ - lib/qc/file.rb
135
+ - lib/qc/project.rb
136
+ - lib/qc/project_settings.rb
137
+ - lib/qc/quant_connect_proxy.rb
138
+ - lib/qc/util.rb
139
+ - lib/qc/version.rb
140
+ - qc.gemspec
141
+ homepage: https://github.com/jorgemanrubia/qc
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.6.13
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Sync and run your QuantConnect backtests from the command line
165
+ test_files: []