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.
Files changed (2) hide show
  1. data/lib/hashrate.rb +248 -0
  2. metadata +79 -0
@@ -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&timespan=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: []