Skip to content

Commit 912faaa

Browse files
committed
add api error recognition
1 parent edd5706 commit 912faaa

8 files changed

+131
-53
lines changed

setup.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
'three_commas.api',
1010
'three_commas.api.ver1',
1111
'three_commas.api.v2',
12-
'three_commas.model'],
13-
version='0.0.24',
12+
'three_commas.model',
13+
'three_commas.utils'],
14+
version='0.0.26',
1415
description='Python api wrapper for 3commas with extended functionality in the api, models, error handling',
1516
url='https://github.com/badass-blockchain/python-three-commas',
1617
author='Sergey Gerodes',

src/three_commas/error.py

+41-21
Original file line numberDiff line numberDiff line change
@@ -20,51 +20,71 @@ class BoToSmallError:
2020
EXTRACT_PY3CW_MESSAGE_PATTERN = re.compile(r"Other error occurred: record_invalid Invalid parameters (\{.*\})\.", re.IGNORECASE)
2121
BOT_WAS_DELETED_ERROR_PATTERN = re.compile(r"Other error occurred: Not found None None", re.IGNORECASE)
2222
BOT_DID_NOT_EXISTED_OR_BELONGS_TO_OTHER_ACCOUNT_ERROR_PATTERN = re.compile(r"Other error occurred: not_found Not Found None", re.IGNORECASE)
23+
API_KEY_NOT_ENOUGH_PERMISSION_PATTERN = re.compile(r"access_denied Api key doesn't have enough permissions", re.IGNORECASE)
24+
API_KEY_INVALID_OR_EXPIRED_PATTERN = re.compile(r'api_key_invalid_or_expired Unauthorized. Invalid or expired api key', re.IGNORECASE)
2325

2426
def __init__(self, error):
2527
self.error: dict = error
26-
self.error_parsed: dict = None
2728
try:
2829
if self._has_error_message():
29-
match = ThreeCommasError.EXTRACT_PY3CW_MESSAGE_PATTERN.findall(error.get('msg'))
30+
match = ThreeCommasError.EXTRACT_PY3CW_MESSAGE_PATTERN.findall(self.get_msg())
3031
if match:
3132
self.error_parsed = eval(match[0])
3233
except:
3334
logger.warning(f'Failed to parse inner error msg {error}')
3435

35-
def bo_to_small_error(self) -> List[BoToSmallError]:
36-
ret = list()
37-
# ret = ThreeCommasError.BoToSmallError(present=False)
38-
if self._has_parsed_error_message() and self.error_parsed.get('base_order_volume'):
39-
# ret.present = True
40-
for sub_message in self.error_parsed.get('base_order_volume'):
41-
bo_min_match = ThreeCommasError.BO_TO_SMALL_ERROR_PATTERN.findall(sub_message)
42-
if bo_min_match:
43-
amount = float(bo_min_match[0][0])
44-
pair = bo_min_match[0][1] or None
45-
ret.append(ThreeCommasError.BoToSmallError(amount=amount, pair=pair))
46-
#ret.matches.append((amount, pair))
47-
# ret.amount.append(float(bo_min_match[0][0]))
48-
# ret.pair.append(bo_min_match[0][1] or None)
36+
def is_api_key_has_no_permission_error(self):
37+
return self._has_error_message() and self.API_KEY_NOT_ENOUGH_PERMISSION_PATTERN.findall(self.get_msg())
4938

50-
return ret
39+
def is_api_key_invalid_or_expired(self):
40+
return self._has_error_message() and self.API_KEY_INVALID_OR_EXPIRED_PATTERN.findall(self.get_msg())
41+
42+
def is_bo_to_small_error(self):
43+
return self._has_error_message() and self.BO_TO_SMALL_ERROR_PATTERN.findall(self.get_msg())
44+
45+
def is_not_found_error(self):
46+
return self._has_error_message() and 'not_found' in self.get_msg() or 'Not found' in self.get_msg()
5147

5248
def is_no_market_pair_error(self) -> List[str]:
49+
return self._has_error_message() and self.NO_MARKET_PAIR_ERROR_PATTERN.findall(self.get_msg())
50+
51+
def get_no_market_pair_error(self) -> List[str]:
5352
if self._has_error_message():
5453
pairs_to_remove = ThreeCommasError.NO_MARKET_PAIR_ERROR_PATTERN.findall(self.error.get('msg'))
5554
if pairs_to_remove:
5655
return pairs_to_remove
56+
return list()
5757

58-
def is_not_found_error(self):
59-
return self._has_error_message() and 'not_found' in self.error.get('msg') or 'Not found' in self.error.get('msg')
58+
def get_bo_to_small_error(self) -> List[BoToSmallError]:
59+
ret = list()
60+
if self._has_error_message():
61+
try:
62+
match = ThreeCommasError.EXTRACT_PY3CW_MESSAGE_PATTERN.findall(self.get_msg())
63+
if match:
64+
error_parsed = eval(match[0])
65+
else:
66+
return list()
67+
except:
68+
return list()
69+
if error_parsed.get('base_order_volume'):
70+
for sub_message in error_parsed.get('base_order_volume'):
71+
bo_min_match = ThreeCommasError.BO_TO_SMALL_ERROR_PATTERN.findall(sub_message)
72+
if bo_min_match:
73+
amount = float(bo_min_match[0][0])
74+
pair = bo_min_match[0][1] or None
75+
ret.append(ThreeCommasError.BoToSmallError(amount=amount, pair=pair))
76+
return ret
6077

6178
def _has_error_message(self):
62-
return self.error and self.error.get('msg')
79+
return self.error and self.get_msg()
6380

6481
def _has_parsed_error_message(self):
6582
return self.error_parsed
6683

84+
def get_msg(self):
85+
return self.error.get('msg')
86+
6787
def __repr__(self):
68-
return f'{self.__class__}({self.error})'
88+
return f'{self.__class__.__name__}({self.error})'
6989

7090

src/three_commas/utils/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .bot_utils import *
2+
from .pairs_utils import *

src/three_commas/utils/bot_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from src.three_commas.model import Bot
2+
from ..model import Bot
33
from typing import List, Union
44

55

src/three_commas/utils/pairs_utils.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from typing import List
2+
3+
def get_base_from_3c_pair(tc_pair: str, account_market_code: str = None) -> str:
4+
if account_market_code is None or account_market_code in {'ftx', 'binance', 'paper_trading'}:
5+
return tc_pair.split('_')[1].upper()
6+
elif account_market_code in {'ftx_futures'}:
7+
return tc_pair.split('_')[1].split('-')[0]
8+
else:
9+
raise RuntimeError(f'Not known market code {account_market_code} in get_base_from_3c_pair')
10+
11+
12+
def filter_market_pairs_with_quote(market_pairs: List[str], quote: str):
13+
return [pair for pair in market_pairs if pair.upper().startswith(quote.upper())]
14+
15+
16+
def pair_is_quote(tc_pair: str, quote: str) -> bool:
17+
return get_quote_from_3c_pair(tc_pair).upper() == quote.upper()
18+
19+
20+
def get_quote_from_3c_pair(tc_pair: str) -> str:
21+
return tc_pair.split('_')[0].upper()
22+
23+
24+
def map_spot_tc_pairs_to_bases(tc_pairs: list, account_market_code: str) -> list:
25+
return list(map(lambda pair: get_base_from_3c_pair(tc_pair=pair, account_market_code=account_market_code), tc_pairs))
26+
27+
28+
def construct_pair_from_quote_and_base(quote: str, base: str) -> str:
29+
return f"{quote.upper()}_{base.upper()}"
30+
31+
32+
def filter_tc_pairs_by_quote(pairs: list, quote: str) -> list:
33+
return list(filter(lambda pair: pair_is_quote(tc_pair=pair, quote=quote), pairs))
34+
35+
36+
def construct_futures_pair_from_base(base: str, account_market_code: str) -> str:
37+
if account_market_code in {'ftx_futures'}:
38+
return f'USD_{base.upper()}-PERP'
39+
else:
40+
raise RuntimeError(f'Not known market code {account_market_code} in construct_futures_pair_from_quote_and_base')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"error": true,
3+
"msg": "Other error occurred: access_denied Api key doesn't have enough permissions None."
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"error": true,
3+
"msg": "Other error occurred: api_key_invalid_or_expired Unauthorized. Invalid or expired api key. None."
4+
}

test/test_three_commas_errors.py

+37-29
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,58 @@
22
import json
33

44

5-
def test_bo_to_small_tc_error_with_pair():
6-
file_path = 'test/sample_data/errors/bo_too_small_with_pair.json'
5+
def read_error(file_path) -> ThreeCommasError:
76
with open(file_path, 'r+') as f:
87
error = json.loads(f.read())
98
error_model = ThreeCommasError(error)
9+
return error_model
10+
1011

11-
bo_error = error_model.bo_to_small_error()
12-
assert len(bo_error) == 1
13-
assert bo_error[0].amount == 33.35
14-
assert bo_error[0].pair == 'USDT_YFI'
12+
def test_bo_to_small_tc_error_with_pair():
13+
error = read_error('test/sample_data/errors/bo_too_small_with_pair.json')
14+
15+
assert error.is_bo_to_small_error()
16+
bo_error = error.get_bo_to_small_error()
17+
assert len(bo_error) == 1
18+
assert bo_error[0].amount == 33.35
19+
assert bo_error[0].pair == 'USDT_YFI'
1520

1621

1722
def test_multiple_bo_error():
18-
file_path = 'test/sample_data/errors/multiple_bo_so_errors.json'
19-
with open(file_path, 'r+') as f:
20-
error = json.loads(f.read())
21-
error_model = ThreeCommasError(error)
23+
error = read_error('test/sample_data/errors/multiple_bo_so_errors.json')
2224

23-
bo_error = error_model.bo_to_small_error()
24-
assert len(bo_error) == 4
25-
assert set(map(lambda be: be.pair, bo_error)) == {'USDT_1INCH', 'USDT_AAVE', 'USDT_ACM', 'USDT_ADA'}
26-
assert list(map(lambda be: be.amount, bo_error)) == [10.0, 10.0, 10.0, 10.0]
25+
bo_error = error.get_bo_to_small_error()
26+
assert len(bo_error) == 4
27+
assert set(map(lambda be: be.pair, bo_error)) == {'USDT_1INCH', 'USDT_AAVE', 'USDT_ACM', 'USDT_ADA'}
28+
assert list(map(lambda be: be.amount, bo_error)) == [10.0, 10.0, 10.0, 10.0]
2729

2830

2931
def test_bo_to_small_tc_error_no_pair():
30-
file_path = 'test/sample_data/errors/bo_too_small_no_pair.json'
31-
with open(file_path, 'r+') as f:
32-
error = json.loads(f.read())
33-
error_model = ThreeCommasError(error)
32+
error = read_error('test/sample_data/errors/bo_too_small_no_pair.json')
3433

35-
bo_error = error_model.bo_to_small_error()
36-
assert bo_error[0].amount == 9.4674
37-
assert not bo_error[0].pair
34+
bo_error = error.get_bo_to_small_error()
35+
assert bo_error[0].amount == 9.4674
36+
assert not bo_error[0].pair
3837

3938

4039
def test_no_bo_error():
41-
file_path = 'test/sample_data/errors/signature_invalid.json'
42-
with open(file_path, 'r+') as f:
43-
error = json.loads(f.read())
44-
error_model = ThreeCommasError(error)
40+
error = read_error('test/sample_data/errors/signature_invalid.json')
4541

46-
bo_error = error_model.bo_to_small_error()
47-
assert len(bo_error) == 0
42+
bo_error = error.get_bo_to_small_error()
43+
assert len(bo_error) == 0
44+
assert not error.is_bo_to_small_error()
4845

49-
error_model = ThreeCommasError({'custom_message': 'some error occured'})
50-
bo_error = error_model.bo_to_small_error()
46+
error = ThreeCommasError({'custom_message': 'some error occured'})
47+
bo_error = error.get_bo_to_small_error()
5148
assert len(bo_error) == 0
49+
50+
51+
def test_api_key_invalid_or_expired():
52+
error = read_error('test/sample_data/errors/api_key_invalid_or_expired_error.json')
53+
assert error.is_api_key_invalid_or_expired()
54+
55+
56+
def test_api_key_has_no_permission_error():
57+
error = read_error('test/sample_data/errors/api_key_has_no_permission_error.json')
58+
assert error.is_api_key_has_no_permission_error()
59+

0 commit comments

Comments
 (0)