2022-03-20 10:09:33 +01:00
|
|
|
import os
|
|
|
|
|
import sys
|
2022-08-13 10:02:54 +02:00
|
|
|
from datetime import timedelta, datetime
|
|
|
|
|
|
2022-03-20 10:09:33 +01:00
|
|
|
import asyncio
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
from service.App import *
|
|
|
|
|
from common.utils import *
|
|
|
|
|
|
2022-04-10 19:38:53 +02:00
|
|
|
import logging
|
|
|
|
|
log = logging.getLogger('notifier')
|
|
|
|
|
|
2022-08-13 10:02:54 +02:00
|
|
|
transaction_file = Path("transactions.txt")
|
|
|
|
|
|
2022-03-20 10:09:33 +01:00
|
|
|
|
|
|
|
|
async def notify_telegram():
|
|
|
|
|
symbol = App.config["symbol"]
|
|
|
|
|
|
2022-03-26 12:43:02 +01:00
|
|
|
status = App.status
|
|
|
|
|
signal = App.signal
|
2022-03-20 10:09:33 +01:00
|
|
|
signal_side = signal.get("side")
|
2022-03-26 12:43:02 +01:00
|
|
|
close_price = signal.get('close_price')
|
|
|
|
|
buy_score = signal.get('buy_score')
|
|
|
|
|
sell_score = signal.get('sell_score')
|
2022-04-10 19:38:53 +02:00
|
|
|
close_time = signal.get('close_time')
|
|
|
|
|
|
|
|
|
|
model = App.config["signal_model"]
|
|
|
|
|
buy_notify_threshold = model["buy_notify_threshold"]
|
|
|
|
|
sell_notify_threshold = model["sell_notify_threshold"]
|
|
|
|
|
buy_signal_threshold = model["buy_signal_threshold"]
|
|
|
|
|
sell_signal_threshold = model["sell_signal_threshold"]
|
|
|
|
|
trade_icon_step = model.get("trade_icon_step", 0)
|
|
|
|
|
notify_frequency_minutes = model.get("notify_frequency_minutes", 1)
|
2022-03-26 12:43:02 +01:00
|
|
|
|
|
|
|
|
# Crypto Currency Symbols: https://github.com/yonilevy/crypto-currency-symbols
|
2022-04-17 19:55:22 +02:00
|
|
|
if symbol == "BTCUSDT":
|
|
|
|
|
symbol_char = "₿"
|
|
|
|
|
elif symbol == "ETHUSDT":
|
|
|
|
|
symbol_char = "Ξ"
|
2022-03-26 12:43:02 +01:00
|
|
|
else:
|
2022-04-17 19:55:22 +02:00
|
|
|
symbol_char = symbol
|
2022-03-26 12:43:02 +01:00
|
|
|
|
|
|
|
|
# Notification logic:
|
2022-03-27 18:58:41 +02:00
|
|
|
# 1. Trade signal in the case it is suggested to really buy or sell: BUY or SELL and one corresponding score
|
|
|
|
|
# 2. Notification signal simply to provide information (separate criteria): both scores
|
2022-08-13 22:38:27 +02:00
|
|
|
# Icons:
|
2022-09-04 17:40:41 +02:00
|
|
|
# DOWN: 📉, ⬇ ⬇️↘️🔽 🔴 (red), 🟥, ▼ (red), ↘ (red)
|
|
|
|
|
# UP: 📈, ⬆, ⬆️ ↗️🔼 🟢 (green), 🟩, ▲ (green), ↗ (green)
|
2022-08-20 11:26:28 +02:00
|
|
|
# ✅ 🔹 (blue) 📌 🔸 (orange)
|
2022-04-10 19:38:53 +02:00
|
|
|
message = ""
|
2022-03-26 12:43:02 +01:00
|
|
|
if signal_side == "BUY":
|
2022-04-10 19:38:53 +02:00
|
|
|
score_steps = (np.abs(buy_score - buy_signal_threshold) // trade_icon_step) if trade_icon_step else 0
|
2022-08-13 20:41:01 +02:00
|
|
|
message = "🟢"*int(score_steps+1) + f" *BUY: {symbol_char} {int(close_price):,} Score: {buy_score:+.2f}*"
|
2022-03-26 12:43:02 +01:00
|
|
|
elif signal_side == "SELL":
|
2022-04-10 19:38:53 +02:00
|
|
|
score_steps = (np.abs(sell_score - sell_signal_threshold) // trade_icon_step) if trade_icon_step else 0
|
2022-08-13 20:41:01 +02:00
|
|
|
message = "🔴"*int(score_steps+1) + f" *SELL: {symbol_char} {int(close_price):,} Score: {-sell_score:+.2f}*"
|
2022-04-10 19:38:53 +02:00
|
|
|
elif (close_time.minute % notify_frequency_minutes) == 0: # Info message with custom frequency
|
|
|
|
|
if buy_score > sell_score:
|
2022-04-17 19:55:22 +02:00
|
|
|
message = f"{symbol_char} {int(close_price):,} 📈{buy_score:+.2f}"
|
2022-04-10 19:38:53 +02:00
|
|
|
else:
|
2022-04-17 19:55:22 +02:00
|
|
|
message = f"{symbol_char} {int(close_price):,} 📉{-sell_score:+.2f}"
|
2022-03-26 12:43:02 +01:00
|
|
|
message = message.replace("+", "%2B") # For Telegram to display plus sign
|
2022-03-20 10:09:33 +01:00
|
|
|
|
2022-04-10 19:38:53 +02:00
|
|
|
if not message:
|
2022-03-20 10:09:33 +01:00
|
|
|
return
|
2022-04-10 19:38:53 +02:00
|
|
|
if buy_score < buy_notify_threshold and sell_score < sell_notify_threshold:
|
|
|
|
|
return # Do not send notifications with low notification threshold (also no buy/sell notifications)
|
2022-03-20 10:09:33 +01:00
|
|
|
|
|
|
|
|
bot_token = App.config["telegram_bot_token"]
|
|
|
|
|
chat_id = App.config["telegram_chat_id"]
|
|
|
|
|
|
2022-08-13 20:41:01 +02:00
|
|
|
#
|
|
|
|
|
# Send signal
|
|
|
|
|
#
|
|
|
|
|
try:
|
|
|
|
|
url = 'https://api.telegram.org/bot' + bot_token + '/sendMessage?chat_id=' + chat_id + '&parse_mode=markdown&text=' + message
|
|
|
|
|
response = requests.get(url)
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
if not response_json.get('ok'):
|
|
|
|
|
log.error(f"Error sending notification.")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error(f"Error sending notification: {e}")
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Send transaction notification (if any)
|
|
|
|
|
# Note that we assume that transactions may happen only if notifications are sent
|
|
|
|
|
#
|
|
|
|
|
transaction = await simulate_trade() # Here we check additional conditions (in addition to signaling)
|
|
|
|
|
|
|
|
|
|
if not transaction:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
profit, profit_percent, profit_descr, profit_percent_descr = await generate_transaction_stats()
|
|
|
|
|
|
|
|
|
|
if transaction.get("status") == "SELL":
|
2022-08-13 22:38:27 +02:00
|
|
|
message = "⚡💰 *SOLD: "
|
2022-08-13 20:41:01 +02:00
|
|
|
elif transaction.get("status") == "BUY":
|
2022-08-13 22:38:27 +02:00
|
|
|
message = "⚡💰 *BOUGHT: "
|
2022-08-13 20:41:01 +02:00
|
|
|
else:
|
|
|
|
|
log.error(f"ERROR: Should not happen")
|
|
|
|
|
|
|
|
|
|
message += f" Profit: {profit_percent:.2f}% {profit:.2f}₮*"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
url = 'https://api.telegram.org/bot' + bot_token + '/sendMessage?chat_id=' + chat_id + '&parse_mode=markdown&text=' + message
|
|
|
|
|
response = requests.get(url)
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
if not response_json.get('ok'):
|
|
|
|
|
log.error(f"Error sending notification.")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error(f"Error sending notification: {e}")
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Send stats about previous transactions (including this one)
|
|
|
|
|
#
|
|
|
|
|
if transaction.get("status") == "SELL":
|
2022-08-20 11:26:28 +02:00
|
|
|
message = "↗ *LONG transactions stats (4 weeks)*\n"
|
2022-08-13 20:41:01 +02:00
|
|
|
elif transaction.get("status") == "BUY":
|
2022-08-20 11:26:28 +02:00
|
|
|
message = "↘ *SHORT transactions stats (4 weeks)*\n"
|
2022-08-13 20:41:01 +02:00
|
|
|
else:
|
|
|
|
|
log.error(f"ERROR: Should not happen")
|
|
|
|
|
|
2022-08-20 11:26:28 +02:00
|
|
|
message += f"🔸sum={profit_percent_descr['count'] * profit_percent_descr['mean']:.2f}% 🔸count={int(profit_percent_descr['count'])}\n"
|
|
|
|
|
message += f"🔸mean={profit_percent_descr['mean']:.2f}% 🔸std={profit_percent_descr['std']:.2f}%\n"
|
|
|
|
|
message += f"🔸min={profit_percent_descr['min']:.2f}% 🔸median={profit_percent_descr['50%']:.2f}% 🔸max={profit_percent_descr['max']:.2f}%\n"
|
2022-08-13 20:41:01 +02:00
|
|
|
|
2022-03-20 10:09:33 +01:00
|
|
|
try:
|
2022-04-10 19:38:53 +02:00
|
|
|
url = 'https://api.telegram.org/bot' + bot_token + '/sendMessage?chat_id=' + chat_id + '&parse_mode=markdown&text=' + message
|
2022-03-20 10:09:33 +01:00
|
|
|
response = requests.get(url)
|
2022-03-26 12:43:02 +01:00
|
|
|
response_json = response.json()
|
|
|
|
|
if not response_json.get('ok'):
|
2022-04-10 19:38:53 +02:00
|
|
|
log.error(f"Error sending notification.")
|
2022-03-20 10:09:33 +01:00
|
|
|
except Exception as e:
|
2022-04-10 19:38:53 +02:00
|
|
|
log.error(f"Error sending notification: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def simulate_trade():
|
|
|
|
|
symbol = App.config["symbol"]
|
|
|
|
|
|
|
|
|
|
status = App.status
|
|
|
|
|
signal = App.signal
|
|
|
|
|
signal_side = signal.get("side")
|
|
|
|
|
close_price = signal.get('close_price')
|
|
|
|
|
buy_score = signal.get('buy_score')
|
|
|
|
|
sell_score = signal.get('sell_score')
|
|
|
|
|
close_time = signal.get('close_time')
|
|
|
|
|
|
2022-08-13 10:02:54 +02:00
|
|
|
# Previous transaction: BUY (we are currently selling) or SELL (we are currently buying)
|
2022-04-10 19:38:53 +02:00
|
|
|
t_status = App.transaction.get("status")
|
|
|
|
|
t_price = App.transaction.get("price")
|
2022-08-13 10:02:54 +02:00
|
|
|
if signal_side == "BUY" and (not t_status or t_status == "SELL"):
|
2022-04-10 19:38:53 +02:00
|
|
|
profit = t_price - close_price if t_price else 0.0
|
2022-08-13 20:41:01 +02:00
|
|
|
t_dict = dict(timestamp=str(close_time), price=close_price, profit=profit, status="BUY")
|
2022-08-13 10:02:54 +02:00
|
|
|
elif signal_side == "SELL" and (not t_status or t_status == "BUY"):
|
2022-04-10 19:38:53 +02:00
|
|
|
profit = close_price - t_price if t_price else 0.0
|
2022-08-13 20:41:01 +02:00
|
|
|
t_dict = dict(timestamp=str(close_time), price=close_price, profit=profit, status="SELL")
|
2022-04-10 19:38:53 +02:00
|
|
|
else:
|
2022-08-13 20:41:01 +02:00
|
|
|
return None
|
2022-04-10 19:38:53 +02:00
|
|
|
|
|
|
|
|
# Save this transaction
|
|
|
|
|
App.transaction = t_dict
|
|
|
|
|
with open(transaction_file, 'a+') as f:
|
2022-08-13 20:41:01 +02:00
|
|
|
f.write(",".join([f"{v:.2f}" if isinstance(v, float) else str(v) for v in t_dict.values()]) + "\n")
|
|
|
|
|
|
|
|
|
|
return t_dict
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def generate_transaction_stats():
|
|
|
|
|
"""Here we assume that the latest transaction is saved in the file and this function computes various properties."""
|
|
|
|
|
|
|
|
|
|
df = pd.read_csv(transaction_file, parse_dates=[0], header=None, names=["timestamp", "close", "profit", "status"])
|
|
|
|
|
|
|
|
|
|
mask = (df['timestamp'] >= (datetime.now() - timedelta(weeks=4)))
|
|
|
|
|
df = df[max(mask.idxmax()-1, 0):] # We add one previous row to use the previous close
|
|
|
|
|
|
|
|
|
|
df["prev_close"] = df["close"].shift()
|
|
|
|
|
df["profit_percent"] = df.apply(lambda x: 100.0*x["profit"]/x["prev_close"], axis=1)
|
|
|
|
|
|
|
|
|
|
df = df.iloc[1:] # Remove the first row which was added to compute relative profit
|
|
|
|
|
|
|
|
|
|
long_df = df[df["status"] == "SELL"]
|
|
|
|
|
short_df = df[df["status"] == "BUY"]
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Determine properties of the latest transaction
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
# Sample output:
|
|
|
|
|
# BTC, LONG or SHORT
|
|
|
|
|
# sell price 24,000 (now), buy price (datetime) 23,000
|
|
|
|
|
# profit abs: 1,000.00,
|
|
|
|
|
# profit rel: 3.21%
|
|
|
|
|
|
|
|
|
|
last_transaction = df.iloc[-1]
|
|
|
|
|
transaction_dt = last_transaction["timestamp"]
|
|
|
|
|
transaction_type = last_transaction["status"]
|
|
|
|
|
profit = last_transaction["profit"]
|
|
|
|
|
profit_percent = last_transaction["profit_percent"]
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Properties of last period of trade
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
if transaction_type == "SELL":
|
|
|
|
|
df2 = long_df
|
|
|
|
|
elif transaction_type == "BUY":
|
|
|
|
|
df2 = short_df
|
|
|
|
|
|
|
|
|
|
# Sample output for abs profit
|
|
|
|
|
# sum 1,200.00, mean 400.00, median 450.00, std 250.00, min -300.0, max 1200.00
|
|
|
|
|
|
|
|
|
|
profit_sum = df2["profit"].sum()
|
|
|
|
|
profit_descr = df2["profit"].describe() # count, mean, std, min, 50% max
|
|
|
|
|
|
|
|
|
|
profit_percent_sum = df2["profit_percent"].sum()
|
|
|
|
|
profit_percent_descr = df2["profit_percent"].describe() # count, mean, std, min, 50% max
|
|
|
|
|
|
|
|
|
|
return profit, profit_percent, profit_descr, profit_percent_descr
|
2022-03-20 10:09:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
pass
|