Skip to content

Commit 693ac35

Browse files
author
DUYN
committed
Add delete tickers functionality
1 parent 67920ea commit 693ac35

File tree

6 files changed

+164
-58
lines changed

6 files changed

+164
-58
lines changed

bot/bot.py

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import Any, Dict
44

5+
from bot.data import remove_ticker, get_tickers, add_ticker, get_company_profile
56
from bot import __version__, OperationalException
67
from bot.data.data_provider_manager import DataProviderManager
78
from bot.data.data_providers import DataProviderException
@@ -14,9 +15,7 @@ class Bot:
1415
def __init__(self, config: Dict[str, Any]) -> None:
1516
logger.info('Starting bot version %s', __version__)
1617

17-
# Init objects
18-
19-
# Make variable private, only bot should change them
18+
# Make variables private, only bot should change them
2019
self.__config = config
2120
self.__data_provider_manager = DataProviderManager(self.config)
2221

@@ -28,11 +27,54 @@ def set_config(self, config: Dict[str, Any]):
2827
self.__config = config
2928

3029
def add_ticker(self, ticker: str) -> None:
30+
logger.info("Adding ticker ...")
31+
32+
if not get_company_profile(ticker, self.config):
33+
profile = self.__data_provider_manager.get_profile(ticker)
34+
35+
if not profile:
36+
raise OperationalException("Could not evaluate {} with the data providers".format(ticker))
37+
38+
company_name = profile.get('profile', {}).get('companyName', None)
39+
category = profile.get('profile', {}).get('industry', None)
40+
41+
if not company_name:
42+
raise OperationalException("Could not evaluate company name for ticker {} with the data providers")
43+
44+
if not company_name:
45+
raise OperationalException("Could not evaluate category for ticker {} with the data providers")
46+
47+
try:
48+
add_ticker(
49+
ticker,
50+
company_name=company_name,
51+
category=category,
52+
config=self.config
53+
)
54+
except Exception:
55+
raise OperationalException(
56+
"Something went wrong with adding ticker {} to the registry".format(ticker)
57+
)
58+
else:
59+
raise OperationalException(
60+
"Ticker {} is already present in registry".format(ticker)
61+
)
62+
63+
def remove_ticker(self, ticker: str) -> None:
64+
logger.info("Removing ticker ...")
65+
66+
if get_company_profile(ticker, self.config):
67+
68+
try:
69+
remove_ticker(ticker, self.config)
70+
except Exception:
71+
raise OperationalException("Something went wrong while deleting the ticker from the registry")
72+
else:
73+
raise OperationalException("Provided ticker {} does not exist".format(ticker))
3174

32-
try:
33-
self.__data_provider_manager.add_ticker(ticker)
34-
except DataProviderException as e:
35-
raise OperationalException(str(e))
75+
def list_tickers(self):
76+
logger.info("Listing tickers ...")
77+
return get_tickers(self.config)
3678

3779

3880

bot/data/__init__.py

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
import sqlite3
3-
from typing import Dict, Any, List
3+
from typing import Dict, Any, List, Tuple
44

55
from bot import setup
66

@@ -31,7 +31,7 @@ def create_tables(config: Dict[str, Any]) -> None:
3131
con.close()
3232

3333
if TRADES_TABLE_NAME not in table_names:
34-
logger.info("Creating database table {}...".format(TRADES_TABLE_NAME))
34+
logger.info("Creating database table {} ...".format(TRADES_TABLE_NAME))
3535
con = create_connection(config)
3636

3737
# Create open trades table
@@ -45,44 +45,65 @@ def create_tables(config: Dict[str, Any]) -> None:
4545

4646

4747
def add_ticker(ticker: str, company_name: str, category: str, config: Dict[str, Any]) -> None:
48+
49+
logger.info("Adding ticker {} to registry...".format(ticker))
50+
4851
con = create_connection(config)
4952
cursor = con.cursor()
5053

51-
logger.info("Adding ticker {} ...".format(ticker))
54+
# Add ticker if not exists
55+
insert_statement = """
56+
INSERT INTO TICKERS (ticker, company_name, category)
57+
VALUES (?, ?, ?);
58+
"""
5259

53-
# Get ticker if exists
54-
select_statement = """SELECT ticker_id from TICKERS where ticker = ?"""
55-
cursor.execute(select_statement, (ticker,))
60+
data_tuple = (ticker, company_name, category)
61+
cursor.execute(insert_statement, data_tuple)
5662

57-
result = cursor.fetchall()
63+
con.commit()
64+
con.close()
5865

59-
if result:
60-
logger.info("Ticker already in database")
61-
else:
62-
# Add ticker if not exists
63-
insert_statement = """
64-
INSERT INTO TICKERS (ticker, company_name, category)
65-
VALUES (?, ?, ?);
66-
"""
6766

68-
data_tuple = (ticker, company_name, category)
69-
cursor.execute(insert_statement, data_tuple)
67+
def remove_ticker(ticker: str, config: Dict[str, Any]) -> None:
68+
69+
logger.info("Removing ticker {} from registry ...".format(ticker))
70+
con = create_connection(config)
71+
cursor = con.cursor()
72+
73+
delete_statement = '''
74+
DELETE from TICKERS where ticker = ?
75+
'''
7076

77+
cursor.execute(delete_statement, (ticker,))
7178
con.commit()
7279
con.close()
7380

74-
def remove_ticker(ticker: str, config: Dict[str, Any]) -> None:
75-
pass
7681

77-
def get_company_info(ticker: str, config: Dict[str, any]) -> List[str]:
82+
def get_company_profile(ticker: str, config: Dict[str, any]) -> Tuple[str]:
7883
con = create_connection(config)
7984
cursor = con.cursor()
8085

81-
logger.info("Getting {} company info ...".format(ticker))
86+
logger.info("Getting {} company info from registry ...".format(ticker))
87+
88+
cursor.execute(" SELECT * FROM TICKERS WHERE ticker=?", (ticker, ))
89+
result = cursor.fetchall()
90+
91+
con.close()
92+
return result
93+
94+
95+
def get_tickers(config: Dict[str, any]) -> List[str]:
96+
con = create_connection(config)
97+
cursor = con.cursor()
98+
99+
logger.info("Getting all tickers from registry ...")
82100

83101
# Get ticker if exists
84-
select_statement = """SELECT ticker_id from TICKERS where ticker = ?"""
85-
cursor.execute(select_statement, (ticker,))
102+
select_statement = '''
103+
SELECT (ticker) from TICKERS
104+
'''
105+
106+
cursor.execute(select_statement)
86107
result = cursor.fetchall()
87108
con.close()
88109
return result

bot/data/data_provider_manager.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import List, Dict, Any
33

44
from bot.data.data_providers import DataProvider, DataProviderException
5-
from . import add_ticker, get_all_table_names, create_tables, get_company_info
5+
from . import get_all_table_names, create_tables
66

77
logger = logging.getLogger(__name__)
88

@@ -29,26 +29,23 @@ def __init__(self, config: Dict[str, Any]) -> None:
2929
from bot.data.data_providers.fmp_data_provider import FMPDataProvider
3030
self.registered_modules.append(FMPDataProvider(self.__config))
3131

32-
def add_ticker(self, ticker: str) -> None:
32+
def evaluate_ticker(self, ticker: str) -> bool:
3333

34-
if get_company_info(ticker, self.__config):
35-
raise DataProviderException("Ticker {} already exists".format(ticker))
36-
else:
37-
for data_provider in self.registered_modules:
34+
for data_provider in self.registered_modules:
3835

39-
if data_provider.evaluate_ticker(ticker):
40-
logger.info("Ticker exists")
41-
profile = data_provider.get_profile(ticker)
36+
if data_provider.evaluate_ticker(ticker):
37+
logger.info("Ticker exists")
38+
return True
4239

43-
company_name = profile.get('profile', {}).get('companyName', {})
44-
industry = profile.get('profile', {}).get('industry', {})
40+
return False
4541

46-
if company_name and industry:
47-
add_ticker(ticker, company_name, industry, self.__config)
48-
logger.info("Ticker {} has been added ...".format(ticker))
49-
return
42+
def get_profile(self, ticker: str) -> Dict:
5043

51-
raise DataProviderException("Could not evaluate ticker {}".format(ticker))
44+
for data_provider in self.registered_modules:
5245

46+
profile = data_provider.get_profile(ticker)
5347

48+
if profile:
49+
return profile
5450

51+
raise DataProviderException("Could not profile for {}".format(ticker))

bot/data/data_providers/fmp_data_provider.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
logger = logging.getLogger(__name__)
1313

14+
1415
class FMPDataProvider(DataProvider):
1516

1617
def __init__(self, config: Dict[str, Any]):

bot/services/service.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@ def cleanup(self) -> None:
4848

4949
def _add_ticker(self, ticker: str) -> None:
5050
self._bot.add_ticker(ticker)
51+
52+
def _remove_ticker(self, ticker: str) -> None:
53+
self._bot.remove_ticker(ticker)

bot/services/telegram.py

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@
1616

1717
# Telegram keyboard buttons
1818
DEFAULT_KEYBOARD_BUTTONS = [
19-
['/add_tickers'],
19+
['/list_tickers', '/add_or_remove_tickers'],
2020
['/help', '/version']
2121
]
2222

23-
ADDING_TICKERS_CONVERSATION_BUTTONS = [
23+
STANDARD_CONVERSATION_BUTTONS = [
2424
['/cancel']
2525
]
2626

27+
TICKERS_CONVERSATION_BUTTONS = [
28+
['/add_tickers', '/remove_tickers', '/cancel']
29+
]
30+
31+
# Conversation states
32+
ADDING, REMOVING, LISTING_TICKERS, CHOOSING = range(4)
33+
2734

2835
def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
2936
"""
@@ -61,10 +68,6 @@ def wrapper(self, *args, **kwargs):
6168
class Telegram(Service):
6269
""" This class handles all telegram communication """
6370

64-
# Conversation states
65-
class AddTickersConversationState:
66-
ADDING, TYPING_REPLY, TYPING_CHOICE = range(3)
67-
6871
def __init__(self, bot) -> None:
6972
"""
7073
Init the Telegram call, and init the super class service
@@ -82,9 +85,14 @@ def startup(self) -> None:
8285

8386
# States of adding a ticker
8487
ticker_conversation_handler = ConversationHandler(
85-
entry_points=[CommandHandler('add_tickers', self._start_adding_tickers)],
88+
entry_points=[CommandHandler('add_or_remove_tickers', self._start_tickers_conversation)],
8689
states={
87-
self.AddTickersConversationState.ADDING: [MessageHandler(Filters.text, self._add_tickers)],
90+
CHOOSING: [
91+
CommandHandler('add_tickers', self._start_adding_tickers),
92+
CommandHandler('remove_tickers', self._start_removing_tickers),
93+
],
94+
ADDING: [MessageHandler(Filters.text, self._add_tickers)],
95+
REMOVING: [MessageHandler(Filters.text, self._remove_tickers)],
8896
},
8997
fallbacks=[CommandHandler('cancel', self._cancel_conversation)]
9098
)
@@ -93,6 +101,7 @@ def startup(self) -> None:
93101
handles = [
94102
CommandHandler('help', self._help),
95103
CommandHandler('version', self._version),
104+
CommandHandler('list_tickers', self._list_tickers),
96105
ticker_conversation_handler
97106
]
98107

@@ -136,7 +145,8 @@ def _help(self, update: Update, context: CallbackContext) -> None:
136145

137146
message = "*/help:* `This help message`\n" \
138147
"*/version:* `Show version`\n" \
139-
"*/add_tickers:* `Add tickers to the registry, so they can be analyzed.`\n"
148+
"*/add_or_remove_tickers:* `Add or remove tickers to the registry`\n" \
149+
"*/list_tickers:* `List all saved tickers in the registry`\n"
140150

141151
self._send_msg(message)
142152

@@ -192,11 +202,22 @@ def _version(self, update: Update, context: CallbackContext) -> None:
192202
"""
193203
self._send_msg('*Version:* `{}`'.format(__version__))
194204

205+
@authorized_only
206+
def _start_tickers_conversation(self, update: Update, context: CallbackContext):
207+
self._send_msg("Make your choice", keyboard_buttons=TICKERS_CONVERSATION_BUTTONS)
208+
return CHOOSING
209+
195210
@authorized_only
196211
def _start_adding_tickers(self, update: Update, context: CallbackContext):
197212
self._send_msg("Please provide the tickers separated by commas, if you submit "
198-
"one ticker you can leave out the comma", keyboard_buttons=ADDING_TICKERS_CONVERSATION_BUTTONS)
199-
return self.AddTickersConversationState.ADDING
213+
"one ticker you can leave out the comma", keyboard_buttons=STANDARD_CONVERSATION_BUTTONS)
214+
return ADDING
215+
216+
@authorized_only
217+
def _start_removing_tickers(self, update: Update, context: CallbackContext):
218+
self._send_msg("Please provide the tickers separated by commas, if you submit "
219+
"one ticker you can leave out the comma", keyboard_buttons=STANDARD_CONVERSATION_BUTTONS)
220+
return REMOVING
200221

201222
@authorized_only
202223
def _add_tickers(self, update: Update, context: CallbackContext):
@@ -221,9 +242,30 @@ def _add_tickers(self, update: Update, context: CallbackContext):
221242
def _remove_tickers(self, update: Update, context: CallbackContext):
222243
text = update.message.text
223244
tickers = [ticker.strip() for ticker in text.split(',')]
224-
added_tickers = []
245+
removed_tickers = []
246+
247+
for ticker in tickers:
248+
249+
try:
250+
self._remove_ticker(ticker)
251+
removed_tickers.append(ticker)
252+
except OperationalException as e:
253+
self._send_msg(str(e))
254+
255+
if removed_tickers:
256+
self._send_msg("{} removed".format(removed_tickers))
257+
225258
return ConversationHandler.END
226259

260+
@authorized_only
261+
def _list_tickers(self, update: Update, context: CallbackContext):
262+
263+
try:
264+
tickers = self._bot.list_tickers()
265+
self._send_msg(tickers)
266+
except Exception as e:
267+
self._send_msg(str(e))
268+
227269
@authorized_only
228270
def _cancel_conversation(self, update: Update, context: CallbackContext):
229271
logger.info("Conversation is canceled")

0 commit comments

Comments
 (0)