From 22118544e2a11b2aad24ebbd91c681906264f0aa Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 31 Jan 2026 14:46:10 -0300 Subject: [PATCH 1/9] feat: Add migrate and model for instrument metric --- app/models/instrument_metric.rb | 3 +++ .../20260131171800_create_instrument_metrics.rb | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 app/models/instrument_metric.rb create mode 100644 db/migrate/20260131171800_create_instrument_metrics.rb diff --git a/app/models/instrument_metric.rb b/app/models/instrument_metric.rb new file mode 100644 index 0000000..a534572 --- /dev/null +++ b/app/models/instrument_metric.rb @@ -0,0 +1,3 @@ +class InstrumentMetric < ApplicationRecord + belongs_to :instrument +end diff --git a/db/migrate/20260131171800_create_instrument_metrics.rb b/db/migrate/20260131171800_create_instrument_metrics.rb new file mode 100644 index 0000000..333d20b --- /dev/null +++ b/db/migrate/20260131171800_create_instrument_metrics.rb @@ -0,0 +1,17 @@ +class CreateInstrumentMetrics < ActiveRecord::Migration[8.1] + def change + create_table :instrument_metrics do |t| + t.references :instrument, null: false, foreign_key: true + t.decimal :price, precision: 15, scale: 4, null: false + t.decimal :dy, precision: 5, scale: 2 + t.decimal :p_vp, precision: 5, scale: 2 + t.decimal :daily_liquidity, precision: 15, scale: 2 + t.decimal :market_cap, precision: 20, scale: 2 + t.datetime :recorded_at, null: false + + t.timestamps + end + + add_index :instrument_metrics, [ :instrument_id, :recorded_at ] + end +end From 208d3a5108fbb49538aaae5c5d6aa6661ecda93b Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 31 Jan 2026 14:46:38 -0300 Subject: [PATCH 2/9] feat: Add relationship with instrument metrics --- app/models/instrument.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/instrument.rb b/app/models/instrument.rb index 22daf6e..7cb0817 100644 --- a/app/models/instrument.rb +++ b/app/models/instrument.rb @@ -2,4 +2,5 @@ class Instrument < ApplicationRecord has_many :holdings, dependent: :destroy has_many :transactions, dependent: :destroy has_many :wallets, through: :holdings + has_many :instrument_metrics, dependent: :destroy end From 06c3c95af8da6840569dde9096e633d15bc204a3 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 31 Jan 2026 14:47:06 -0300 Subject: [PATCH 3/9] feat: Install sidekiq and redis gem --- Gemfile | 4 ++++ Gemfile.lock | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/Gemfile b/Gemfile index e2cf0d4..a4a41a2 100644 --- a/Gemfile +++ b/Gemfile @@ -43,6 +43,10 @@ gem "thruster", require: false gem "chartkick" +gem "sidekiq", "~> 7.3" + +gem "redis", "~> 5.3" + # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] gem "image_processing", "~> 1.2" diff --git a/Gemfile.lock b/Gemfile.lock index 3d3fe7b..1da9ee3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -261,6 +261,10 @@ GEM erb psych (>= 4.0.0) tsort + redis (5.4.1) + redis-client (>= 0.22.0) + redis-client (0.26.4) + connection_pool regexp_parser (2.11.3) reline (0.6.3) io-console (~> 0.5) @@ -326,6 +330,12 @@ GEM securerandom (0.4.1) shoulda-matchers (6.5.0) activesupport (>= 5.2.0) + sidekiq (7.3.9) + base64 + connection_pool (>= 2.3.0) + logger + rack (>= 2.2.4) + redis-client (>= 0.22.2) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) @@ -411,6 +421,7 @@ DEPENDENCIES propshaft puma (>= 5.0) rails (~> 8.1.1) + redis (~> 5.3) rspec-rails (~> 7.1, >= 7.1.1) rubocop (~> 1.69) rubocop-performance (~> 1.24) @@ -419,6 +430,7 @@ DEPENDENCIES rubocop-rspec (~> 3.3) ruby-lsp shoulda-matchers (~> 6.5) + sidekiq (~> 7.3) simplecov (~> 0.21.2) solid_cable solid_cache From 15c423ba6dcbd4b095d8339d8af0f81b71b9c7ec Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 31 Jan 2026 14:47:29 -0300 Subject: [PATCH 4/9] feat: Add regis service in compose --- compose.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compose.yml b/compose.yml index 68fbfc7..f75b4b6 100644 --- a/compose.yml +++ b/compose.yml @@ -50,9 +50,19 @@ services: networks: - ledger_net + redis: + container_name: ledger_redis + image: "redis:latest" + command: redis-server + volumes: + - redis_data:/data + networks: + - ledger_net + volumes: postgres_data: gem_cache: + redis_data: networks: ledger_net: From ffb53b88adc0a9d513166d78ae62638607df023f Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 31 Jan 2026 14:47:48 -0300 Subject: [PATCH 5/9] feat: Config sidekiq --- config/initializers/sidekiq.rb | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 config/initializers/sidekiq.rb diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb new file mode 100644 index 0000000..c665c01 --- /dev/null +++ b/config/initializers/sidekiq.rb @@ -0,0 +1,7 @@ +Sidekiq.configure_server do |config| + config.redis = { url: ENV.fetch("REDIS_URL") } +end + +Sidekiq.configure_client do |config| + config.redis = { url: ENV.fetch("REDIS_URL") } +end From 3b0a8f28b86b4278e3c1a58e5a5a759e7fbe1df2 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 31 Jan 2026 14:48:01 -0300 Subject: [PATCH 6/9] feat: Update schema --- db/schema.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 32eebba..e5e0599 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_12_25_031419) do +ActiveRecord::Schema[8.1].define(version: 2026_01_31_171800) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -26,6 +26,20 @@ t.index ["wallet_id"], name: "index_holdings_on_wallet_id" end + create_table "instrument_metrics", force: :cascade do |t| + t.datetime "created_at", null: false + t.decimal "daily_liquidity", precision: 15, scale: 2 + t.decimal "dy", precision: 5, scale: 2 + t.bigint "instrument_id", null: false + t.decimal "market_cap", precision: 20, scale: 2 + t.decimal "p_vp", precision: 5, scale: 2 + t.decimal "price", precision: 15, scale: 4, null: false + t.datetime "recorded_at", null: false + t.datetime "updated_at", null: false + t.index ["instrument_id", "recorded_at"], name: "index_instrument_metrics_on_instrument_id_and_recorded_at" + t.index ["instrument_id"], name: "index_instrument_metrics_on_instrument_id" + end + create_table "instruments", force: :cascade do |t| t.datetime "created_at", null: false t.string "kind" @@ -91,6 +105,7 @@ add_foreign_key "holdings", "instruments" add_foreign_key "holdings", "wallets" + add_foreign_key "instrument_metrics", "instruments" add_foreign_key "strategies", "wallets" add_foreign_key "strategy_rules", "strategies" add_foreign_key "transactions", "instruments" From 22f7108c7337d993aad2166ff0760fde419e66ae Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 31 Jan 2026 14:48:24 -0300 Subject: [PATCH 7/9] feat: Add router for sidekiq --- config/routes.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 3b75d34..1dc3c3d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,14 @@ +require "sidekiq/web" + Rails.application.routes.draw do devise_for :users # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html + Sidekiq::Web.use Rack::Auth::Basic do |username, password| + username == ENV["SIDEKIQ_USERNAME"] && password == ENV["SIDEKIQ_PASSWORD"] + end + + mount Sidekiq::Web => "/sidekiq" # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. # Can be used by load balancers and uptime monitors to verify that the app is live. get "up" => "rails/health#show", as: :rails_health_check From a4c89fb3eb8f1059dd868e9d56efae3307bb20cb Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 31 Jan 2026 14:52:52 -0300 Subject: [PATCH 8/9] style: Fomat code --- app/controllers/dashboard_controller.rb | 42 ++++++++++++------------- app/models/strategy_rule.rb | 4 +-- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 9e471ad..df50ad2 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,11 +1,11 @@ class DashboardController < ApplicationController def index @wallets = current_user.wallets.includes(:holdings, :transactions, :strategy) - + @total_wallets = @wallets.count @active_wallets = @wallets.active.count @inactive_wallets = @wallets.inactive.count - + @wallet_summaries = @wallets.map do |wallet| { wallet: wallet, @@ -16,12 +16,12 @@ def index has_strategy: wallet.strategy.present? } end - + @total_invested = @wallet_summaries.sum { |ws| ws[:total_invested] } @total_current_value = @wallet_summaries.sum { |ws| ws[:current_value] } @total_profit_loss = @total_current_value - @total_invested @total_profit_loss_percentage = @total_invested.zero? ? 0 : ((@total_profit_loss / @total_invested) * 100) - + @recent_transactions = current_user.wallets .joins(:transactions) .includes(transactions: :instrument) @@ -29,43 +29,43 @@ def index .limit(10) .flat_map(&:transactions) .first(10) - - + + @wallet_distribution = @wallet_summaries.map do |summary| - [summary[:wallet].name, summary[:current_value].to_f] + [ summary[:wallet].name, summary[:current_value].to_f ] end.to_h - + transactions_last_6_months = current_user.wallets .joins(:transactions) .where("transactions.occurred_at >= ?", 6.months.ago) .pluck("transactions.occurred_at") - + @transactions_by_month = transactions_last_6_months .compact .group_by { |date| date.beginning_of_month.strftime("%b %Y") } .transform_values(&:count) .sort_by { |month, _| Date.parse("01 #{month}") } .to_h - + @buy_vs_sell = { "Buys" => current_user.wallets.joins(:transactions).merge(Transaction.buy).count, "Sells" => current_user.wallets.joins(:transactions).merge(Transaction.sell).count } - + holdings_with_instruments = current_user.wallets .joins(holdings: :instrument) .select("instruments.ticker, holdings.average_price, holdings.quantity") - + @top_instruments = holdings_with_instruments .group_by(&:ticker) - .map { |ticker, holdings| + .map { |ticker, holdings| total = holdings.sum { |h| (h.average_price || 0) * (h.quantity || 0) } - [ticker, total.to_f] + [ ticker, total.to_f ] } .sort_by { |_, value| -value } .first(5) .to_h - + @wallet_comparison_data = @wallet_summaries.map do |summary| [ summary[:wallet].name, @@ -76,14 +76,14 @@ def index ] end.to_h end - + private - + def calculate_total_invested(wallet) - wallet.transactions.buy.sum('price * quantity') + wallet.transactions.buy.sum("price * quantity") end - + def calculate_current_value(wallet) - wallet.holdings.sum('average_price * quantity') + wallet.holdings.sum("average_price * quantity") end -end \ No newline at end of file +end diff --git a/app/models/strategy_rule.rb b/app/models/strategy_rule.rb index cd9d567..3547c31 100644 --- a/app/models/strategy_rule.rb +++ b/app/models/strategy_rule.rb @@ -6,8 +6,8 @@ class StrategyRule < ApplicationRecord validates :asset_kind, presence: true validates :strategy_id, presence: true - validate :validate_percentage_rule, if: -> { rule_type == 'percentage' } - validate :validate_prohibition_rule, if: -> { rule_type == 'prohibition' } + validate :validate_percentage_rule, if: -> { rule_type == "percentage" } + validate :validate_prohibition_rule, if: -> { rule_type == "prohibition" } # Métodos auxiliares def prohibition? From e6c329e4c9ff2648b3e0f31d47c2717c47f99ae8 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 31 Jan 2026 14:55:33 -0300 Subject: [PATCH 9/9] chore: Update gem --- Gemfile.lock | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1da9ee3..bce4a41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -83,7 +83,7 @@ GEM bindex (0.8.1) bootsnap (1.21.1) msgpack (~> 1.2) - brakeman (7.1.2) + brakeman (8.0.1) racc builder (3.3.0) bullet (8.1.0) @@ -203,7 +203,7 @@ GEM pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.8.0) + prism (1.9.0) propshaft (1.3.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) @@ -255,8 +255,9 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.3.1) - rbs (3.10.2) + rbs (3.10.3) logger + tsort rdoc (7.1.0) erb psych (>= 4.0.0) @@ -287,8 +288,8 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) - rspec-support (3.13.6) - rubocop (1.82.1) + rspec-support (3.13.7) + rubocop (1.84.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -296,7 +297,7 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.48.0, < 2.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) rubocop-ast (1.49.0) @@ -376,7 +377,7 @@ GEM thruster (0.1.17-x86_64-linux) timeout (0.6.0) tsort (0.2.0) - turbo-rails (2.0.21) + turbo-rails (2.0.23) actionpack (>= 7.1.0) railties (>= 7.1.0) tzinfo (2.0.6)