mirror of
https://github.com/cupcakearmy/mercatus.git
synced 2024-11-01 00:04:11 +01:00
source
This commit is contained in:
parent
29bb1f3b29
commit
54595d9e54
37
Background.py
Normal file
37
Background.py
Normal 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
41
Commands.py
Normal 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
38
LimitedDict.py
Normal 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
33
LimitedList.py
Normal 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
40
Market.py
Normal 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
59
Mercatus.py
Normal 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
17
Utils.py
Normal 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
|
Loading…
Reference in New Issue
Block a user