module RbaRumble
Constants
- ANNOUNCEMENT_CUTOFF_HOUR
- ANNOUNCEMENT_CUTOFF_MIN
- ANNOUNCEMENT_DATES_2026
-
RBA Board monetary policy meeting dates (Melbourne time, announcement ~14:30).
- DEFAULT_BET
- PAYOUT_MULTIPLIER
- PLUGIN
- STYLE_LOSE
- STYLE_TITLE
- STYLE_WIN
Public Instance Methods
Source
# File lib/rba_rumble.rb, line 56 def after_announcement?(time: Time.now) announcement_day?(time: time) && !before_announcement?(time: time) end
Source
# File lib/rba_rumble.rb, line 89 def already_settled?(network:, channel:, time: Time.now, store: RabbotDb) flag = GameStore.load( plugin: PLUGIN, network: network, channel: channel, key: settled_key(time: time), store: store ) flag == true end
Source
# File lib/rba_rumble.rb, line 44 def announcement_date(time: Time.now) time.getlocal("+10:00").to_date end
Source
# File lib/rba_rumble.rb, line 39 def announcement_day?(time: Time.now) today = time.getlocal("+10:00").to_date ANNOUNCEMENT_DATES_2026.include?(today) end
Source
# File lib/rba_rumble.rb, line 48 def before_announcement?(time: Time.now) return false unless announcement_day?(time: time) local = time.getlocal("+10:00") (local.hour < ANNOUNCEMENT_CUTOFF_HOUR) || (local.hour == ANNOUNCEMENT_CUTOFF_HOUR && local.min < ANNOUNCEMENT_CUTOFF_MIN) end
Source
# File lib/rba_rumble.rb, line 60 def bets_key(time: Time.now) "bets:#{announcement_date(time: time)}" end
Source
# File lib/rba_rumble.rb, line 228 def fetch_cash_history(fetch: nil) body = if fetch fetch.call(RbaRates::CASH_CSV_URL) else RabbotHttp.fetch(RbaRates::CASH_CSV_URL) end parse_cash_history(body) end
Source
# File lib/rba_rumble.rb, line 360 def format_result(user:, pick:, amount:, rates:, won:, balance:) move_word = rates[:move].to_s.upcase outcome_style = won ? STYLE_WIN : STYLE_LOSE outcome_word = won ? "WIN" : "LOSS" lines = [ IrcFormat.report_header(tag: "RBA", title: "Rate Rumble", style: STYLE_TITLE), "#{user} bet #{pick} #{amount} — prior #{rates[:prior][:rate]}% → now #{rates[:current][:rate]}% (#{move_word})", IrcFormat.decorate("#{outcome_word} — balance #{balance} credits", outcome_style), IrcFormat.report_footer("cash rate target", "Reserve Bank of Australia") ] lines.join("\n") end
Source
# File lib/rba_rumble.rb, line 64 def load_bets(network:, channel:, time: Time.now, store: RabbotDb) GameStore.load( plugin: PLUGIN, network: network, channel: channel, key: bets_key(time: time), store: store ) || { "bets" => {} } end
Source
# File lib/rba_rumble.rb, line 100 def mark_settled!(network:, channel:, time: Time.now, store: RabbotDb) GameStore.save( plugin: PLUGIN, network: network, channel: channel, key: settled_key(time: time), game: true, store: store ) end
Source
# File lib/rba_rumble.rb, line 214 def parse_cash_history(body) rows = CSV.parse(body.to_s) series_row = rows.find { |row| row[0].to_s.strip == "Series ID" } return [] unless series_row cash_index = series_row.index(RbaRates::CASH_SERIES_ID) return [] unless cash_index rows.select { |row| row[0].to_s.match?(/\A(?:\d{2}-[A-Za-z]{3}-\d{4}|[A-Za-z]{3}-\d{4})\z/) } .map { |row| { date: row[0].to_s.strip, rate: row[cash_index].to_s.strip } } rescue StandardError [] end
Source
# File lib/rba_rumble.rb, line 123 def parse_rate_date(text) value = text.to_s.strip return nil if value.empty? if value.match?(/\A\d{2}-[A-Za-z]{3}-\d{4}\z/) Date.strptime(value, "%d-%b-%Y") else Date.strptime(value, "%b-%Y") end rescue StandardError nil end
Source
# File lib/rba_rumble.rb, line 169 def parse_rumble_args(parts) tokens = Array(parts).map(&:to_s) return { error: usage_message } if tokens.empty? if tokens[0].casecmp("settle").zero? return { action: :settle } end pick = tokens[0].downcase.to_sym unless %i[hold cut hike].include?(pick) return { error: usage_message } end amount = DEFAULT_BET amount = tokens[1].to_i if tokens[1]&.match?(/\A\d+\z/) unless Game.valid_amount?(amount) return { error: "Bet must be between #{Game::MIN_BET} and #{Game::MAX_BET} credits." } end { action: :bet, pick: pick, amount: amount } end
Source
# File lib/rba_rumble.rb, line 237 def place_bet(user:, pick:, amount:, network:, channel:, time: Time.now, store: Game.default_store, db_store: RabbotDb) unless announcement_day?(time: time) return { error: "Rate Rumble runs on RBA announcement days only." } end unless before_announcement?(time: time) return { error: "Betting closed — announcement at 14:30 AEST. Use !rba rumble settle." } end state = load_bets(network: network, channel: channel, time: time, store: db_store) key = GameStore.norm_nick(user) return { error: "#{user}, you already placed a Rate Rumble bet today." } if state["bets"][key] balance = Game.balance_for(user, store: store) if balance < amount return { error: "#{user}, you need #{amount} credits (balance: #{balance})." } end Game.deduct_bet(user, amount, store: store) state = state.dup state["bets"] = state["bets"].dup state["bets"][key] = { "user" => user, "pick" => pick.to_s, "amount" => amount } save_bets(network: network, channel: channel, state: state, time: time, store: db_store) { message: [ IrcFormat.report_header(tag: "RBA", title: "Rate Rumble", style: STYLE_TITLE), "#{user} bet #{pick} #{amount} — settles after 14:30 AEST", IrcFormat.report_footer("cash rate target", "Reserve Bank of Australia") ].join("\n") } end
Source
# File lib/rba_rumble.rb, line 308 def play(user:, pick:, amount:, network: nil, channel: nil, time: Time.now, store: Game.default_store, db_store: RabbotDb, fetch: nil) if network && channel if pick == :settle return settle(network: network, channel: channel, time: time, store: store, db_store: db_store, fetch: fetch) end return place_bet( user: user, pick: pick, amount: amount, network: network, channel: channel, time: time, store: store, db_store: db_store ) end # Legacy instant settle when no channel context (tests). unless announcement_day?(time: time) return { error: "Rate Rumble runs on RBA announcement days only." } end rates = prior_and_current_rates(fetch_cash_history(fetch: fetch)) return { error: "Could not determine cash rate move." } unless rates balance = Game.balance_for(user, store: store) if balance < amount return { error: "#{user}, you need #{amount} credits (balance: #{balance})." } end won = rates[:move] == pick Game.deduct_bet(user, amount, store: store) winnings = won ? amount * PAYOUT_MULTIPLIER : 0 Game.credit_winnings(user, winnings, store: store) if winnings.positive? new_balance = Game.balance_for(user, store: store) { win: won, rates: rates, message: format_result( user: user, pick: pick, amount: amount, rates: rates, won: won, balance: new_balance ) } rescue StandardError { error: "Rate Rumble failed." } end
Source
# File lib/rba_rumble.rb, line 136 def poll_settle(bot, fetch: nil, store: Game.default_store, db_store: RabbotDb, time: Time.now) return [] unless bot return [] unless after_announcement?(time: time) cash_rows = fetch_cash_history(fetch: fetch) return [] unless rate_updated_on_announcement_day?(cash_rows, time: time) messages = [] bot.config.channels.each do |channel_name| network = bot.config.server next if already_settled?(network: network, channel: channel_name, time: time, store: db_store) state = load_bets(network: network, channel: channel_name, time: time, store: db_store) next if (state["bets"] || {}).empty? result = settle( network: network, channel: channel_name, time: time, store: store, db_store: db_store, fetch: fetch ) next if result[:error] mark_settled!(network: network, channel: channel_name, time: time, store: db_store) messages << { channel: channel_name, text: result[:message] } end messages rescue StandardError [] end
Source
# File lib/rba_rumble.rb, line 196 def prior_and_current_rates(cash_rows) rows = cash_rows.select { |row| !row[:rate].to_s.strip.empty? } return nil if rows.length < 2 current = rows[-1] prior = rows[-2] prior_rate = prior[:rate].to_f current_rate = current[:rate].to_f move = if current_rate > prior_rate :hike elsif current_rate < prior_rate :cut else :hold end { prior: prior, current: current, move: move } end
Source
# File lib/rba_rumble.rb, line 111 def rate_updated_on_announcement_day?(cash_rows, time: Time.now) rows = cash_rows.select { |row| !row[:rate].to_s.strip.empty? } return false if rows.length < 2 today = announcement_date(time: time) current = rows[-1] current_date = parse_rate_date(current[:date]) return false unless current_date current_date == today || current[:rate].to_f != rows[-2][:rate].to_f end
Source
# File lib/rba_rumble.rb, line 74 def save_bets(network:, channel:, state:, time: Time.now, store: RabbotDb) GameStore.save( plugin: PLUGIN, network: network, channel: channel, key: bets_key(time: time), game: state, store: store ) end
Source
# File lib/rba_rumble.rb, line 269 def settle(network:, channel:, time: Time.now, store: Game.default_store, db_store: RabbotDb, fetch: nil) unless announcement_day?(time: time) return { error: "Rate Rumble settles on RBA announcement days only." } end state = load_bets(network: network, channel: channel, time: time, store: db_store) bets = state["bets"] || {} return { error: "No Rate Rumble bets to settle." } if bets.empty? rates = prior_and_current_rates(fetch_cash_history(fetch: fetch)) return { error: "Could not determine cash rate move." } unless rates move_word = rates[:move].to_s.upcase lines = [ IrcFormat.report_header(tag: "RBA", title: "Rate Rumble settled", style: STYLE_TITLE), "Prior #{rates[:prior][:rate]}% → now #{rates[:current][:rate]}% (#{move_word})" ] bets.each_value do |bet| user = bet["user"] pick = bet["pick"].to_sym amount = bet["amount"].to_i won = pick == rates[:move] winnings = won ? amount * PAYOUT_MULTIPLIER : 0 Game.credit_winnings(user, winnings, store: store) if winnings.positive? balance = Game.balance_for(user, store: store) outcome_style = won ? STYLE_WIN : STYLE_LOSE outcome_word = won ? "WIN" : "LOSS" lines << "#{user} bet #{pick} #{amount} — #{IrcFormat.decorate(outcome_word, outcome_style)} (balance #{balance})" end save_bets(network: network, channel: channel, state: { "bets" => {} }, time: time, store: db_store) mark_settled!(network: network, channel: channel, time: time, store: db_store) lines << IrcFormat.report_footer("cash rate target", "Reserve Bank of Australia") { message: lines.join("\n") } rescue StandardError { error: "Rate Rumble settle failed." } end
Source
# File lib/rba_rumble.rb, line 85 def settled_key(time: Time.now) "settled:#{announcement_date(time: time)}" end
Source
# File lib/rba_rumble.rb, line 192 def usage_message "Usage: !rba rumble hold|cut|hike [amount] | settle — bet before 14:30 AEST on announcement days" end