Skip to content

Instantly share code, notes, and snippets.

@wakproductions
Created February 28, 2021 22:33
Show Gist options
  • Save wakproductions/34634132bb1277391d269ab5c998d71e to your computer and use it in GitHub Desktop.
Save wakproductions/34634132bb1277391d269ab5c998d71e to your computer and use it in GitHub Desktop.
Rufus vs Whenever Ruby Schedulers - Stonks on Rails #3
# docker-compose.yml
version: '3'

services:
  web:
    build: ./
    command: 'rails server -b 0.0.0.0 -p 3000'
    ports:
      - 4000:3000
    stdin_open: true
    tty: true
    volumes:
      - ./:/app
      - ${STOCK_TREND_FINDER_DATA_DIR}:${STOCK_TREND_FINDER_DATA_DIR}

  jobs:
    build: ./
    command: 'rake stf:run_jobs_daemon'
    stdin_open: true
    tty: true
    volumes:
      - ./:/app
      - ${STOCK_TREND_FINDER_DATA_DIR}:${STOCK_TREND_FINDER_DATA_DIR}
# lib/tasks/stf.rake
namespace :stf do

  desc "Just test the output of whenever"
  task :test_output => :environment do
    puts "This is a test output message at #{Time.current}"
  end

  desc "Runs the daily and weekly market data jobs"
  task :run_jobs_daemon => :environment do
    JobScheduler::ScheduleJobs.call

    while 1 do
      # infinite loop until Ctrl+C hit - keeps rake task from ending and allows job threads to run
      sleep 600
    end
  end

end
# lib/job_scheduler/schedule_jobs.rb
module JobScheduler
  class ScheduleJobs < CallableServiceBase
    include Jobs
    include JobLogger

    SCHEDULE = [
      { cron: '1 12 * * MON', method: :update_company_list }, # 12:01am Mondays
      { cron: '8 12 * * MON', method: :update_sp500_list }, # 12:08am Mondays
      { cron: '25 4 * * MON-FRI', method: :prepopulate_stock_prices }, # 12:25am weekdays
      { cron: '35,45 9 * * MON-FRI', method: :real_time_quotes_update }, # 9am hour weekday updates
      { cron: '0,30 10-15 * * MON-FRI', method: :real_time_quotes_update }, # 10am-4pm weekday updates (every half hour),
      { cron: '5 16 * * MON-FRI', method: :finalize_daily_quotes }, # 4:05pm weekdays
      { cron: '0 21 * * MON-FRI', method: :save_report_snapshots }, # 9pm weekdays
      { cron: '30 12 * * SAT', method: :pull_tda_fundamentals }, # 12:30am Saturday (~30 minutes?)
      { cron: '0 6 * * SAT', method: :run_db_maintenance }, # 6am on Saturday
      # { cron: '*/5 * * * * *', method: :test_cron },
    ]

    def call(_args)
      log('Starting Rufus scheduler')
      rufus = Rufus::Scheduler.singleton

      def rufus.on_error(job, error)
        JobLogger.log("rufus-scheduler #{error.inspect} in job #{job.inspect}", level: :error)
      end

      SCHEDULE.each do |details|
        log("Scheduling #{details[:method]}")
        rufus.cron(details[:cron], method(details[:method]))
      end

      log("Done scheduling Rufus")
    end

    def test_cron
      log("Testing Cron - #{Time.current}")
    end

  end
end
# lib/job_scheduler/jobs.rb
module JobScheduler
  module Jobs
    module_function
    extend JobScheduler::JobLogger

    def finalize_daily_quotes
      log("Finalizing daily_stock_prices: #{Time.current}")
      (log("Market closed today, no real time quotes to finalize necessary"); return) unless StockMarketDays.is_market_day?

      ActiveRecord::Base.connection_pool.with_connection do
        MarketDataPull::TDAmeritrade::DailyQuotes::FinalizeDailyQuotesFromRealTimeSnapshot.call
      end

      log("Done finalizing daily_stock_prices: #{Time.current}")
    end

    def prepopulate_stock_prices
      # ...
    end

    def pull_tda_fundamentals
      # ...
    end

    def real_time_quotes_update
      # ...
    end

    def run_db_maintenance
      # ...
    end

    def save_report_snapshots
      # ...
    end

    def update_company_list
      # ...
    end

    def update_sp500_list
      # ...
    end
  end
end
# lib/job_scheduler/job_logger.rb
module JobScheduler
  module JobLogger
    module_function

    LOG_FILE = Rails.root.join('log', 'jobs.log')
    @@jobs_logger = Logger.new(LOG_FILE)

    def log(message, level: :info)
      puts message
      @@jobs_logger.send(level, message)
    end
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment