-
Notifications
You must be signed in to change notification settings - Fork 0
/
calculate.py
356 lines (309 loc) · 13.4 KB
/
calculate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
#!/usr/bin/env python3
import click
import configparser
import pprint
import readchar
import requests
import signal
import sys
import time
import json
from pathlib import Path
from nicehash import nicehash
# A cheap way to lookup the values for wtm profitability requests
with open("algo_query_map.json", "r") as f:
query_map = json.load(f)
def sighandler(signum, frame):
sys.exit(0)
def get_nh_data(algorithm):
nh_algos = (
requests.get("https://api2.nicehash.com/main/api/v2/mining/algorithms")
.json()
.get("miningAlgorithms")
)
algo_positions = [i.get("algorithm") for i in nh_algos]
req_data = []
for algo in algorithm:
algo = algo.upper()
req_data.append(nh_algos[algo_positions.index(algo)])
return req_data
def get_nh_wtm_data(algorithm, coin_filter):
query = query_map.get(algorithm.lower()).get("url")
raw_data = (
requests.get(query).json().get(query_map.get(algorithm.lower()).get("key"))
)
coins = list(raw_data.keys())
rev_sum = 0.0
if "abcxyzfakecoin123" in coin_filter:
return float(raw_data.get(coins[0]).get("btc_revenue24"))
for i in coin_filter:
for c in coins:
if i.upper() in raw_data.get(c).get("tag"):
rev_sum += float(raw_data.get(c).get("btc_revenue24"))
return rev_sum
def get_optimal(algo):
market = "USA"
if (
market
not in requests.get(
f"https://api2.nicehash.com/main/api/v2/hashpower/orderBook?algorithm={algo}"
)
.json()
.get("stats")
.keys()
):
market = "EU"
return (
requests.get(
f"https://api2.nicehash.com/main/api/v2/hashpower/order/price?market={market}&algorithm={algo}"
)
.json()
.get("price")
)
def change_order(private_api, order_id, new_price, order_limit, order_algo, nh_algos):
try:
private_api.set_price_and_limit_hashpower_order(
order_id, new_price, order_limit, order_algo, nh_algos
)
except Exception as e:
e = str(e)
signal.signal(signal.SIGINT, sighandler)
@click.command()
@click.option(
"--algorithm", "-a", "algorithm", default=None, multiple=True, required=False
)
@click.option(
"--coin", "-c", "coin", required=False, default=["abcxyzfakecoin123"], multiple=True
)
# The way we're implementing the watch flag feels like a hack
# It is an optional arg that, if not provided, has a value of 0 which is interpreted as "don't loop"
# But if you provide the -w flag it then gets a "default" value of 5 unless you provide your own integer
# after the flag (eg. `-w` would use a value of 5 and `-w 10` would use a value of 10.)
@click.option("--watch", "-w", is_flag=False, flag_value=5, default=0)
@click.option("--config", "config_file", required=False, default="./config.ini")
@click.option("--manage", "-m", is_flag=True, required=False)
@click.option("--list", "-l", "algo_list", is_flag=True, required=False)
def run(algorithm, coin, watch, config_file, manage, algo_list):
if algo_list:
click.secho("Supported Algorithms", bold=True)
[click.echo(f" - {i}") for i in list(query_map.keys())]
sys.exit(0)
# Check config path exists
config = Path(config_file)
if not config.is_file():
click.secho(f"{config} does not exist.", bold=True, fg="red")
sys.exit(1)
# Parse the config since we know it exists
config = configparser.ConfigParser()
config.read(config_file)
# Create private API interface
# Also, I hate the nicehash python library. No auth validation on the client instance, smh
# After we make `private_api` we won't know if the credentials are good until the first time we use it
# And even then we'll only get a 404 so like... idgaf, just make sure your stuff is right in the config
private_api = nicehash.private_api(
config["DEFAULT"]["host"],
config["DEFAULT"]["organization_id"],
config["DEFAULT"]["key"],
config["DEFAULT"]["secret"],
)
managed_orders = config.sections()
rounds_out_of_profit = 0
seconds_with_work = 0
seconds_without_work = 0
# Artificially slow down the rate of raising our order price to be more in-line with the speed at which we can
# lower it again. This should help smooth out spikes but we might need to make this a user parameter in the future
without_work_threshold = 120
# Nicehash only allows us to lower the price once every 10 minutes
cooldown = 0
while True:
out_width_array = []
# Get the Nicehash data about the algorithms requested
nh_data = get_nh_data(algorithm)
# Loop through the returned algo data and process each algo
for i in nh_data:
# Get the optimal order price for our algo and cast it to a float
optimal = float(get_optimal(i.get("algorithm")))
marketFactor = i.get("displayMarketFactor")
# Get the current profitability from WTM for the same algo and cast it to a float too
if coin is None:
coin = "abcxyzfakecoin123"
wtm_profitability = round(
float(get_nh_wtm_data(i.get("algorithm"), coin)), 4
)
# Print the optimal, profitability
msg = f"Optimal: {optimal}"
out_width_array.append(len(msg))
print(msg)
msg = f"Profit/{marketFactor}: {wtm_profitability}"
out_width_array.append(len(msg))
print(msg)
# Calculate Expected profit percentage and print that too
perc_profit = round((1 - (optimal / wtm_profitability)) * 100, 2)
if perc_profit > 0.0:
color = "green"
rounds_out_of_profit = 0
else:
color = "red"
rounds_out_of_profit += 1
msg = f"Theoretical Profit: {perc_profit}%"
if manage:
print(str(rounds_out_of_profit * watch) + " seconds out of profit")
out_width_array.append(len(msg))
click.secho(msg, fg=color)
if len(managed_orders) > 0 and manage:
for order_id in managed_orders:
if not config.getboolean(order_id, "manage"):
continue
order_details = private_api.request(
# Get the order details (requires manual request using private_api)
"GET",
f"/main/api/v2/hashpower/order/{order_id}",
"",
"",
)
# Further sanity check that the order is active
if order_details.get("status").get("code") != "ACTIVE":
continue
order_price = float(order_details.get("price"))
order_algo = order_details.get("algorithm").get("algorithm")
order_limit = order_details.get("limit")
msg = f"Current Order Price: {order_price}"
out_width_array.append(len(msg))
print(msg)
exp_perc_profit = round(
(1 - (order_price / wtm_profitability)) * 100, 2
)
msg = f"Expected Profit: {exp_perc_profit}%"
out_width_array.append(len(msg))
color = "green" if exp_perc_profit > 0 else "red"
click.secho(msg, fg=color)
nh_algos = requests.get(
"https://api2.nicehash.com/main/api/v2/mining/algorithms"
).json()
buy_info = requests.get(
"https://api2.nicehash.com/main/api/v2/public/buy/info"
).json()
for i in buy_info.get("miningAlgorithms"):
if i.get("name").upper() == order_algo:
step = abs(float(i.get("down_step")))
break
accepted_speed = float(order_details.get("acceptedCurrentSpeed"))
requested_speed = float(order_limit)
if requested_speed == 0:
requested_speed = float(
private_api.get_hashpower_orderbook(order_algo)
.get("stats")
.get("USA")
.get("totalSpeed")
)
# Check if order is lower than optimal and lower than profitability or we have no accepted work
# Raise the price to be closer to optimal if it's below it
if (
(
(optimal - (optimal * 0.02))
<= order_price
<= (optimal + (optimal * 0.02))
and accepted_speed < (requested_speed / 2)
)
or (
accepted_speed < (requested_speed / 2)
and seconds_without_work >= without_work_threshold
)
) and order_price < wtm_profitability:
new_price = round(order_price + step, 4)
if new_price > wtm_profitability:
new_price = round(wtm_profitability, 4)
msg = f"Calculated new price: {new_price}"
out_width_array.append(len(msg))
print(msg)
change_order(
private_api,
order_id,
new_price,
order_limit,
order_algo,
nh_algos,
)
# If the order price goes above the profitable, try to lower it and start a counter to cancel it (not yet implemented)
if (
order_price > wtm_profitability
or order_price > optimal
and cooldown <= 0
):
try:
private_api.set_price_and_limit_hashpower_order(
order_id,
round(order_price - step, 4),
order_limit,
order_algo,
nh_algos,
)
except Exception as e:
cooldown = int(str(e).split(" ")[-1].split('"')[0]) + 1
out_width_array.append(len(msg))
# If we have an order that's in profit but have waited the "without_work_threshold"
# duration and still have no accepted work, raise the price
if (
order_price < wtm_profitability
and perc_profit + 1 <= exp_perc_profit >= perc_profit + 1
):
new_price = round(order_price + step, 4)
msg = f"Calculated new price: {new_price}"
out_width_array.append(len(msg))
print(msg)
if new_price < wtm_profitability:
change_order(
private_api,
order_id,
new_price,
order_limit,
order_algo,
nh_algos,
)
seconds_without_work = 0
# If we have workers but our price is higher than optimal lower it by, at most, the value of $step.
# If $step were to take us below optimal, lower it to the optimal but never lower.
if (
order_price < wtm_profitability
and perc_profit - 1 <= exp_perc_profit >= perc_profit + 1
and cooldown <= 0
):
new_price = round(order_price - step, 4)
if new_price < optimal:
new_price = optimal
msg = f"Calculated new price: {new_price}"
out_width_array.append(len(msg))
print(msg)
if new_price < wtm_profitability:
change_order(
private_api,
order_id,
new_price,
order_limit,
order_algo,
nh_algos,
)
seconds_with_work = 0
cooldown = 600
usage_percentage = round((accepted_speed / requested_speed) * 100, 2)
print(f"Currently using {usage_percentage}% of requested work limit")
if usage_percentage < 25:
seconds_without_work += watch
seconds_with_work = 0
print(
f"{seconds_without_work} seconds since we've had at least half our requested limit, will increase order price in {without_work_threshold - seconds_without_work} seconds"
)
else:
seconds_without_work = 0
seconds_with_work += watch
print(f"Work accepted for {seconds_with_work} seconds at current price")
cooldown = cooldown if cooldown >= 0 else 0
print(f"Cooldown Remaining: {cooldown}s")
cooldown -= watch
print("-" * max(out_width_array))
if watch:
time.sleep(watch)
if watch == 0:
signal.raise_signal(signal.SIGINT)
if __name__ == "__main__":
run()