From 19b00ee62ff86206b4f28bcf4d7c2d48c7416ac8 Mon Sep 17 00:00:00 2001 From: JUSTY Date: Wed, 26 May 2021 13:42:55 +0200 Subject: [PATCH 1/4] Implement ex 1 and 2 (only for crypto and foreign stock) --- API/Api.py | 46 +++++++++++++++++++ API/Bitbay.py | 32 +++++++++++++ API/Bittrex.py | 32 +++++++++++++ API/Yahoo.py | 26 +++++++++++ API/__pycache__/Api.cpython-38.pyc | Bin 0 -> 1884 bytes API/__pycache__/Bitbay.cpython-38.pyc | Bin 0 -> 1738 bytes API/__pycache__/Bittrex.cpython-38.pyc | Bin 0 -> 2035 bytes API/__pycache__/Yahoo.cpython-38.pyc | Bin 0 -> 1263 bytes API/__pycache__/const.cpython-38.pyc | Bin 0 -> 944 bytes API/const.py | 9 ++++ Services.py | 60 +++++++++++++++++++++++++ Wallet.py | 31 +++++++++++++ app.py | 15 +++++++ resources.json | 30 +++++++++++++ utils.py | 9 ++++ 15 files changed, 290 insertions(+) create mode 100644 API/Api.py create mode 100644 API/Bitbay.py create mode 100644 API/Bittrex.py create mode 100644 API/Yahoo.py create mode 100644 API/__pycache__/Api.cpython-38.pyc create mode 100644 API/__pycache__/Bitbay.cpython-38.pyc create mode 100644 API/__pycache__/Bittrex.cpython-38.pyc create mode 100644 API/__pycache__/Yahoo.cpython-38.pyc create mode 100644 API/__pycache__/const.cpython-38.pyc create mode 100644 API/const.py create mode 100644 Services.py create mode 100644 Wallet.py create mode 100644 app.py create mode 100644 resources.json create mode 100644 utils.py diff --git a/API/Api.py b/API/Api.py new file mode 100644 index 00000000..a4626062 --- /dev/null +++ b/API/Api.py @@ -0,0 +1,46 @@ +import requests +from abc import ABC, abstractmethod + + +def request(url): + headers = {'content-type': 'application/json'} + response = requests.request("GET", url, headers=headers) + if 199 < response.status_code < 300: + return response.json() + elif 300 < response.status_code < 399: + raise Exception('Redirection. Need additional action in order to complete request.') + elif 400 < response.status_code < 499: + raise Exception('Client error. Server cannot understand the request.') + elif 500 < response.status_code < 599: + raise Exception('Server error. Please contact with API providers.') + return None + + +class Api(ABC): + def __init__(self, name, base_url): + self.__name = name + self.__base_url = base_url + + @property + def name(self): + return self.__name + + @property + def url(self): + return self.__base_url + + @abstractmethod + def markets(self): + pass + + @abstractmethod + def orderbook(self, first, second): + pass + + @abstractmethod + def transfer_fee(self, currency): + pass + + @abstractmethod + def taker_fee(self): + pass diff --git a/API/Bitbay.py b/API/Bitbay.py new file mode 100644 index 00000000..e466ad45 --- /dev/null +++ b/API/Bitbay.py @@ -0,0 +1,32 @@ +from API.Api import Api, request +from API.const import transfer + + +class Bitbay(Api): + + def __init__(self): + super().__init__('bitbay', 'https://api.bitbay.net/rest/trading') + self.__fees = transfer + + @property + def markets(self): + markets = request(f'{self.url}/ticker') + return list(markets['items'].keys()) + + def orderbook(self, first, second): + orderbook = request(f'{self.url}/orderbook/{first}-{second}') + asks = list(map(lambda x: {'rate': float(x['ra']), 'quantity': float( + x['ca'])}, orderbook['sell'])) + bids = list(map(lambda x: {'rate': float(x['ra']), 'quantity': float( + x['ca'])}, orderbook['buy'])) + return asks, bids + + def transfer_fee(self, currency): + if currency in self.__fees: + return self.__fees[currency] + else: + raise ValueError(f'Unidentified currency symbol - {currency}') + + @property + def taker_fee(self): + return 0.0042 \ No newline at end of file diff --git a/API/Bittrex.py b/API/Bittrex.py new file mode 100644 index 00000000..134579c8 --- /dev/null +++ b/API/Bittrex.py @@ -0,0 +1,32 @@ +from API.Api import Api, request + + +class Bittrex(Api): + def __init__(self): + super().__init__('bittrex', 'https://api.bittrex.com/v3') + + fees = request(f'{self.url}/currencies') + self.__fees = {x['symbol']: float(x['txFee']) for x in fees} + + @property + def markets(self): + markets = request(f'{self.url}/markets') + return list(map(lambda x: x['symbol'], markets)) + + def orderbook(self, first, second): + orderbook = request(f'{self.url}/markets/{first}-{second}/orderbook') + bids = list(map(lambda x: {'rate': float(x['rate']), 'quantity': float( + x['quantity'])}, orderbook['bid'])) + asks = list(map(lambda x: {'rate': float(x['rate']), 'quantity': float( + x['quantity'])}, orderbook['ask'])) + return asks, bids + + def transfer_fee(self, currency): + if currency in self.__fees: + return self.__fees[currency] + else: + raise ValueError(f'Unidentified currency symbol - {currency}') + + @property + def taker_fee(self): + return 0.0075 diff --git a/API/Yahoo.py b/API/Yahoo.py new file mode 100644 index 00000000..ce75bb77 --- /dev/null +++ b/API/Yahoo.py @@ -0,0 +1,26 @@ +import yfinance as yf + +from API.Api import Api + + +class Yahoo(Api): + def markets(self): + pass + + def orderbook(self, first, second): + pass + + def transfer_fee(self, currency): + pass + + def taker_fee(self): + pass + + def __init__(self): + super().__init__('Yahoo', "") + + @classmethod + def get_price(cls, stock_name, base_currency): + # TODO handle exception and check currency (info('currency')) + ticker = yf.Ticker(stock_name) + return (ticker.info["dayLow"] + ticker.info["dayHigh"]) / 2 diff --git a/API/__pycache__/Api.cpython-38.pyc b/API/__pycache__/Api.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..024e3683883ddd9f9c3afb6daf444ff738a8507f GIT binary patch literal 1884 zcmbtUOK;>v5bm};GoELLhj>IBEQAD>!tmPD3Zb3NhSf@J6zwcRvPKxk)zLWd*xv4* zg^Xq|n_LkbIfBH2aOOYoAAIGsXE<@9sy)e?WI;l-ROPbk@pV^!;{Uuh6)()lm+y(Qa-l=Yisv@wtiUXb0RA{+MSUL%SBEp@T7x zFJihT=5^@RV!neu#{5_zVVwcR0HXODyUSjjXIfIdfriD$fG@ z_Rf8um#vwIKuGoES3F;`g~w06LA4y;6Oc#|Q^LsG6#~2nAfeYN^{`)PcuD5v60<#> zdvQK5QlW(>#e;=VdX#)i#eSz^A8e-yZp9N)=5pjsgnWoeag=4b_7)l8>nHoA?n2Ml9klx}Fm@LObo*;nu~H!+531CA@&X1=O~dLln!|V_x!tQ zU!}TOw{*iW$r2rgFXEe`W;5R8%O17w-1WB^L=rHPZ4}i+wPJ_JaM?%G=W>9LGcIY* zm!RZx`0W)GMJHp{(j^Sq-G8aSfvNHZQpAI&2a#=jeGTQjdW))KBmTKK9hQdEY`v=V zjKXDW9?2u2l~*g?rQ+-FS1#_NJ68dG+jORE93(hlWm5?p8n|?nvTbH~KhKZesA2XA z^THFGCZd}b+5d)WRj6*fkWysv@nygs{%|vOAY^zT#9LVGyop1?tULdAH`_Wos&KYy zIOc2@o?X93yd-LbA(A|t=dei0?uFrlMU++^`8-xAUHr=wLh56~FGB-K<0FaC@?}!5 zlG-HolrzSn36MQhbnl8HT<$s+@7Pzx#Q9B0lrQaQKbF+>3W|O*#{VpU(*3a7|3`F1 MW@PX$WV?gE0PG2basU7T literal 0 HcmV?d00001 diff --git a/API/__pycache__/Bitbay.cpython-38.pyc b/API/__pycache__/Bitbay.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c684d8fcf70ccf00012f8e9d052dad664dbd5f9 GIT binary patch literal 1738 zcmbVNPmkL~6rUN79VeUJZlNHy2pUm@6r?7AdaA00ExW3UXoLvmU_~;HXHrb;I6E_u zNMtT#rC#_3rBZv`17B#aoZur=LhzoQq`T>jk><^K{=D~l@BQZa(e7@8K)ZVXk01VV z2>BB~E;bGyUqIKNfDlB`lw>rZ84DQ2Z%?FiEsozBZ5o1bOKknebV;+f?K4` ztaCI?Y)#42nbf9Dt#4E`(Ics1JaDlAh_dNp7$ga(AOU+qMpRJ2&iufUyfU<;Xhe8t zB=AH{_%POBuPz#A1or$Vgp;5SAa<;GFEPVt9@CX>9B_`#p*{T$2t$_Sd1>VZxMo;E zLPrp|@ARxUEEOzn{%Fip-|2RvY0|0IIumKS3cTuqOCp&(ZgcDE*;Fd)hhZ{FOc*LO zWOu^~Mi`Ey)aTT4Ez=Q<^)QUnNb4{>CoexfzBA~bJUr=t_igL;;9>v4@!;#DdxK0T z;cik>g`6E~@C{US+S23|AL1n(0Pnj}I(enyEDt$3!C zoW%2%o@c{6ZQW`Clk1?+HolM2nLJS{S8H(g5?v(0rm!>5hKL?e3%c+_BvH&>oq zJo8%aka8v4kp}|b9_u}R>$k7|=C@Bjugqbg-Zoq}5xh;b`abOHGDK_>yUk|1fyHPN zWikwHBMh@#%u<{;!w|@tRy$=qvHn!$P`%8&e6tF(q;8`45Cjyls#e87KqzdlxYNWV z&=W8 zC6>#91oaQ#2uN_ufqVa!xpG1rxu9O)y|JBaHxdUd&GYQcc;5HE_r13d7ZzFsp1<$> z_T>Yckl*pg^aE?;V;Jf>7*04%$dLMU$b3f0r-Uf~x;SP6?iSP0T_u$=t zy(Vv&J?|;8iQfdrbGmUm&N3+uB0A-X!5pJNhw3Jnj2w|)jGJE}hKvqZkP#VCq} zItRi9-`fb{3YeG_Gd|R< zH6BM9fE}+l5eY(a34@Veua5;mJc_d*=&U7a6eeoDbM{f&(Ti z2Gt!wFc3lkR9lH;;M@2MB4`Fdl!QtJfq^dP;Y)b|j03}9A#{-~(R^tZ%Q-GFp`YMa zZ-D{!$S#Ex(lUh^%dAqE1wDeK0-ts-_qxMS?uqQh#Gq18Ato^%ObWPW1#`}N66rBe zPpHH)DPbe+2D|pwl5n`g!}Ud2`U?ih->jim&Zbzb&YswYGPwlDwVlK&)7CH?x0!qi zR~praGrr_&u!)`(hE9;B3p1{wx}@*`LT|t@B;AL}8A%0F#R_IL?}FwnLs+SMi@P90 ztOVPvHcT~G+^bfd!YS+l1AdylyjfG)eNJ!Wt!^s0kUMF*_d>pqVgzj{zFa{m)sCPz zL)~-hC%E*)?rB?wnb6+;BphXNb_l#+y~wL*Fe3R18k2<6Ovp>HhV&VB=BfKneywKb z6x8`tXlp0t{}-!#19AK(qUC8sz7?uH`6?{6-7?kAAeNAQSBWScaos#cs%^lfv<)Aq z*+{R!1t=YbhS1k(3!-&dY4eNcqK;7A^@DR%iBZMdFe#wN?UFrLF5|l}%Fmz`p+>Nn zzJvB;Y9YV+a1?U^DH+6quU2jDaJB4Gt2b65PZ!}r-Fg%z6R{;_Dr-r5_34>d^n516 zks1hz{Q{Y(nm~)TSiW3`GJWV=vQS(m8dMm^QfIxdo?ZF=&(EKI`;qb2riSfV-_0!C zD;=Y}lwpaT@pro0Zegz*g+mbpx)lV&lur_z&j$f$G%0sX8`IucrqCR-LsP91QzhR) z(*~m#s`eL&B&l1VQSi%n#56+;0*Ate;?a4R(U$Fb9=s*4_u95@Ku_<$U#mpOWh=q0 UsrXLI0vc2acO7XmF)msE0%^mz_W%F@ literal 0 HcmV?d00001 diff --git a/API/__pycache__/Yahoo.cpython-38.pyc b/API/__pycache__/Yahoo.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2181515c1c452cacbd0b1feb32685bc25e7964cd GIT binary patch literal 1263 zcmah}L2uJA6t?4}O}lnOLjnyhh!eR?8sdZyVrMUHGup&PhT=Lo_FTSeL$75UmM0qhaHmxFl=>Y1?=oqvN88 z=^PjCac;oWn2aD@a!EUvV2rC}mM9ONTJt;DeA(-6?QeAt-amQK+wbmf_V(7__69l? z&)0hE?{<5ZV0Ab)^+BZiveb3-3&H`*NxMR)NIc6DLEPU7Lt#9YIbaGM881ndE=^6# zxX8s#WXeahQ79qRVNvw4W;52MiuD?cZ8UHJ2k!sH>r}jcJW@*L@%T1nt5i|06RCJ2 zWgDE$SeGl-dHDaj-z2PR;OjP7qm23$)4e&SX@1w(9U4>-_0`K@N^(ZNQ|zS+huUHX ztfLS%ByfzUM?3|$zwov$B{V3MS+D{QWO-Z&^$4G|r=rF( z%Vxq_bx~B?(zYV zDoE9ZJE@EA6a+hGUnL_G()U^nc6 zy|54V!vQ!5hu|@;fnuWD1E}UB1XtuQLb~c6mjz8ooLP}{9p}n8#BUgD*1jUQ#!0MiJCf6%k-F3 zq%o9AF;o#MMFe*k!HYEy8%q@ncT{RHS*32dEvXgsXcZU1DV`4ww)fFoQ8XM$FJbO WL+aNMl53yDx{4&%*Z*G$=bwLm*xxPy literal 0 HcmV?d00001 diff --git a/API/const.py b/API/const.py new file mode 100644 index 00000000..b2fa2723 --- /dev/null +++ b/API/const.py @@ -0,0 +1,9 @@ +transfer = { + 'AAVE': 0.54, 'ALG': 426.0, 'AMLT': 1743.0, 'BAT': 156.0, 'BCC': 0.001, 'BCP': 1237.0, 'BOB': 11645.0, + 'BSV': 0.003, 'BTC': 0.0005, 'BTG': 0.001, 'COMP': 0.1, 'DAI': 81.0, 'DASH': 0.001, 'DOT': 0.1, 'EOS': 0.1, + 'ETH': 0.006, 'EXY': 520.0, 'GAME': 479.0, 'GGC': 112.0, 'GNT': 403.0, 'GRT': 84.0, 'LINK': 2.70, + 'LML': 1500.0, "LSK": 0.3, "LTC": 0.001, "LUNA": 0.02, "MANA": 100.0, "MKR": 0.025, "NEU": 572.0, + "NPXS": 46451.0, "OMG": 14.0, "PAY": 1523.0, "QARK": 1019.0, "REP": 3.2, "SRN": 5717.0, "SUSHI": 8.8, + "TRX": 1.0, "UNI": 2.5, "USDC": 125.0, "USDT": 190.0, "XBX": 5508.0, "XIN": 5.0, "XLM": 0.005, "XRP": 0.1, + "XTZ": 0.1, "ZEC": 0.004, "ZRX": 56.0 +} \ No newline at end of file diff --git a/Services.py b/Services.py new file mode 100644 index 00000000..48d55803 --- /dev/null +++ b/Services.py @@ -0,0 +1,60 @@ +from API.Bitbay import Bitbay +from API.Bittrex import Bittrex +from API.Yahoo import Yahoo +from Wallet import Wallet + +API = { + "currency": None, + "crypto_currency": { + "bitbay": Bitbay(), + "bittrex": Bittrex() + }, + "polish_stock": None, + "foreign_stock": Yahoo() +} + + +class Service: + + @classmethod + def get_markings(cls, wallet: Wallet): + markings = [] + for crypto in wallet.crypto_currencies: + markings.append({"name": crypto['name'], + "price": cls.__sell_crypto(crypto['name'], crypto['quantity'], + wallet.base_currency)}) + for stock in wallet.foreign_stock: + markings.append({"name": stock['name'], + "price": cls.__sell_foreign_stock(stock['name'], stock['quantity'], + wallet.base_currency)}) + + return markings + + @classmethod + def __sell_crypto(cls, resource, quantity, base): + # TODO add multiprocessing + value = [] + for key in API['crypto_currency']: + _, bids = API['crypto_currency'][key].orderbook(resource, base) + + volume = quantity + price = 0 + for bid in bids: + if volume >= bid['quantity']: + price += bid['quantity'] * bid['rate'] + volume -= bid['quantity'] + elif volume > 0: + price += volume * bid['rate'] + volume = 0 + if volume == 0: + pass + value.append(price) + return max(value) + + @classmethod + def __sell_polish_stock(cls, resource, quantity, base): + return None + + @classmethod + def __sell_foreign_stock(cls, resource, quantity, base): + return quantity * API['foreign_stock'].get_price(resource, base) diff --git a/Wallet.py b/Wallet.py new file mode 100644 index 00000000..f2e1e87f --- /dev/null +++ b/Wallet.py @@ -0,0 +1,31 @@ +from utils import read_json + + +class Wallet: + def __init__(self, filename): + portfolio_data = read_json(filename) + self.__base_currency = portfolio_data["base_currency"] + self.__currency = portfolio_data["currency"] + self.__crypto_currency = portfolio_data["crypto_currency"] + self.__polish_stock = portfolio_data["polish_stock"] + self.__foreign_stock = portfolio_data["foreign_stock"] + + @property + def base_currency(self): + return self.__base_currency + + @property + def currency(self): + return self.__currency + + @property + def crypto_currencies(self): + return self.__crypto_currency + + @property + def polish_stock(self): + return self.__polish_stock + + @property + def foreign_stock(self): + return self.__foreign_stock diff --git a/app.py b/app.py new file mode 100644 index 00000000..e8df4717 --- /dev/null +++ b/app.py @@ -0,0 +1,15 @@ +from flask import Flask, render_template, request + +from Wallet import Wallet + +app = Flask(__name__) + + +@app.route('/') +def index(): + wallet = Wallet('justy', 'resources.json') + return "hello world" + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/resources.json b/resources.json new file mode 100644 index 00000000..1a4b885d --- /dev/null +++ b/resources.json @@ -0,0 +1,30 @@ +{ + "base_currency": "USD", + "currency": [ + { + "name": "EUR", + "quantity": 1200, + "rate": 1.21 + }, + { + "name": "PLN", + "quantity": 5100, + "rate": 0.27 + } + ], + "crypto_currency": [ + { + "name": "BTC", + "quantity": 0.0045, + "rate": 33827.6 + } + ], + "polish_stock": [], + "foreign_stock": [ + { + "name": "AAPL", + "quantity": 1.5, + "rate": 33827.6 + } + ] +} \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 00000000..cb249fda --- /dev/null +++ b/utils.py @@ -0,0 +1,9 @@ +import json + + +def read_json(filename): + with open(filename) as json_file: + data = json.load(json_file) + + return data + From ba9e6d42a433165c1f51799ecf81428682973ae4 Mon Sep 17 00:00:00 2001 From: JUSTY Date: Fri, 28 May 2021 19:07:13 +0200 Subject: [PATCH 2/4] implement ex 2 with front --- API/EOD.py | 29 +++++++++ API/NBP.py | 31 ++++++++++ API/__pycache__/EOD.cpython-38.pyc | Bin 0 -> 1492 bytes API/__pycache__/NBP.cpython-38.pyc | Bin 0 -> 1440 bytes API/__pycache__/const.cpython-38.pyc | Bin 944 -> 986 bytes API/const.py | 4 +- Services.py | 87 +++++++++++++++++---------- app.py | 7 ++- resources.json | 15 ++++- static/css/styles.css | 6 ++ static/js/myscripts.js | 4 ++ templates/index.html | 37 ++++++++++++ templates/navbar.html | 14 +++++ 13 files changed, 197 insertions(+), 37 deletions(-) create mode 100644 API/EOD.py create mode 100644 API/NBP.py create mode 100644 API/__pycache__/EOD.cpython-38.pyc create mode 100644 API/__pycache__/NBP.cpython-38.pyc create mode 100644 static/css/styles.css create mode 100644 static/js/myscripts.js create mode 100644 templates/index.html create mode 100644 templates/navbar.html diff --git a/API/EOD.py b/API/EOD.py new file mode 100644 index 00000000..f989db8d --- /dev/null +++ b/API/EOD.py @@ -0,0 +1,29 @@ +from API import const +from API.Api import Api, request +from API.NBP import NBP + + +class EOD(Api): + def __init__(self): + super().__init__("EOD", + "https://eodhistoricaldata.com/api/real-time/") + + def markets(self): + pass + + def orderbook(self, first, second): + pass + + def transfer_fee(self, currency): + pass + + def taker_fee(self): + pass + + def get_price(self, stock_name, base_currency): + json = request(f'{self.url}{stock_name}.WAR?api_token={const.EOD_TOKEN}&fmt=json') + if json['high'] == 'NA' or json['low'] == 'NA': + price = NBP().convert('PLN', json['previousClose'], base_currency) + else: + price = NBP().convert('PLN', (json['high'] + json['low']) / 2, base_currency) + return price diff --git a/API/NBP.py b/API/NBP.py new file mode 100644 index 00000000..6f257f60 --- /dev/null +++ b/API/NBP.py @@ -0,0 +1,31 @@ +from API.Api import request, Api + + +class NBP(Api): + def __init__(self): + super().__init__('NBP', 'http://api.nbp.pl/api/exchangerates/rates/c/') + + def markets(self): + pass + + def orderbook(self, first, second): + pass + + def transfer_fee(self, currency): + pass + + def taker_fee(self): + pass + + def convert(self, cur_from: str, quantity: float, cur_to: str): + if cur_from == cur_to: + return quantity + if cur_from == 'PLN': + json = request(f'{self.url}{cur_to}') + return quantity / float(json['rates'][0]['ask']) + elif cur_to == 'PLN': + json = request(f'{self.url}' + f'{cur_from}') + return quantity * float(json['rates'][0]['bid']) + + conv_value = self.convert(cur_from, quantity, 'PLN') + return self.convert('PLN', conv_value, cur_to) diff --git a/API/__pycache__/EOD.cpython-38.pyc b/API/__pycache__/EOD.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57a98b5b9cbc40a44261331e214bdad9e3397f04 GIT binary patch literal 1492 zcmah}&2JM&6rb7I_3k=$3H^Yg_K;I6OKq$oP6*l>5~1*6B0`0D1T&Lor7Ixw#O9qmkq|b(#E#5gyM^{h;gliRFCU=f(|S( zyNG$J$($#k#t-HHLiHNdD4QuItL*t%z>ZOArN>h7v6Sb**b;LcF;|uUpUd~belnY} zNhb}?mn%3fwo}aq84chVR@N1)0>O|4IiP=%1^fjy-T?#d{S_S27iltJ7;KY&Oegs!+IL zV@NH3c>m7!P+^ERgid%SmFWepSVRI0*2UD0ddA5}C6KS~9 zTm-Ayy=L@V`Zc}aZP2;P^XZ9R8^;h4IJa%i%Ua9|lslY1oux&iaRa8-!J7I4$yZ1& zAlU$7yC*}N$)ezV#T}d%g$?Z92`r)R0EuYFk680_nqe=3r*C(sNlOUQCOm`+C#lJq f+k&^y<;h)HL3ux?(Y40^pK%i0W6+`Nw8P#3XL&dz literal 0 HcmV?d00001 diff --git a/API/__pycache__/NBP.cpython-38.pyc b/API/__pycache__/NBP.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2ee21e1c780712f763813b00954b27206e91b6f GIT binary patch literal 1440 zcmah}OK%e~5cXp?*`!HT-k@^efP^BUMj%cIAzDBP38IR?WfjS6*A9eic7yF8T5V6Q z)C>Orkm!*c|Af1*oH%pi#EjFlQH#JUkH?DiR6my@R-Y>OIqPi&?GJ9s#?9BC}SO89S>4GoFC{I zhv@)OB&L$YY>)J*q>}AM;0)aDk~ol|tiT$|s*GT*$eOIfT2+y%sYot-;<6!|yFpyv zBLRsQAcRH7J8SDH^#D_XJVHW_#GD3V$qxDA!S*2&Nx+PEG^%V}e{JpfcDtR72P^r; zcx9X++g9(>*Gc|L>BK72p3<}piH2q}R@y~E4Dx{$;wyE5QCT0>nhN zOzl2uWAK>BSK84F!0Rd!&w0{-t*ed_Jyh02u$lQ^K>r8lDc;a*Ab-NWZo;W|+u|DC zxUfIaQ0IzKX_3q0h_ymXr8kOVh*8Win}~TRNqwdu#)165P}vkUN+(*YJl#AF*swau zO`q$F?XM^f^S*n}GXYe4hBC)Ot&*7cYwpH;J)8{}zpGR^5Rf9LC$m2RHiFZjh zQO63YFN4-um<9=<5e;CTrWdD_Ez&Lja%U(FY{5JkDIr{4h*2RY8S)Jw;MTLL#5b45 zSD~?F`V12E@3^sAFXI+*UGt!IQ&I<3G}p{rNm c_vi0MV>@}#oVTOSjP6V$zKa*@pX?I<11qN^tN;K2 literal 0 HcmV?d00001 diff --git a/API/__pycache__/const.cpython-38.pyc b/API/__pycache__/const.cpython-38.pyc index 4dd4dc614aad34f8abfb84efd5a4d55378aa2707..acc0498dc9f9f74d6561a7299bf037d24a3d8a76 100644 GIT binary patch delta 102 zcmdnMev6$ql$V!_0SJ;?*C)K5$Scd3Gf_K}BZ@IaE0{r3d*h5`Cc`RmGlRr53sVDA zb8|zZR1-Z@Ljy}ALn9MEO{QBMB}IvO#c8QUQJk*+F7YA$-mZR=IhfrT6DIpJCo-{d F0RVV<8vOtO delta 59 zcmcb`zJZ-Ll$V!_0SK0-uSz&Dkyn;6W1@B@OE80`*2X!>OuT-YjJG&SiW2jR(^88j O8!)>u#!W6~P6Pn!n-M_( diff --git a/API/const.py b/API/const.py index b2fa2723..bdd36448 100644 --- a/API/const.py +++ b/API/const.py @@ -6,4 +6,6 @@ "NPXS": 46451.0, "OMG": 14.0, "PAY": 1523.0, "QARK": 1019.0, "REP": 3.2, "SRN": 5717.0, "SUSHI": 8.8, "TRX": 1.0, "UNI": 2.5, "USDC": 125.0, "USDT": 190.0, "XBX": 5508.0, "XIN": 5.0, "XLM": 0.005, "XRP": 0.1, "XTZ": 0.1, "ZEC": 0.004, "ZRX": 56.0 -} \ No newline at end of file +} + +EOD_TOKEN = "60af85057712e4.51092124" diff --git a/Services.py b/Services.py index 48d55803..100b3223 100644 --- a/Services.py +++ b/Services.py @@ -1,60 +1,85 @@ +from multiprocessing import Pool + from API.Bitbay import Bitbay from API.Bittrex import Bittrex +from API.EOD import EOD +from API.NBP import NBP from API.Yahoo import Yahoo from Wallet import Wallet API = { - "currency": None, + "currency": NBP(), "crypto_currency": { "bitbay": Bitbay(), "bittrex": Bittrex() }, - "polish_stock": None, + "polish_stock": EOD(), "foreign_stock": Yahoo() } +def search_bids(bids, quantity): + volume = quantity + price = 0 + for bid in bids: + if volume >= bid['quantity']: + price += bid['quantity'] * bid['rate'] + volume -= bid['quantity'] + elif volume > 0: + price += volume * bid['rate'] + volume = 0 + if volume == 0: + pass + return price + + class Service: @classmethod def get_markings(cls, wallet: Wallet): markings = [] + for currency in wallet.currency: + currency['price'] = round( + cls.__sell_currency(currency['name'], float(currency['quantity']), wallet.base_currency), 2) + markings.append(currency) for crypto in wallet.crypto_currencies: - markings.append({"name": crypto['name'], - "price": cls.__sell_crypto(crypto['name'], crypto['quantity'], - wallet.base_currency)}) + crypto['price'] = round(cls.__sell_crypto(crypto['name'], float(crypto['quantity']), wallet.base_currency), + 2) + markings.append(crypto) + for stock in wallet.polish_stock: + stock['price'] = round( + cls.__sell_polish_stock(stock['name'], float(stock['quantity']), wallet.base_currency), 2) + markings.append(stock) for stock in wallet.foreign_stock: - markings.append({"name": stock['name'], - "price": cls.__sell_foreign_stock(stock['name'], stock['quantity'], - wallet.base_currency)}) + stock['price'] = round( + cls.__sell_foreign_stock(stock['name'], float(stock['quantity']), wallet.base_currency), 2) + markings.append(stock) return markings - @classmethod - def __sell_crypto(cls, resource, quantity, base): - # TODO add multiprocessing + @staticmethod + def __sell_currency(cur_from, quantity, cur_to): + return API['currency'].convert(cur_from, quantity, cur_to) + + @staticmethod + def __sell_crypto(resource, quantity, base): value = [] - for key in API['crypto_currency']: - _, bids = API['crypto_currency'][key].orderbook(resource, base) - - volume = quantity - price = 0 - for bid in bids: - if volume >= bid['quantity']: - price += bid['quantity'] * bid['rate'] - volume -= bid['quantity'] - elif volume > 0: - price += volume * bid['rate'] - volume = 0 - if volume == 0: - pass - value.append(price) + _, bidsBittrex = API['crypto_currency']['bittrex'].orderbook(resource, base) + _, bidsBitbay = API['crypto_currency']['bitbay'].orderbook(resource, base) + + with Pool(processes=4) as pool: + r1 = pool.apply_async(search_bids, (bidsBittrex, quantity)) + r2 = pool.apply_async(search_bids, (bidsBitbay, quantity)) + + value.append(r1.get()) + value.append(r2.get()) + return max(value) - @classmethod - def __sell_polish_stock(cls, resource, quantity, base): - return None + @staticmethod + def __sell_polish_stock(resource, quantity, base): + return quantity * API['polish_stock'].get_price(resource, base) - @classmethod - def __sell_foreign_stock(cls, resource, quantity, base): + @staticmethod + def __sell_foreign_stock(resource, quantity, base): return quantity * API['foreign_stock'].get_price(resource, base) diff --git a/app.py b/app.py index e8df4717..a7e83a28 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,5 @@ from flask import Flask, render_template, request - +from Services import Service from Wallet import Wallet app = Flask(__name__) @@ -7,8 +7,9 @@ @app.route('/') def index(): - wallet = Wallet('justy', 'resources.json') - return "hello world" + wallet = Wallet('resources.json') + markings = Service.get_markings(wallet) + return render_template("index.html", markings_list=markings) if __name__ == '__main__': diff --git a/resources.json b/resources.json index 1a4b885d..f8ccd760 100644 --- a/resources.json +++ b/resources.json @@ -19,12 +19,23 @@ "rate": 33827.6 } ], - "polish_stock": [], + "polish_stock": [ + { + "name": "PZU", + "quantity": 2.57, + "rate": 9.8415 + }, + { + "name": "CDR", + "quantity": 3.128, + "rate": 47.547 + } + ], "foreign_stock": [ { "name": "AAPL", "quantity": 1.5, - "rate": 33827.6 + "rate": 125.28 } ] } \ No newline at end of file diff --git a/static/css/styles.css b/static/css/styles.css new file mode 100644 index 00000000..2119e4ce --- /dev/null +++ b/static/css/styles.css @@ -0,0 +1,6 @@ + #table-pricing { + width: 40%; + margin-left: auto; + margin-right: auto; + margin-top: 5%; + } \ No newline at end of file diff --git a/static/js/myscripts.js b/static/js/myscripts.js new file mode 100644 index 00000000..7ed2547d --- /dev/null +++ b/static/js/myscripts.js @@ -0,0 +1,4 @@ +$('#navbarSupportedContent .navbar-nav li').on('click', function () { + $('#navbarSupportedContent .navbar-nav').find('li.active').removeClass('active'); + $(this).parent('li').addClass('active'); +}); \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 00000000..26e04352 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,37 @@ + + + + + My portfolio + + + + + +{% include 'navbar.html' %} + + + {% if markings_list %} + + + + + + + {% endif %} + + + + {% for dict_item in markings_list %} + + {% for value in dict_item.values() %} + + {% endfor %} + + {% endfor %} + + +
Name Quantity Rate Price
{{ value }}
+ + \ No newline at end of file diff --git a/templates/navbar.html b/templates/navbar.html new file mode 100644 index 00000000..ec0e07de --- /dev/null +++ b/templates/navbar.html @@ -0,0 +1,14 @@ + \ No newline at end of file From 03c83c1bbf3165e35d1d848f7a88b6e58d0205e8 Mon Sep 17 00:00:00 2001 From: JUSTY Date: Sun, 13 Jun 2021 17:12:39 +0200 Subject: [PATCH 3/4] final app version with web front --- API/NBP.py | 4 +- API/Yahoo.py | 8 +- API/__pycache__/Bitbay.cpython-38.pyc | Bin 1738 -> 1738 bytes API/__pycache__/NBP.cpython-38.pyc | Bin 1440 -> 1464 bytes API/__pycache__/Yahoo.cpython-38.pyc | Bin 1263 -> 1374 bytes API/__pycache__/const.cpython-38.pyc | Bin 986 -> 1001 bytes API/const.py | 4 +- Services.py | 116 +++++++++++++++++++------- Wallet.py | 49 +++++++++-- app.py | 47 ++++++++++- resources.json | 12 +-- static/.idea/modules.xml | 9 ++ static/.idea/static.iml | 13 +++ static/.idea/vcs.xml | 6 ++ static/.idea/workspace.xml | 57 +++++++++++++ static/css/styles.css | 70 +++++++++++++++- static/js/myscripts.js | 4 - static/portfolio.png | Bin 0 -> 40611 bytes templates/.idea/modules.xml | 8 ++ templates/.idea/templates.iml | 12 +++ templates/.idea/vcs.xml | 6 ++ templates/.idea/workspace.xml | 82 ++++++++++++++++++ templates/add_resources.html | 48 +++++++++++ templates/base.html | 17 ++++ templates/footer.html | 5 ++ templates/index.html | 48 +++-------- templates/navbar.html | 9 +- templates/print_resources.html | 43 ++++++++++ utils.py | 4 + 29 files changed, 578 insertions(+), 103 deletions(-) create mode 100644 static/.idea/modules.xml create mode 100644 static/.idea/static.iml create mode 100644 static/.idea/vcs.xml create mode 100644 static/.idea/workspace.xml delete mode 100644 static/js/myscripts.js create mode 100644 static/portfolio.png create mode 100644 templates/.idea/modules.xml create mode 100644 templates/.idea/templates.iml create mode 100644 templates/.idea/vcs.xml create mode 100644 templates/.idea/workspace.xml create mode 100644 templates/add_resources.html create mode 100644 templates/base.html create mode 100644 templates/footer.html create mode 100644 templates/print_resources.html diff --git a/API/NBP.py b/API/NBP.py index 6f257f60..a137a766 100644 --- a/API/NBP.py +++ b/API/NBP.py @@ -22,10 +22,10 @@ def convert(self, cur_from: str, quantity: float, cur_to: str): return quantity if cur_from == 'PLN': json = request(f'{self.url}{cur_to}') - return quantity / float(json['rates'][0]['ask']) + return round(quantity / float(json['rates'][0]['ask']), 2) elif cur_to == 'PLN': json = request(f'{self.url}' + f'{cur_from}') - return quantity * float(json['rates'][0]['bid']) + return round(quantity * float(json['rates'][0]['bid']), 2) conv_value = self.convert(cur_from, quantity, 'PLN') return self.convert('PLN', conv_value, cur_to) diff --git a/API/Yahoo.py b/API/Yahoo.py index ce75bb77..0f84ccc4 100644 --- a/API/Yahoo.py +++ b/API/Yahoo.py @@ -1,6 +1,7 @@ import yfinance as yf from API.Api import Api +from API.NBP import NBP class Yahoo(Api): @@ -21,6 +22,9 @@ def __init__(self): @classmethod def get_price(cls, stock_name, base_currency): - # TODO handle exception and check currency (info('currency')) ticker = yf.Ticker(stock_name) - return (ticker.info["dayLow"] + ticker.info["dayHigh"]) / 2 + price = (ticker.info["dayLow"] + ticker.info["dayHigh"]) / 2 + if ticker.info['currency'] == base_currency: + return price + else: + return NBP().convert(ticker.info['currency'], price, base_currency) diff --git a/API/__pycache__/Bitbay.cpython-38.pyc b/API/__pycache__/Bitbay.cpython-38.pyc index 3c684d8fcf70ccf00012f8e9d052dad664dbd5f9..b8c60b3958ae5fc0385f73dc82e5571b561e8a91 100644 GIT binary patch delta 93 zcmX@bdy1Dkl$V!_0SG3&Kf00ImYMO^WKZUeQc^&XA{h|D2PgPJEV;=dEJhr+Skp3# fic4-y_G8gwRN7q2vXqfq2_&rsBGf1Avdskm^}7{H delta 93 zcmX@bdy1Dkl$V!_0SE-$*KXvtWoC?;?8&@QN)jkiBn=|?-~>O2B|BM!#fT${H7&EK exFl+_AB!HN{N`GgrHtJ2AZcX~p)y&QZ7u*!e-e5C diff --git a/API/__pycache__/NBP.cpython-38.pyc b/API/__pycache__/NBP.cpython-38.pyc index a2ee21e1c780712f763813b00954b27206e91b6f..7e60ebf67f0de6704ec35e14969efdc79f8ebe1c 100644 GIT binary patch delta 251 zcmZ3$y@Q)Kl$V!_0SKn^97~wJk++|@eg=?N!&t+d%~Zrw!xRi;r!WH9E+q^#3|Wk` z8ETkkGt6abW~^l?VX9#&VXk3GVUlD>VFqFrNrq-dX@(TmW{^H+u)Z3&KA>K4pgb0R zY#@Ci5Pgb3YZkBo9l(^rxRAM)6~s$ntYNKTUdSBGpvmrcYq9~0sO(E7pfhhVCuOE+ zvKB$urA0ZnSc~#Y^HL^r#xBRm!6?KiH+c_BD4QHmOOgC!an?=%j5#_b delta 260 zcmdnNy?~oHl$V!_0SG>y-;mJ0k++|@z6Z#wVXR@!W-8*TVG0JaQy774yAp;PhAhU} z3^mNN8RjxIGuE=yFqJUXu%s|aGNdpAF^eQaGov&EPzN(uM*&<1P?tDR4nqekNQVeW z2g3qppq)%9j0>%qYgs|u6vi6X8s>$}!3>&geo@Q;K7LWGMTsS;#YK!j2i{^%EY7~g zoRpcO$x_4w7AP&snViSsAtwXmi2!jih+tskU}OPeAw~%ZIr$1pD4R4;qDW@4K5Hic D`WrZz diff --git a/API/__pycache__/Yahoo.cpython-38.pyc b/API/__pycache__/Yahoo.cpython-38.pyc index 2181515c1c452cacbd0b1feb32685bc25e7964cd..7f81718b2718c3901c729609622ecc7004bf6634 100644 GIT binary patch delta 742 zcmZWnJ#W-N5Zw>k=ewK_A|eF}C_;hU1d5Z0NEGQpfoM#m10md)!@CA^KKnHGA)RzU z5&l5rh7KqxDUkRLNc@2<=&9(bn6V*2!Pd@d-|X9&_jZ4FzxTWcp4VgeJ^J?f@z-PT zaxlb&Vv1)h~9CxsjuJN>gxf zc&y(!4#MEnJ${qB^#R|zWj6o@UxJI1v6B?a$~dKti(ElVpiN!Mb_-A%o)pD&t^UII zcTVY|rMvM0K$l?Gu0{2kFN|F- z<{FF}MQNT^QPi+Ma?_2XIGdCuBF$0MFkHZ}QE$m>1FA;jHjXl=AIhsYURo>pTqx@j zTZyTF;mR7bN*=NIr?|BeALJMvM_29omE61z13bXDjaVk3b3cu-Nn@pXQh>yS>7Z>d zG*q}l##w3VPjYK$>^Y>d{!ijlT}4f1lbUKSU-bAzKH!$9f61NK1c6849EI}~NRnA= zq|S8ptWa=;5buxtSZCQ^3mP9bh}4)t7LwH^NpQI<+rKXb2g>=K&|M~JKFP6HYAGW3Ob0n37yQ0tg@;5sCpC|wT delta 611 zcmZWmyKdA#6!pvVTNRR?IW|NHc(Q{@BFWUfsf3by8q@60)8&h`B1U-ZJ=FzkEoIe+=<^VV^= z{ASG+%2O=&V&-O^^C`r@ye&_G5=WjA8h$`56#L*s{$J9*iYEOO3SiP*b0ag&~C3prSVHyPWtAI4L5G(yp;#?5K$L$ zc3(NDaa>fX#w67`G$THWZp7H31MVMHk0&jA-u&b{49q2e{EV2V%=VH*=HseLl2%6H z@R5TB^9oky$Qj&t>qfO=_Tjm3zeAYcu=F$n%V%la`It%PZ$(z5Wh5-fyowEomDT@( zeARVwfSJ^bWJlf}^qKi2Hoo`VHx3Bq2p$qp4EvyoH`VoCsqhJ{g#RK-o#)rFo0b>L6@%MK1yTx9dT2z*qoVt>shy`ds5zAytW?ROj$>q$+Oe{0phKEuqx{F99f0Mw!vD*ylh diff --git a/API/const.py b/API/const.py index bdd36448..54466aaa 100644 --- a/API/const.py +++ b/API/const.py @@ -8,4 +8,6 @@ "XTZ": 0.1, "ZEC": 0.004, "ZRX": 56.0 } -EOD_TOKEN = "60af85057712e4.51092124" +EOD_TOKEN = '60af85057712e4.51092124' + +service = None diff --git a/Services.py b/Services.py index 100b3223..6d30cd35 100644 --- a/Services.py +++ b/Services.py @@ -1,4 +1,4 @@ -from multiprocessing import Pool +from multiprocessing import Pool, cpu_count from API.Bitbay import Bitbay from API.Bittrex import Bittrex @@ -34,52 +34,108 @@ def search_bids(bids, quantity): class Service: + def __init__(self, wallet: Wallet): + self.__wallet = wallet - @classmethod - def get_markings(cls, wallet: Wallet): + @property + def base_currency(self): + return self.__wallet.base_currency + + def analyse_portfolio(self, depth): + with Pool(processes=cpu_count()) as pool: + r1 = pool.apply_async(self.analyse_resources_category, ('currency', self.__wallet.currency, depth)) + r2 = pool.apply_async(self.analyse_resources_category, + ('crypto_currency', self.__wallet.crypto_currencies, depth)) + r3 = pool.apply_async(self.analyse_resources_category, + ('polish_stock', self.__wallet.polish_stock, depth)) + r4 = pool.apply_async(self.analyse_resources_category, + ('foreign_stock', self.__wallet.foreign_stock, depth)) + + markings = r1.get() + r2.get() + r3.get() + r4.get() + + return markings + + def analyse_resources_category(self, category, resources, depth): markings = [] - for currency in wallet.currency: - currency['price'] = round( - cls.__sell_currency(currency['name'], float(currency['quantity']), wallet.base_currency), 2) - markings.append(currency) - for crypto in wallet.crypto_currencies: - crypto['price'] = round(cls.__sell_crypto(crypto['name'], float(crypto['quantity']), wallet.base_currency), - 2) - markings.append(crypto) - for stock in wallet.polish_stock: - stock['price'] = round( - cls.__sell_polish_stock(stock['name'], float(stock['quantity']), wallet.base_currency), 2) - markings.append(stock) - for stock in wallet.foreign_stock: - stock['price'] = round( - cls.__sell_foreign_stock(stock['name'], float(stock['quantity']), wallet.base_currency), 2) - markings.append(stock) + for resource in resources: + resource_name = resource['name'] + resource_quantity = float(resource['quantity']) + resource_rate = float(resource['rate']) + + best_price, exchange_name_best_price = self.__sell_resource(category, resource_name, resource_quantity, + self.__wallet.base_currency) + depth_best_price, _ = self.__sell_resource(category, resource_name, resource_quantity * depth / 100, + self.__wallet.base_currency) + netto = self.tax(best_price, resource_quantity, resource_rate, 100) + depth_netto = self.tax(depth_best_price, resource_quantity, resource_rate, depth) + + markings.append( + {'name': resource_name, 'quantity': resource_quantity, 'rate': resource_rate, + 'price': best_price, 'depth_price': round(depth_best_price, 2), 'with_tax': netto, + 'depth_with_tax': depth_netto, 'exchange': exchange_name_best_price}) return markings + def add_resource(self, request): + name = str(request.form['name']) + quantity = float(request.form['quantity']) + rate = float(request.form['rate']) + if quantity < 0 or rate < 0: + raise ValueError + invest_type = int(request.form['type']) + self.__wallet.add_resource(name, quantity, rate, invest_type) + + def save_to_json(self): + self.__wallet.save_to_json() + + def read_json(self, filename): + self.__wallet.read_from_json(filename) + + @staticmethod + def tax(sell_price, quantity, rate, depth): + revenue = round(sell_price - (quantity * (depth / 100) * rate), 2) + return revenue if revenue < 0 else round(revenue * 0.81, 2) + + def __sell_resource(self, resource_category, cur_from, quantity, cur_to): + if resource_category == 'currency': + return self.__sell_currency(cur_from, quantity, cur_to) + elif resource_category == 'crypto_currency': + return self.__sell_crypto(cur_from, quantity, cur_to) + elif resource_category == 'polish_stock': + return self.__sell_polish_stock(cur_from, quantity, cur_to) + elif resource_category == 'foreign_stock': + return self.__sell_foreign_stock(cur_from, quantity, cur_to) + @staticmethod def __sell_currency(cur_from, quantity, cur_to): - return API['currency'].convert(cur_from, quantity, cur_to) + return API['currency'].convert(cur_from, quantity, cur_to), ' - ' @staticmethod def __sell_crypto(resource, quantity, base): value = [] - _, bidsBittrex = API['crypto_currency']['bittrex'].orderbook(resource, base) - _, bidsBitbay = API['crypto_currency']['bitbay'].orderbook(resource, base) + _, bidsBittrex = API['crypto_currency']['bittrex'].orderbook(resource, 'USD') + _, bidsBitbay = API['crypto_currency']['bitbay'].orderbook(resource, 'USD') - with Pool(processes=4) as pool: - r1 = pool.apply_async(search_bids, (bidsBittrex, quantity)) - r2 = pool.apply_async(search_bids, (bidsBitbay, quantity)) + r1 = search_bids(bidsBittrex, quantity) + r2 = search_bids(bidsBitbay, quantity) - value.append(r1.get()) - value.append(r2.get()) + value.append((API['currency'].convert('USD', r1, base), 'BTX')) + value.append((API['currency'].convert('USD', r2, base), 'BTB')) - return max(value) + return max(value, key=lambda item: item[0]) @staticmethod def __sell_polish_stock(resource, quantity, base): - return quantity * API['polish_stock'].get_price(resource, base) + return quantity * API['polish_stock'].get_price(resource, base), ' - ' @staticmethod def __sell_foreign_stock(resource, quantity, base): - return quantity * API['foreign_stock'].get_price(resource, base) + return quantity * API['foreign_stock'].get_price(resource, base), ' - ' + + @staticmethod + def get_price_buy(volume, rate, taker_fee): + return volume * (1 + (volume * taker_fee)) * rate + + @staticmethod + def get_price_sell(volume, rate, taker_fee): + return volume * (1 - volume * taker_fee) * rate diff --git a/Wallet.py b/Wallet.py index f2e1e87f..e00fef0b 100644 --- a/Wallet.py +++ b/Wallet.py @@ -1,14 +1,9 @@ -from utils import read_json +from utils import read_json, save_json class Wallet: def __init__(self, filename): - portfolio_data = read_json(filename) - self.__base_currency = portfolio_data["base_currency"] - self.__currency = portfolio_data["currency"] - self.__crypto_currency = portfolio_data["crypto_currency"] - self.__polish_stock = portfolio_data["polish_stock"] - self.__foreign_stock = portfolio_data["foreign_stock"] + self.read_from_json(filename) @property def base_currency(self): @@ -29,3 +24,43 @@ def polish_stock(self): @property def foreign_stock(self): return self.__foreign_stock + + def add_resource(self, name, quantity, rate, res_type): + if res_type == 1: + self.__currency.append({ + 'name': name, + 'quantity': quantity, + 'rate': rate + }) + elif res_type == 2: + self.__crypto_currency.append({ + 'name': name, + 'quantity': quantity, + 'rate': rate + }) + elif res_type == 3: + self.__polish_stock.append({ + 'name': name, + 'quantity': quantity, + 'rate': rate + }) + elif res_type == 4: + self.__foreign_stock.append({ + 'name': name, + 'quantity': quantity, + 'rate': rate + }) + + def save_to_json(self): + resources = {'base_currency': self.__base_currency, 'currency': self.__currency, + 'crypto_currency': self.__crypto_currency, 'polish_stock': self.__polish_stock, + 'foreign_stock': self.__foreign_stock} + save_json(resources) + + def read_from_json(self, filename): + portfolio_data = read_json(filename) + self.__base_currency = portfolio_data["base_currency"] + self.__currency = portfolio_data["currency"] + self.__crypto_currency = portfolio_data["crypto_currency"] + self.__polish_stock = portfolio_data["polish_stock"] + self.__foreign_stock = portfolio_data["foreign_stock"] diff --git a/app.py b/app.py index a7e83a28..714117f9 100644 --- a/app.py +++ b/app.py @@ -1,16 +1,57 @@ +import os + from flask import Flask, render_template, request +from werkzeug.utils import secure_filename + +from API import const from Services import Service from Wallet import Wallet app = Flask(__name__) +app.config['UPLOAD_PATH'] = 'json_files' + + +def init(): + const.service = Service(Wallet('json_files/empty_resources.json')) @app.route('/') def index(): - wallet = Wallet('resources.json') - markings = Service.get_markings(wallet) - return render_template("index.html", markings_list=markings) + return render_template("index.html") + + +@app.route('/add', methods=['POST', 'GET']) +def add(): + if request.method == 'POST': + if 'name' in request.form: + try: + const.service.add_resource(request) + return render_template("add_resources.html", add_confirm='Resource added.') + except ValueError: + return render_template("add_resources.html", error_message='Invalid argument') + elif 'file' in request.files: + f = request.files['file'] + filename = secure_filename(f.filename) + f.save(os.path.join(app.config['UPLOAD_PATH'], filename)) + const.service.read_json(f'json_files/{f.filename}') + return render_template("add_resources.html", add_confirm='Portfolio correctly added.') + else: + const.service.save_to_json() + return render_template("add_resources.html", add_confirm='Portfolio saved to json. ') + return render_template("add_resources.html") + + +@app.route('/pricing', methods=['POST', 'GET']) +def pricing(): + if request.method == 'POST': + percentage = float(request.form['depth']) + markings = const.service.analyse_portfolio(depth=percentage) + base_currency = const.service.base_currency + return render_template('print_resources.html', markings_list=markings, percentage=percentage, + base_currency=base_currency) + return render_template('print_resources.html') if __name__ == '__main__': + init() app.run(debug=True) diff --git a/resources.json b/resources.json index f8ccd760..7f578a23 100644 --- a/resources.json +++ b/resources.json @@ -15,21 +15,11 @@ "crypto_currency": [ { "name": "BTC", - "quantity": 0.0045, + "quantity": 0.045, "rate": 33827.6 } ], "polish_stock": [ - { - "name": "PZU", - "quantity": 2.57, - "rate": 9.8415 - }, - { - "name": "CDR", - "quantity": 3.128, - "rate": 47.547 - } ], "foreign_stock": [ { diff --git a/static/.idea/modules.xml b/static/.idea/modules.xml new file mode 100644 index 00000000..8eee7da2 --- /dev/null +++ b/static/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/static/.idea/static.iml b/static/.idea/static.iml new file mode 100644 index 00000000..817397a2 --- /dev/null +++ b/static/.idea/static.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/.idea/vcs.xml b/static/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/static/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/static/.idea/workspace.xml b/static/.idea/workspace.xml new file mode 100644 index 00000000..10791964 --- /dev/null +++ b/static/.idea/workspace.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1622305742937 + + + + + + + + + + + + \ No newline at end of file diff --git a/static/css/styles.css b/static/css/styles.css index 2119e4ce..1cb3733f 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -1,6 +1,72 @@ - #table-pricing { +.welcome { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + padding: 2rem; +} + +.menu { + display: flex; + align-items: center; + justify-content:space-around; + flex-direction: column; + padding: 2rem; +} + +.menu-btn { + margin-bottom: 5px; width: 40%; +} + +.row { + align-items: flex-end; +} +#table-pricing { + width: 60%; + margin-left: auto; + margin-right: auto; +} + +#container { + width: 70%; margin-left: auto; margin-right: auto; margin-top: 5%; - } \ No newline at end of file + display: flex; + flex-direction: column; + align-content: space-around; +} + +#btn_add { + background-color: green; + width: 9rem; +} + +#btn_save { + background-color: orange; + width: 100%; +} + +#resources { + display: flex; + flex-direction: column; +} + +.percentage { + margin-top: 2%; + margin-bottom: 3%; + display: flex; + align-items: center; + justify-content: center; +} + +#percentage_form { + display: flex; +} + +footer { + position: absolute; + bottom: 0; + width: 100%; +} \ No newline at end of file diff --git a/static/js/myscripts.js b/static/js/myscripts.js deleted file mode 100644 index 7ed2547d..00000000 --- a/static/js/myscripts.js +++ /dev/null @@ -1,4 +0,0 @@ -$('#navbarSupportedContent .navbar-nav li').on('click', function () { - $('#navbarSupportedContent .navbar-nav').find('li.active').removeClass('active'); - $(this).parent('li').addClass('active'); -}); \ No newline at end of file diff --git a/static/portfolio.png b/static/portfolio.png new file mode 100644 index 0000000000000000000000000000000000000000..c12309268f2c1b7078bdfaf52c867f1815d5b1a0 GIT binary patch literal 40611 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 AQ~&?~ literal 0 HcmV?d00001 diff --git a/templates/.idea/modules.xml b/templates/.idea/modules.xml new file mode 100644 index 00000000..748ac78f --- /dev/null +++ b/templates/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/templates/.idea/templates.iml b/templates/.idea/templates.iml new file mode 100644 index 00000000..24643cc3 --- /dev/null +++ b/templates/.idea/templates.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/.idea/vcs.xml b/templates/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/templates/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/templates/.idea/workspace.xml b/templates/.idea/workspace.xml new file mode 100644 index 00000000..5dce69ac --- /dev/null +++ b/templates/.idea/workspace.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+ + + +{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 00000000..e936214d --- /dev/null +++ b/templates/base.html @@ -0,0 +1,17 @@ + + + + + My portfolio + + + + +{% include 'navbar.html' %} +
+ {% block content %}{% endblock %} +
+{% include 'footer.html' %} + + \ No newline at end of file diff --git a/templates/footer.html b/templates/footer.html new file mode 100644 index 00000000..9ddb230c --- /dev/null +++ b/templates/footer.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 26e04352..1fbe60ae 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,37 +1,15 @@ - - - - - My portfolio - - - - - -{% include 'navbar.html' %} - - - {% if markings_list %} - - - - - - - {% endif %} +{% extends "base.html" %} - - - {% for dict_item in markings_list %} - - {% for value in dict_item.values() %} - - {% endfor %} - - {% endfor %} - +{% block content %} -
Name Quantity Rate Price
{{ value }}
- - \ No newline at end of file +
+ +

Portfolio App

+

Made using Flask framework!

+
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/navbar.html b/templates/navbar.html index ec0e07de..2970a12b 100644 --- a/templates/navbar.html +++ b/templates/navbar.html @@ -1,13 +1,10 @@