diff --git a/app.py b/app.py index 683eff8..928e2f0 100644 --- a/app.py +++ b/app.py @@ -11,10 +11,10 @@ # env variable bot_id -BOT_ID = os.environ.get("SLACK_BOT_ID") +BOT_ID = os.environ.get('SLACK_BOT_ID') # constants -AT_BOT = "<@" + BOT_ID + ">" +AT_BOT = '<@{}>'.format(BOT_ID) # instantiate Slack client slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN')) @@ -23,72 +23,72 @@ user_manager = HackcoinUserManager() def handle_command(command, channel, user_id): - """ - Receives commands directed at the bot and determines if they - are valid commands. If so, then acts on the commands. If not, - returns back what it needs for clarification. - """ + ''' + Receives commands directed at the bot and determines if they + are valid commands. If so, then acts on the commands. If not, + returns back what it needs for clarification. + ''' user_manager.load_user(user_id, channel=channel) # Set defaults. - response = "Type *@hackcoinbot help* for a guide!" + response = 'Type *@hackcoinbot help* for a guide!' attachment = [] - command_tokens = [w.lower() for w in command.strip().split(" ")] + command_tokens = [w.lower() for w in command.strip().split(' ')] command_type = get_command_type(command_tokens) - if command_type == "price": + if command_type == 'price': response, attachment = get_price(command_tokens) - if command_type == "buy": + if command_type == 'buy': response = buy(user_manager, command_tokens, user_id, channel=channel) - if command_type == "sell": + if command_type == 'sell': response = sell(user_manager, command_tokens, user_id, channel=channel) - if command_type == "balance": + if command_type == 'balance': response = user_manager.check_balance(user_id, channel=channel) - if command_type == "portfolio": + if command_type == 'portfolio': response, attachment = portfolio(user_manager, user_id, channel=channel) - if command_type == "leaderboard": + if command_type == 'leaderboard': response = user_manager.check_leaderboard(user_id, channel=channel) - if command_type == "help": + if command_type == 'help': response = print_help() - if command_type == "meme": + if command_type == 'meme': response = random.choice([ - " litecoin is a shit investment ", - " :seansmile: buy ethereum now !!", - " is tonight the night we go to MARU ?? ", - " :blondesassyparrot: cash me ousside :blondesassyparrot: how bout dah :blondesassyparrot: ", - " you have been blessed by a r a r e p u p :doge: ", - " g o o d b o i ", - " :sakibwouldlikethat: sakib would like that " + ' litecoin is a shit investment ', + ' :seansmile: buy ethereum now !!', + ' is tonight the night we go to MARU ?? ', + ' :blondesassyparrot: cash me ousside :blondesassyparrot: how bout dah :blondesassyparrot: ', + ' you have been blessed by a r a r e p u p :doge: ', + ' g o o d b o i ', + ' :sakibwouldlikethat: sakib would like that ' ]) - if command_type == "greet": + if command_type == 'greet': response = random.choice([ - " :wave: ", - " :seansmile: ", - " :fastparrot: " + ' :wave: ', + ' :seansmile: ', + ' :fastparrot: ' ]) - print datetime.now(), user_manager.users[user_id]['first_name'], command_type, command + print(datetime.now(), user_manager.users[user_id]['first_name'], command_type, command) if response is not None: - slack_client.api_call("chat.postMessage", channel=channel, + slack_client.api_call('chat.postMessage', channel=channel, text=response, attachments=attachment, as_user=True) def parse_slack_output(slack_rtm_output): """ - The Slack Real Time Messaging API is an events firehose. - this parsing function returns None unless a message is - directed at the Bot, based on its ID. + The Slack Real Time Messaging API is an events firehose. + this parsing function returns None unless a message is + directed at the Bot, based on its ID. """ output_list = slack_rtm_output @@ -116,29 +116,47 @@ def parse_slack_output(slack_rtm_output): return None, None, None +def normalize_events(events): + okay_events = [] + + for event in events: + is_bot = (event.get('bot_id') is not None) and (event.get('user') != BOT_ID) + has_text = event.get('text') is not None + + if is_bot or not has_text: + continue + + is_direct_message = event['channel'][0] == 'D' + okay_events.append(event) + def listen(): # 0.25 second delay between reading from firehose READ_WEBSOCKET_DELAY = 0.25 CHECK_MARKET_REMINDER = True if slack_client.rtm_connect(): - print("hackcoinbot says hello!") + print('hackcoinbot says hello!') while True: + data_read = slack_client.rtm_read() + if CHECK_MARKET_REMINDER and is_one_hour_left(): - slack_client.api_call("chat.postMessage", - channel="#tradingfloor", - text=" The stock market closes in *1 hour* :hourglass:", + slack_client.api_call('chat.postMessage', + channel='#tradingfloor', + text=' The stock market closes in *1 hour* :hourglass:', as_user=True) CHECK_MARKET_REMINDER = False - command, channel, user_id = parse_slack_output(slack_client.rtm_read()) + print('\n\n') + print(data_read) + print('\n\n') + command, channel, user_id = parse_slack_output(data_read) if command and channel and user_id: handle_command(command, channel, user_id) time.sleep(READ_WEBSOCKET_DELAY) else: - print("Connection failed. Invalid Slack token or bot ID?") + print('Connection failed. Invalid Slack token or bot ID?') -if __name__ == "__main__": +if __name__ == '__main__': listen() diff --git a/handlers/get_price.py b/handlers/get_price.py index 91ad1bb..6df3e94 100644 --- a/handlers/get_price.py +++ b/handlers/get_price.py @@ -15,23 +15,23 @@ def get_price(command_tokens): except: return "I couldn't find the price you wanted :cry:", None - title = "{} ({})".format( + title = '{} ({})'.format( quote['NAME'], quote['TICKER'] ) - bar_color = "good" + bar_color = 'good' try: if float(quote['CHANGE_AMT']) < 0: - bar_color = "danger" + bar_color = 'danger' except: pass - chart_url = "http://finviz.com/chart.ashx?t={}&ty=c&ta=1&p=d&s=l".format( + chart_url = 'http://finviz.com/chart.ashx?t={}&ty=c&ta=1&p=d&s=l'.format( quote['TICKER'] ) - change_text = "{} ({}%)".format( + change_text = '{} ({}%)'.format( quote['CHANGE_AMT'], quote['CHANGE'] ) @@ -40,28 +40,28 @@ def get_price(command_tokens): attachment = [ { - "fallback": "Check Price", - "color": bar_color, - "title": title, - "fields": [ + 'fallback': 'Check Price', + 'color': bar_color, + 'title': title, + 'fields': [ { - "title": "Price", - "value": quote['PRICE'], - "short": True + 'title': 'Price', + 'value': quote['PRICE'], + 'short': True }, { - "title": "Change", - "value": change_text, - "short": True + 'title': 'Change', + 'value': change_text, + 'short': True } ], - "image_url": chart_url, - "footer": "Google Finance | Finviz", - "ts": now + 'image_url': chart_url, + 'footer': 'Google Finance | Finviz', + 'ts': now } ] - return "", attachment + return '', attachment def get_crpyto_price(command_tokens): try: @@ -72,49 +72,49 @@ def get_crpyto_price(command_tokens): except: return "We don't support that coin yet :cry:", None - title = "{} ({})".format( + title = '{} ({})'.format( quote['NAME'], quote['TICKER'] ) - bar_color = "good" + bar_color = 'good' try: if quote['CHANGE_AMT'] < 0: - bar_color = "danger" + bar_color = 'danger' except: pass change_pct = quote['CHANGE_AMT'] * 100.0 / (quote['PRICE'] - quote['CHANGE_AMT']) - change_text = "{:04.3f} ({:04.3f}%)".format( + change_text = '{:04.3f} ({:04.3f}%)'.format( quote['CHANGE_AMT'], change_pct ) attachment = [ { - "fallback": "Check Price", - "color": bar_color, - "title": title, - "fields": [ + 'fallback': 'Check Price', + 'color': bar_color, + 'title': title, + 'fields': [ { - "title": "Price", - "value": "{:05.3f}".format(quote['PRICE']), - "short": True + 'title': 'Price', + 'value': '{:05.3f}'.format(quote['PRICE']), + 'short': True }, { - "title": "Change (1hr)", - "value": change_text, - "short": True + 'title': 'Change (1hr)', + 'value': change_text, + 'short': True }, { - "title": "Volume (24hr)", - "value": "{:05.3f}".format(quote['VOLUME']), - "short": True + 'title': 'Volume (24hr)', + 'value': '{:05.3f}'.format(quote['VOLUME']), + 'short': True }, ], - "footer": "Google Finance | Finviz", - "ts": quote['TIMESTAMP'] + 'footer': 'Google Finance | Finviz', + 'ts': quote['TIMESTAMP'] } ] - return "", attachment + return '', attachment diff --git a/handlers/main.py b/handlers/main.py index f3543ab..d05add2 100644 --- a/handlers/main.py +++ b/handlers/main.py @@ -1,39 +1,39 @@ def get_command_type(command_tokens): - if len(command_tokens) == 0: + if not command_tokens: return None - if "$" in command_tokens[0]: - return "price" + if '$' in command_tokens[0]: + return 'price' - if command_tokens[0] in set(["buy", "b"]): - return "buy" + if command_tokens[0] in ('buy', 'b'): + return 'buy' - if command_tokens[0] in set(["sell", "s"]): - return "sell" + if command_tokens[0] in ('sell', 's'): + return 'sell' - if command_tokens[0] == "balance": - return "balance" + if command_tokens[0] == 'balance': + return 'balance' - if command_tokens[0] in set(["portfolio", "p"]): - return "portfolio" + if command_tokens[0] in ('portfolio', 'p'): + return 'portfolio' - if command_tokens[0] in set(["leader", "leaderboard", "l", "top"]): - return "leaderboard" + if command_tokens[0] in ('leader', 'leaderboard', 'l', 'top'): + return 'leaderboard' - if command_tokens[0] == "help": - return "help" + if command_tokens[0] == 'help': + return 'help' - if command_tokens[0] in set(["hello", "hey", "greet", "hi"]): - return "greet" + if command_tokens[0] in ('hello', 'hey', 'greet', 'hi'): + return 'greet' - if command_tokens[0] in set(["meme", "shit", "shitpost"]): - return "meme" + if command_tokens[0] in ('meme', 'shit', 'shitpost'): + return 'meme' def is_private_message(slack_client, channel_id): im_ids = [ x['id'] for x in - slack_client.api_call("im.list")['ims'] + slack_client.api_call('im.list')['ims'] ] return channel_id in set(im_ids) diff --git a/handlers/portfolio.py b/handlers/portfolio.py index 002f7e9..48262e0 100644 --- a/handlers/portfolio.py +++ b/handlers/portfolio.py @@ -7,13 +7,13 @@ def portfolio(user_manager, user_id, channel=None): attachment = [ { - "fallback": "Check Portfolio", - "color": "good", - "author_name": user_manager.users[user_id]['first_name'], - "author_icon": user_manager.get_user_thumbnail_url(user_id), - "title": "Portfolio", - "text": response, - "ts": now + 'fallback': 'Check Portfolio', + 'color': 'good', + 'author_name': user_manager.users[user_id]['first_name'], + 'author_icon': user_manager.get_user_thumbnail_url(user_id), + 'title': 'Portfolio', + 'text': response, + 'ts': now } ] - return "", attachment + return '', attachment diff --git a/handlers/trade.py b/handlers/trade.py index 1708edf..89f03f3 100644 --- a/handlers/trade.py +++ b/handlers/trade.py @@ -1,44 +1,37 @@ -def is_float(s): - try: - float(s) - return True - except ValueError: - return False +from utils import coerce_decimal + def buy(user_manager, command_tokens, user_id, channel=None): try: - if is_float(command_tokens[2]): + val = coerce_decimal(command_tokens[2]) + + if not val is None: ticker = command_tokens[1] - shares = float(command_tokens[2]) - elif is_float(command_tokens[1]): - ticker = command_tokens[2] - shares = float(command_tokens[1]) + shares = val else: - return "Not sure what you mean. The *buy* command syntax is *buy* [ticker] [number of shares]" + return 'Not sure what you mean. The *buy* command syntax is *buy* [ticker] [number of shares]' user_manager.buy_shares(ticker, shares, user_id, channel=channel) except Exception, e: - print e - return "Not sure what you mean. The *buy* command syntax is *buy* [ticker] [number of shares]" + print(e) + return 'Not sure what you mean. The *buy* command syntax is *buy* [ticker] [number of shares]' return None def sell(user_manager, command_tokens, user_id, channel=None): try: - if is_float(command_tokens[2]): + val = coerce_decimal(command_tokens[2]) + if not val is None: ticker = command_tokens[1] - shares = float(command_tokens[2]) - elif is_float(command_tokens[1]): - ticker = command_tokens[2] - shares = float(command_tokens[1]) + shares = val else: - return "Not sure what you mean. The *sell* command syntax is *sell* [ticker] [number of shares]" + return 'Not sure what you mean. The *sell* command syntax is *sell* [ticker] [number of shares]' user_manager.sell_shares(ticker, shares, user_id, channel=channel) except Exception, e: - print e - return "Not sure what you mean. The *sell* command syntax is *sell* [ticker] [number of shares]" + print(e) + return 'Not sure what you mean. The *sell* command syntax is *sell* [ticker] [number of shares]' return None diff --git a/markets/cryptos.py b/markets/cryptos.py index 3cdb709..ddd65eb 100644 --- a/markets/cryptos.py +++ b/markets/cryptos.py @@ -1,45 +1,47 @@ -from datetime import datetime -import time -import json -import urllib2 +from decimal import Decimal + import requests -coins = { + +COINS = { 'btc': 'Bitcoin', 'eth': 'Ethereum', 'ltc': 'Litecoin', 'bch': 'Bitcoin Cash' } +BASE_URL = 'https://api.cryptonator.com/api/ticker/{}-usd' + def fetch_quote(symbol): symbol = symbol.lower() - if symbol[-2:] in set(['-c', '.c']): + + if symbol[-2:] in ('-c', '.c'): symbol = symbol[:-2] - if symbol not in coins.keys(): + + if symbol not in COINS: return None quote = None try: - url = "https://api.cryptonator.com/api/ticker/{}-usd".format(symbol) - u = urllib2.urlopen(url) - content = u.read() - data = json.loads(content) + url = BASE_URL.format(symbol) + resp = requests.get(url) + data = resp.json() quote = { - "NAME": coins[symbol], - "TICKER": data["ticker"]["base"], - "PRICE": float(data["ticker"]["price"]), - "PRICEF": float(data["ticker"]["price"]), - "VOLUME": float(data["ticker"]["volume"]), - "CHANGE_AMT": float(data["ticker"]["change"]), - "TIMESTAMP": data["timestamp"] + 'NAME': COINS[symbol], + 'TICKER': data['ticker']['base'], + 'PRICE': Decimal(data['ticker']['price']), + 'PRICEF': Decimal(data['ticker']['price']), + 'VOLUME': Decimal(data['ticker']['volume']), + 'CHANGE_AMT': Decimal(data['ticker']['change']), + 'TIMESTAMP': data['timestamp'] } - except Exception, e: - print e + except Exception as e: + print(e) return quote -if __name__ == "__main__": - print fetch_quote('BTC') +if __name__ == '__main__': + print(fetch_quote('BTC')) diff --git a/markets/is_open.py b/markets/is_open.py index ab66676..dd31254 100644 --- a/markets/is_open.py +++ b/markets/is_open.py @@ -1,18 +1,24 @@ from datetime import datetime, time -def is_open(market = "stocks"): +def is_open(market='stocks'): now = datetime.now() weekday = now.weekday() a = time(hour=9, minute=30) b = time(hour=16) - if market == "stocks": + + if market == 'stocks': return a <= now.time() <= b and weekday <= 4 + else: + return True -def is_one_hour_left(market = "stocks"): +def is_one_hour_left(market='stocks'): now = datetime.now() weekday = now.weekday() a = datetime(year=now.year, month=now.month, day=now.day, hour=15) b = datetime(year=now.year, month=now.month, day=now.day, hour=15, minute=1) - if market == "stocks": + + if market == 'stocks': return a <= now <= b and weekday <= 4 + else: + return True diff --git a/markets/stocks.py b/markets/stocks.py index 700bd54..56add55 100644 --- a/markets/stocks.py +++ b/markets/stocks.py @@ -1,7 +1,13 @@ from datetime import datetime +from decimal import Decimal import time import json -import urllib2 + +import requests + + +BASE_URL = 'http://finance.google.com/finance/info?client=ig&infotype=infoquoteall&q={}' +TICKERS_PER_BATCH = 100 def fetch_quote(ticker): quote = None @@ -9,71 +15,72 @@ def fetch_quote(ticker): try: # try different exchanges - url = "http://finance.google.com/finance/info?client=ig&infotype=infoquoteall&q={}".format(exchange+ticker) - u = urllib2.urlopen(url) - content = u.read() - data = json.loads(content[3:]) - info = data[0] - - if "," in info['l']: - info['l'] = info['l'].replace(',','') - quote = { - "NAME": info['name'], - "TICKER": info['t'], - "PRICE": info['l'], - "PRICEF": float(info['l']), - "CHANGE": float(info['cp']), - "CHANGE_AMT": info['c'], - "TIME": info['ltt'], - "DATE": datetime.strptime(info['lt_dts'], "%Y-%m-%dT%H:%M:%SZ") + url = BASE_URL.format(exchange + ticker) + resp = requests.get(url) + data = json.loads(resp.text[3:]) + info = data[0] + + if ',' in info['l']: + info['l'] = info['l'].replace(',', '') + + quote = { + 'NAME': info['name'], + 'TICKER': info['t'], + 'PRICE': info['l'], + 'PRICEF': Decimal(info['l']), + 'CHANGE': Decimal(info['cp']), + 'CHANGE_AMT': info['c'], + 'TIME': info['ltt'], + 'DATE': datetime.strptime(info['lt_dts'], '%Y-%m-%dT%H:%M:%SZ') } + break - except Exception, e: - print e + except Exception as e: + print(e) return quote def batch_fetch_quotes(tickers): # Cap to 100 tickers per call (max api limit) - TICKERS_PER_BATCH = 100 cur = 0 quotes = {} while cur < len(tickers): - ticker_string = ",".join(tickers[cur:cur + TICKERS_PER_BATCH]) - url = "http://finance.google.com/finance/info?client=ig&infotype=infoquoteall&q={}".format(ticker_string) - u = urllib2.urlopen(url) - content = u.read() + ticker_string = ','.join(tickers[cur:cur + TICKERS_PER_BATCH]) + url = BASE_URL.format(ticker_string) + resp = requests.get(url) + try: - data = json.loads(content[3:]) + data = json.loads(resp.text[3:]) except Exception, e: - print e + print(e) for info in data: try: - if "," in info['l']: - info['l'] = info['l'].replace(',','') - - quote = { - "NAME": info['name'], - "TICKER": info['t'], - "PRICE": info['l'], - "PRICEF": float(info['l']), - "CHANGE": float(info['cp']), - "CHANGE_AMT": info['c'], - "TIME": info['ltt'], - "DATE": datetime.strptime(info['lt_dts'], "%Y-%m-%dT%H:%M:%SZ") + if ',' in info['l']: + info['l'] = info['l'].replace(',', '') + + quote = { + 'NAME': info['name'], + 'TICKER': info['t'], + 'PRICE': info['l'], + 'PRICEF': Decimal(info['l']), + 'CHANGE': Decimal(info['cp']), + 'CHANGE_AMT': info['c'], + 'TIME': info['ltt'], + 'DATE': datetime.strptime(info['lt_dts'], '%Y-%m-%dT%H:%M:%SZ') } ticker = info['t'] - if "." in ticker: + if '.' in ticker: ticker = ticker.replace('.', '-') + quotes[ticker] = quote - except Exception, e: - print e + except Exception as e: + print(e) cur += TICKERS_PER_BATCH time.sleep(0.1) @@ -85,5 +92,5 @@ def batch_fetch_quotes(tickers): return quotes -if __name__ == "__main__": - print fetch_quote('AAPL') +if __name__ == '__main__': + print(fetch_quote('AAPL')) diff --git a/requirements.txt b/requirements.txt index e5704ff..67959a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ slackclient +requests diff --git a/users/user.py b/users/user.py index f107d21..7daf4f0 100644 --- a/users/user.py +++ b/users/user.py @@ -76,7 +76,7 @@ def check_balance(self, user_id, channel=None): balance += total_value response = '{} has a balance of {} Hackcoins :money_with_wings:'.format( - self.users[user_id]["first_name"], + self.users[user_id]['first_name'], balance ) @@ -91,18 +91,18 @@ def check_portfolio(self, user_id, channel=None): for symbol in crypto_symbols: quotes[symbol] = cryptos.fetch_quote(symbol) - response = "" + response = '' balance = 0 coins = self.users[user_id]['coins'] balance += coins - response += "{} Hackcoins :coin: \n".format(coins) + response += '{} Hackcoins :coin: \n'.format(coins) for ticker in tickers: - type_text = "shares" + type_text = 'shares' if ticker[-2:] == '.c': - type_text = "coins" + type_text = 'coins' shares = self.users[user_id]['positions'][ticker]['shares'] avg_price = float(self.users[user_id]['positions'][ticker]['average_price']) @@ -113,7 +113,7 @@ def check_portfolio(self, user_id, channel=None): net_profit = (stock_price - avg_price) * shares balance += (stock_price * shares) - response += "{:5} {:5} {} (buy {:04.2f} | now {:04.2f} | net profit {:04.2f})\n".format( + response += '{:5} {:5} {} (buy {:04.2f} | now {:04.2f} | net profit {:04.2f})\n'.format( ticker, shares, type_text, @@ -122,7 +122,7 @@ def check_portfolio(self, user_id, channel=None): net_profit ) - response += "\nTotal account value = {:04.2f} Hackcoins :coin: \n".format(balance) + response += '\nTotal account value = {:04.2f} Hackcoins :coin: \n'.format(balance) return response @@ -146,20 +146,20 @@ def check_leaderboard(self, user_id, channel=None): stock_price = quotes[ticker]['PRICEF'] total_value = stock_price * shares balance += total_value - balance = float("{:06.2f}".format(balance)) + balance = float('{:06.2f}'.format(balance)) balance_tuples.append((user_id, balance)) balance_tuples.sort(key=lambda x: x[1]) balance_tuples = balance_tuples[::-1] - response = ":racehorse: Hackcoin leaderboard :racehorse: \n" + response = ':racehorse: Hackcoin leaderboard :racehorse: \n' for i, balance in enumerate(balance_tuples): - response += "{})\t{}\t{} ".format(i+1, self.users[balance[0]]['first_name'], balance[1]) + response += '{})\t{}\t{} '.format(i+1, self.users[balance[0]]['first_name'], balance[1]) if i == 0: - response += " :100:" + response += ' :100:' - response += "\n" + response += '\n' return response def buy_shares(self, ticker, shares, user_id, channel=None): @@ -204,7 +204,7 @@ def buy_shares(self, ticker, shares, user_id, channel=None): self.users[user_id]['positions'][ticker]['shares'] = shares self.users[user_id]['positions'][ticker]['average_price'] = stock_price - response = "" + response = '' if is_crypto: response_text = '{} coins of {} bought at {} each (total {} Hackcoins)'.format( @@ -224,35 +224,35 @@ def buy_shares(self, ticker, shares, user_id, channel=None): average_price = self.users[user_id]['positions'][ticker]['average_price'] cumul_pct = (stock_price - average_price) * 100.0 / average_price - attach_color = "good" + attach_color = 'good' if cumul_pct < 0: - attach_color = "danger" + attach_color = 'danger' - title_text = "Shares purchased!" + title_text = 'Shares purchased!' if is_crypto: - title_text = "Coins purchased!" + title_text = 'Coins purchased!' attachment = [ { - "fallback": title_text, - "color": attach_color, - "author_name": self.users[user_id]['first_name'], - "author_icon": self.get_user_thumbnail_url(user_id), - "title": title_text, - "text": response_text, - "fields": [ + 'fallback': title_text, + 'color': attach_color, + 'author_name': self.users[user_id]['first_name'], + 'author_icon': self.get_user_thumbnail_url(user_id), + 'title': title_text, + 'text': response_text, + 'fields': [ { - "title": "Average Buy Price", - "value": "{:04.2f} (overall {:04.2f}%)".format(average_price, cumul_pct), - "short": False + 'title': 'Average Buy Price', + 'value': '{:04.2f} (overall {:04.2f}%)'.format(average_price, cumul_pct), + 'short': False }, { - "title": "Remaining Coins", - "value": "{:04.2f}".format(self.users[user_id]['coins']), - "short": False + 'title': 'Remaining Coins', + 'value': '{:04.2f}'.format(self.users[user_id]['coins']), + 'short': False } ], - "ts": now + 'ts': now } ] else: @@ -263,9 +263,9 @@ def buy_shares(self, ticker, shares, user_id, channel=None): total_can_buy = shares_can_buy * stock_price - type_text = "shares" + type_text = 'shares' if is_crypto: - type_text = "coins" + type_text = 'coins' response = 'You can buy up to *{}* {} of {} for a total of *{}* Hackcoins :take_my_money:'.format( shares_can_buy, @@ -274,7 +274,7 @@ def buy_shares(self, ticker, shares, user_id, channel=None): total_can_buy ) else: - response = 'Markets are closed right now {} :scream:'.format(self.users[user_id]["first_name"]) + response = 'Markets are closed right now {} :scream:'.format(self.users[user_id]['first_name']) # notify their account balance slack_client.api_call('chat.postMessage', @@ -291,9 +291,9 @@ def sell_shares(self, ticker, shares, user_id, channel=None): except: is_crypto = False - type_text = "shares" + type_text = 'shares' if is_crypto: - type_text = "coins" + type_text = 'coins' attachment = [] now = datetime.now().strftime('%s') @@ -321,7 +321,7 @@ def sell_shares(self, ticker, shares, user_id, channel=None): if self.users[user_id]['positions'][ticker]['shares'] == 0: del self.users[user_id]['positions'][ticker] - response = "" + response = '' response_text = '{} {} of {} sold at {} each (total {} coins)'.format( shares, @@ -334,36 +334,36 @@ def sell_shares(self, ticker, shares, user_id, channel=None): cumul_pct = (stock_price - average_price) * 100.0 / average_price net_profit = shares * 1.0 * (stock_price - average_price) - attach_color = "good" + attach_color = 'good' if cumul_pct < 0: - attach_color = "danger" + attach_color = 'danger' attachment = [ { - "fallback": "{} sold!".format(type_text.capitalize()), - "color": attach_color, - "author_name": self.users[user_id]['first_name'], - "author_icon": self.get_user_thumbnail_url(user_id), - "title": "{} sold!".format(type_text.capitalize()), - "text": response_text, - "fields": [ + 'fallback': '{} sold!'.format(type_text.capitalize()), + 'color': attach_color, + 'author_name': self.users[user_id]['first_name'], + 'author_icon': self.get_user_thumbnail_url(user_id), + 'title': '{} sold!'.format(type_text.capitalize()), + 'text': response_text, + 'fields': [ { - "title": "Average Buy Price", - "value": "{:04.2f}".format(average_price), - "short": False + 'title': 'Average Buy Price', + 'value': '{:04.2f}'.format(average_price), + 'short': False }, { - "title": "Total Return", - "value": "{:04.2f} coins (overall {:04.2f}%)".format(net_profit, cumul_pct), - "short": False + 'title': 'Total Return', + 'value': '{:04.2f} coins (overall {:04.2f}%)'.format(net_profit, cumul_pct), + 'short': False }, { - "title": "Remaining Coins", - "value": "{:04.2f}".format(self.users[user_id]['coins']), - "short": False + 'title': 'Remaining Coins', + 'value': '{:04.2f}'.format(self.users[user_id]['coins']), + 'short': False } ], - "ts": now + 'ts': now } ] else: @@ -372,7 +372,7 @@ def sell_shares(self, ticker, shares, user_id, channel=None): ticker ) else: - response = 'Markets are closed right now {} :scream:'.format(self.users[user_id]["first_name"]) + response = 'Markets are closed right now {} :scream:'.format(self.users[user_id]['first_name']) # notify their account balance slack_client.api_call('chat.postMessage', diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..f92c897 --- /dev/null +++ b/utils.py @@ -0,0 +1,13 @@ +from decimal import Decimal, InvalidOperation + + +def coerce_decimal(s): + val = None + + try: + val = Decimal(s) + except InvalidOperation: + pass + + return val +