This commit is contained in:
cupcakearmy 2019-06-12 21:31:06 +02:00
parent 29bb1f3b29
commit 54595d9e54
7 changed files with 265 additions and 0 deletions

37
Background.py Normal file
View File

@ -0,0 +1,37 @@
import asyncio
import threading
def interval(every: float or int, autorun=False, iterations=-1, isolated=False, *args_root, **kwargs_root):
def wrapper(fn):
async def decorator(*args, **kwargs):
it = 0
first = True
while iterations == -1 or it < iterations:
it += 1
if first:
first = False
else:
await asyncio.sleep(every)
await fn(*args, **kwargs)
def capsule(*args, **kwargs):
def loop_in_thread(l):
asyncio.set_event_loop(l)
l.run_until_complete(decorator(*args, **kwargs))
loop = asyncio.get_event_loop()
threading.Thread(target=loop_in_thread, args=(loop,)).start()
if autorun:
if isolated:
capsule(*args_root, **kwargs_root)
else:
asyncio.run(decorator(*args_root, **kwargs_root))
else:
return capsule if isolated else decorator
return wrapper

41
Commands.py Normal file
View File

@ -0,0 +1,41 @@
from telegram.ext import CallbackContext
from telegram import Update
from LimitedList import LimitedList
from Utils import parse_command, config, Section
def get_watchlist(context: CallbackContext) -> LimitedList:
return LimitedList(context.user_data.setdefault(Section.Watchlist.value, []),
config[Section.Watchlist.value]['max_items'])
def watchlist_add(update: Update, context: CallbackContext):
value, *rest = parse_command(update)
get_watchlist(context).add(value)
update.message.reply_text('Saved 💾')
def watchlist_delete(update: Update, context: CallbackContext):
value, *rest = parse_command(update)
update.message.reply_text('Deleted 🗑' if get_watchlist(context).delete(value) else 'Not found ❓')
def watchlist_all(update: Update, context: CallbackContext):
items = get_watchlist(context).all()
update.message.reply_text('\n'.join(items))
def watchlist_clear(update: Update, context: CallbackContext):
get_watchlist(context).clear()
update.message.reply_text('Cleared 🧼')
def set_api_key(update: Update, context: CallbackContext):
value, *rest = parse_command(update)
context.user_data[Section.API_Key.value] = value
update.message.reply_text('API key saved 🔑')
def get_api_key(update: Update, context: CallbackContext):
update.message.reply_text(context.user_data.get(Section.API_Key.value, 'API Key not set'))

38
LimitedDict.py Normal file
View File

@ -0,0 +1,38 @@
import time
import math
class LimitedDict:
def __init__(self, init: dict, limit: int):
self.dict = init
self.limit = limit
def set(self, key, value):
# Delete oldest element if there are too many
if len(self.dict) + 1 > self.limit:
timestamp = math.inf
who = None
for cur, item in self.dict.items():
if item['when'] < timestamp:
timestamp = item['when']
who = cur
del self.dict[who]
self.dict[key] = {
'value': value,
'when': int(time.time())
}
def get(self, key):
value = self.dict.get(key, None)
return value['value'] if value else None
def delete(self, key):
self.dict.pop(key, None)
def clear(self):
self.dict.clear()
def all(self):
return [x['value'] for x in self.dict.values()]

33
LimitedList.py Normal file
View File

@ -0,0 +1,33 @@
from typing import List
class LimitedList:
def __init__(self, init: List[str], limit: int):
self.data = init
self.limit = limit
def _is_index(self, i: int) -> bool:
return False if i < 0 or i > len(self.data) - 1 else True
def add(self, value: str):
# Delete oldest element if there are too many
if len(self.data) + 1 > self.limit:
self.data = self.data[1:]
self.data.append(value)
def get(self, i: int):
return self.data[i] if self._is_index(i) else None
def delete(self, value: str):
if value in self.data:
self.data.remove(value)
return True
return False
def clear(self):
self.data = []
def all(self):
return self.data

40
Market.py Normal file
View File

@ -0,0 +1,40 @@
import io
from typing import BinaryIO
from alpha_vantage.techindicators import TechIndicators
from alpha_vantage.timeseries import TimeSeries
import matplotlib.pyplot as plt
import pandas as pd
from datetime import datetime
class Market:
def __init__(self, key: str):
self.ti = TechIndicators(key=key, output_format='pandas')
self.ts = TimeSeries(key=key, output_format='pandas')
def get_wma(self, stock: str, since: datetime) -> BinaryIO:
plt.clf()
fig = plt.figure()
ax = plt.gca()
ax.set_ylabel('Dollar')
def sort_data_and_plot(data: pd.DataFrame, x: str, y: str, label: str):
data = data.reset_index()
data['date'] = data['date'].astype('datetime64[ns]')
data = data.loc[since < data['date']]
data.plot(x=x, y=y, ax=ax, label=label)
df: pd.DataFrame = self.ts.get_daily(symbol=stock, outputsize='full')[0]
sort_data_and_plot(data=df, x='date', y='1. open', label='Value')
for period in [45, 90, 120]:
df: pd.DataFrame = self.ti.get_wma(symbol=stock, interval='daily', time_period=period)[0]
sort_data_and_plot(data=df, x='date', y='WMA', label=str(period))
plt.plot()
# plt.show()
buffer = io.BytesIO()
fig.savefig(buffer, format='png')
buffer.seek(0)
return buffer

59
Mercatus.py Normal file
View File

@ -0,0 +1,59 @@
from asyncio import sleep
from datetime import datetime, timedelta
from telegram.ext import Updater, CommandHandler
from Background import interval
from Market import Market
from Utils import persistence, config, Section
from Commands import watchlist_add, watchlist_delete, watchlist_all, watchlist_clear, set_api_key, get_api_key
updater: Updater = Updater(config['token'], use_context=True, persistence=persistence)
@interval(every=1.0 * 60 * 60, autorun=False, isolated=True)
async def send_updates():
delta = datetime.now() - timedelta(days=365 * 1)
for key, data in persistence.get_user_data().items():
if Section.API_Key.value not in data:
continue
market = Market(data[Section.API_Key.value])
updater.bot.send_message(key, text='Getting updates 🌎')
first = True
for item in data.get(Section.Watchlist.value, []):
if first:
first = False
else:
msg = updater.bot.send_message(key, text='Waiting 60 seconds for API... ⏳')
await sleep(60)
msg.delete()
msg = updater.bot.send_message(key, text='Calculating... ⏳')
updater.bot.send_photo(key, photo=market.get_wma(item, delta))
msg.delete()
updater.bot.send_message(key, text=item)
# Repeat after every hour
# threading.Timer(1.0 * 60 * 60, send_updates).start()
def main():
global updater
dp = updater.dispatcher
dp.add_handler(CommandHandler('add', watchlist_add))
dp.add_handler(CommandHandler('delete', watchlist_delete))
dp.add_handler(CommandHandler('list', watchlist_all))
dp.add_handler(CommandHandler('clear', watchlist_clear))
dp.add_handler(CommandHandler('setKey', set_api_key))
dp.add_handler(CommandHandler('getKey', get_api_key))
print('Started 🚀')
send_updates()
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()

17
Utils.py Normal file
View File

@ -0,0 +1,17 @@
from telegram import Update
from telegram.ext import PicklePersistence
from yaml import load, Loader
from enum import Enum
config = load(open('./config.yml', 'r'), Loader=Loader)
persistence = PicklePersistence('./data/mercatus')
class Section(Enum):
Watchlist = 'watchlist'
API_Key = 'api_key'
def parse_command(update: Update) -> (str, str):
key, value = (update.message.text.split(' ', 1)[1].split(' ', 1) + [None])[:2]
return key, value