Day22 - 用 Ruby on Rails 处理台湾证券交易所资料-DB 设计

前言

有了前 2 篇从「台湾证券交易所」取得 CSV 档後,接着要把资料存入 DB,在存入前,需要先有 DB,本篇以 DB 设计为主

说明

预期会有

  1. 一个 Model 纪录股票名称、代号 (Stock)
  2. 一个 Model 纪录每日收盘行情相关资讯 (DailyQuote)
  3. 一个 Model 纪录除权除息计算结果表 (ExStock)
  4. StockDailyQuote 为一对多关联
  5. StockExStock 为一对多关联

实作

class CreateStocks < ActiveRecord::Migration[6.1]
  def change
    create_table :stocks do |t|
      t.string   :name, null: false
      t.string   :code, null: false
      t.datetime :deleted_at

      t.timestamps
    end

    add_index :stocks, :code, unique: true
  end
end
class CreateDailyQuotes < ActiveRecord::Migration[6.1]
  def change
    create_table :daily_quotes do |t|
      t.string  :code, null: false
      t.date    :transaction_date, null: false # 收盘日期
      t.bigint  :trade_volume                  # 成交股数
      t.bigint  :number_of_transactions        # 成交笔数
      t.bigint  :trade_price                   # 成交金额
      t.float   :opening_price                 # 开盘价
      t.float   :highest_price                 # 最高价
      t.float   :lowest_price                  # 最低价
      t.float   :closing_price                 # 收盘价
      t.string  :ups_and_downs                 # 涨跌
      t.float   :price_difference              # 价差
      t.float   :last_best_bid_price           # 最後揭示买价
      t.bigint  :last_best_bid_volume          # 最後揭示买量
      t.float   :last_best_ask_price           # 最後揭示卖价
      t.bigint  :last_best_ask_volume          # 最後揭示卖量
      t.float   :price_earning_ratio           # 本益比

      t.timestamps
    end

    add_index :daily_quotes, %i[code transaction_date], unique: true
  end
end
class CreateExStocks < ActiveRecord::Migration[6.1]
  def change
    create_table :ex_stocks do |t|
      t.string  :code,                        null: false
      t.date    :data_date,                   null: false # 资料日期
      t.float   :closing_price_before,        null: false # 除权息前收盘价
      t.float   :reference_price,             null: false # 除权息参考价
      t.float   :dr_value,                    null: false # 权值+息值
      t.integer :dividend_right,              null: false # 权/息
      t.float   :limit_up,                    null: false # 涨停价格
      t.float   :limit_down,                  null: false # 跌停价格
      t.float   :opening_reference_price,     null: false # 开盘竞价基准
      t.float   :ex_dividend_reference_price, null: false # 减除股利参考价
      t.string  :reporting_day                            # 最近一次申报资料 季别/日期
      t.float   :price_book                               # 最近一次申报每股 (单位)净值
      t.float   :eps                                      # 最近一次申报每股 (单位)盈余

      t.timestamps
    end

    add_index :ex_stocks, %i[code data_date], unique: true
  end
end
# app/models/stock.rb

class Stock < ApplicationRecord
  validates :name, :code, presence: true
  validates :code, uniqueness: true

  has_many :daily_quotes,               foreign_key: :code, primary_key: :code, dependent: :destroy
  has_many :exs, class_name: "ExStock", foreign_key: :code, primary_key: :code, dependent: :destroy
  
  scope :latest_transaction_date, -> (date = nil) do
    date = date ? date.to_date : DailyQuote.latest_transaction_date
    self.joins(:daily_quotes).where(daily_quotes: { transaction_date: date })
  end
end
# app/models/daily_quote.rb

class DailyQuote < ApplicationRecord
  acts_as_paranoid

  validates :code, :transaction_date, presence: true
  validates :code, uniqueness: { scope: :transaction_date,
    message: "该收盘日期已有纪录" }

  belongs_to :stock, foreign_key: :code, primary_key: :code

  scope :latest_transaction_date, -> { maximum(:transaction_date) }
end
# app/models/ex_stock.rb

class ExStock < ApplicationRecord
  validates :code, :data_date, :closing_price_before, :reference_price, :dr_value,
            :dividend_right, :limit_up, :limit_down, :opening_reference_price, :ex_dividend_reference_price,
            presence: true
  validates :code, uniqueness: { scope: :data_date, message: "该资料日期已有纪录" }

  belongs_to :stock, foreign_key: :code, primary_key: :code

  DIVIDEND_RIGHT = {
    "息"   => "xd",
    "权"   => "xr",
    "权息" => "dr",
  }

  enum dividend_right: {
    xd: 0, # 除息 (Exclude Dividend)
    xr: 1, # 除权 (Exclude Right)
    dr: 2, # 除权除息(同时) (DR (Dividend + Right))
  }

  scope :latest_data_date, -> { maximum(:data_date) }
end

小结

设计建立好 DB 後,接下来的两篇会示范把前两篇抓下来的资料存到 DB~


铁人赛文章连结:https://ithelp.ithome.com.tw/articles/10272854
medium 文章连结:https://link.medium.com/s8TWR4KuTjb
本文同步发布於 小菜的 Blog https://riverye.com/

备注:之後文章修改更新,以个人部落格为主


<<:  Day19 - vue cli 使用 bootstrap modal

>>:  2021-Day30. The End

[Day4] Jetpack Compose: 要如何让元件和我们来点互动?

知道怎麽构建UI後,我们来学学怎麽跟UI的互动 clickable 最简单的方式就是新增Modifi...

Day10:例外处理,留下来或我跟你走

程序在执行的时候,有些时候我们会遇到一些例外的情况,我们一般会使用 try-catch 来拦截程序执...

Day 19-制作购物车系统之将资料汇入脚本

今天要把前面几天的资料(包括MongoDB连线、产品等)汇入到脚本 以下内容有参考教学影片,底下有附...

[Day 29] 还在吵架的 subgrid

Grid 与 subgrid subgrid 是一种很奇妙的跨维度设定,在 w3c 当中有详细解释。...

Nice day 27 (iphone10s 功能挖掘)-读书辅助器延伸

前言 今天我们就来对昨天的读书辅助装置做个改进,让使用起来更加方便还有顺畅,目前的改善方向应该是对昨...