class Qc::CommandRunner

Constants

BACKTEST_DELAY_IN_SECONDS
COMPILE_POLLING_DELAY_IN_SECONDS
DEFAULT_FILE_EXTENSIONS
DEFAULT_IGNORED_FILES
SUPPORTED_COMMANDS

Attributes

options[R]
project_settings[RW]
quant_connect_proxy[R]

Public Class Methods

new(quant_connect_proxy, options = OpenStruct.new) click to toggle source
# File lib/qc/command_runner.rb, line 15
def initialize(quant_connect_proxy, options = OpenStruct.new)
  @quant_connect_proxy = quant_connect_proxy
  @project_settings = read_project_settings
  @options = options
end

Public Instance Methods

run(command) click to toggle source
# File lib/qc/command_runner.rb, line 21
def run(command)
  if command == :login
    do_run(command)
  else
    require_login do
      do_run(command)
    end
  end
end
run_default() click to toggle source
# File lib/qc/command_runner.rb, line 31
def run_default
  run(:default)
end

Private Instance Methods

ask_for_extensions() click to toggle source
# File lib/qc/command_runner.rb, line 274
def ask_for_extensions
  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}'"
  file_extensions = DEFAULT_FILE_EXTENSIONS if file_extensions.empty?
  file_extensions
end
ask_for_project() click to toggle source
# File lib/qc/command_runner.rb, line 257
def ask_for_project
  puts "Fetching projets from Quantconnect..."
  projects = quant_connect_proxy.list_projects
  puts "Select the project you want to associate with this directory"
  projects.each.with_index do |project, index|
    puts "[#{index + 1}] - #{project.name}"
  end
  index = ask_for_value "Project number?"
  index = index.to_i
  if index >= 1 && index < projects.length + 1
    projects[index - 1]
  else
    puts "Invalid value (please type a number between #{1} and #{projects.length})"
    ask_for_project
  end
end
ask_for_value(question) click to toggle source
# File lib/qc/command_runner.rb, line 252
def ask_for_value(question)
  puts question
  STDIN.gets.chomp
end
changed_files() click to toggle source
# File lib/qc/command_runner.rb, line 293
def changed_files
  all_files = fetch_all_files

  return all_files unless project_settings.last_sync_at

  changed_files = all_files.find_all do |file|
    ::File.mtime(file) > project_settings.last_sync_at
  end

  changed_files
end
create_project_settings() click to toggle source
# File lib/qc/command_runner.rb, line 53
def create_project_settings
  Qc::ProjectSettings.new.tap do |project_settings|
    project_settings.execution_count = 1
    project_settings.ignored_files = DEFAULT_IGNORED_FILES
  end
end
credentials() click to toggle source
# File lib/qc/command_runner.rb, line 37
def credentials
  quant_connect_proxy&.credentials
end
credentials=(new_credentials) click to toggle source
# File lib/qc/command_runner.rb, line 41
def credentials=(new_credentials)
  quant_connect_proxy.credentials = new_credentials
end
current_backtest_label() click to toggle source
# File lib/qc/command_runner.rb, line 226
def current_backtest_label
  @current_backtest_label ||= "#{project_name}-#{project_settings.execution_count}"
end
do_run(command) click to toggle source
# File lib/qc/command_runner.rb, line 73
def do_run(command)
  case command
    when :default
      do_run_default
    when *SUPPORTED_COMMANDS
      send "run_#{command}"
    else
      raise "Unknonw command '#{command}'. Supported commands: #{SUPPORTED_COMMANDS.collect(&:to_s).join(', ')}"
  end
end
do_run_backtest() click to toggle source
# File lib/qc/command_runner.rb, line 191
def do_run_backtest
  backtest = quant_connect_proxy.create_backtest project_settings.project_id, project_settings.last_compile_id, current_backtest_label
  puts "Backtest '#{current_backtest_label}' for compile #{project_settings.last_compile_id} sent to the queue with id #{backtest.id}"

  open_results_in_quant_connect if options.open_results

  puts "Waiting for backtest to start..."
  last_completed_percentage = nil
  begin
    if last_completed_percentage != backtest.progress
      puts "Completed: #{backtest.progress}%..."
    end
    last_completed_percentage = backtest.progress
    backtest = quant_connect_proxy.read_backtest project_settings.project_id, backtest.id
    sleep BACKTEST_DELAY_IN_SECONDS unless backtest.completed?
  end while !backtest.completed?

  show_backtest_results backtest
  update_project_settings_after_running_backtest(backtest)
  import_backtest_results_into_tradervue(backtest) if options.import_into_tradervue

  backtest.success?
end
do_run_default() click to toggle source
# File lib/qc/command_runner.rb, line 174
def do_run_default
  return false unless validate_initialized_project!

  failed = %i(push compile backtest).find do |command|
    !run(command)
  end

  !failed
end
fetch_all_files() click to toggle source
# File lib/qc/command_runner.rb, line 305
def fetch_all_files
  Dir["**/*.{#{project_settings.file_extensions}}"].reject {|file| ignore_file?(file)}
end
ignore_file?(file) click to toggle source
# File lib/qc/command_runner.rb, line 309
def ignore_file?(file)
  ignored_files.find do |ignored_file|
    file =~ /#{ignored_file}/
  end
end
ignored_files() click to toggle source
# File lib/qc/command_runner.rb, line 315
def ignored_files
  project_settings.ignored_files || DEFAULT_IGNORED_FILES
end
import_backtest_results_into_tradervue(backtest) click to toggle source
# File lib/qc/command_runner.rb, line 343
def import_backtest_results_into_tradervue(backtest)
  show_title 'Importing results into Tradervue'

  raise "You need to set TRADERVUE_LOGIN and TRADERVUE_PASSWORD" unless tradervue_login && tradervue_password
  orders = backtest.result['Orders'].values
  orders_as_tradervue_executions = orders.collect do |order|
    {
        "datetime" => in_est_timezone(order),
        "symbol" => order['Symbol']['Value'],
        "quantity" => order['Quantity'],
        "price" => order['Price'],
        "option" => '',
        "commission" => '',
        "transfee" => 0,
        "ecnfee" => 0
    }
  end

  result = tradervue_importer.import_data(orders_as_tradervue_executions, account_tag: current_backtest_label)
  puts result
  puts "#{orders_as_tradervue_executions.length} orders imported successfully"
  open_results_in_tradervue(backtest)
end
in_est_timezone(order) click to toggle source
# File lib/qc/command_runner.rb, line 367
def in_est_timezone(order)
  time = DateTime.parse order['Time']
  time.to_time.utc.localtime("-05:00")
end
increase_execution_count() click to toggle source
# File lib/qc/command_runner.rb, line 221
def increase_execution_count
  project_settings.execution_count ||= 0
  project_settings.execution_count += 1
end
initialized_project?() click to toggle source
# File lib/qc/command_runner.rb, line 339
def initialized_project?
  project_settings.project_id
end
logged_in?() click to toggle source
# File lib/qc/command_runner.rb, line 60
def logged_in?
  !!credentials
end
macos?() click to toggle source
# File lib/qc/command_runner.rb, line 334
def macos?
  host_os = RbConfig::CONFIG['host_os']
  host_os =~ /darwin|mac os/
end
open_results_in_quant_connect() click to toggle source
# File lib/qc/command_runner.rb, line 164
def open_results_in_quant_connect
  return false unless validate_initialized_project!
  return false unless validate_on_mac!

  open_workflow_file = ::File.expand_path ::File.join(__dir__, '..', '..', 'automator', 'open-qc-results.workflow')
  open_command = "automator -i #{project_settings.project_id} #{open_workflow_file}"
  puts "Opening backtest results for project #{project_settings.project_id}"
  system open_command
end
open_results_in_tradervue(backtest) click to toggle source
# File lib/qc/command_runner.rb, line 372
def open_results_in_tradervue(backtest)
  return false unless validate_on_mac!

  url = "https://www.tradervue.com/trades?tag=#{current_backtest_label}"
  puts "Opening results in tradervue: #{url}"
  system "open #{url}"
end
project_name() click to toggle source
# File lib/qc/command_runner.rb, line 230
def project_name
  @project_name ||= ::File.basename(::File.expand_path(::File.join(Util.project_dir, '..')))
end
project_settings_file() click to toggle source
# File lib/qc/command_runner.rb, line 248
def project_settings_file
  ::File.join(Qc::Util.project_dir, 'settings.yml')
end
read_project_settings() click to toggle source
# File lib/qc/command_runner.rb, line 45
def read_project_settings
  if ::File.exist?(project_settings_file)
    YAML.load(::File.open(project_settings_file))
  else
    create_project_settings
  end
end
require_login() { || ... } click to toggle source
# File lib/qc/command_runner.rb, line 64
def require_login
  if credentials
    yield
  else
    puts "Please sign in by executing 'qc login' first"
    false
  end
end
run_backtest() click to toggle source
# File lib/qc/command_runner.rb, line 147
def run_backtest
  show_title 'Run backtest'
  return false unless validate_initialized_project!

  unless project_settings.last_compile_id
    puts "Project not compiled. Please run 'qc compile'"
    return false
  end

  do_run_backtest
end
run_compile() click to toggle source
# File lib/qc/command_runner.rb, line 125
def run_compile
  show_title 'Compile'
  return false unless validate_initialized_project!

  compile = quant_connect_proxy.create_compile project_settings.project_id
  puts "Compile request sent to the queue with id #{compile.id}"

  begin
    puts "Waiting for compilation result..."
    compile = quant_connect_proxy.read_compile project_settings.project_id, compile.id
    sleep COMPILE_POLLING_DELAY_IN_SECONDS if compile.in_queue?
  end while compile.in_queue?

  puts "Compile success" if compile.success?
  puts "Compile failed" if compile.error?

  project_settings.last_compile_id = compile.id
  save_project_settings

  compile.success?
end
run_init() click to toggle source
# File lib/qc/command_runner.rb, line 100
def run_init
  FileUtils.mkdir_p(Qc::Util.project_dir)
  project = ask_for_project
  self.project_settings.project_id = project.id
  self.project_settings.file_extensions = ask_for_extensions
  save_project_settings
  true
end
run_login() click to toggle source
# File lib/qc/command_runner.rb, line 84
def run_login
  puts "Please introduce your QuantConnect API credentials. You can find them in your preferences in https://www.quantconnect.com/account."
  user_id = ask_for_value 'User id:'
  access_token = ask_for_value 'Access token:'

  self.credentials = Qc::Credentials.new(user_id, access_token)

  if valid_login?
    credentials.save_to_home
    true
  else
    puts "Invalid credentials"
    false
  end
end
run_logout() click to toggle source
# File lib/qc/command_runner.rb, line 109
def run_logout
  credentials.destroy
  puts "Logged out successfully"
  true
end
run_open() click to toggle source
# File lib/qc/command_runner.rb, line 159
def run_open
  show_title 'Open backtest results'
  return open_results_in_quant_connect
end
run_push() click to toggle source
# File lib/qc/command_runner.rb, line 115
def run_push
  show_title 'Push files'

  return false unless validate_initialized_project!

  sync_changed_files
  save_current_timestamp
  true
end
save_current_timestamp() click to toggle source
# File lib/qc/command_runner.rb, line 319
def save_current_timestamp
  project_settings.last_sync_at = Time.now
  save_project_settings
end
save_project_settings() click to toggle source
# File lib/qc/command_runner.rb, line 244
def save_project_settings
  ::File.open(project_settings_file, 'w') {|file| file.write self.project_settings.to_yaml}
end
show_backtest_results(backtest) click to toggle source
# File lib/qc/command_runner.rb, line 234
def show_backtest_results(backtest)
  puts "Backtest finished" if backtest.success?
  puts "Backtest failed" if backtest.error?
  puts backtest.to_s
end
show_title(title) click to toggle source
# File lib/qc/command_runner.rb, line 184
def show_title(title)
  separator = '-' * title.length
  puts "\n#{separator}"
  puts title
  puts "#{separator}\n"
end
sync_changed_files() click to toggle source
# File lib/qc/command_runner.rb, line 281
def sync_changed_files
  if changed_files.empty?
    puts "No changes detected"
  end

  changed_files.each do |file|
    puts "Uploading #{file}..."
    content = ::File.read(file).strip
    quant_connect_proxy.put_file project_settings.project_id, ::File.basename(file), content
  end
end
tradervue_importer() click to toggle source
# File lib/qc/command_runner.rb, line 380
def tradervue_importer
  @tradervue_importer ||= TradervueImporter.new(tradervue_login, tradervue_password)
end
tradervue_login() click to toggle source
# File lib/qc/command_runner.rb, line 388
def tradervue_login
  ENV['TRADERVUE_LOGIN']
end
tradervue_password() click to toggle source
# File lib/qc/command_runner.rb, line 384
def tradervue_password
  ENV['TRADERVUE_PASSWORD']
end
update_project_settings_after_running_backtest(backtest) click to toggle source
# File lib/qc/command_runner.rb, line 215
def update_project_settings_after_running_backtest(backtest)
  project_settings.last_backtest_id = backtest.id
  increase_execution_count
  save_project_settings
end
valid_login?() click to toggle source
# File lib/qc/command_runner.rb, line 240
def valid_login?
  quant_connect_proxy.valid_login?
end
validate_initialized_project!() click to toggle source
# File lib/qc/command_runner.rb, line 324
def validate_initialized_project!
  puts "Please run 'qc init' to initialize your project" unless initialized_project?
  initialized_project?
end
validate_on_mac!() click to toggle source
# File lib/qc/command_runner.rb, line 329
def validate_on_mac!
  puts "This command is only supported in macos" unless macos?
  macos?
end