Initial commit
This commit is contained in:
389
app/models/trade.rb
Normal file
389
app/models/trade.rb
Normal file
@@ -0,0 +1,389 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'peatio/influxdb'
|
||||
class Trade < ApplicationRecord
|
||||
# == Constants ============================================================
|
||||
|
||||
extend Enumerize
|
||||
ZERO = '0.0'.to_d
|
||||
|
||||
# == Relationships ========================================================
|
||||
|
||||
belongs_to :market, required: true
|
||||
belongs_to :maker_order, class_name: 'Order', foreign_key: :maker_order_id, required: true
|
||||
belongs_to :taker_order, class_name: 'Order', foreign_key: :taker_order_id, required: true
|
||||
belongs_to :maker, class_name: 'Member', foreign_key: :maker_id, required: true
|
||||
belongs_to :taker, class_name: 'Member', foreign_key: :taker_id, required: true
|
||||
has_many :bonus
|
||||
# == Validations ==========================================================
|
||||
|
||||
validates :price, :amount, :total, numericality: { greater_than_or_equal_to: 0.to_d }
|
||||
|
||||
# == Scopes ===============================================================
|
||||
|
||||
scope :h24, -> { where('created_at > ?', 24.hours.ago) }
|
||||
scope :with_market, ->(market) { where(market_id: market) }
|
||||
scope :last_month, -> { where('created_at > ?', 1.month.ago) }
|
||||
|
||||
# == Callbacks ============================================================
|
||||
|
||||
after_validation(on: :create) do
|
||||
# Set taker type before creation
|
||||
self.taker_type = taker_order&.side
|
||||
end
|
||||
|
||||
after_commit on: :create do
|
||||
EventAPI.notify ['market', market_id, 'trade_completed'].join('.'), \
|
||||
Serializers::EventAPI::TradeCompleted.call(self)
|
||||
end
|
||||
before_save :rial_total!
|
||||
# before_create :rial_total!
|
||||
|
||||
# == Class Methods ========================================================
|
||||
|
||||
class << self
|
||||
def to_csv
|
||||
attributes = %w[id price amount maker_order_id taker_order_id market_id maker_id taker_id total created_at updated_at]
|
||||
CSV.generate(headers: true) do |csv|
|
||||
csv << attributes
|
||||
|
||||
all.each do |trade|
|
||||
data = attributes[0...-2].map { |attr| trade.send(attr) }
|
||||
data += attributes[-2..-1].map { |attr| trade.send(attr).iso8601 }
|
||||
csv << data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def public_from_influx(market, limit = 100, options = {})
|
||||
trades_query = ['SELECT id, price, amount, total, taker_type, market, created_at FROM trades WHERE market=%{market}']
|
||||
trades_query << 'AND taker_type=%{type}' if options[:type].present?
|
||||
trades_query << 'AND created_at>=%{start_time}' if options[:start_time].present?
|
||||
trades_query << 'AND created_at<=%{end_time}' if options[:end_time].present?
|
||||
trades_query << 'AND price=%{price_eq}' if options[:price_eq].present?
|
||||
trades_query << 'AND price>=%{price_gt}' if options[:price_gt].present?
|
||||
trades_query << 'AND price=%{price_lt}' if options[:price_lt].present?
|
||||
trades_query << 'ORDER BY desc'
|
||||
|
||||
unless limit.to_i.zero?
|
||||
trades_query << 'LIMIT %{limit}'
|
||||
options.merge!(limit: limit)
|
||||
end
|
||||
Peatio::InfluxDB.client(keyshard: market).query trades_query.join(' '), params: options.merge(market: market) do |_name, _tags, points|
|
||||
return points.map(&:deep_symbolize_keys!)
|
||||
end
|
||||
end
|
||||
|
||||
# Low, High, First, Last, sum total (amount * price), sum 24 hours amount and average 24 hours price calculated using VWAP ratio for 24 hours trades
|
||||
def market_ticker_from_influx(market)
|
||||
tickers_query = 'SELECT MIN(price), MAX(price), FIRST(price), LAST(price), SUM(total) AS volume, SUM(amount) AS amount, SUM(total) / SUM(amount) AS vwap FROM trades WHERE market=%{market} AND time > now() - 24h'
|
||||
Peatio::InfluxDB.client(keyshard: market).query tickers_query, params: { market: market } do |_name, _tags, points|
|
||||
return points.map(&:deep_symbolize_keys!).first
|
||||
end
|
||||
end
|
||||
|
||||
def trade_from_influx_before_date(market, date)
|
||||
trades_query = 'SELECT id, price, amount, total, taker_type, market, created_at FROM trades WHERE market=%{market} AND created_at < %{date} ORDER BY DESC LIMIT 1 '
|
||||
Peatio::InfluxDB.client(keyshard: market).query trades_query, params: { market: market, date: date.to_i } do |_name, _tags, points|
|
||||
return points.map(&:deep_symbolize_keys!).first
|
||||
end
|
||||
end
|
||||
|
||||
def trade_from_influx_after_date(market, date)
|
||||
trades_query = 'SELECT id, price, amount, total, taker_type, market, created_at FROM trades WHERE market=%{market} AND created_at >= %{date} ORDER BY ASC LIMIT 1 '
|
||||
Peatio::InfluxDB.client(keyshard: market).query trades_query, params: { market: market, date: date.to_i } do |_name, _tags, points|
|
||||
return points.map(&:deep_symbolize_keys!).first
|
||||
end
|
||||
end
|
||||
|
||||
def nearest_trade_from_influx(market, date)
|
||||
res = trade_from_influx_before_date(market, date)
|
||||
res.blank? ? trade_from_influx_after_date(market, date) : res
|
||||
end
|
||||
end
|
||||
|
||||
# == Instance Methods =====================================================
|
||||
|
||||
def irt_price
|
||||
rls / total
|
||||
end
|
||||
|
||||
def rial_total!
|
||||
return self.rls = total if market.quote.code == 'irt'
|
||||
|
||||
tether_market = Market.find_by(quote: 'irt', base: 'usdt')
|
||||
tether_price = tether_market.price_now
|
||||
self.rls = tether_price * self.total
|
||||
end
|
||||
|
||||
def order_fee(order)
|
||||
maker_order_id == order.id ? order.maker_fee : order.taker_fee
|
||||
end
|
||||
|
||||
def side(member)
|
||||
return unless member
|
||||
|
||||
order_for_member(member).side
|
||||
end
|
||||
|
||||
def order_for_member(member)
|
||||
return unless member
|
||||
|
||||
if member.id == maker_id
|
||||
maker_order
|
||||
elsif member.id == taker_id
|
||||
taker_order
|
||||
end
|
||||
end
|
||||
|
||||
def sell_order
|
||||
[maker_order, taker_order].find { |o| o.side == 'sell' }
|
||||
end
|
||||
|
||||
def buy_order
|
||||
[maker_order, taker_order].find { |o| o.side == 'buy' }
|
||||
end
|
||||
|
||||
def trigger_event
|
||||
::AMQP::Queue.enqueue_event("private", maker.uid, "trade", for_notify(maker))
|
||||
::AMQP::Queue.enqueue_event("private", taker.uid, "trade", for_notify(taker))
|
||||
::AMQP::Queue.enqueue_event("public", market.id, "trades", {trades: [for_global]})
|
||||
end
|
||||
|
||||
def for_notify(member = nil)
|
||||
{ id: id,
|
||||
price: price.to_s || ZERO,
|
||||
amount: amount.to_s || ZERO,
|
||||
total: total.to_s || ZERO,
|
||||
market: market.id,
|
||||
side: side(member),
|
||||
taker_type: taker_type,
|
||||
created_at: created_at.to_i,
|
||||
order_id: order_for_member(member).id }
|
||||
end
|
||||
|
||||
def for_global
|
||||
{ tid: id,
|
||||
taker_type: taker_type,
|
||||
date: created_at.to_i,
|
||||
price: price.to_s || ZERO,
|
||||
amount: amount.to_s || ZERO }
|
||||
end
|
||||
|
||||
def record_complete_operations!
|
||||
transaction do
|
||||
|
||||
record_liability_debit!
|
||||
record_liability_credit!
|
||||
record_liability_transfer!
|
||||
record_revenues!
|
||||
end
|
||||
end
|
||||
|
||||
def revert_trade!
|
||||
transaction do
|
||||
revert_sell_side!
|
||||
revert_buy_side!
|
||||
revert_fees!
|
||||
end
|
||||
end
|
||||
|
||||
def influx_data
|
||||
{ values: { id: id,
|
||||
price: price,
|
||||
amount: amount,
|
||||
total: total,
|
||||
taker_type: taker_type,
|
||||
created_at: created_at.to_i },
|
||||
tags: { market: market.id } }
|
||||
end
|
||||
|
||||
def write_to_influx
|
||||
Peatio::InfluxDB.client(keyshard: market_id).write_point(self.class.table_name, influx_data, "ns")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def record_liability_debit!
|
||||
seller_outcome = amount
|
||||
buyer_outcome = total
|
||||
|
||||
# Debit locked fiat/crypto Liability account for member who created ask.
|
||||
Operations::Liability.debit!(
|
||||
amount: seller_outcome,
|
||||
currency: sell_order.outcome_currency,
|
||||
reference: self,
|
||||
kind: :locked,
|
||||
member_id: sell_order.member_id,
|
||||
)
|
||||
# Debit locked fiat/crypto Liability account for member who created bid.
|
||||
Operations::Liability.debit!(
|
||||
amount: buyer_outcome,
|
||||
currency: buy_order.outcome_currency,
|
||||
reference: self,
|
||||
kind: :locked,
|
||||
member_id: buy_order.member_id,
|
||||
)
|
||||
end
|
||||
|
||||
def record_liability_credit!
|
||||
seller_income = total - total * order_fee(sell_order)
|
||||
buyer_income = amount - amount * order_fee(buy_order)
|
||||
|
||||
# Credit main fiat/crypto Liability account for member who created ask.
|
||||
Operations::Liability.credit!(
|
||||
amount: buyer_income,
|
||||
currency: buy_order.income_currency,
|
||||
reference: self,
|
||||
kind: :main,
|
||||
member_id: buy_order.member_id
|
||||
)
|
||||
|
||||
# Credit main fiat/crypto Liability account for member who created bid.
|
||||
Operations::Liability.credit!(
|
||||
amount: seller_income,
|
||||
currency: sell_order.income_currency,
|
||||
reference: self,
|
||||
kind: :main,
|
||||
member_id: sell_order.member_id
|
||||
)
|
||||
end
|
||||
|
||||
def record_liability_transfer!
|
||||
# Unlock unused funds.
|
||||
[maker_order, taker_order].each do |order|
|
||||
if order.volume.zero? && !order.locked.zero?
|
||||
Operations::Liability.transfer!(
|
||||
amount: order.locked,
|
||||
currency: order.outcome_currency,
|
||||
reference: self,
|
||||
from_kind: :locked,
|
||||
to_kind: :main,
|
||||
member_id: order.member_id
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def record_revenues!
|
||||
seller_fee = total * order_fee(sell_order)
|
||||
buyer_fee = amount * order_fee(buy_order)
|
||||
|
||||
# Credit main fiat/crypto Revenue account.
|
||||
Operations::Revenue.credit!(
|
||||
amount: seller_fee,
|
||||
currency: sell_order.income_currency,
|
||||
reference: self,
|
||||
member_id: sell_order.member_id
|
||||
)
|
||||
|
||||
# Credit main fiat/crypto Revenue account.
|
||||
Operations::Revenue.credit!(
|
||||
amount: buyer_fee,
|
||||
currency: buy_order.income_currency,
|
||||
reference: self,
|
||||
member_id: buy_order.member_id
|
||||
)
|
||||
end
|
||||
|
||||
def revert_sell_side!
|
||||
seller_outcome = amount
|
||||
seller_income = total - total * order_fee(sell_order)
|
||||
|
||||
# Revert Trade for Sell side
|
||||
# Debit main fiat/crypto Liability account for member who created bid.
|
||||
Operations::Liability.debit!(
|
||||
amount: seller_income,
|
||||
currency: sell_order.income_currency,
|
||||
reference: self,
|
||||
kind: :main,
|
||||
member_id: sell_order.member_id
|
||||
)
|
||||
Account.find_by(currency_id: sell_order.income_currency.id, member_id: sell_order.member_id).sub_funds(seller_income)
|
||||
|
||||
# Credit main fiat/crypto Liability account for member who created ask.
|
||||
Operations::Liability.credit!(
|
||||
amount: seller_outcome,
|
||||
currency: sell_order.outcome_currency,
|
||||
reference: self,
|
||||
kind: :main,
|
||||
member_id: sell_order.member_id
|
||||
)
|
||||
Account.find_by(currency_id: sell_order.outcome_currency.id, member_id: sell_order.member_id).plus_funds(seller_outcome)
|
||||
end
|
||||
|
||||
def revert_buy_side!
|
||||
buyer_outcome = total
|
||||
buyer_income = amount - amount * order_fee(buy_order)
|
||||
|
||||
# Revert Trade for Buy side
|
||||
# Debit main fiat/crypto Liability account for member who created ask
|
||||
Operations::Liability.debit!(
|
||||
amount: buyer_income,
|
||||
currency: buy_order.income_currency,
|
||||
reference: self,
|
||||
kind: :main,
|
||||
member_id: buy_order.member_id
|
||||
)
|
||||
Account.find_by(currency_id: buy_order.income_currency.id, member_id: buy_order.member_id).sub_funds(buyer_income)
|
||||
|
||||
# Credit main fiat/crypto Liability account for member who created bid.
|
||||
Operations::Liability.credit!(
|
||||
amount: buyer_outcome,
|
||||
currency: buy_order.outcome_currency,
|
||||
reference: self,
|
||||
kind: :main,
|
||||
member_id: buy_order.member_id
|
||||
)
|
||||
Account.find_by(currency_id: buy_order.outcome_currency.id, member_id: buy_order.member_id).plus_funds(buyer_outcome)
|
||||
end
|
||||
|
||||
def revert_fees!
|
||||
seller_fee = total * order_fee(sell_order)
|
||||
buyer_fee = amount * order_fee(buy_order)
|
||||
|
||||
# Revert Revenues
|
||||
Operations::Revenue.debit!(
|
||||
amount: seller_fee,
|
||||
currency: sell_order.income_currency,
|
||||
reference: self,
|
||||
member_id: sell_order.member_id
|
||||
)
|
||||
|
||||
Operations::Revenue.debit!(
|
||||
amount: buyer_fee,
|
||||
currency: buy_order.income_currency,
|
||||
reference: self,
|
||||
member_id: buy_order.member_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
# Schema version: 20210120133912
|
||||
#
|
||||
# Table name: trades
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# price :decimal(32, 16) not null
|
||||
# amount :decimal(32, 16) not null
|
||||
# total :decimal(32, 16) default(0.0), not null
|
||||
# maker_order_id :integer not null
|
||||
# taker_order_id :integer not null
|
||||
# market_id :string(20) not null
|
||||
# maker_id :integer not null
|
||||
# taker_id :integer not null
|
||||
# taker_type :string(20) default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_trades_on_created_at (created_at)
|
||||
# index_trades_on_maker_id (maker_id)
|
||||
# index_trades_on_maker_order_id (maker_order_id)
|
||||
# index_trades_on_market_id_and_created_at (market_id,created_at)
|
||||
# index_trades_on_taker_id (taker_id)
|
||||
# index_trades_on_taker_order_id (taker_order_id)
|
||||
#
|
||||
Reference in New Issue
Block a user