From bc5a16d2c5dae08aa8210eb38ba89f180901f71a Mon Sep 17 00:00:00 2001 From: blaszczykk Date: Wed, 26 May 2021 11:01:23 +0200 Subject: [PATCH 1/7] initial commit --- main.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 00000000..8e6a970b --- /dev/null +++ b/main.py @@ -0,0 +1,33 @@ +# 1. Dajemy możliwość naniesienia na wykres punktu odniesienia - ceny kupna zasobów. +# - Wykres jest odświeżany w czasie rzeczywistym i reprezentuje strumienie danych dotyczące +# trzech zasobów giełdowych, jak w poprzedniej liście. +# - Podczas działania programu użytkownik ma mieć możliwość wielokrotnego wprowadzenia +# informacji co, w jakiej ilości i za ile kupił. Może to robić w odstępach czasowych +# i dla różnych zasobów, w nieokreślonej kolejności. +# - Na podstawie wprowadzonych przez użytkownika danych wyliczamy dotychczasową średnią zakupu +# danego waloru i nanosimy poziomą, przerywaną linią na wykres wartości zasobu. +# - Zwrócić uwagę na zakresy wartości na osi y, wszystko ma się mieścić w zakresie wartości. + +# 2. Dodajemy możliwość wprowadzenia sprzedaży zasobów analogicznie do kupna. +# - Po sprzedaży aktualizujemy obecną średnią cenę zakupu (nie uwzględniającą już tych jednostek, +# które zostały sprzedane. Zasada FIFO - first in first out) +# przykład: jeśli kupiliśmy 10 jednostek po 4000$, następnie 20 jednostek po 6000$, a na końcu +# 20 jednostek po 10000$, a następnie sprzedaliśmy 10 jednostek za 50000$ to nasz zysk wynosi +# 460000$ a obecna średnia cena zakupu to 8000$. +# - Przy sprzedaży obliczamy osiągnięty zysk/stratę i nanosimy informację o zysku/stracie +# w okolicy wykresu danego zasobu. + +# 3. Program ma umożliwiać zapis (i odczyt) wprowadzonych danych w formacie .json +# tak, by po ponownym uruchomieniu można było wprowadzić nazwę pliku przechowującego dane +# i nie gromadzić danych od nowa. + +import matplotlib.pyplot as plt +from matplotlib.animation import FuncAnimation +from matplotlib.offsetbox import OffsetImage, AnnotationBbox +from datetime import datetime, timedelta +from pathlib import Path +from PIL import Image +import requests +import time + + From c63ea4e54274d8aebfb2d6ce06e322b3b9e1945f Mon Sep 17 00:00:00 2001 From: blaszczykk Date: Wed, 26 May 2021 11:58:50 +0200 Subject: [PATCH 2/7] initial refactor of the code from L5 --- main.py | 357 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- utils.py | 30 +++++ 2 files changed, 385 insertions(+), 2 deletions(-) create mode 100644 utils.py diff --git a/main.py b/main.py index 8e6a970b..ba2c0f45 100644 --- a/main.py +++ b/main.py @@ -25,9 +25,362 @@ from matplotlib.animation import FuncAnimation from matplotlib.offsetbox import OffsetImage, AnnotationBbox from datetime import datetime, timedelta -from pathlib import Path -from PIL import Image +from utils import * import requests import time +time_samples, data_storage, avg_storage, rsi_storage, vol_storage, askbid_storage, trans_storage \ + = ([] for _ in range(7)) + + +def get_data(crypto_pairs, data_storage, askbid_storage): + + curr_temp, askbid_temp = ([] for _ in range(2)) + + for pair in crypto_pairs: + try: + request_orders = requests.get( + f"https://bitbay.net/API/Public/{pair[0]}{pair[1]}/ticker.json" + ) + orders = request_orders.json() + curr_temp.append([f'{pair[0]}-{pair[1]}', (orders['ask'], orders['bid'])]) + askbid_temp.append((orders['ask'], orders['bid'])) + + except requests.exceptions.RequestException: + print("Connection problem with the ticker API.") + return None + + askbid_storage.append(askbid_temp) + data_storage.append(curr_temp) + + +def get_transactions(crypto_pairs, transaction_storage, limit, timeframe): + + trans_temp = [] + + for pair in crypto_pairs: + + now = datetime.now() + delta = timedelta(minutes=timeframe) + unix_epoch_time = int((now - delta).timestamp()) * 1000 + + try: + request_volume = requests.get( + f"https://api.bitbay.net/rest/trading/transactions/{pair[0]}-{pair[1]}", + params={'limit': limit, 'fromTime': unix_epoch_time} + ) + transactions = request_volume.json() + trans_temp.append(transactions) + + except requests.exceptions.RequestException: + print("Connection problem with the transactions API.") + trans_temp.append(None) + + transaction_storage.append(trans_temp) + + +def get_volume(transaction_storage, volume_storage): + + vol_temp = [] + + for curr_pair in range(3): + latest_trans = transaction_storage[-1][curr_pair] + trans_amount = len(latest_trans['items']) + volume = sum([float(latest_trans['items'][tran]['a']) for tran in range(trans_amount)]) + vol_temp.append(volume) + + volume_storage.append(vol_temp) + + +def calculate_mov_avg(askbid_storage, avg_storage, window_size): + + storage_slice = askbid_storage[-window_size:] + temp = [] + + for curr_pair in range(3): + inner_temp = [] + + for ask_or_bid in range(2): + summation = 0 + + for sample in range(0, len(storage_slice)): + summation += storage_slice[sample][curr_pair][ask_or_bid] + summation /= len(storage_slice) + inner_temp.append(summation) + + temp.append(inner_temp) + + avg_storage.append(temp) + + +def calculate_rsi(askbid_storage, rsi_storage, window_size): + + storage_slice = askbid_storage[-window_size:] + temp = [] + + for curr_pair in range(3): + inner_temp = [] + + for ask_or_bid in range(2): + upward, upward_counter = 0, 0 + downward, downward_counter = 0, 0 + + for sample in range(1, len(storage_slice)): + if storage_slice[sample-1][curr_pair][ask_or_bid] \ + < storage_slice[sample][curr_pair][ask_or_bid]: + + up = storage_slice[sample][curr_pair][ask_or_bid] \ + - storage_slice[sample-1][curr_pair][ask_or_bid] + upward += up + upward_counter += 1 + + elif storage_slice[sample-1][curr_pair][ask_or_bid] \ + > storage_slice[sample][curr_pair][ask_or_bid]: + + down = storage_slice[sample-1][curr_pair][ask_or_bid] \ + - storage_slice[sample][curr_pair][ask_or_bid] + downward += down + downward_counter += 1 + + if upward_counter == 0: + a = 1 + else: + a = upward / upward_counter + + if downward_counter == 0: + b = 1 + else: + b = downward / downward_counter + + try: + rsi = 100 - (100 / (1 + (a / b))) + except ZeroDivisionError: + a, b = 1, 1 + rsi = 100 - (100 / (1 + (a / b))) + inner_temp.append(rsi) + + temp.append(inner_temp) + + rsi_storage.append(temp) + + +def classify_trend(rsi_storage, trend_list): + + for curr_pair in range(3): + latest_ask_rsi = rsi_storage[-1][curr_pair][0] + if latest_ask_rsi >= 65: + trend_list[curr_pair] = 'upward' + elif latest_ask_rsi <= 35: + trend_list[curr_pair] = 'downward' + else: + trend_list[curr_pair] = 'horizontal' + + +def choose_trend_icon(trend): + + if trend == 'upward': + return upward_icon + elif trend == 'downward': + return downward_icon + else: + return question_icon + + +def select_candidate(trends_list, volume_slice): + + temp = [] + for curr_pair in range(3): + + if trends_list[curr_pair] != 'downward': + temp.append(volume_slice[curr_pair]) + + if temp: + highest_volume = max(temp) + return volume_slice.index(highest_volume) + else: + return None + + +def check_volatility(transaction_storage, pair, threshold, samples): + + trans_slice = transaction_storage[-samples:] + temp = [] + for sample in range(len(trans_slice)): + curr_trans = trans_slice[sample][pair] + trans_amount = len(curr_trans['items']) + inner_temp = [float(curr_trans['items'][tran]['r']) for tran in range(trans_amount)] + temp.extend(inner_temp) + + percentage = calculate_percent_diff(max(temp), min(temp)) + + return (lambda perc: True if perc > threshold else False)(percentage) + + +def check_liquidity(transaction_storage, pair, threshold): + + trans_slice = transaction_storage[-1:] + curr_trans = trans_slice[0][pair] + trans_amount = len(curr_trans['items']) + + temp_asks = [float(curr_trans['items'][tran]['r']) for tran in range(trans_amount) + if curr_trans['items'][tran]['ty'] == "Buy"] + temp_bids = [float(curr_trans['items'][tran]['r']) for tran in range(trans_amount) + if curr_trans['items'][tran]['ty'] == "Sell"] + + ask = sum(temp_asks) / len(temp_asks) + bid = sum(temp_bids) / len(temp_bids) + + percentage = calculate_percent_diff(ask, bid) + + return (lambda spread: True if spread < threshold else False)(percentage) + + +def draw_figure(frame_number): + + plt.style.use('Solarize_Light2') + time_samples.append(time.strftime("%H:%M:%S", time.localtime())) + + get_data(PAIRS, data_storage, askbid_storage) + get_transactions(PAIRS, trans_storage, limit=30, timeframe=15) + get_volume(trans_storage, vol_storage) + calculate_mov_avg(askbid_storage, avg_storage, AVG_WINDOW) + calculate_rsi(askbid_storage, rsi_storage, RSI_WINDOW) + + trends_of_pairs = ['']*3 + classify_trend(rsi_storage, trends_of_pairs) + candidate = select_candidate(trends_of_pairs, vol_storage[-1]) + + plt.ion() + plt.clf() + plt.suptitle("Cryptocurrency Exchange Rates, RSI and Volume") + + for curr_pair in range(3): + + plt.subplot(3, 3, curr_pair+1) + + asks, bids, avg_asks, avg_bids = ([] for _ in range(4)) + + for sample in data_storage: + asks.append(sample[curr_pair][1][0]) + bids.append(sample[curr_pair][1][1]) + for avg_sample in avg_storage: + avg_asks.append(avg_sample[curr_pair][0]) + avg_bids.append(avg_sample[curr_pair][1]) + + volatile = check_volatility(trans_storage, curr_pair, VOLATILE_PERC, VOLATILE_SAMPLES) + liquid = check_liquidity(trans_storage, curr_pair, SPREAD_PERC) + + plt.plot(time_samples, asks, "-o", label=data_storage[0][curr_pair][0] + " ask") + plt.plot(time_samples, bids, "-o", label=data_storage[0][curr_pair][0] + " bid") + plt.plot(time_samples, avg_asks, "o:", color="#185986", + label=data_storage[0][curr_pair][0] + " ask mov avg") + plt.plot(time_samples, avg_bids, "o:", color="#1b6762", + label=data_storage[0][curr_pair][0] + " bid mov avg") + + axes = plt.gca() + + icon_trend = choose_trend_icon(trends_of_pairs[curr_pair]) + imagebox_trend = OffsetImage(icon_trend, zoom=0.4) + imagebox_trend.image.axes = axes + ab_trend = AnnotationBbox(imagebox_trend, (0.5, 0.5), xycoords='axes fraction', + boxcoords="offset points", pad=0.3, frameon=0) + axes.add_artist(ab_trend) + + if volatile: + + imagebox_volatile = OffsetImage(volatile_icon, zoom=0.1) + imagebox_volatile.image.axes = axes + ab_volatile = AnnotationBbox(imagebox_volatile, (0.95, 1.4), xycoords='axes fraction', + boxcoords="offset points", pad=0, frameon=0, + annotation_clip=False) + axes.add_artist(ab_volatile) + + if liquid: + + imagebox_liquid = OffsetImage(liquid_icon, zoom=0.1) + imagebox_liquid.image.axes = axes + ab_liquid = AnnotationBbox(imagebox_liquid, (0.9, 1.4), xycoords='axes fraction', + boxcoords="offset points", pad=0, frameon=0, + annotation_clip=False) + axes.add_artist(ab_liquid) + + if candidate == curr_pair: + for loc, spine in axes.spines.items(): + if loc == 'bottom' or loc == 'top': + spine.set_position(("outward", 1)) + spine.set_capstyle('butt') + else: + spine.set_position(("outward", -1)) + spine.set_linewidth(3) + spine.set_edgecolor('#859900') + spine.set_alpha(0.7) + + plt.xlabel("Time", fontsize=9) + plt.ylabel("Exchange Rates", fontsize=9) + plt.xticks(rotation='vertical', fontsize=7) + plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left', + ncol=2, mode="expand", borderaxespad=0.) + + for curr_pair in range(3): + + plt.subplot(3, 3, curr_pair+4) + rsi_asks, rsi_bids = ([] for _ in range(2)) + + for rsi_sample in rsi_storage: + rsi_asks.append(rsi_sample[curr_pair][0]) + rsi_bids.append(rsi_sample[curr_pair][1]) + + plt.plot(time_samples, rsi_asks, "o:", label=data_storage[0][curr_pair][0] + " ask RSI") + plt.plot(time_samples, rsi_bids, "o:", label=data_storage[0][curr_pair][0] + " bid RSI") + plt.xlabel("Time", fontsize=9) + plt.ylabel("RSI", fontsize=9) + plt.xticks(rotation='vertical', fontsize=7) + plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left', + ncol=2, mode="expand", borderaxespad=0.) + + for curr_pair in range(3): + + plt.subplot(3, 3, curr_pair+7) + volume = [] + + for vol_sample in vol_storage: + volume.append(vol_sample[curr_pair]) + + plt.bar(time_samples, volume, align="center") + plt.xlabel("Time", fontsize=9) + plt.ylabel("Volume", fontsize=9) + ax = plt.gca() + ax.margins(y=0.2) + plt.xticks(rotation='vertical', fontsize=7) + + if len(time_samples) > 9: + del time_samples[0] + del data_storage[0] + del avg_storage[0] + del vol_storage[0] + del askbid_storage[0] + del rsi_storage[0] + del trans_storage[0] + + plt.tight_layout() + plt.subplots_adjust(top=0.85) + + +if __name__ == '__main__': + + PAIRS = [('LTC', 'PLN'), ('ETH', 'PLN'), ('DASH', 'PLN')] + FREQ = 5 + AVG_WINDOW = int(input('Przedział z jakiego liczyć średnią (max 10): ')) + RSI_WINDOW = int(input('Przedział z jakiego liczyć wskaźnik RSI? (max 10): ')) + VOLATILE_SAMPLES = int(input('Przedział z jakiego badać zmienność zasobu? (max 10): ')) + VOLATILE_PERC = float(input('Procentowy próg do uznania zasobu za zmienny? (%): ')) + SPREAD_PERC = float(input('Maksymalny procent spreadu do uznania zasobu za charakteryzujący ' + 'się płynnym rynkiem? (%): ')) + + downward_icon, upward_icon, question_icon = get_icons('downward', 'upward', 'question') + volatile_icon, liquid_icon = get_icons('fire', 'liquidity', transparent=False) + + animation = FuncAnimation(plt.figure(), draw_figure, interval=1000*FREQ) + plt.get_current_fig_manager().window.state('zoomed') + plt.show() diff --git a/utils.py b/utils.py new file mode 100644 index 00000000..39816f05 --- /dev/null +++ b/utils.py @@ -0,0 +1,30 @@ +from PIL import Image +from pathlib import Path + + +def calculate_percent_diff(maxi, mini): + if maxi == mini: + return 100.0 + try: + return (maxi / mini) * 100.0 - 100 + except ZeroDivisionError: + return 0 + + +def get_icons(*icon_names, transparent=True): + + processed_list = [] + for icon_name in icon_names: + + path = Path.cwd() / 'icons' / f'{icon_name}-256p.png' + icon_image = Image.open(path) + + if transparent: + alpha_channel = icon_image.getchannel('A') + with_alpha = alpha_channel.point(lambda i: 32 if i > 0 else 0) + icon_image.putalpha(with_alpha) + + processed_list.append(icon_image) + + return processed_list + From 13829b14a6191d0a54caf9906914b1b7759c2751 Mon Sep 17 00:00:00 2001 From: blaszczykk Date: Wed, 26 May 2021 12:01:33 +0200 Subject: [PATCH 3/7] fix: except ZeroDivision --- main.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index ba2c0f45..a00d29be 100644 --- a/main.py +++ b/main.py @@ -228,8 +228,15 @@ def check_liquidity(transaction_storage, pair, threshold): temp_bids = [float(curr_trans['items'][tran]['r']) for tran in range(trans_amount) if curr_trans['items'][tran]['ty'] == "Sell"] - ask = sum(temp_asks) / len(temp_asks) - bid = sum(temp_bids) / len(temp_bids) + try: + ask = sum(temp_asks) / len(temp_asks) + except ZeroDivisionError: + return 0 + + try: + bid = sum(temp_bids) / len(temp_bids) + except ZeroDivisionError: + return 0 percentage = calculate_percent_diff(ask, bid) From 3df272c2e45dbec6f90c9bbb34635eb642f558e1 Mon Sep 17 00:00:00 2001 From: blaszczykk Date: Wed, 26 May 2021 12:04:04 +0200 Subject: [PATCH 4/7] cleanup --- main.py | 17 +++++++++++------ utils.py | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/main.py b/main.py index a00d29be..a1444a08 100644 --- a/main.py +++ b/main.py @@ -378,12 +378,17 @@ def draw_figure(frame_number): PAIRS = [('LTC', 'PLN'), ('ETH', 'PLN'), ('DASH', 'PLN')] FREQ = 5 - AVG_WINDOW = int(input('Przedział z jakiego liczyć średnią (max 10): ')) - RSI_WINDOW = int(input('Przedział z jakiego liczyć wskaźnik RSI? (max 10): ')) - VOLATILE_SAMPLES = int(input('Przedział z jakiego badać zmienność zasobu? (max 10): ')) - VOLATILE_PERC = float(input('Procentowy próg do uznania zasobu za zmienny? (%): ')) - SPREAD_PERC = float(input('Maksymalny procent spreadu do uznania zasobu za charakteryzujący ' - 'się płynnym rynkiem? (%): ')) + # AVG_WINDOW = int(input('Przedział z jakiego liczyć średnią (max 10): ')) + # RSI_WINDOW = int(input('Przedział z jakiego liczyć wskaźnik RSI? (max 10): ')) + # VOLATILE_SAMPLES = int(input('Przedział z jakiego badać zmienność zasobu? (max 10): ')) + # VOLATILE_PERC = float(input('Procentowy próg do uznania zasobu za zmienny? (%): ')) + # SPREAD_PERC = float(input('Maksymalny procent spreadu do uznania zasobu za charakteryzujący ' + # 'się płynnym rynkiem? (%): ')) + AVG_WINDOW = 5 + RSI_WINDOW = 10 + VOLATILE_SAMPLES = 5 + VOLATILE_PERC = 1.9 + SPREAD_PERC = 0.8 downward_icon, upward_icon, question_icon = get_icons('downward', 'upward', 'question') volatile_icon, liquid_icon = get_icons('fire', 'liquidity', transparent=False) diff --git a/utils.py b/utils.py index 39816f05..99f3bb42 100644 --- a/utils.py +++ b/utils.py @@ -21,7 +21,7 @@ def get_icons(*icon_names, transparent=True): if transparent: alpha_channel = icon_image.getchannel('A') - with_alpha = alpha_channel.point(lambda i: 32 if i > 0 else 0) + with_alpha = alpha_channel.point(lambda a: 32 if a > 0 else 0) icon_image.putalpha(with_alpha) processed_list.append(icon_image) From 902a5d8d7f2babf64c2e6c90ae3eb7775502e194 Mon Sep 17 00:00:00 2001 From: blaszczykk Date: Fri, 28 May 2021 23:13:32 +0200 Subject: [PATCH 5/7] further refactor of the code from l5 --- main.py | 104 ++++++++++++++++++++++++++++--------------------------- utils.py | 20 ++++++++++- 2 files changed, 72 insertions(+), 52 deletions(-) diff --git a/main.py b/main.py index a1444a08..468e7b45 100644 --- a/main.py +++ b/main.py @@ -21,11 +21,10 @@ # tak, by po ponownym uruchomieniu można było wprowadzić nazwę pliku przechowującego dane # i nie gromadzić danych od nowa. +from utils import * import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation from matplotlib.offsetbox import OffsetImage, AnnotationBbox -from datetime import datetime, timedelta -from utils import * import requests import time @@ -61,9 +60,7 @@ def get_transactions(crypto_pairs, transaction_storage, limit, timeframe): for pair in crypto_pairs: - now = datetime.now() - delta = timedelta(minutes=timeframe) - unix_epoch_time = int((now - delta).timestamp()) * 1000 + unix_epoch_time = get_unix_time(timeframe) try: request_volume = requests.get( @@ -168,6 +165,7 @@ def calculate_rsi(askbid_storage, rsi_storage, window_size): def classify_trend(rsi_storage, trend_list): for curr_pair in range(3): + latest_ask_rsi = rsi_storage[-1][curr_pair][0] if latest_ask_rsi >= 65: trend_list[curr_pair] = 'upward' @@ -177,16 +175,6 @@ def classify_trend(rsi_storage, trend_list): trend_list[curr_pair] = 'horizontal' -def choose_trend_icon(trend): - - if trend == 'upward': - return upward_icon - elif trend == 'downward': - return downward_icon - else: - return question_icon - - def select_candidate(trends_list, volume_slice): temp = [] @@ -212,7 +200,10 @@ def check_volatility(transaction_storage, pair, threshold, samples): inner_temp = [float(curr_trans['items'][tran]['r']) for tran in range(trans_amount)] temp.extend(inner_temp) - percentage = calculate_percent_diff(max(temp), min(temp)) + try: + percentage = calculate_percent_diff(max(temp), min(temp)) + except ValueError: + percentage = 0 return (lambda perc: True if perc > threshold else False)(percentage) @@ -238,7 +229,10 @@ def check_liquidity(transaction_storage, pair, threshold): except ZeroDivisionError: return 0 - percentage = calculate_percent_diff(ask, bid) + try: + percentage = calculate_percent_diff(ask, bid) + except ValueError: + percentage = 0 return (lambda spread: True if spread < threshold else False)(percentage) @@ -275,9 +269,6 @@ def draw_figure(frame_number): avg_asks.append(avg_sample[curr_pair][0]) avg_bids.append(avg_sample[curr_pair][1]) - volatile = check_volatility(trans_storage, curr_pair, VOLATILE_PERC, VOLATILE_SAMPLES) - liquid = check_liquidity(trans_storage, curr_pair, SPREAD_PERC) - plt.plot(time_samples, asks, "-o", label=data_storage[0][curr_pair][0] + " ask") plt.plot(time_samples, bids, "-o", label=data_storage[0][curr_pair][0] + " bid") plt.plot(time_samples, avg_asks, "o:", color="#185986", @@ -287,30 +278,32 @@ def draw_figure(frame_number): axes = plt.gca() - icon_trend = choose_trend_icon(trends_of_pairs[curr_pair]) + icon_trend = (lambda trend: upward_icon if trend == 'upward' + else (downward_icon if trend == 'downward' + else question_icon))(trends_of_pairs[curr_pair]) imagebox_trend = OffsetImage(icon_trend, zoom=0.4) imagebox_trend.image.axes = axes ab_trend = AnnotationBbox(imagebox_trend, (0.5, 0.5), xycoords='axes fraction', boxcoords="offset points", pad=0.3, frameon=0) axes.add_artist(ab_trend) - if volatile: - - imagebox_volatile = OffsetImage(volatile_icon, zoom=0.1) - imagebox_volatile.image.axes = axes - ab_volatile = AnnotationBbox(imagebox_volatile, (0.95, 1.4), xycoords='axes fraction', - boxcoords="offset points", pad=0, frameon=0, - annotation_clip=False) - axes.add_artist(ab_volatile) - - if liquid: - - imagebox_liquid = OffsetImage(liquid_icon, zoom=0.1) - imagebox_liquid.image.axes = axes - ab_liquid = AnnotationBbox(imagebox_liquid, (0.9, 1.4), xycoords='axes fraction', - boxcoords="offset points", pad=0, frameon=0, - annotation_clip=False) - axes.add_artist(ab_liquid) + volatile_test = check_volatility(trans_storage, curr_pair, VOLATILE_PERC, VOLATILE_SAMPLES) + vol_icon = (lambda test: volatile_icon if test else tp_volatile_icon)(volatile_test) + imagebox_volatile = OffsetImage(vol_icon, zoom=0.1) + imagebox_volatile.image.axes = axes + ab_volatile = AnnotationBbox(imagebox_volatile, (0.95, 1.4), xycoords='axes fraction', + boxcoords="offset points", pad=0, frameon=0, + annotation_clip=False) + axes.add_artist(ab_volatile) + + liquid_test = check_liquidity(trans_storage, curr_pair, SPREAD_PERC) + liq_icon = (lambda test: liquid_icon if test else tp_liquid_icon)(liquid_test) + imagebox_liquid = OffsetImage(liq_icon, zoom=0.1) + imagebox_liquid.image.axes = axes + ab_liquid = AnnotationBbox(imagebox_liquid, (0.9, 1.4), xycoords='axes fraction', + boxcoords="offset points", pad=0, frameon=0, + annotation_clip=False) + axes.add_artist(ab_liquid) if candidate == curr_pair: for loc, spine in axes.spines.items(): @@ -320,9 +313,21 @@ def draw_figure(frame_number): else: spine.set_position(("outward", -1)) spine.set_linewidth(3) - spine.set_edgecolor('#859900') + # spine.set_edgecolor('#859900') + spine.set_edgecolor('#ffae1a') + # spine.set_edgecolor('#ff751a') spine.set_alpha(0.7) + # nanosimy poziomą, przerywaną linią + # global counter + # counter += 1 + # if counter >= 4 and counter < 10: + # axes.axhline(y=999, color='r', linestyle='dashed') + # elif counter >= 10 and counter < 15: + # axes.axhline(y=99, color='r', linestyle='dashed') + # else: + # axes.axhline(y=999, color='r', linestyle='dashed') + plt.xlabel("Time", fontsize=9) plt.ylabel("Exchange Rates", fontsize=9) plt.xticks(rotation='vertical', fontsize=7) @@ -361,14 +366,8 @@ def draw_figure(frame_number): ax.margins(y=0.2) plt.xticks(rotation='vertical', fontsize=7) - if len(time_samples) > 9: - del time_samples[0] - del data_storage[0] - del avg_storage[0] - del vol_storage[0] - del askbid_storage[0] - del rsi_storage[0] - del trans_storage[0] + clear_older_data(data_storage, avg_storage, vol_storage, askbid_storage, rsi_storage, + trans_storage, trigger_list=time_samples, treshold=10) plt.tight_layout() plt.subplots_adjust(top=0.85) @@ -376,7 +375,7 @@ def draw_figure(frame_number): if __name__ == '__main__': - PAIRS = [('LTC', 'PLN'), ('ETH', 'PLN'), ('DASH', 'PLN')] + PAIRS = [('LTC', 'PLN'), ('ETH', 'PLN'), ('BCC', 'PLN')] FREQ = 5 # AVG_WINDOW = int(input('Przedział z jakiego liczyć średnią (max 10): ')) # RSI_WINDOW = int(input('Przedział z jakiego liczyć wskaźnik RSI? (max 10): ')) @@ -387,10 +386,13 @@ def draw_figure(frame_number): AVG_WINDOW = 5 RSI_WINDOW = 10 VOLATILE_SAMPLES = 5 - VOLATILE_PERC = 1.9 - SPREAD_PERC = 0.8 + VOLATILE_PERC = 5 + SPREAD_PERC = 2.85 + + counter = 0 - downward_icon, upward_icon, question_icon = get_icons('downward', 'upward', 'question') + downward_icon, upward_icon, question_icon, tp_volatile_icon, tp_liquid_icon \ + = get_icons('downward', 'upward', 'question', 'fire', 'liquidity') volatile_icon, liquid_icon = get_icons('fire', 'liquidity', transparent=False) animation = FuncAnimation(plt.figure(), draw_figure, interval=1000*FREQ) diff --git a/utils.py b/utils.py index 99f3bb42..73629124 100644 --- a/utils.py +++ b/utils.py @@ -1,8 +1,10 @@ -from PIL import Image +from datetime import datetime, timedelta from pathlib import Path +from PIL import Image def calculate_percent_diff(maxi, mini): + if maxi == mini: return 100.0 try: @@ -11,6 +13,15 @@ def calculate_percent_diff(maxi, mini): return 0 +def clear_older_data(*lists_to_clear, trigger_list, treshold): + + if len(trigger_list) >= treshold: + + del trigger_list[0] + for list_to_clear in lists_to_clear: + del list_to_clear[0] + + def get_icons(*icon_names, transparent=True): processed_list = [] @@ -28,3 +39,10 @@ def get_icons(*icon_names, transparent=True): return processed_list + +def get_unix_time(timeframe): + + now = datetime.now() + delta = timedelta(minutes=timeframe) + + return int((now - delta).timestamp()) * 1000 From fafafaddff99c6cc6a051fc4d4ab48d9137813f2 Mon Sep 17 00:00:00 2001 From: blaszczykk Date: Mon, 7 Jun 2021 13:49:27 +0200 Subject: [PATCH 6/7] feat: added gui --- api.py | 59 +++++++ calc.py | 148 +++++++++++++++++ gui.py | 482 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ utils.py | 4 +- 4 files changed, 691 insertions(+), 2 deletions(-) create mode 100644 api.py create mode 100644 calc.py create mode 100644 gui.py diff --git a/api.py b/api.py new file mode 100644 index 00000000..46767124 --- /dev/null +++ b/api.py @@ -0,0 +1,59 @@ +import requests +from utils import * + + +def get_data(crypto_pairs, data_storage, askbid_storage): + + curr_temp, askbid_temp = ([] for _ in range(2)) + + for pair in crypto_pairs: + try: + request_orders = requests.get( + f"https://bitbay.net/API/Public/{pair[0]}{pair[1]}/ticker.json" + ) + orders = request_orders.json() + curr_temp.append([f'{pair[0]}-{pair[1]}', (orders['ask'], orders['bid'])]) + askbid_temp.append((orders['ask'], orders['bid'])) + + except requests.exceptions.RequestException: + print("Connection problem with the ticker API.") + return None + + askbid_storage.append(askbid_temp) + data_storage.append(curr_temp) + + +def get_transactions(crypto_pairs, transaction_storage, limit, timeframe): + + trans_temp = [] + + for pair in crypto_pairs: + + unix_epoch_time = get_unix_time(timeframe) + + try: + request_volume = requests.get( + f"https://api.bitbay.net/rest/trading/transactions/{pair[0]}-{pair[1]}", + params={'limit': limit, 'fromTime': unix_epoch_time} + ) + transactions = request_volume.json() + trans_temp.append(transactions) + + except requests.exceptions.RequestException: + print("Connection problem with the transactions API.") + trans_temp.append(None) + + transaction_storage.append(trans_temp) + + +def get_volume(transaction_storage, volume_storage): + + vol_temp = [] + + for curr_pair in range(3): + latest_trans = transaction_storage[-1][curr_pair] + trans_amount = len(latest_trans['items']) + volume = sum([float(latest_trans['items'][tran]['a']) for tran in range(trans_amount)]) + vol_temp.append(volume) + + volume_storage.append(vol_temp) diff --git a/calc.py b/calc.py new file mode 100644 index 00000000..adbaeee6 --- /dev/null +++ b/calc.py @@ -0,0 +1,148 @@ +from utils import * + + +def calculate_mov_avg(askbid_storage, avg_storage, window_size): + + storage_slice = askbid_storage[-window_size:] + temp = [] + + for curr_pair in range(3): + inner_temp = [] + + for ask_or_bid in range(2): + summation = 0 + + for sample in range(0, len(storage_slice)): + summation += storage_slice[sample][curr_pair][ask_or_bid] + summation /= len(storage_slice) + inner_temp.append(summation) + + temp.append(inner_temp) + + avg_storage.append(temp) + + +def calculate_rsi(askbid_storage, rsi_storage, window_size): + + storage_slice = askbid_storage[-window_size:] + temp = [] + + for curr_pair in range(3): + inner_temp = [] + + for ask_or_bid in range(2): + upward, upward_counter = 0, 0 + downward, downward_counter = 0, 0 + + for sample in range(1, len(storage_slice)): + if storage_slice[sample-1][curr_pair][ask_or_bid] \ + < storage_slice[sample][curr_pair][ask_or_bid]: + + up = storage_slice[sample][curr_pair][ask_or_bid] \ + - storage_slice[sample-1][curr_pair][ask_or_bid] + upward += up + upward_counter += 1 + + elif storage_slice[sample-1][curr_pair][ask_or_bid] \ + > storage_slice[sample][curr_pair][ask_or_bid]: + + down = storage_slice[sample-1][curr_pair][ask_or_bid] \ + - storage_slice[sample][curr_pair][ask_or_bid] + downward += down + downward_counter += 1 + + if upward_counter == 0: + a = 1 + else: + a = upward / upward_counter + + if downward_counter == 0: + b = 1 + else: + b = downward / downward_counter + + try: + rsi = 100 - (100 / (1 + (a / b))) + except ZeroDivisionError: + a, b = 1, 1 + rsi = 100 - (100 / (1 + (a / b))) + inner_temp.append(rsi) + + temp.append(inner_temp) + + rsi_storage.append(temp) + + +def classify_trend(rsi_storage, trend_list): + + for curr_pair in range(3): + + latest_ask_rsi = rsi_storage[-1][curr_pair][0] + if latest_ask_rsi >= 65: + trend_list[curr_pair] = 'upward' + elif latest_ask_rsi <= 35: + trend_list[curr_pair] = 'downward' + else: + trend_list[curr_pair] = 'horizontal' + + +def select_candidate(trends_list, volume_slice): + + temp = [] + for curr_pair in range(3): + + if trends_list[curr_pair] != 'downward': + temp.append(volume_slice[curr_pair]) + + if temp: + highest_volume = max(temp) + return volume_slice.index(highest_volume) + else: + return None + + +def check_volatility(transaction_storage, pair, threshold, samples): + + trans_slice = transaction_storage[-samples:] + temp = [] + for sample in range(len(trans_slice)): + curr_trans = trans_slice[sample][pair] + trans_amount = len(curr_trans['items']) + inner_temp = [float(curr_trans['items'][tran]['r']) for tran in range(trans_amount)] + temp.extend(inner_temp) + + try: + percentage = calculate_percent_diff(max(temp), min(temp)) + except ValueError: + percentage = 0 + + return (lambda perc: True if perc > threshold else False)(percentage) + + +def check_liquidity(transaction_storage, pair, threshold): + + trans_slice = transaction_storage[-1:] + curr_trans = trans_slice[0][pair] + trans_amount = len(curr_trans['items']) + + temp_asks = [float(curr_trans['items'][tran]['r']) for tran in range(trans_amount) + if curr_trans['items'][tran]['ty'] == "Buy"] + temp_bids = [float(curr_trans['items'][tran]['r']) for tran in range(trans_amount) + if curr_trans['items'][tran]['ty'] == "Sell"] + + try: + ask = sum(temp_asks) / len(temp_asks) + except ZeroDivisionError: + return 0 + + try: + bid = sum(temp_bids) / len(temp_bids) + except ZeroDivisionError: + return 0 + + try: + percentage = calculate_percent_diff(ask, bid) + except ValueError: + percentage = 0 + + return (lambda spread: True if spread < threshold else False)(percentage) diff --git a/gui.py b/gui.py new file mode 100644 index 00000000..f8240d38 --- /dev/null +++ b/gui.py @@ -0,0 +1,482 @@ +import time +import json +import matplotlib +from matplotlib import pyplot as plt +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from matplotlib.offsetbox import OffsetImage, AnnotationBbox +from matplotlib.animation import FuncAnimation +import tkinter as tk +from tkinter import ttk +from api import * +from calc import * + + +matplotlib.use('TkAgg') +plt.style.use('Solarize_Light2') +fig = plt.figure() + +time_samples, data_storage, avg_storage, rsi_storage, vol_storage, askbid_storage, trans_storage \ + = ([] for _ in range(7)) + +curr_avg_prices = [None]*3 +curr_balance = [0]*3 + +class CryptoApp(tk.Tk): + + def __init__(self, *args, **kwargs): + + tk.Tk.__init__(self, *args, **kwargs) + tk.Tk.wm_title(self, 'Cryptotrading App') + ttk.Style().theme_use('clam') + + container = tk.Frame(self) + container.pack(side='top', fill='both', expand=True) + container.grid_rowconfigure(0, weight=1) + container.grid_columnconfigure(0, weight=1) + + self.frames = {} + + for F in (StartPage, GraphPage, WalletPage): + + frame = F(container, self) + self.frames[F] = frame + frame.grid(row=0, column=0, sticky='nsew') + + self.show_frame(StartPage) + + def show_frame(self, cont): + + frame = self.frames[cont] + frame.tkraise() + + +class StartPage(tk.Frame): + + def __init__(self, parent, controller): + + input_labels = 'Interval for the average (max 10): ', \ + 'Interval for the RSI (max 10): ', \ + 'Interval for volatility check (max 10): ', \ + 'Threshold above which the asset is volatile (%): ', \ + 'Maximum spread for which the asset is liquid (%): ' + + init_constants = ['AVG_WINDOW', 'RSI_WINDOW', 'VOLATILE_SAMPLES', 'VOLATILE_PERC', + 'SPREAD_PERC'] + + def fetch_constants(entries): + + for index in range(len(entries)): + + if len(entries[index].get()) == 0: + pass + else: + try: + globals()[f'{init_constants[index]}'] = int(entries[index].get()) + except ValueError: + pass + + tk.Frame.__init__(self, parent) + title = tk.Label(self, text='Cryptocurrency App L6', font=('Tahoma', 14, 'bold')) + title.pack(pady=40, padx=10) + + init_inputs = [] + for input_label in input_labels: + label = tk.Label(self, text=input_label) + entry = tk.Entry(self) + label.pack() + entry.pack() + init_inputs.append(entry) + + button_submit = ttk.Button(self, text='Submit', width=30, + command=lambda: fetch_constants(init_inputs)) + button_submit.pack(ipady=10, pady=40) + + button_graph = ttk.Button(self, text='Exchange Rates Graph', width=30, + command=lambda: controller.show_frame(GraphPage)) + button_graph.pack(ipady=10) + + button_wallet = ttk.Button(self, text='My Wallet', width=30, + command=lambda: controller.show_frame(WalletPage)) + button_wallet.pack(ipady=10) + + button_quit = ttk.Button(self, text='Quit', command=quit) + button_quit.pack(side='bottom', ipadx=50, ipady=10, pady=10) + + +class WalletPage(tk.Frame): + + def __init__(self, parent, controller): + + global PAIRS + global curr_avg_prices + global curr_balance + + queue_first_curr, queue_sec_curr, queue_third_curr = ([] for _ in range(3)) + wallet = [0]*3 + + tk.Frame.__init__(self, parent) + self.columnconfigure((0, 5), weight=5) + self.rowconfigure(0, weight=2) + self.rowconfigure(14, weight=5) + self.rowconfigure(17, weight=9) + + def calculate_curr_avg_and_wallet(amount, price, queue, num): + + if (len(amount.get()) == 0 or len(price.get()) == 0) or float(amount.get()) == 0: + return None + else: + try: + amount_bought = float(amount.get()) + price_bought = float(price.get()) + except ValueError: + return None + + queue.append([amount_bought, price_bought]) + wallet[num] += amount_bought + + temp = 0 + for transaction in queue: + temp += transaction[1] + curr_avg_prices[num] = temp / len(queue) + + def calculate_curr_balance(amount, price, queue, num): + + if (len(amount.get()) == 0 or len(price.get()) == 0) or float(amount.get()) == 0: + return None + else: + try: + amount_sold = float(amount.get()) + price_sold = float(price.get()) + except ValueError: + return None + + if amount_sold > wallet[num]: + return None + else: + wallet[num] -= amount_sold + + for transaction in queue: + + if amount_sold > transaction[0]: + curr_balance[num] += (transaction[0] * (price_sold - transaction[1])) + amount_sold -= transaction[0] + transaction[0] = 0 + else: + curr_balance[num] += (amount_sold * (price_sold - transaction[1])) + transaction[0] -= amount_sold + break + + queue = list(filter(lambda trans: trans[0] != 0, queue)) + + def save_to_json(): + + trans_list = [queue_first_curr, queue_sec_curr, queue_third_curr] + data = {} + for curr in PAIRS: + data[f'{curr[0]}'] = {} + print(data) + for index_elem in range(len(data)): + data[f'{curr[0]}']['balance'] = curr_balance[index_elem] + data[f'{curr[0]}']['transactions'] = trans_list[index_elem] + + now = datetime.now().strftime("%Y%m%d-%H%M%S") + with open(f'{now}.txt', 'w') as outfile: + json.dump(data, outfile) + + def read_json(): + pass + + title = tk.Label(self, text='My Wallet', font=('Tahoma', 14, 'bold')) + title.grid(row=0, column=1, columnspan=4, sticky='ew') + + label_bought = tk.Label(self, text='Bought', font=('Tahoma', 12, 'bold')) + label_sold = tk.Label(self, text='Sold', font=('Tahoma', 12, 'bold')) + label_sold.grid(row=2, column=3, columnspan=2) + label_bought.grid(row=2, column=1, columnspan=2) + + label_amount_b = tk.Label(self, text='Amount') + label_price_b = tk.Label(self, text='Price') + label_amount_s = tk.Label(self, text='Amount') + label_price_s = tk.Label(self, text='Price') + label_amount_b.grid(row=3, column=1) + label_amount_s.grid(row=3, column=3) + label_price_b.grid(row=3, column=2) + label_price_s.grid(row=3, column=4) + + # 1 + label_curr_one_bought = tk.Label(self, text=f'{PAIRS[0][0]}-{PAIRS[0][1]}') + label_curr_one_sold = tk.Label(self, text=f'{PAIRS[0][0]}-{PAIRS[0][1]}') + label_curr_one_bought.grid(row=4, column=1, columnspan=2) + label_curr_one_sold.grid(row=4, column=3, columnspan=2) + + entry_curr_one_amount_b = tk.Entry(self) + entry_curr_one_price_b = tk.Entry(self) + entry_curr_one_amount_b.grid(row=5, column=1, padx=10) + entry_curr_one_price_b.grid(row=5, column=2) + + entry_curr_one_amount_s = tk.Entry(self) + entry_curr_one_price_s = tk.Entry(self) + entry_curr_one_amount_s.grid(row=5, column=3, padx=10) + entry_curr_one_price_s.grid(row=5, column=4) + + button_curr_one_submit_b = ttk.Button(self, text='Submit', width=30, + command=lambda: + calculate_curr_avg_and_wallet(entry_curr_one_amount_b, + entry_curr_one_price_b, + queue_first_curr, 0)) + button_curr_one_submit_s = ttk.Button(self, text='Submit', width=30, + command=lambda: + calculate_curr_balance(entry_curr_one_amount_s, + entry_curr_one_price_s, + queue_first_curr, 0)) + button_curr_one_submit_b.grid(row=6, column=1, columnspan=2) + button_curr_one_submit_s.grid(row=6, column=3, columnspan=2) + + # 2 + label_curr_two_bought = tk.Label(self, text=f'{PAIRS[1][0]}-{PAIRS[1][1]}') + label_curr_two_sold = tk.Label(self, text=f'{PAIRS[1][0]}-{PAIRS[1][1]}') + label_curr_two_bought.grid(row=7, column=1, columnspan=2) + label_curr_two_sold.grid(row=7, column=3, columnspan=2) + + entry_curr_two_amount_b = tk.Entry(self) + entry_curr_two_price_b = tk.Entry(self) + entry_curr_two_amount_b.grid(row=8, column=1) + entry_curr_two_price_b.grid(row=8, column=2) + + entry_curr_two_amount_s = tk.Entry(self) + entry_curr_two_price_s = tk.Entry(self) + entry_curr_two_amount_s.grid(row=8, column=3) + entry_curr_two_price_s.grid(row=8, column=4) + + button_curr_two_submit_b = ttk.Button(self, text='Submit', width=30, + command=lambda: + calculate_curr_avg_and_wallet(entry_curr_two_amount_b, + entry_curr_two_price_b, + queue_sec_curr, 1)) + button_curr_two_submit_s = ttk.Button(self, text='Submit', width=30, + command=lambda: + calculate_curr_balance(entry_curr_two_amount_s, + entry_curr_two_price_s, + queue_first_curr, 1)) + button_curr_two_submit_b.grid(row=9, column=1, columnspan=2) + button_curr_two_submit_s.grid(row=9, column=3, columnspan=2) + + # 3 + label_curr_three_bought = tk.Label(self, text=f'{PAIRS[2][0]}-{PAIRS[2][1]}') + label_curr_three_sold = tk.Label(self, text=f'{PAIRS[2][0]}-{PAIRS[2][1]}') + label_curr_three_bought.grid(row=10, column=1, columnspan=2) + label_curr_three_sold.grid(row=10, column=3, columnspan=2) + + entry_curr_three_amount_b = tk.Entry(self) + entry_curr_three_price_b = tk.Entry(self) + entry_curr_three_amount_b.grid(row=11, column=1) + entry_curr_three_price_b.grid(row=11, column=2) + + entry_curr_three_amount_s = tk.Entry(self) + entry_curr_three_price_s = tk.Entry(self) + entry_curr_three_amount_s.grid(row=11, column=3) + entry_curr_three_price_s.grid(row=11, column=4) + + button_curr_two_submit_b = ttk.Button(self, text='Submit', width=30, + command=lambda: + calculate_curr_avg_and_wallet(entry_curr_three_amount_b, + entry_curr_three_price_b, + queue_third_curr, 2)) + button_curr_two_submit_s = ttk.Button(self, text='Submit', width=30, + command=lambda: + calculate_curr_balance(entry_curr_three_amount_s, + entry_curr_three_price_s, + queue_first_curr, 2)) + button_curr_two_submit_b.grid(row=12, column=1, columnspan=2) + button_curr_two_submit_s.grid(row=12, column=3, columnspan=2) + + # json + + button_save_json = ttk.Button(self, text='Save to file', width=30, + command=save_to_json) + button_save_json.grid(row=13, column=1, columnspan=2, ipady=10, pady=30) + + button_load_json = ttk.Button(self, text='Load from file', width=30) + button_load_json.grid(row=13, column=3, columnspan=2, ipady=10, pady=30) + + # nav + button_graph = ttk.Button(self, text='Exchange Rates Graph', width=30, + command=lambda: controller.show_frame(GraphPage)) + button_graph.grid(row=15, column=2, columnspan=2, ipady=10) + + button_menu = ttk.Button(self, text='Menu', width=30, + command=lambda: controller.show_frame(StartPage)) + button_menu.grid(row=16, column=2, columnspan=2, ipady=10) + + +class GraphPage(tk.Frame): + + def __init__(self, parent, controller): + tk.Frame.__init__(self, parent) + + canvas = FigureCanvasTkAgg(fig, self) + canvas.draw() + canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) + canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + button_wallet = ttk.Button(self, text='My Wallet', width=30, + command=lambda: controller.show_frame(WalletPage)) + button_wallet.pack() + + button_menu = ttk.Button(self, text='Menu', width=30, + command=lambda: controller.show_frame(StartPage)) + button_menu.pack() + + +def draw_figure(frame_number): + + time_samples.append(time.strftime("%H:%M:%S", time.localtime())) + + get_data(PAIRS, data_storage, askbid_storage) + get_transactions(PAIRS, trans_storage, limit=30, timeframe=45) + get_volume(trans_storage, vol_storage) + calculate_mov_avg(askbid_storage, avg_storage, AVG_WINDOW) + calculate_rsi(askbid_storage, rsi_storage, RSI_WINDOW) + + trends_of_pairs = ['']*3 + classify_trend(rsi_storage, trends_of_pairs) + candidate = select_candidate(trends_of_pairs, vol_storage[-1]) + + plt.clf() + fig.suptitle("Cryptocurrency Exchange Rates, RSI and Volume") + + for curr_pair in range(3): + + plt.subplot(3, 3, curr_pair+1) + + asks, bids, avg_asks, avg_bids = ([] for _ in range(4)) + + for sample in data_storage: + asks.append(sample[curr_pair][1][0]) + bids.append(sample[curr_pair][1][1]) + for avg_sample in avg_storage: + avg_asks.append(avg_sample[curr_pair][0]) + avg_bids.append(avg_sample[curr_pair][1]) + + plt.plot(time_samples, asks, "-o", label=data_storage[0][curr_pair][0] + " ask") + plt.plot(time_samples, bids, "-o", label=data_storage[0][curr_pair][0] + " bid") + plt.plot(time_samples, avg_asks, "o:", color="#185986", + label=data_storage[0][curr_pair][0] + " ask mov avg") + plt.plot(time_samples, avg_bids, "o:", color="#1b6762", + label=data_storage[0][curr_pair][0] + " bid mov avg") + + axes = plt.gca() + + balance_value = curr_balance[curr_pair] + if balance_value < 0: + axes.text(0.1, 1.4, f'Balance: {balance_value}', horizontalalignment='center', + color='#a50000', verticalalignment='center', transform=axes.transAxes) + else: + axes.text(0.1, 1.4, f'Balance: {balance_value}', horizontalalignment='center', + verticalalignment='center', transform=axes.transAxes) + + icon_trend = (lambda trend: upward_icon if trend == 'upward' + else (downward_icon if trend == 'downward' + else question_icon))(trends_of_pairs[curr_pair]) + imagebox_trend = OffsetImage(icon_trend, zoom=0.4) + imagebox_trend.image.axes = axes + ab_trend = AnnotationBbox(imagebox_trend, (0.5, 0.5), xycoords='axes fraction', + boxcoords="offset points", pad=0.3, frameon=0) + axes.add_artist(ab_trend) + + volatile_test = check_volatility(trans_storage, curr_pair, VOLATILE_PERC, VOLATILE_SAMPLES) + vol_icon = (lambda test: volatile_icon if test else tp_volatile_icon)(volatile_test) + imagebox_volatile = OffsetImage(vol_icon, zoom=0.1) + imagebox_volatile.image.axes = axes + ab_volatile = AnnotationBbox(imagebox_volatile, (0.95, 1.4), xycoords='axes fraction', + boxcoords="offset points", pad=0, frameon=0, + annotation_clip=False) + axes.add_artist(ab_volatile) + + liquid_test = check_liquidity(trans_storage, curr_pair, SPREAD_PERC) + liq_icon = (lambda test: liquid_icon if test else tp_liquid_icon)(liquid_test) + imagebox_liquid = OffsetImage(liq_icon, zoom=0.09) + imagebox_liquid.image.axes = axes + ab_liquid = AnnotationBbox(imagebox_liquid, (0.9, 1.4), xycoords='axes fraction', + boxcoords="offset points", pad=0, frameon=0, + annotation_clip=False) + axes.add_artist(ab_liquid) + + if candidate == curr_pair: + for loc, spine in axes.spines.items(): + if loc == 'bottom' or loc == 'top': + spine.set_position(("outward", 1)) + spine.set_capstyle('butt') + else: + spine.set_position(("outward", -1)) + spine.set_linewidth(3) + spine.set_edgecolor('#ffae1a') + spine.set_alpha(0.7) + + curr_avg = curr_avg_prices[curr_pair] + if curr_avg is not None: + axes.axhline(y=curr_avg, color='r', linestyle='dashed') + + plt.xlabel("Time", fontsize=9) + plt.ylabel("Exchange Rates", fontsize=9) + plt.xticks(rotation='vertical', fontsize=7) + plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left', + ncol=2, mode="expand", borderaxespad=0.) + + for curr_pair in range(3): + + plt.subplot(3, 3, curr_pair + 4) + rsi_asks, rsi_bids = ([] for _ in range(2)) + + for rsi_sample in rsi_storage: + rsi_asks.append(rsi_sample[curr_pair][0]) + rsi_bids.append(rsi_sample[curr_pair][1]) + + plt.plot(time_samples, rsi_asks, "o:", label=data_storage[0][curr_pair][0] + " ask RSI") + plt.plot(time_samples, rsi_bids, "o:", label=data_storage[0][curr_pair][0] + " bid RSI") + plt.xlabel("Time", fontsize=9) + plt.ylabel("RSI", fontsize=9) + plt.xticks(rotation='vertical', fontsize=7) + plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left', + ncol=2, mode="expand", borderaxespad=0.) + + for curr_pair in range(3): + + plt.subplot(3, 3, curr_pair + 7) + volume = [] + + for vol_sample in vol_storage: + volume.append(vol_sample[curr_pair]) + + plt.bar(time_samples, volume, align="center") + plt.xlabel("Time", fontsize=9) + plt.ylabel("Volume", fontsize=9) + ax = plt.gca() + ax.margins(y=0.2) + plt.xticks(rotation='vertical', fontsize=7) + + clear_older_data(data_storage, avg_storage, vol_storage, askbid_storage, rsi_storage, + trans_storage, trigger_list=time_samples, threshold=10) + + plt.tight_layout() + plt.subplots_adjust(top=0.85) + + +if __name__ == '__main__': + + PAIRS = [('LTC', 'PLN'), ('ETH', 'PLN'), ('BCC', 'PLN')] + FREQ = 5 + AVG_WINDOW = 5 + RSI_WINDOW = 10 + VOLATILE_SAMPLES = 5 + VOLATILE_PERC = 5 + SPREAD_PERC = 2 + + downward_icon, upward_icon, question_icon, tp_volatile_icon, tp_liquid_icon \ + = get_icons('downward', 'upward', 'question', 'fire', 'liquidity') + volatile_icon, liquid_icon = get_icons('fire', 'liquidity', transparent=False) + + app = CryptoApp() + app.state('zoomed') + ani = FuncAnimation(fig, draw_figure, interval=1000*FREQ) + app.mainloop() diff --git a/utils.py b/utils.py index 73629124..22dc9fab 100644 --- a/utils.py +++ b/utils.py @@ -13,9 +13,9 @@ def calculate_percent_diff(maxi, mini): return 0 -def clear_older_data(*lists_to_clear, trigger_list, treshold): +def clear_older_data(*lists_to_clear, trigger_list, threshold): - if len(trigger_list) >= treshold: + if len(trigger_list) >= threshold: del trigger_list[0] for list_to_clear in lists_to_clear: From 89f40211cbfa920ef8a61a02b8acb8ff3e7bd42f Mon Sep 17 00:00:00 2001 From: blaszczykk Date: Mon, 7 Jun 2021 18:13:43 +0200 Subject: [PATCH 7/7] feat: basic gui complete --- gui.py | 89 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/gui.py b/gui.py index f8240d38..28a6a354 100644 --- a/gui.py +++ b/gui.py @@ -1,26 +1,17 @@ import time import json +import copy import matplotlib from matplotlib import pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.offsetbox import OffsetImage, AnnotationBbox from matplotlib.animation import FuncAnimation import tkinter as tk -from tkinter import ttk +from tkinter import ttk, filedialog from api import * from calc import * -matplotlib.use('TkAgg') -plt.style.use('Solarize_Light2') -fig = plt.figure() - -time_samples, data_storage, avg_storage, rsi_storage, vol_storage, askbid_storage, trans_storage \ - = ([] for _ in range(7)) - -curr_avg_prices = [None]*3 -curr_balance = [0]*3 - class CryptoApp(tk.Tk): def __init__(self, *args, **kwargs): @@ -137,7 +128,7 @@ def calculate_curr_avg_and_wallet(amount, price, queue, num): temp = 0 for transaction in queue: temp += transaction[1] - curr_avg_prices[num] = temp / len(queue) + curr_avg_prices[num] = round((temp / len(queue)), 2) def calculate_curr_balance(amount, price, queue, num): @@ -166,26 +157,44 @@ def calculate_curr_balance(amount, price, queue, num): transaction[0] -= amount_sold break - queue = list(filter(lambda trans: trans[0] != 0, queue)) + temp = copy.deepcopy(queue) + temp = list(filter(lambda t: t[0] != 0, temp)) + queue.clear() + queue.extend(temp) def save_to_json(): trans_list = [queue_first_curr, queue_sec_curr, queue_third_curr] + data = {} - for curr in PAIRS: - data[f'{curr[0]}'] = {} - print(data) - for index_elem in range(len(data)): - data[f'{curr[0]}']['balance'] = curr_balance[index_elem] - data[f'{curr[0]}']['transactions'] = trans_list[index_elem] - - now = datetime.now().strftime("%Y%m%d-%H%M%S") - with open(f'{now}.txt', 'w') as outfile: + for curr_pair in range(3): + data[f'{PAIRS[curr_pair][0]}'] = {} + data[f'{PAIRS[curr_pair][0]}']['balance'] = curr_balance[curr_pair] + data[f'{PAIRS[curr_pair][0]}']['avgprice'] = curr_avg_prices[curr_pair] + data[f'{PAIRS[curr_pair][0]}']['transactions'] = trans_list[curr_pair] + + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + with open(f'{timestamp}.txt', 'w') as outfile: json.dump(data, outfile) - def read_json(): - pass + def read_from_json(first_queue, sec_queue, third_queue): + + filename = filedialog.askopenfilename(title='Select your wallet file', + initialdir=Path.cwd(), + filetypes=(('Text files', '*.txt'), + ('All files', '*.*'))) + with open(filename, "r") as wallet_file: + prev_data = json.load(wallet_file) + + temp = [] + for curr_pair in range(3): + curr_balance[curr_pair] = prev_data[f'{PAIRS[curr_pair][0]}']['balance'] + curr_avg_prices[curr_pair] = prev_data[f'{PAIRS[curr_pair][0]}']['avgprice'] + temp.append(prev_data[f'{PAIRS[curr_pair][0]}']['transactions']) + + first_queue, sec_queue, third_queue = temp[0], temp[1], temp[2] + # wallet layout title = tk.Label(self, text='My Wallet', font=('Tahoma', 14, 'bold')) title.grid(row=0, column=1, columnspan=4, sticky='ew') @@ -199,8 +208,8 @@ def read_json(): label_amount_s = tk.Label(self, text='Amount') label_price_s = tk.Label(self, text='Price') label_amount_b.grid(row=3, column=1) - label_amount_s.grid(row=3, column=3) label_price_b.grid(row=3, column=2) + label_amount_s.grid(row=3, column=3) label_price_s.grid(row=3, column=4) # 1 @@ -211,11 +220,10 @@ def read_json(): entry_curr_one_amount_b = tk.Entry(self) entry_curr_one_price_b = tk.Entry(self) - entry_curr_one_amount_b.grid(row=5, column=1, padx=10) - entry_curr_one_price_b.grid(row=5, column=2) - entry_curr_one_amount_s = tk.Entry(self) entry_curr_one_price_s = tk.Entry(self) + entry_curr_one_amount_b.grid(row=5, column=1, padx=10) + entry_curr_one_price_b.grid(row=5, column=2) entry_curr_one_amount_s.grid(row=5, column=3, padx=10) entry_curr_one_price_s.grid(row=5, column=4) @@ -240,11 +248,10 @@ def read_json(): entry_curr_two_amount_b = tk.Entry(self) entry_curr_two_price_b = tk.Entry(self) - entry_curr_two_amount_b.grid(row=8, column=1) - entry_curr_two_price_b.grid(row=8, column=2) - entry_curr_two_amount_s = tk.Entry(self) entry_curr_two_price_s = tk.Entry(self) + entry_curr_two_amount_b.grid(row=8, column=1) + entry_curr_two_price_b.grid(row=8, column=2) entry_curr_two_amount_s.grid(row=8, column=3) entry_curr_two_price_s.grid(row=8, column=4) @@ -269,11 +276,10 @@ def read_json(): entry_curr_three_amount_b = tk.Entry(self) entry_curr_three_price_b = tk.Entry(self) - entry_curr_three_amount_b.grid(row=11, column=1) - entry_curr_three_price_b.grid(row=11, column=2) - entry_curr_three_amount_s = tk.Entry(self) entry_curr_three_price_s = tk.Entry(self) + entry_curr_three_amount_b.grid(row=11, column=1) + entry_curr_three_price_b.grid(row=11, column=2) entry_curr_three_amount_s.grid(row=11, column=3) entry_curr_three_price_s.grid(row=11, column=4) @@ -296,7 +302,10 @@ def read_json(): command=save_to_json) button_save_json.grid(row=13, column=1, columnspan=2, ipady=10, pady=30) - button_load_json = ttk.Button(self, text='Load from file', width=30) + button_load_json = ttk.Button(self, text='Load from file', width=30, + command=lambda: read_from_json(queue_first_curr, + queue_sec_curr, + queue_third_curr)) button_load_json.grid(row=13, column=3, columnspan=2, ipady=10, pady=30) # nav @@ -476,6 +485,16 @@ def draw_figure(frame_number): = get_icons('downward', 'upward', 'question', 'fire', 'liquidity') volatile_icon, liquid_icon = get_icons('fire', 'liquidity', transparent=False) + matplotlib.use('TkAgg') + plt.style.use('Solarize_Light2') + fig = plt.figure() + + time_samples, data_storage, avg_storage, rsi_storage, vol_storage, askbid_storage, trans_storage \ + = ([] for _ in range(7)) + + curr_avg_prices = [None] * 3 + curr_balance = [0] * 3 + app = CryptoApp() app.state('zoomed') ani = FuncAnimation(fig, draw_figure, interval=1000*FREQ)