ostium-python-sdk 0.1.3__py3-none-any.whl
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.
- ostium_python_sdk/__init__.py +4 -0
- ostium_python_sdk/abi.py +4071 -0
- ostium_python_sdk/balance.py +54 -0
- ostium_python_sdk/config.py +33 -0
- ostium_python_sdk/constants.py +11 -0
- ostium_python_sdk/formulae.py +352 -0
- ostium_python_sdk/formulae_wrapper.py +294 -0
- ostium_python_sdk/ostium.py +320 -0
- ostium_python_sdk/price.py +37 -0
- ostium_python_sdk/sdk.py +33 -0
- ostium_python_sdk/subgraph.py +187 -0
- ostium_python_sdk/utils.py +604 -0
- ostium_python_sdk-0.1.3.dist-info/METADATA +93 -0
- ostium_python_sdk-0.1.3.dist-info/RECORD +16 -0
- ostium_python_sdk-0.1.3.dist-info/WHEEL +5 -0
- ostium_python_sdk-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
import time
|
|
4
|
+
from .abi import usdc_abi
|
|
5
|
+
from web3 import Web3
|
|
6
|
+
|
|
7
|
+
REFRESH_BALANCE_SECONDS_INTERVAL = 60 * 5
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Balance:
|
|
11
|
+
def __init__(self, w3: Web3, usdc_address: str) -> None:
|
|
12
|
+
self.web3 = w3
|
|
13
|
+
self.usdc_address = usdc_address
|
|
14
|
+
self.usdc_contract = self.web3.eth.contract(
|
|
15
|
+
address=self.usdc_address, abi=usdc_abi)
|
|
16
|
+
# Format: {address: {'ether': value, 'usdc': value, 'last_refresh': timestamp}}
|
|
17
|
+
self.balances = {}
|
|
18
|
+
|
|
19
|
+
def get_balance(self, address, refresh=False):
|
|
20
|
+
if address not in self.balances:
|
|
21
|
+
self.balances[address] = {'ether': None,
|
|
22
|
+
'usdc': None, 'last_refresh': None}
|
|
23
|
+
|
|
24
|
+
balance_info = self.balances[address]
|
|
25
|
+
if balance_info['last_refresh'] is None:
|
|
26
|
+
too_old = True
|
|
27
|
+
else:
|
|
28
|
+
too_old = time.time() - \
|
|
29
|
+
balance_info['last_refresh'] > REFRESH_BALANCE_SECONDS_INTERVAL
|
|
30
|
+
|
|
31
|
+
if (refresh or too_old or balance_info['ether'] is None or balance_info['usdc'] is None):
|
|
32
|
+
self.read_balances(address)
|
|
33
|
+
|
|
34
|
+
return self.balances[address]['ether'], self.balances[address]['usdc']
|
|
35
|
+
|
|
36
|
+
def read_balances(self, address):
|
|
37
|
+
# print(f'actual [{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] {address} - reading balances')
|
|
38
|
+
start_time = time.time()
|
|
39
|
+
self.balances[address] = {
|
|
40
|
+
'ether': Decimal(self.get_ether_balance(address)),
|
|
41
|
+
'usdc': Decimal(self.get_usdc_balance(address)),
|
|
42
|
+
'last_refresh': start_time
|
|
43
|
+
}
|
|
44
|
+
end_time = time.time()
|
|
45
|
+
|
|
46
|
+
def get_usdc_balance(self, address):
|
|
47
|
+
balance = self.usdc_contract.functions.balanceOf(address).call()
|
|
48
|
+
balance = Web3.to_wei(balance, 'szabo')
|
|
49
|
+
balance = Web3.from_wei(balance, 'ether')
|
|
50
|
+
return balance
|
|
51
|
+
|
|
52
|
+
def get_ether_balance(self, address):
|
|
53
|
+
ret = Web3.from_wei(self.web3.eth.get_balance(address), 'ether')
|
|
54
|
+
return ret
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class NetworkConfig:
|
|
7
|
+
rpc_url: str
|
|
8
|
+
graph_url: str
|
|
9
|
+
contracts: Dict[str, str]
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def mainnet(cls) -> 'NetworkConfig':
|
|
13
|
+
return cls(
|
|
14
|
+
rpc_url="https://arb-mainnet.g.alchemy.com/v2/-dfuyiAmKg9seY_LJcy61q60GZELrNhX",
|
|
15
|
+
graph_url="https://subgraph.satsuma-prod.com/391a61815d32/ostium/ost-prod/api",
|
|
16
|
+
contracts={
|
|
17
|
+
"usdc": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
18
|
+
"trading": "0x6D0bA1f9996DBD8885827e1b2e8f6593e7702411",
|
|
19
|
+
"tradingStorage": "0xcCd5891083A8acD2074690F65d3024E7D13d66E7"
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def testnet(cls) -> 'NetworkConfig':
|
|
25
|
+
return cls(
|
|
26
|
+
rpc_url="https://arb-sepolia.g.alchemy.com/v2/xussaWLkWxKtZjCIlUjAYiMGV6_dRO-8",
|
|
27
|
+
graph_url="https://subgraph.satsuma-prod.com/391a61815d32/ostium/ost-sep-final/api",
|
|
28
|
+
contracts={
|
|
29
|
+
"usdc": "0xe73B11Fb1e3eeEe8AF2a23079A4410Fe1B370548",
|
|
30
|
+
"trading": "0x2A9B9c988393f46a2537B0ff11E98c2C15a95afe",
|
|
31
|
+
"tradingStorage": "0x0b9F5243B29938668c9Cfbd7557A389EC7Ef88b8"
|
|
32
|
+
}
|
|
33
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
# Constants
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
PRECISION_2 = Decimal('100')
|
|
6
|
+
MAX_PROFIT_P = Decimal('900') # 900% * PRECISION_6
|
|
7
|
+
MAX_STOP_LOSS_P = Decimal('85')
|
|
8
|
+
PRECISION_6 = Decimal('1000000')
|
|
9
|
+
PRECISION_12 = Decimal('1000000000000')
|
|
10
|
+
PRECISION_18 = Decimal('1000000000000000000')
|
|
11
|
+
LIQ_THRESHOLD_P = Decimal('90') # -90% (of collateral)
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from .constants import MAX_PROFIT_P, MAX_STOP_LOSS_P, PRECISION_2, PRECISION_6, PRECISION_12, PRECISION_18, LIQ_THRESHOLD_P
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This is a copy-cat of formulae repo originally written in TypeScript
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# GetStopLossPrice and GetTakeProfitPrice are consolidated into one function.
|
|
10
|
+
# bool is_tp is True if we want to calculate the take profit price, False if we want to calculate the stop loss price
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def GetTakeProfitPrice(is_tp: bool, open_price: Decimal, leverage: Decimal, long: bool, profit_p: Decimal) -> Decimal:
|
|
15
|
+
"""
|
|
16
|
+
Calculate the take profit / stop loss price based on desired profit percentage (aka: MAX_PROFIT_P=900 or MAX_STOP_LOSS_P=85).
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
open_price (Decimal): The opening price of the trade
|
|
20
|
+
profit_p (Decimal): The desired profit percentage
|
|
21
|
+
leverage (Decimal): The leverage amount
|
|
22
|
+
long (bool): Whether this is a long position
|
|
23
|
+
profit_p (Decimal): The desired profit percentage - 900% for Tp or 85% for SL
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
str: The Tp / SL price as a string
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
open_price = Decimal(open_price)
|
|
30
|
+
profit_p = Decimal(profit_p)
|
|
31
|
+
leverage = Decimal(leverage)
|
|
32
|
+
|
|
33
|
+
price_diff = (open_price * profit_p) / (leverage * Decimal('100'))
|
|
34
|
+
|
|
35
|
+
if (is_tp):
|
|
36
|
+
price = open_price + price_diff if long else open_price - price_diff
|
|
37
|
+
else:
|
|
38
|
+
price = open_price - price_diff if long else open_price + price_diff
|
|
39
|
+
|
|
40
|
+
return Decimal(price if price > 0 else '0')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def CurrentTradeProfitP(open_price: str, current_price: str, long: bool, leverage: str) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Calculate the current trade profit percentage.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
open_price (str): The opening price of the trade
|
|
49
|
+
current_price (str): The current price
|
|
50
|
+
long (bool): Whether this is a long position
|
|
51
|
+
leverage (str): The leverage amount
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
str: The profit percentage as a string
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
|
|
58
|
+
open_price_d = Decimal(open_price)
|
|
59
|
+
current_price_d = Decimal(current_price)
|
|
60
|
+
leverage_d = Decimal(leverage)
|
|
61
|
+
|
|
62
|
+
if long:
|
|
63
|
+
price_diff = current_price_d - open_price_d
|
|
64
|
+
else:
|
|
65
|
+
price_diff = open_price_d - current_price_d
|
|
66
|
+
|
|
67
|
+
profit_p = (price_diff / open_price_d) * (leverage_d)
|
|
68
|
+
|
|
69
|
+
if profit_p > MAX_PROFIT_P:
|
|
70
|
+
return (MAX_PROFIT_P)
|
|
71
|
+
else:
|
|
72
|
+
return (profit_p)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
return str(e)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def GetTradeLiquidationPrice(
|
|
78
|
+
open_price: str,
|
|
79
|
+
long: bool,
|
|
80
|
+
collateral: str,
|
|
81
|
+
leverage: str,
|
|
82
|
+
rollover_fee: str,
|
|
83
|
+
funding_fee: str
|
|
84
|
+
) -> Decimal:
|
|
85
|
+
try:
|
|
86
|
+
open_price = Decimal(open_price)
|
|
87
|
+
collateral = Decimal(collateral)
|
|
88
|
+
rollover_fee = Decimal(rollover_fee)
|
|
89
|
+
funding_fee = Decimal(funding_fee)
|
|
90
|
+
leverage = Decimal(leverage)
|
|
91
|
+
|
|
92
|
+
liq_price_distance = (
|
|
93
|
+
open_price *
|
|
94
|
+
(collateral * LIQ_THRESHOLD_P / 100 - rollover_fee - funding_fee) /
|
|
95
|
+
collateral /
|
|
96
|
+
leverage
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# print('# liq_price_distance', liq_price_distance)
|
|
100
|
+
liq_price = open_price - liq_price_distance if long else open_price + liq_price_distance
|
|
101
|
+
|
|
102
|
+
liq_price = liq_price if liq_price > 0 else 0
|
|
103
|
+
# print('---> liq_price', liq_price)
|
|
104
|
+
return liq_price
|
|
105
|
+
|
|
106
|
+
except Exception as error:
|
|
107
|
+
raise Exception(f"Unable to compute Liquidation Price: {error}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def GetCurrentRolloverFee(
|
|
111
|
+
acc_rollover: str,
|
|
112
|
+
last_rollover_block: str,
|
|
113
|
+
rollover_fee_per_block: str,
|
|
114
|
+
latest_block: str
|
|
115
|
+
) -> Decimal:
|
|
116
|
+
try:
|
|
117
|
+
acc_rollover = Decimal(acc_rollover)
|
|
118
|
+
last_rollover_block = Decimal(last_rollover_block)
|
|
119
|
+
rollover_fee_per_block = Decimal(rollover_fee_per_block)
|
|
120
|
+
latest_block = Decimal(latest_block)
|
|
121
|
+
current_fee = acc_rollover + \
|
|
122
|
+
(latest_block - last_rollover_block) * rollover_fee_per_block
|
|
123
|
+
return current_fee
|
|
124
|
+
|
|
125
|
+
except Exception as error:
|
|
126
|
+
raise Exception(f"Unable to compute Current Rollover Fee: {error}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def GetTradeRolloverFee(
|
|
130
|
+
trade_rollover: str,
|
|
131
|
+
current_rollover: str,
|
|
132
|
+
collateral: str,
|
|
133
|
+
leverage: str
|
|
134
|
+
) -> Decimal:
|
|
135
|
+
try:
|
|
136
|
+
current_rollover = Decimal(current_rollover)
|
|
137
|
+
trade_rollover = Decimal(trade_rollover)
|
|
138
|
+
collateral = Decimal(collateral)
|
|
139
|
+
leverage = Decimal(leverage)
|
|
140
|
+
|
|
141
|
+
rollover_fee = (
|
|
142
|
+
(current_rollover - trade_rollover) *
|
|
143
|
+
collateral *
|
|
144
|
+
leverage /
|
|
145
|
+
PRECISION_18 /
|
|
146
|
+
PRECISION_2
|
|
147
|
+
)
|
|
148
|
+
return rollover_fee
|
|
149
|
+
|
|
150
|
+
except Exception as error:
|
|
151
|
+
raise Exception(f"Unable to compute Trade Rollover Fee: {error}")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def GetTradeFundingFee(
|
|
155
|
+
trade_funding: str,
|
|
156
|
+
current_funding: str,
|
|
157
|
+
collateral: str,
|
|
158
|
+
leverage: str
|
|
159
|
+
) -> Decimal:
|
|
160
|
+
try:
|
|
161
|
+
current_funding = Decimal(current_funding)
|
|
162
|
+
trade_funding = Decimal(trade_funding)
|
|
163
|
+
collateral = Decimal(collateral)
|
|
164
|
+
leverage = Decimal(leverage)
|
|
165
|
+
|
|
166
|
+
funding_fee = (
|
|
167
|
+
(current_funding - trade_funding) *
|
|
168
|
+
collateral *
|
|
169
|
+
leverage /
|
|
170
|
+
PRECISION_18 /
|
|
171
|
+
PRECISION_2
|
|
172
|
+
)
|
|
173
|
+
return funding_fee
|
|
174
|
+
|
|
175
|
+
except Exception as error:
|
|
176
|
+
raise Exception(f"Unable to compute Trade Funding Fee: {error}")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def GetFundingRate(
|
|
180
|
+
acc_funding_long: str,
|
|
181
|
+
acc_funding_short: str,
|
|
182
|
+
last_funding_rate: str,
|
|
183
|
+
last_funding_velocity: str,
|
|
184
|
+
max_funding_fee_per_block: str,
|
|
185
|
+
last_funding_block: str,
|
|
186
|
+
latest_block: str,
|
|
187
|
+
long_oi: str,
|
|
188
|
+
short_oi: str
|
|
189
|
+
) -> dict:
|
|
190
|
+
try:
|
|
191
|
+
acc_funding_long = Decimal(acc_funding_long)
|
|
192
|
+
acc_funding_short = Decimal(acc_funding_short)
|
|
193
|
+
last_funding_rate = Decimal(last_funding_rate)
|
|
194
|
+
last_funding_velocity = Decimal(last_funding_velocity)
|
|
195
|
+
max_funding_fee_per_block = Decimal(max_funding_fee_per_block)
|
|
196
|
+
last_funding_block = Decimal(last_funding_block)
|
|
197
|
+
latest_block = Decimal(latest_block)
|
|
198
|
+
long_oi = Decimal(long_oi)
|
|
199
|
+
short_oi = Decimal(short_oi)
|
|
200
|
+
|
|
201
|
+
block_diff = latest_block - last_funding_block
|
|
202
|
+
|
|
203
|
+
# Calculate skew
|
|
204
|
+
total_oi = long_oi + short_oi
|
|
205
|
+
skew = Decimal('0')
|
|
206
|
+
if total_oi > 0:
|
|
207
|
+
skew = (long_oi - short_oi) / total_oi
|
|
208
|
+
|
|
209
|
+
# Calculate funding rate
|
|
210
|
+
funding_rate = last_funding_rate + last_funding_velocity * block_diff
|
|
211
|
+
|
|
212
|
+
# Cap funding rate
|
|
213
|
+
if funding_rate > max_funding_fee_per_block:
|
|
214
|
+
funding_rate = max_funding_fee_per_block
|
|
215
|
+
elif funding_rate < -max_funding_fee_per_block:
|
|
216
|
+
funding_rate = -max_funding_fee_per_block
|
|
217
|
+
|
|
218
|
+
# Calculate accumulated funding
|
|
219
|
+
funding_long = acc_funding_long + funding_rate * block_diff
|
|
220
|
+
funding_short = acc_funding_short - funding_rate * block_diff
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
'accFundingLong': str(funding_long),
|
|
224
|
+
'accFundingShort': str(funding_short),
|
|
225
|
+
'skew': str(skew)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
except Exception as error:
|
|
229
|
+
raise Exception(f"Unable to compute Funding Rate: {error}")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def GetPriceImpact(
|
|
233
|
+
mid_price: str,
|
|
234
|
+
bid_price: str,
|
|
235
|
+
ask_price: str,
|
|
236
|
+
spread_p: str,
|
|
237
|
+
is_limit: bool,
|
|
238
|
+
is_buy: bool,
|
|
239
|
+
is_close: bool,
|
|
240
|
+
trade_size: str,
|
|
241
|
+
trade_size_ref: str
|
|
242
|
+
) -> dict:
|
|
243
|
+
try:
|
|
244
|
+
mid_price = Decimal(mid_price)
|
|
245
|
+
bid_price = Decimal(bid_price)
|
|
246
|
+
ask_price = Decimal(ask_price)
|
|
247
|
+
spread_p = Decimal(spread_p)
|
|
248
|
+
trade_size = Decimal(trade_size)
|
|
249
|
+
trade_size_ref = Decimal(trade_size_ref)
|
|
250
|
+
|
|
251
|
+
# Calculate base spread
|
|
252
|
+
base_spread = (ask_price - bid_price) / mid_price
|
|
253
|
+
|
|
254
|
+
# Calculate impact spread
|
|
255
|
+
impact_spread = base_spread * (trade_size / trade_size_ref)
|
|
256
|
+
|
|
257
|
+
# Calculate price after impact
|
|
258
|
+
price_after_impact = mid_price
|
|
259
|
+
if not is_limit:
|
|
260
|
+
if is_buy:
|
|
261
|
+
price_after_impact = mid_price * \
|
|
262
|
+
(Decimal('1') + impact_spread / Decimal('2'))
|
|
263
|
+
else:
|
|
264
|
+
price_after_impact = mid_price * \
|
|
265
|
+
(Decimal('1') - impact_spread / Decimal('2'))
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
'priceAfterImpact': str(price_after_impact),
|
|
269
|
+
'impactSpread': str(impact_spread)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
except Exception as error:
|
|
273
|
+
raise Exception(f"Unable to compute Price Impact: {error}")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def CurrentTradeProfitRaw(
|
|
277
|
+
open_price: str,
|
|
278
|
+
current_price: str,
|
|
279
|
+
is_buy: bool,
|
|
280
|
+
leverage: str,
|
|
281
|
+
collateral: str
|
|
282
|
+
) -> str:
|
|
283
|
+
try:
|
|
284
|
+
open_price = Decimal(open_price)
|
|
285
|
+
current_price = Decimal(current_price)
|
|
286
|
+
leverage = Decimal(leverage)
|
|
287
|
+
collateral = Decimal(collateral)
|
|
288
|
+
|
|
289
|
+
# Calculate price difference based on position direction
|
|
290
|
+
if is_buy:
|
|
291
|
+
price_diff = current_price - open_price
|
|
292
|
+
else:
|
|
293
|
+
price_diff = open_price - current_price
|
|
294
|
+
|
|
295
|
+
# Calculate profit
|
|
296
|
+
profit = (price_diff * collateral * leverage) / \
|
|
297
|
+
(open_price * PRECISION_2)
|
|
298
|
+
|
|
299
|
+
return str(profit)
|
|
300
|
+
|
|
301
|
+
except Exception as error:
|
|
302
|
+
raise Exception(f"Unable to compute Current Trade Profit Raw: {error}")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def CurrentTotalProfitRaw(
|
|
306
|
+
open_price: str,
|
|
307
|
+
current_price: str,
|
|
308
|
+
is_buy: bool,
|
|
309
|
+
leverage: str,
|
|
310
|
+
collateral: str,
|
|
311
|
+
rollover_fee: str,
|
|
312
|
+
funding_fee: str
|
|
313
|
+
) -> str:
|
|
314
|
+
try:
|
|
315
|
+
# Get trade profit
|
|
316
|
+
trade_profit = Decimal(CurrentTradeProfitRaw(
|
|
317
|
+
open_price,
|
|
318
|
+
current_price,
|
|
319
|
+
is_buy,
|
|
320
|
+
leverage,
|
|
321
|
+
collateral
|
|
322
|
+
))
|
|
323
|
+
|
|
324
|
+
# Subtract fees
|
|
325
|
+
total_profit = trade_profit - \
|
|
326
|
+
Decimal(rollover_fee) - Decimal(funding_fee)
|
|
327
|
+
|
|
328
|
+
return str(total_profit)
|
|
329
|
+
|
|
330
|
+
except Exception as error:
|
|
331
|
+
raise Exception(f"Unable to compute Current Total Profit Raw: {error}")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def CurrentTotalProfitP(total_profit: str, collateral: str) -> str:
|
|
335
|
+
try:
|
|
336
|
+
total_profit = Decimal(total_profit)
|
|
337
|
+
collateral = Decimal(collateral)
|
|
338
|
+
|
|
339
|
+
# Calculate profit percentage
|
|
340
|
+
profit_percentage = (total_profit * PRECISION_6) / collateral
|
|
341
|
+
|
|
342
|
+
# Cap at MAX_PROFIT_P if needed
|
|
343
|
+
if profit_percentage > MAX_PROFIT_P:
|
|
344
|
+
return str(MAX_PROFIT_P)
|
|
345
|
+
|
|
346
|
+
return str(profit_percentage)
|
|
347
|
+
|
|
348
|
+
except Exception as error:
|
|
349
|
+
raise Exception(
|
|
350
|
+
f"Unable to compute Current Total Profit Percentage: {error}")
|
|
351
|
+
|
|
352
|
+
# given desired TP percentage, like 35, 50, 75, 100, 500 and 900 which is max: gives you the TP price
|