mirror of
https://github.com/cupcakearmy/mercatus.git
synced 2024-11-01 08:14:10 +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