Skip to content

Commit

Permalink
Implement spot transactions brtr/coin_elite#730
Browse files Browse the repository at this point in the history
  • Loading branch information
pan-xiong committed Oct 5, 2023
1 parent 354f121 commit b500c20
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 8 deletions.
19 changes: 17 additions & 2 deletions app/controllers/origin_transactions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def index
@page_index = 8
sort = params[:sort].presence || "event_time"
sort_type = params[:sort_type].presence || "desc"
txs = OriginTransaction.available.where('user_id is null and event_time >= ?', DateTime.parse('2023-01-01')).order("#{sort} #{sort_type}")
txs = OriginTransaction.available.year_to_date.where(user_id: nil).order("#{sort} #{sort_type}")
@total_txs = txs
@symbol = params[:search]
@campaign = params[:campaign]
Expand All @@ -24,7 +24,7 @@ def users
sort = params[:sort].presence || "event_time"
sort_type = params[:sort_type].presence || "desc"
@symbol = params[:search]
txs = OriginTransaction.available.where(user_id: current_user.id).order("#{sort} #{sort_type}")
txs = OriginTransaction.available.year_to_date.where(user_id: current_user.id).order("#{sort} #{sort_type}")
@total_txs = txs
txs = txs.where(campaign: params[:campaign]) if params[:campaign].present?
txs = txs.where(source: params[:source]) if params[:source].present?
Expand Down Expand Up @@ -83,8 +83,23 @@ def add_key
redirect_to users_origin_transactions_path
end

def revenue_chart
@page_index = 34
infos = TransactionsSnapshotInfo.where(event_date: [period_date..Date.yesterday]).order(event_date: :asc)
@records = infos.map do |info|
{info.event_date => info.total_revenue}
end.inject(:merge)
end

private
def tx_params
params.require(:origin_transaction).permit(:campaign)
end

def period_date
case params[:period]
when "quarter" then Date.today.last_quarter.to_date
else Date.today.last_month.to_date
end
end
end
18 changes: 16 additions & 2 deletions app/models/origin_transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,33 @@ def self.available
OriginTransaction.where('(from_symbol IN (?) AND amount >= 50) OR from_symbol NOT IN (?)', SKIP_SYMBOLS, SKIP_SYMBOLS)
end

def self.year_to_date
OriginTransaction.where('event_time >= ?', DateTime.parse('2023-01-01'))
end

def self.total_summary(user_id=nil)
records = OriginTransaction.available
profit_records = records.select{|r| r.revenue > 0}
loss_records = records.select{|r| r.revenue < 0}
total_cost = calculate_field(records, :amount)
total_estimated_revenue = records.where(trade_type: 'buy').sum(&:revenue)
total_roi = total_cost.zero? ? 0 : total_estimated_revenue / total_cost
date = Date.yesterday
infos = TransactionsSnapshotInfo.includes(:snapshot_records).where("event_date <= ?", date)

{
profit_count: profit_records.count,
profit_amount: calculate_field(profit_records),
loss_count: loss_records.count,
loss_amount: calculate_field(loss_records),
total_cost: calculate_field(records, :amount),
total_cost: total_cost,
total_revenue: records.where(trade_type: 'sell').sum(&:revenue),
total_estimated_revenue: records.where(trade_type: 'buy').sum(&:revenue)
total_estimated_revenue: total_estimated_revenue,
total_roi: total_roi,
max_profit: infos.max_profit(user_id: user_id),
max_profit_date: $redis.get("user_#{user_id}_#{date.to_s}_spots_max_profit_date"),
max_loss: infos.max_loss(user_id: user_id),
max_loss_date: $redis.get("user_#{user_id}_#{date.to_s}_spots_max_loss_date"),
}
end

Expand Down
42 changes: 42 additions & 0 deletions app/models/transactions_snapshot_info.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
class TransactionsSnapshotInfo < ApplicationRecord
has_many :snapshot_records, class_name: 'TransactionsSnapshotRecord', foreign_key: :transactions_snapshot_info_id

def total_profit
snapshot_records.available.year_to_date.profit.sum(&:revenue)
end

def total_loss
snapshot_records.available.year_to_date.loss.sum(&:revenue)
end

def total_revenue
snapshot_records.available.year_to_date.where(trade_type: 'buy').sum(&:revenue)
end

def self.max_profit(user_id: nil, date: Date.yesterday)
redis_key = "user_#{user_id}_#{date.to_s}_spots_max_profit"
total_profit = $redis.get(redis_key).to_f
if total_profit == 0
infos = TransactionsSnapshotInfo.joins(:snapshot_records)
max_profit = infos.max {|a, b| a.total_profit <=> b.total_profit}
if max_profit
total_profit = max_profit.total_profit
$redis.set(redis_key, total_profit, ex: 10.hours)
$redis.set("#{redis_key}_date", max_profit.event_date.strftime("%Y-%m-%d"), ex: 10.hours)
end
end
total_profit
end

def self.max_loss(user_id: nil, date: Date.yesterday)
redis_key = "user_#{user_id}_#{date.to_s}_spots_max_loss"
total_loss = $redis.get(redis_key).to_f
if total_loss == 0
infos = TransactionsSnapshotInfo.joins(:snapshot_records)
max_loss = infos.min {|a, b| a.total_loss <=> b.total_loss}
if max_loss
total_loss = max_loss.total_loss
$redis.set(redis_key, total_loss, ex: 10.hours)
$redis.set("#{redis_key}_date", max_loss.event_date.strftime("%Y-%m-%d"), ex: 10.hours)
end
end
total_loss
end
end
15 changes: 14 additions & 1 deletion app/models/transactions_snapshot_record.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
class TransactionsSnapshotRecord < ApplicationRecord
SKIP_SYMBOLS = %w(USDC BTC ETH).freeze

belongs_to :snapshot_info, class_name: 'TransactionsSnapshotInfo', foreign_key: :transactions_snapshot_info_id

scope :profit, -> { where("revenue > 0") }
scope :loss, -> { where("revenue < 0") }

def self.available
TransactionsSnapshotRecord.where('(from_symbol IN (?) AND amount >= 50) OR from_symbol NOT IN (?)', SKIP_SYMBOLS, SKIP_SYMBOLS)
end

def self.year_to_date
TransactionsSnapshotRecord.where('event_time >= ?', DateTime.parse('2023-01-01'))
end

def self.total_summary
records = TransactionsSnapshotRecord.where(trade_type: 'buy')
records = TransactionsSnapshotRecord.available.year_to_date.where(trade_type: 'buy')
result = $redis.get("origin_transactions_snapshots_total_summary")
if result.nil?
profit_records = records.select{|r| r.revenue > 0}
Expand Down
5 changes: 4 additions & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
</ul>
</li>
<li class="nav-item dropdown">
<button class="nav-link btn dropdown-toggle <%= 'active' if @page_index.in?([8, 9, 10, 22, 23]) %>" type="button" id="dropdownMenuTradingHistory" data-bs-toggle="dropdown" data-bs-display="static" aria-expanded="false">
<button class="nav-link btn dropdown-toggle <%= 'active' if @page_index.in?([8, 9, 10, 22, 23, 34]) %>" type="button" id="dropdownMenuTradingHistory" data-bs-toggle="dropdown" data-bs-display="static" aria-expanded="false">
现货交易记录
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuTradingHistory">
Expand All @@ -69,6 +69,9 @@
<li class="nav-item">
<%= link_to "原始交易记录快照", transactions_snapshot_infos_path, class: "nav-link #{@page_index == 10 ? 'active' : ''}" %>
</li>
<li class="nav-item">
<%= link_to "现货收益曲线图", revenue_chart_origin_transactions_path, class: "nav-link #{@page_index == 34 ? 'active' : ''}" %>
</li>
<li class="nav-item">
<%= link_to "合并交易记录列表", combine_transactions_path, class: "nav-link #{@page_index == 9 ? 'active' : ''}" %>
</li>
Expand Down
48 changes: 48 additions & 0 deletions app/views/origin_transactions/_total_revenue_chart.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<div class="mt-3">
<canvas id="revenueChart" style="height: 300px; width: 100%" data-data="<%= @records.to_json %>" ></canvas>
</div>

<script>
document.addEventListener('turbolinks:load', () => {
var canvas = document.getElementById('revenueChart');
if(canvas){
var ctx = canvas.getContext('2d');
var chart_data = JSON.parse(ctx.canvas.dataset.data)
const result = Object.values(chart_data)
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: Object.keys(chart_data),
datasets: [
{
label: "绝对收益走势图(每天)",
data: result,
tension: 0.4,
backgroundColor: value => {
const rate = Object.values(value)[3];
return rate > 0 ? 'rgb(54, 162, 235)' : 'rgb(255, 99, 132)'
},
yAxisID: 'y'
},
],
},
options: {
scales: {
x: {
grid: {
display: false,
}
},
y: {
position: 'left',
ticks: {
maxTicksLimit: 5
},
},
},
responsive: false
}
});
}
})
</script>
3 changes: 2 additions & 1 deletion app/views/origin_transactions/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@
</div>
<div>
<div class="p-3">
<p>总投入: <%= @total_summary[:total_cost].to_f.round(4) %> / 总盈利: <%= @total_summary[:total_estimated_revenue].to_f.round(4) %> </p>
<p>总投入: <%= @total_summary[:total_cost].to_f.round(4) %> / 总盈利: <%= @total_summary[:total_estimated_revenue].to_f.round(4) %> / 总ROI: <%= ((@total_summary[:total_roi]) * 100).round(4) %>%</p>
<p>盈利总数量: <%= @total_summary[:profit_count] %> / 盈利总金额: <%= @total_summary[:profit_amount].to_f.round(4) %> </p>
<p>亏损总数量: <%= @total_summary[:loss_count] %> / 亏损总金额: <%= @total_summary[:loss_amount].to_f.round(4) %> </p>
<p>历史最高盈利: <%= @total_summary[:max_profit].round(4) %> ( <%= @total_summary[:max_profit_date] %> ) / 历史最高亏损: <%= @total_summary[:max_loss].round(4) %> ( <%= @total_summary[:max_loss_date] %> )</p>
</div>
</div>
<div id="trading-histories-container">
Expand Down
20 changes: 20 additions & 0 deletions app/views/origin_transactions/revenue_chart.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div class="m-3 row" id="analysis_page">
<h2>
现货收益走势图
</h2>
<div class="mt-5">
<%= form_tag revenue_chart_origin_transactions_path, id: "period_targets", method: "GET", class: "row" do %>
<div class="btn-group" role="group" aria-label="Basic radio toggle button group" style="margin-left:3rem;width:33%;">
<%= radio_button_tag :period, "quarter", params[:period] == "quarter", class: "btn-check", id: "btnradio1", onclick: "this.form.submit();" %>
<label class="btn btn-outline-primary quarter" for="btnradio1">三个月</label>
<%= radio_button_tag :period, "month", params[:period] == "month", class: "btn-check", id: "btnradio2", onclick: "this.form.submit();" %>
<label class="btn btn-outline-primary month" for="btnradio2">一个月</label>
</div>
<% end %>
</div>
<div class="chart mb-5">
<div>
<%= render partial: "total_revenue_chart" %>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
get :refresh
get :users
post :add_key
get :revenue_chart
end
end

Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/origin_transactions_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
end

describe "GET users" do
before { 10.times{ create(:origin_transaction, user_id: user.id) } }
before { 10.times{ create(:origin_transaction, user_id: user.id, event_time: Time.current - 1.day) } }

it "renders a successful response" do
get :users
Expand Down

0 comments on commit b500c20

Please sign in to comment.