hashrate 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/hashrate.rb +248 -0
- metadata +79 -0
data/lib/hashrate.rb
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'json'
|
3
|
+
require 'linefit'
|
4
|
+
|
5
|
+
class Hashrate
|
6
|
+
BTC_PER_BLOCK = 25
|
7
|
+
MH = 1e6
|
8
|
+
GH = 1e9
|
9
|
+
TH = 1e12
|
10
|
+
|
11
|
+
# Calculate expected earnings for a bitcoin miner
|
12
|
+
# based on a timespan and hashrate.
|
13
|
+
#
|
14
|
+
# Example (six months ago to now with 100 GH/s):
|
15
|
+
# >> Hashrate.earning(Time.new.to_i - (60 * 60 * 24 * 30 * 6), Time.new.to_i, 1000 * Hashrate::GH)
|
16
|
+
# => 201.08229099734106
|
17
|
+
#
|
18
|
+
# Arguments:
|
19
|
+
# start: starting mining time
|
20
|
+
# stop: stopping mining time
|
21
|
+
# hashrate: rate of mining in hashes per second
|
22
|
+
def self.earning(start, stop, hashrate)
|
23
|
+
# make sure this is loaded before doing anything else
|
24
|
+
# (mmmm, spaghetti)
|
25
|
+
self.get_difficulties
|
26
|
+
|
27
|
+
# ensure start and stop are in the right format and order
|
28
|
+
start, stop = [start, stop].map{|t|
|
29
|
+
# convert datetime to time
|
30
|
+
t = t.to_time if t.respond_to? :to_time
|
31
|
+
|
32
|
+
# convert time to unix timestamp
|
33
|
+
t.to_i
|
34
|
+
}.sort
|
35
|
+
|
36
|
+
difficulty = self.average_difficulty(start, stop)
|
37
|
+
# puts "difficulty: #{difficulty}"
|
38
|
+
# puts "time: #{(stop-start)}"
|
39
|
+
|
40
|
+
# time to find one share between start and stop (in seconds)
|
41
|
+
# with your hashrate
|
42
|
+
time_for_one_share = (difficulty * 2**32 / hashrate)
|
43
|
+
|
44
|
+
# number of shares we'll find in (stop-start) time
|
45
|
+
expected_shares = (stop-start) / time_for_one_share
|
46
|
+
|
47
|
+
# difficulty_time(start, stop) * hashrate * BTC_PER_BLOCK
|
48
|
+
# btc_per_second = average_difficulty(start, stop) * BTC_PER_BLOCK / hashrate
|
49
|
+
|
50
|
+
expected_shares * self::BTC_PER_BLOCK
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def self.get_difficulties
|
56
|
+
# if the data hasn't been fetched
|
57
|
+
# or the last time it was fetched is > 6 hours ago
|
58
|
+
# re-download the data
|
59
|
+
# (best used in Rails production mode when object
|
60
|
+
# instances are persistant)
|
61
|
+
|
62
|
+
if (defined?(@@data)).nil? ||
|
63
|
+
(defined?(@@difficulties)).nil? ||
|
64
|
+
(defined?(@@difficulties_updated)).nil? ||
|
65
|
+
(Time.now.to_i - @@difficulties_updated) > 60 * 60 * 6
|
66
|
+
|
67
|
+
url = "https://blockchain.info/charts/difficulty?showDataPoints=false×pan=all&show_header=true&daysAverageString=1&scale=0&format=json&address="
|
68
|
+
|
69
|
+
# consolidate the json to just the dates the difficulty changed
|
70
|
+
# data = JSON.parse(File.read('difficulties.json'))
|
71
|
+
@@data = JSON.parse(open(url).read)
|
72
|
+
@@difficulties = []
|
73
|
+
@@data.values.first.each{|value|
|
74
|
+
if @@difficulties.empty? || @@difficulties.last["y"] != value["y"]
|
75
|
+
@@difficulties << value
|
76
|
+
end
|
77
|
+
}
|
78
|
+
|
79
|
+
# add 24 months in the future based on the last 12 months
|
80
|
+
@@difficulties += extrapolate(24, 12)
|
81
|
+
|
82
|
+
@@difficulties_updated = Time.now.to_i
|
83
|
+
end
|
84
|
+
|
85
|
+
@@difficulties
|
86
|
+
end
|
87
|
+
|
88
|
+
# the actual or expected (if in the future)
|
89
|
+
# difficulty at the unix timestamp time
|
90
|
+
def self.difficulty(time)
|
91
|
+
self.get_difficulties[difficulty_index(time)]
|
92
|
+
end
|
93
|
+
|
94
|
+
# returns the weighted average difficulty between start and stop
|
95
|
+
def self.average_difficulty(start, stop)
|
96
|
+
# puts "> average_difficulty(#{start}, #{stop})"
|
97
|
+
|
98
|
+
difficulties = self.difficulties_between(start, stop)
|
99
|
+
|
100
|
+
# print "difficulties: "
|
101
|
+
# p difficulties
|
102
|
+
|
103
|
+
total = 0
|
104
|
+
|
105
|
+
0.upto(difficulties.size-2){|i|
|
106
|
+
difficulty = difficulties[i]
|
107
|
+
time = difficulties[i+1]["x"] - difficulty["x"]
|
108
|
+
|
109
|
+
total += difficulty["y"] * time
|
110
|
+
}
|
111
|
+
|
112
|
+
# return the average
|
113
|
+
total / (stop - start)
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def self.extrapolate(months_ahead=6, months_ago=12)
|
118
|
+
# add in expected future values for the next 6 months
|
119
|
+
# based on the last 12 months
|
120
|
+
index = difficulty_index(Time.new.to_i - 60 * 60 * 24 * 30 * months_ago)
|
121
|
+
past = @@difficulties[index..-1]
|
122
|
+
x = past.map{|o| o["x"]}
|
123
|
+
y = past.map{|o| Math.log2 o["y"]}
|
124
|
+
|
125
|
+
lineFit = LineFit.new
|
126
|
+
lineFit.setData(x,y)
|
127
|
+
|
128
|
+
intercept, slope = lineFit.coefficients
|
129
|
+
|
130
|
+
# def y(x)
|
131
|
+
# 2 ** (@slope * x + @intercept)
|
132
|
+
# end
|
133
|
+
|
134
|
+
# {
|
135
|
+
# intercept: intercept,
|
136
|
+
# slope: slope,
|
137
|
+
# rSquared: lineFit.rSquared,
|
138
|
+
# meanSqError: lineFit.meanSqError
|
139
|
+
# }
|
140
|
+
|
141
|
+
# calculate average interval
|
142
|
+
intervals = []
|
143
|
+
0.upto(past.size-2){|i|
|
144
|
+
intervals << past[i+1]["x"] - past[i]["x"]
|
145
|
+
}
|
146
|
+
average_interval = intervals.inject{|a,b| a+b}/intervals.size
|
147
|
+
# puts "average: #{average_interval}"
|
148
|
+
|
149
|
+
difficulties = []
|
150
|
+
|
151
|
+
t = past.last["x"]
|
152
|
+
future_date = t + 60 * 60 * 24 * 30 * months_ahead
|
153
|
+
while t < future_date
|
154
|
+
difficulties << {
|
155
|
+
"x" => t,
|
156
|
+
"y" => 2 ** (slope * t + intercept) # y(t)
|
157
|
+
}
|
158
|
+
t += average_interval
|
159
|
+
end
|
160
|
+
|
161
|
+
difficulties
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.difficulty_index(time)
|
165
|
+
i = -1
|
166
|
+
t = 0
|
167
|
+
while t <= time
|
168
|
+
i += 1
|
169
|
+
t = @@difficulties[i]["x"]
|
170
|
+
end
|
171
|
+
|
172
|
+
i-1
|
173
|
+
end
|
174
|
+
|
175
|
+
# returns a set of points between, and including, start and stop
|
176
|
+
# filling in the values at start and stop so that you could sum the
|
177
|
+
# differences between the x values and it would == stop-start
|
178
|
+
def self.difficulties_between(start, stop)
|
179
|
+
# puts "> difficulties_between(#{start}, #{stop})"
|
180
|
+
|
181
|
+
start_i = self.difficulty_index(start)
|
182
|
+
stop_i = self.difficulty_index(stop)
|
183
|
+
|
184
|
+
# print "start_i: "
|
185
|
+
# p start_i
|
186
|
+
# print "stop_i: "
|
187
|
+
# p stop_i
|
188
|
+
|
189
|
+
difficulties = self.get_difficulties[start_i..stop_i]
|
190
|
+
|
191
|
+
return [] if difficulties.empty?
|
192
|
+
|
193
|
+
# clone the last entry with the `stop` x-value
|
194
|
+
difficulties << {"x" => stop, "y" => difficulties.last["y"]}
|
195
|
+
|
196
|
+
# bump up the first entry to `start`
|
197
|
+
difficulties.first["x"] = start
|
198
|
+
|
199
|
+
difficulties
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
# binding.pry
|
206
|
+
|
207
|
+
# exit
|
208
|
+
|
209
|
+
|
210
|
+
|
211
|
+
# difficulties_between(1231524905+1, 1231524905+86400) == [{"x"=>1231524906, "y"=>1.0}, {"x"=>1231611305, "y"=>1.0}]
|
212
|
+
|
213
|
+
# difficulties_between(1231524905+1, 1262196905 + 1) == [{"x"=>1231524906, "y"=>1.0}, {"x"=>1262196905, "y"=>1.1828995343128408}, {"x"=>1262196906, "y"=>1.1828995343128408}]
|
214
|
+
|
215
|
+
|
216
|
+
|
217
|
+
|
218
|
+
# p average_difficulty(1231524905+1, 1231524905+86400) = 1.0
|
219
|
+
|
220
|
+
# p average_difficulty(1231524905+1, 1262196905 + 1) == 1.0000000059630783
|
221
|
+
|
222
|
+
# p average_difficulty(1231524905+1, 1263320105) == 1.0064611250566535
|
223
|
+
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
# double check here: http://www.bitcoinx.com/profit/
|
228
|
+
# difficulty: 1
|
229
|
+
# BTC/block: 25
|
230
|
+
# hash rate: 1 MH/s
|
231
|
+
# Coins per 24h at these conditions == 502.9142 BTC
|
232
|
+
# p earning(1231524905, 1231524905+86400, 1 * MH) == 502.9141902923584
|
233
|
+
|
234
|
+
|
235
|
+
# t = @difficulties[-2]["x"]
|
236
|
+
|
237
|
+
# t = 1287339305
|
238
|
+
# p Hashrate.earning(t - (60 * 60 * 24 * 30), t - 1, 1000 * Hashrate::GH)
|
239
|
+
|
240
|
+
# binding.pry
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
# should be 502.9142, I think?
|
245
|
+
|
246
|
+
# binding.pry
|
247
|
+
|
248
|
+
# Time.at(seconds_since_epoc_integer).to_datetime
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hashrate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christian Genco
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-01-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>'
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.7'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>'
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.7'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: linefit
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.3.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.3.1
|
46
|
+
description: A calculator for expected bitcoin mining profit based on the future difficulty
|
47
|
+
of the blockchain
|
48
|
+
email: christian.genco@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- lib/hashrate.rb
|
54
|
+
homepage: https://github.com/christiangenco/hashrate
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.24
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Bitcoin mining profit calculator
|
79
|
+
test_files: []
|