├── advisors ├── __init__.py ├── yahoo_advisor.py ├── etoro_advisor.py └── strategy_advisor.py ├── backtesting ├── __init__.py └── backtesting.py ├── interfaces ├── __init__.py ├── messenger.py ├── strategy.py └── advisor.py ├── temp ├── trader_portfolios │ └── .gitignore └── .gitignore ├── requirements.txt ├── my_logging.py ├── messengers ├── __init__.py └── smtp.py ├── default_settings.py ├── science ├── dbscan.py └── cluster.py ├── backtest.py ├── .gitignore ├── main.py ├── strategy ├── first.py └── __init__.py ├── helpers.py └── etoro └── __init__.py /advisors/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backtesting/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /temp/trader_portfolios/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /temp/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | !trader_portfolios -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.7.4 2 | numpy>=1.21 3 | matplotlib==1.5.1 4 | scipy==0.17.1 5 | sklearn 6 | pandas==0.18.1 7 | cython==0.24.1 8 | statsmodels==0.8.0rc1 9 | -------------------------------------------------------------------------------- /interfaces/messenger.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod, abstractproperty 2 | 3 | class ABCMessenger(): 4 | __metaclass__ = ABCMeta 5 | 6 | def __init__(self, loop): 7 | self.loop = loop 8 | 9 | @abstractmethod 10 | async def send(self, message: str, recipients: dict=[], title: str=''): 11 | pass 12 | -------------------------------------------------------------------------------- /my_logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import settings 3 | 4 | logger = logging.getLogger(__name__) 5 | if settings.debug: 6 | logger.setLevel(logging.DEBUG) 7 | else: 8 | logger.setLevel(logging.INFO) 9 | handler = logging.StreamHandler() 10 | formatter = logging.Formatter('%(asctime)s - %(module)s: %(lineno)d - %(levelname)s - %(message)s') 11 | handler.setFormatter(formatter) 12 | logger.addHandler(handler) -------------------------------------------------------------------------------- /interfaces/strategy.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod, abstractproperty 2 | 3 | class ABCStrategy(): 4 | __metaclass__ = ABCMeta 5 | 6 | @abstractmethod 7 | def __init__(self, balance, instrument, trade_obj): 8 | pass 9 | 10 | @abstractmethod 11 | def start(self): 12 | pass 13 | 14 | @abstractmethod 15 | def tick(self, asc: float, bid: float, date: str): 16 | pass 17 | 18 | @abstractmethod 19 | def finish(self): 20 | pass 21 | -------------------------------------------------------------------------------- /interfaces/advisor.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod, abstractproperty 2 | import settings 3 | import aiohttp 4 | 5 | class ABCAdvisor(): 6 | __metaclass__ = ABCMeta 7 | 8 | def __init__(self, in_loop, **kwargs): 9 | if 'messenger' in kwargs: 10 | kwargs['messenger'].clients.append(self) 11 | self.message = None 12 | self.session = aiohttp.ClientSession(loop=in_loop) 13 | 14 | @abstractmethod 15 | async def loop(self): 16 | pass 17 | 18 | def get_message(self): 19 | return self.message 20 | -------------------------------------------------------------------------------- /messengers/__init__.py: -------------------------------------------------------------------------------- 1 | from messengers.smtp import SmtpAlert 2 | from interfaces.messenger import ABCMessenger 3 | from settings import messenger, recipients 4 | import sys 5 | 6 | 7 | class MessageManager(ABCMessenger): 8 | 9 | def __init__(self, loop): 10 | self.loop = loop 11 | 12 | def send(self, message, title=''): 13 | if hasattr(sys.modules[__name__], messenger): 14 | my_messenger = getattr(sys.modules[__name__], messenger)(self.loop) 15 | if isinstance(message, list): 16 | message = "\r\n\r\n".join(message) 17 | my_messenger.send(message, title=title, recipients=recipients[messenger]) -------------------------------------------------------------------------------- /default_settings.py: -------------------------------------------------------------------------------- 1 | debug = False 2 | 3 | payload = { 4 | 'Password': '', 5 | 'UserLoginIdentifier': '', 6 | 'Username': '', 7 | } 8 | 9 | smtp_host = '' 10 | smtp_port = '' 11 | smtp_login = '' 12 | smtp_password = '' 13 | 14 | messenger = 'SmtpAlert' 15 | recipients = { 16 | 'SmtpAlert': ['artem@webart-tech.ru'] 17 | } 18 | 19 | trade_strategy = 'First' 20 | account_type = 'Demo' 21 | 22 | stocks = { 23 | 'AAPL': 'Apple', 24 | 'TWTR': 'Twitter', 25 | 'CBS': 'CBS', 26 | 'GOOG': 'Google', 27 | 'FB': 'FaceBook', 28 | 'MSFT': 'Microsoft', 29 | 'NVDA': 'Nvidia', 30 | 'CSCO': 'Cisco', 31 | 'YHOO': 'Yahoo', 32 | 'AMZN': 'Amazon', 33 | 'INTC': 'Intel', 34 | 'EBAY': 'Ebay', 35 | 'YNDX': 'Yandex', 36 | 'QIWI': 'Qiwi', 37 | 'TSLA': 'Tesla', 38 | 'EA': 'Electronic Arts', 39 | 'ADBE': 'Adobe', 40 | 'BIDU': 'Baidu', 41 | 'AAL': 'American Airlines', 42 | 'NTDOY': 'Nintendo', 43 | } 44 | 45 | strtime_send_message = '00:00' 46 | fee_relative = {'first_case': 1.5, 'second_case': 0.5} 47 | fast_grow_points = 10 -------------------------------------------------------------------------------- /science/dbscan.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import matplotlib.pyplot as plt 3 | import statsmodels.api as sm 4 | import numpy as np 5 | 6 | excel = pd.ExcelFile('temp/PET_PRI_SPT_S1_M.xls') 7 | df = excel.parse(excel.sheet_names[1]) 8 | df = df.rename(columns=dict(zip(df.columns, ['Date','WTI','Brent']))) 9 | df = df[18:] 10 | df.index = df['Date'] 11 | #df = df[['WTI','Brent']] 12 | 13 | # model = ARIMA(df, order=(2, 1, 0)) 14 | # results_AR = model.fit(disp=-1) 15 | # plt.plot(ts_log_diff) 16 | # plt.plot(results_AR.fittedvalues, color='red') 17 | # plt.title('RSS: %.4f'% sum((results_AR.fittedvalues-ts_log_diff)**2)) 18 | 19 | # 20 | #plt.plot(moving_avg, color='red') 21 | 22 | mod = sm.tsa.statespace.SARIMAX(df['WTI'].astype(float), trend='n', order=(0,1,0), seasonal_order=(0,1,1,12)) 23 | results = mod.fit() 24 | predict = results.predict(dinamic=True) 25 | plt.plot(predict, color='red') 26 | ts_log_moving_avg_diff = df['WTI'].ewm(12).mean() 27 | plt.plot(ts_log_moving_avg_diff, color='green') 28 | plt.plot(df['WTI'], color='blue') 29 | plt.savefig("temp/wti_and_brent_all_data.png",dpi=200) -------------------------------------------------------------------------------- /messengers/smtp.py: -------------------------------------------------------------------------------- 1 | from interfaces.messenger import ABCMessenger 2 | import smtplib 3 | import settings 4 | from my_logging import logger 5 | from email.mime.multipart import MIMEMultipart 6 | from email.mime.text import MIMEText 7 | from my_logging import logger as logging 8 | 9 | 10 | class SmtpAlert(ABCMessenger): 11 | 12 | def __init__(self, loop): 13 | ABCMessenger.__init__(self, loop) 14 | logger.info('Connect with {}:{}'.format(settings.smtp_host, settings.smtp_port)) 15 | self.smtp = smtplib.SMTP(host=settings.smtp_host, port=settings.smtp_port, timeout=5) 16 | self.smtp.starttls() 17 | self.smtp.login(settings.smtp_login, settings.smtp_password) 18 | self.smtp.debuglevel = 0 19 | 20 | 21 | def send(self, message, recipients, title): 22 | msg = MIMEMultipart("alternative") 23 | msg["Subject"] = title 24 | msg["From"] = ", ".join(recipients) 25 | part1 = MIMEText(message) 26 | msg.attach(part1) 27 | try: 28 | self.smtp.sendmail(settings.smtp_login, recipients, msg.as_string().encode('ascii')) 29 | except smtplib.SMTPServerDisconnected: 30 | logging.error('Smtp Error') 31 | -------------------------------------------------------------------------------- /backtesting/backtesting.py: -------------------------------------------------------------------------------- 1 | class BackTesting(object): 2 | def __init__(self, dataframe, class_strategy, balance, instrument, trade_obj): 3 | if dataframe: 4 | if 'asc' not in dataframe[0]: 5 | raise Exception('Option asc was not found in dataframe') 6 | if 'bid' not in dataframe[0]: 7 | raise Exception('Option bid was not found in dataframe') 8 | if 'date' not in dataframe[0]: 9 | raise Exception('Option date was not found in dataframe') 10 | object_strategy = class_strategy(balance, instrument, trade_obj) 11 | if hasattr(object_strategy, 'tick'): 12 | start = getattr(object_strategy, 'start') 13 | start(dataframe[0]['asc'], dataframe[0]['bid'], dataframe[0]['date']) 14 | method_tick = getattr(object_strategy, 'tick') 15 | for data in dataframe: 16 | method_tick(data['asc'], data['bid'], data['date']) 17 | getattr(object_strategy, 'finish')(dataframe[-1]['asc'], dataframe[-1]['bid'], dataframe[-1]['date']) 18 | else: 19 | raise Exception('Method tick was not found') 20 | else: 21 | raise Exception('Dataframe is empty!') -------------------------------------------------------------------------------- /backtest.py: -------------------------------------------------------------------------------- 1 | import strategy 2 | from backtesting.backtesting import BackTesting 3 | import etoro 4 | import asyncio 5 | import os 6 | from my_logging import logger 7 | import time 8 | 9 | 10 | class BaseTrade(object): 11 | def __init__(self): 12 | self.instrument = 'EURUSD' 13 | self.balance = 5000 14 | self.shoulder = 100 15 | self.total_marg = 0 16 | 17 | def back_testing(self, dataframe, strategy, trade_obj): 18 | BackTesting(dataframe, strategy, self.balance, self.instrument, trade_obj) 19 | 20 | if __name__ == "__main__": 21 | filelist = [f for f in os.listdir("temp/mybalance/") if f.endswith(".png")] 22 | for f in filelist: 23 | os.remove("temp/mybalance/" + f) 24 | trade_obj = BaseTrade() 25 | loop = asyncio.get_event_loop() 26 | for i in range(1, 8): 27 | dataframe = [] 28 | history_items = loop.run_until_complete(etoro.get_history(i)) 29 | if 'Candles' in history_items and history_items['Candles'] is not None: 30 | if history_items['Candles'][0]['Candles']: 31 | for item in reversed(history_items['Candles'][0]['Candles']): 32 | dataframe.append({'asc': item['Close'], 33 | 'bid': item['Open'], 34 | 'date': item['FromDate']}) 35 | trade_obj.back_testing(dataframe, strategy.StrategyManager, trade_obj) 36 | time.sleep(1) 37 | logger.info('Total marg: {}'.format(trade_obj.total_marg)) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | .idea/.name 93 | .idea/encodings.xml 94 | .idea/etoro.iml 95 | .idea/misc.xml 96 | .idea/modules.xml 97 | .idea/workspace.xml 98 | settings.py 99 | -------------------------------------------------------------------------------- /science/cluster.py: -------------------------------------------------------------------------------- 1 | # Author: Gael Varoquaux gael.varoquaux@normalesup.org 2 | # License: BSD 3 clause 3 | 4 | import datetime 5 | import numpy as np 6 | try: 7 | from matplotlib.finance import quotes_historical_yahoo 8 | except ImportError: 9 | from matplotlib.finance import quotes_historical_yahoo_ochl as quotes_historical_yahoo 10 | from sklearn import cluster, covariance 11 | 12 | from settings import stocks 13 | 14 | def analysis(): 15 | now = datetime.datetime.now() 16 | ############################################################################### 17 | 18 | d1 = datetime.datetime(2016, now.month, 1) 19 | d2 = datetime.datetime(2016, now.month, 30) 20 | 21 | symbol_dict = stocks 22 | 23 | symbols, names = np.array(list(symbol_dict.items())).T 24 | 25 | quotes = [quotes_historical_yahoo(symbol, d1, d2, asobject=True) 26 | for symbol in symbols] 27 | 28 | open = np.array([q.open for q in quotes]).astype(np.float) 29 | close = np.array([q.close for q in quotes]).astype(np.float) 30 | 31 | # The daily variations of the quotes are what carry most information 32 | variation = close - open 33 | 34 | ############################################################################### 35 | # Learn a graphical structure from the correlations 36 | edge_model = covariance.GraphLassoCV() 37 | 38 | # standardize the time series: using correlations rather than covariance 39 | # is more efficient for structure recovery 40 | X = variation.copy().T 41 | X /= X.std(axis=0) 42 | edge_model.fit(X) 43 | 44 | ############################################################################### 45 | # Cluster using affinity propagation 46 | _, labels = cluster.affinity_propagation(edge_model.covariance_) 47 | n_labels = labels.max() 48 | 49 | message = '' 50 | 51 | for i in range(n_labels + 1): 52 | message += 'Cluster %i: %s\r\n' % ((i + 1), ', '.join(names[labels == i])) 53 | 54 | return message 55 | 56 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from my_logging import logger as logging 3 | from concurrent.futures import ThreadPoolExecutor 4 | from advisors.etoro_advisor import EtoroAdvisor 5 | from advisors.yahoo_advisor import YahooAdvisor 6 | from advisors.strategy_advisor import StrategyAdvisor 7 | from messengers import MessageManager 8 | import time 9 | import settings 10 | # from science import cluster 11 | 12 | 13 | if '__main__' == __name__: 14 | try: 15 | is_running = True 16 | 17 | def messages_listen(): 18 | while is_running: 19 | messages = [] 20 | for client in messenger.clients: 21 | message = client.get_message() 22 | if message is not None and message: 23 | messages.append(message) 24 | client.message = '' 25 | if messages and not settings.debug: 26 | messenger.send(messages, title='Мои финансы') 27 | time.sleep(1) 28 | executor = ThreadPoolExecutor(2) 29 | 30 | loop = asyncio.get_event_loop() 31 | # loop.set_default_executor(executor) 32 | messenger = MessageManager(loop) 33 | messenger.clients = [] 34 | 35 | etoro = EtoroAdvisor(loop, messenger=messenger) 36 | yahoo = YahooAdvisor(loop, messenger=messenger) 37 | strategy = StrategyAdvisor(loop, messenger=messenger) 38 | while is_running: 39 | tasks = [ 40 | loop.create_task(etoro.loop()), 41 | loop.create_task(yahoo.loop()), 42 | loop.create_task(strategy.loop()), 43 | loop.create_task(strategy.fast_change_detect()), 44 | # loop.create_task(strategy.check_fast_orders()), 45 | ] 46 | asyncio.ensure_future(loop.run_in_executor(executor, messages_listen)) 47 | loop.run_until_complete(asyncio.wait(tasks)) 48 | except KeyboardInterrupt: 49 | logging.info('Exit') 50 | is_running = False 51 | try: 52 | # loop._default_executor.shutdown(wait=True) 53 | loop.close() 54 | except: pass 55 | 56 | 57 | -------------------------------------------------------------------------------- /strategy/first.py: -------------------------------------------------------------------------------- 1 | from my_logging import logger 2 | 3 | class First(): 4 | def __init__(self): 5 | self.direct = 'buy' 6 | self.backdirect = {'buy': 'sell', 'sell': 'buy'} 7 | self.last_price = 0 8 | self.my_store = [] 9 | 10 | def start(self, cls, start_date): 11 | pass 12 | 13 | def tick(self, cls, asc, bid, date, coef): 14 | profit = 10 15 | if len(self.my_store) <= 10: 16 | self.my_store.append(asc) 17 | else: 18 | self.my_store.append(asc) 19 | start = self.my_store[-10] 20 | end = self.my_store[-1] 21 | if self.last_price == 0: 22 | extremum = (end-start) * coef 23 | if extremum > profit: 24 | self.last_price = asc 25 | self.order(cls, asc, 'sell') 26 | if extremum < 0: 27 | pass 28 | #self.last_price = asc 29 | #self.order(cls, asc, 'buy') 30 | else: 31 | if self.direct == 'buy': 32 | profit_pt = (asc - self.last_price) * coef 33 | else: 34 | profit_pt = (self.last_price - asc) * coef 35 | logger.debug('DifPrice: {} Coef: {}, Profit: {} Last-Price: {}'.format((asc - self.last_price), coef, profit_pt, self.last_price)) 36 | if profit_pt > profit or profit_pt < -1*profit/2: 37 | if profit_pt > profit: 38 | logger.debug('plus') 39 | else: 40 | logger.debug('minus') 41 | self.order(cls, asc, self.backdirect[self.direct]) 42 | self.last_price = 0 43 | 44 | 45 | def finish(self, cls, finish_date): 46 | if cls._count_item > 0: 47 | cls.sell(cls._count_item) 48 | else: 49 | cls.buy(cls._count_item*-1) 50 | 51 | def order(self, cls, asc, direct, count=1): 52 | 53 | if direct == 'buy': 54 | cls.buy(count) 55 | self.direct = 'buy' 56 | else: 57 | cls.sell(count) 58 | self.direct = 'sell' 59 | -------------------------------------------------------------------------------- /advisors/yahoo_advisor.py: -------------------------------------------------------------------------------- 1 | import etoro 2 | import settings 3 | 4 | from interfaces.advisor import ABCAdvisor 5 | import datetime 6 | 7 | 8 | class YahooAdvisor(ABCAdvisor): 9 | def __init__(self, in_loop, **kwargs): 10 | self.last_run = None 11 | super().__init__(in_loop, **kwargs) 12 | self._message = None 13 | 14 | async def loop(self): 15 | datetime_obj = datetime.datetime.today() 16 | current_time = datetime_obj.time() 17 | if str(current_time).find(settings.strtime_send_message) != 0: 18 | return False 19 | if self.last_run is not None and current_time.hour == self.last_run.hour: 20 | return False 21 | self._message = '\r\nYahoo\r\n\r\n' 22 | self._message += 'Recommendation\r\n' 23 | for stock in settings.stocks: 24 | url = 'https://query2.finance.yahoo.com/v10/finance/quoteSummary/{stock}?formatted=true' \ 25 | '&crumb=a9I3lxfM3R3&lang=en-US®ion=US' \ 26 | '&modules=upgradeDowngradeHistory%2CrecommendationTrend%2CearningsTrend' \ 27 | '&corsDomain=finance.yahoo.com'.format( 28 | stock=stock 29 | ) 30 | yahoo_data = await etoro.get(self.session, url) 31 | if yahoo_data is not None: 32 | data = yahoo_data['quoteSummary']['result'][0] 33 | recomendations = data['recommendationTrend']['trend'] 34 | self._message += '\r\n{} ({})\r\n'.format(settings.stocks[stock], stock) 35 | for recomendation in recomendations: 36 | self._message += '{period}: strongBuy: {strongBuy}, buy {buy}, sell:{sell}, hold:{hold}, ' \ 37 | 'strongSell: {strongSell}\r\n'.format(period=recomendation['period'], 38 | strongBuy=recomendation['strongBuy'], 39 | buy=recomendation['buy'], 40 | hold=recomendation['hold'], 41 | sell=recomendation['sell'], 42 | strongSell=recomendation['strongSell'], ) 43 | earnings = data['earningsTrend']['trend'] 44 | self._message += '\r\nEarning Trend\r\n' 45 | for earning in earnings: 46 | self._message += '{period}: Growth: {growth}, earningsEstimate: {earningsEstimate}\r\n'.format( 47 | period=earning['period'], growth=earning['growth']['fmt'] if 'fmt' in earning['growth'] else '', 48 | earningsEstimate=earning['earningsEstimate']['avg']['fmt'] 49 | if 'fmt' in earning['earningsEstimate']['avg'] else 'None') 50 | self.message = self._message 51 | self.last_run = datetime.datetime.today() 52 | -------------------------------------------------------------------------------- /helpers.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | import os 4 | import json 5 | import time 6 | from typing import TypeVar 7 | from my_logging import logger as logging 8 | 9 | 10 | DictInt = TypeVar('DictInt', dict, list) 11 | defaul_time_cache = 60 12 | 13 | def id_generator(size=8, chars='absdef' + string.digits): 14 | return ''.join(random.choice(chars) for _ in range(size)) 15 | 16 | 17 | def device_id(): 18 | pattern = "xxxxtxxx-xxxx-4xxx-yxxx-xxxxxxtxxxxx" 19 | pattern = pattern.replace('t', hex(int(time.time()) % 16).replace('0x', '')) 20 | pattern_list = list(pattern) 21 | for key, symblol in enumerate(list(pattern_list)): 22 | if symblol == 'x' or symblol == 'y': 23 | n = 16 * random.random() 24 | if n: 25 | n /= 3 26 | else: 27 | n = 8 28 | pattern_list[key] = hex(int(n)).replace('0x', '') 29 | return "".join(pattern_list) 30 | 31 | 32 | def set_cache(key: string, data: DictInt) -> None: 33 | base_path = os.path.dirname(__file__) 34 | try: 35 | with open(os.path.join(base_path, 'temp', key), 'w') as fd: 36 | fd.write(json.dumps(data)) 37 | except TypeError: 38 | logging.error('Type error') 39 | set_cache(key, {}) 40 | except json.JSONDecodeError: 41 | logging.error('Json decode error') 42 | 43 | def cookies_parse(response_cookies): 44 | cookies_dict = {} 45 | cookies = response_cookies 46 | for cookie in str(cookies).split('\r\n'): 47 | cookie = cookie.split(' ') 48 | if len(cookie) > 1: 49 | cookie_list = cookie[1].split('=') 50 | if len(cookie_list) == 2: 51 | cookies_dict[cookie_list[0]] = cookie_list[1] 52 | elif len(cookie_list) > 2: 53 | cookies_dict[cookie_list[0]] = cookie_list[1] 54 | for i in range(len(cookie_list) - 2): 55 | cookies_dict[cookie_list[0]] += '=' 56 | return cookies_dict 57 | 58 | 59 | def get_cache(key: string, number_of_time: int=1) -> dict: 60 | base_path = os.path.dirname(__file__) 61 | path = os.path.join(base_path, 'temp', key) 62 | if os.path.isfile(path): 63 | mod_time = time.time() - os.path.getmtime(path) 64 | if number_of_time and mod_time > (number_of_time*defaul_time_cache): 65 | return {} 66 | with open(path, 'r') as fd: 67 | file_content = fd.read() 68 | fd.close() 69 | try: 70 | return json.loads(file_content) 71 | except json.decoder.JSONDecodeError: 72 | return {} 73 | else: 74 | return {} 75 | 76 | 77 | def get_list_instruments(aggregate_data, type='Buy'): 78 | if not aggregate_data: 79 | return {} 80 | max_store = {'count': 0, 'ids': []} 81 | for instr_id in aggregate_data[type]: 82 | if max_store['count'] < aggregate_data[type][instr_id]: 83 | max_store['count'] = aggregate_data[type][instr_id] 84 | max_store['ids'] = [instr_id] 85 | if max_store['count'] == aggregate_data[type][instr_id]: 86 | if instr_id not in max_store['ids']: 87 | max_store['ids'].append(instr_id) 88 | return max_store 89 | 90 | 91 | 92 | 93 | 94 | if '__main__' == __name__: 95 | print(device_id()) 96 | -------------------------------------------------------------------------------- /strategy/__init__.py: -------------------------------------------------------------------------------- 1 | from strategy.first import First 2 | from interfaces.strategy import ABCStrategy 3 | from settings import trade_strategy 4 | import sys 5 | from my_logging import logger 6 | import matplotlib.pyplot as plt 7 | import uuid 8 | 9 | 10 | class StrategyManager(ABCStrategy): 11 | 12 | def __init__(self, balance, instrument='', trade_obj=None, buy=None, sell=None): 13 | self._start_balance = balance 14 | self._balance = balance 15 | self._count_item = 0 16 | self.asc = 0 17 | self.bid = 0 18 | self._counter = {'total': 0, 'buy': 0, 'sell': 0} 19 | self.balance_change = [] 20 | self.parent = trade_obj 21 | self.buy_method = buy 22 | self.sell_method = sell 23 | if hasattr(sys.modules[__name__], trade_strategy): 24 | self.class_strategy = getattr(sys.modules[__name__], trade_strategy)() 25 | 26 | def start(self, start_asc=None, start_bid=None, start_date=None): 27 | logger.info('Start Date: {} Start balance: {}!'.format(start_date, self._balance)) 28 | self.class_strategy.start(self, start_date) 29 | 30 | def tick(self, asc, bid, date): 31 | split_asc = str(asc).split('.') 32 | if len(split_asc[0]) > 1: 33 | new_bid = self.bid + 0.03 34 | coef = 100 35 | else: 36 | new_bid = self.bid + 0.0003 37 | coef = 10000 38 | self.asc = asc 39 | if asc == bid: 40 | self.bid = new_bid 41 | else: 42 | self.bid = asc 43 | self.class_strategy.tick(self, asc, bid, date, coef) 44 | if not self.balance_change or (self.balance_change and self.balance_change[-1] != self._balance): 45 | if not self._count_item: 46 | self.balance_change.append(self._balance) 47 | logger.debug('Asc: {} Balance: {}. Total: {} Buy: {} Sell: {} Orders: {}'.format(asc, 48 | self._balance, 49 | self._counter['total'], 50 | self._counter['buy'], 51 | self._counter['sell'], 52 | self._count_item)) 53 | 54 | def finish(self, finish_asc, finish_bid, finish_date): 55 | self.class_strategy.finish(self, finish_date) 56 | logger.info('Finish Date: {} Balance: {}. Total: {} Buy: {} Sell: {} Orders: {} Marg: {}'.format(finish_date, 57 | self._balance, 58 | self._counter['total'], 59 | self._counter['buy'], 60 | self._counter['sell'], 61 | self._count_item, 62 | self._balance - self._start_balance)) 63 | if self.parent is not None: 64 | self.parent.total_marg += self._balance - self._start_balance 65 | self.balance_change.append(self._balance) 66 | plt.plot(self.balance_change, color='red') 67 | plt.savefig("temp/mybalance/my_balance{}.png".format(uuid.uuid4()), dpi=200) 68 | plt.close() 69 | 70 | 71 | def buy(self, count): 72 | if self.buy_method is None: 73 | if count * self.asc > self._balance and self._count_item >= 0: 74 | logger.info('Buy. Not enough money! Need: {}. Balance: {}'.format(count * self.asc, self._balance)) 75 | self._counter['total'] += 1 76 | self._counter['buy'] += 1 77 | if self.asc > 0: 78 | tmp_count = self._count_item + count 79 | self._balance -= count * self.asc 80 | self._count_item = tmp_count 81 | logger.debug('Buy asc: {}'.format(self.asc)) 82 | else: 83 | self.buy_method(count) 84 | 85 | def sell(self, count): 86 | if self.sell_method is None: 87 | if count * self.asc > self._balance and self._count_item <= 0: 88 | logger.info('Sell. Not enough money! Need: {}. Balance: {}'.format(count * self.asc, self._balance)) 89 | return False 90 | self._counter['total'] += 1 91 | self._counter['sell'] += 1 92 | if self.asc > 0: 93 | tmp_count = self._count_item - count 94 | self._balance += count * self.asc 95 | self._count_item = tmp_count 96 | logger.debug('Sell asc: {}'.format(self.asc)) 97 | else: 98 | self.sell_method(count) -------------------------------------------------------------------------------- /etoro/__init__.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import asyncio 3 | import json 4 | from my_logging import logger as logging 5 | from datetime import datetime 6 | import settings 7 | import helpers 8 | import concurrent 9 | 10 | 11 | LAST_REQUEST = None 12 | REQUEST_COUNT = 0 13 | 14 | async def trader_list(session, activeweeksmin=30, blocked=False, bonusonly=False, copyinvestmentpctmax=0, 15 | gainmax=80, gainmin=5, highleveragepctmax=12, istestaccount=False, lastactivitymax=7, 16 | optin=True, page=1, pagesize=500, period='OneYearAgo', profitablemonthspctmin=65, 17 | riskscoremax=4, riskscoremin=1, sort='-gain', tradesmin=15, weeklyddmin=-10, 18 | weeklyddmax=-3, dailyddmin=-7, dailyddmax=-1): 19 | list_trades_url = 'https://www.etoro.com/sapi/rankings/rankings/?client_request_id={client_request_id}&' \ 20 | 'activeweeksmin={activeweeksmin}&blocked={blocked}&bonusonly={bonusonly}©investmentpctmax=' \ 21 | '{copyinvestmentpctmax}&gainmax={gainmax}&gainmin={gainmin}&' \ 22 | 'highleveragepctmax={highleveragepctmax}&istestaccount={istestaccount}&lastactivitymax=' \ 23 | '{lastactivitymax}&optin={optin}&page={page}&pagesize={pagesize}&period={period}&' \ 24 | 'profitablemonthspctmin={profitablemonthspctmin}&riskscoremax={riskscoremax}&' \ 25 | 'riskscoremin={riskscoremin}&sort={sort}&tradesmin={tradesmin}&' \ 26 | 'weeklyddmin={weeklyddmin}&weeklyddmax={weeklyddmax}&dailyddmax={dailyddmax}' \ 27 | '&dailyddmin={dailyddmin}'.format(client_request_id=helpers.device_id(), 28 | activeweeksmin=activeweeksmin, 29 | blocked='false' if not blocked else 'true', bonusonly=bonusonly, 30 | copyinvestmentpctmax=copyinvestmentpctmax, gainmax=gainmax, 31 | gainmin=gainmin, highleveragepctmax=highleveragepctmax, 32 | istestaccount=istestaccount, lastactivitymax=lastactivitymax, 33 | optin='false' if not optin else 'true', page=page, 34 | pagesize=pagesize, period=period, 35 | profitablemonthspctmin=profitablemonthspctmin, 36 | riskscoremax=riskscoremax, riskscoremin=riskscoremin, 37 | sort=sort, tradesmin=tradesmin, 38 | weeklyddmax=weeklyddmax, weeklyddmin=weeklyddmin, 39 | dailyddmin=dailyddmin, dailyddmax=dailyddmax) 40 | return await get(session, list_trades_url) 41 | 42 | async def get_history(instrument_id=1, count=1000): 43 | url = 'https://candle.etoro.com/candles/desc.json/ThirtyMinutes/{}/{}'.format(count, instrument_id) 44 | #url = 'https://candle.etoro.com/candles/desc.json/FiveMinutes/1000/1' 45 | async with aiohttp.ClientSession() as session: 46 | data = await get(session, url) 47 | return data 48 | 49 | async def instruments_rate(session): 50 | url = 'https://www.etoro.com/sapi/trade-real/instruments/?client_request_id={}' \ 51 | '&InstrumentDataFilters=Activity,Rates,TradingData'.format(helpers.device_id()) 52 | return await get(session, url) 53 | 54 | async def instruments(session): 55 | url = 'https://api.etorostatic.com/sapi/instrumentsmetadata/V1.1/instruments' 56 | return await get(session, url) 57 | 58 | async def user_info(session, login): 59 | url = 'https://www.etoro.com/api/logininfo/v1.1/users/{}?client_request_id={}'.format(login, helpers.device_id()) 60 | return await get(session, url) 61 | 62 | async def user_portfolio(session, user_id): 63 | url = 'https://www.etoro.com/sapi/trade-real/portfolios/public?client_request_id={}&cid={}'.format( 64 | helpers.device_id(), user_id) 65 | return await get(session, url) 66 | 67 | async def watch_list(session): 68 | url = 'https://www.etoro.com/api/watchlist/v1/watchlists?client_request_id={}&doNotReturnBadRequest=true'.format( 69 | helpers.device_id() 70 | ) 71 | return await get(session, url) 72 | 73 | async def get(session, url, json_flag=True, recursion_level=1): 74 | global LAST_REQUEST 75 | global REQUEST_COUNT 76 | if recursion_level > 10: 77 | logging.error('Recursion is too deep') 78 | raise Exception('Recursion is too deep') 79 | if LAST_REQUEST is not None: 80 | dif_datetime = datetime.now() - LAST_REQUEST 81 | if dif_datetime.seconds < 1: 82 | REQUEST_COUNT += 1 83 | if REQUEST_COUNT > 2: 84 | REQUEST_COUNT = 0 85 | logging.debug('I am sleep') 86 | await asyncio.sleep(1) 87 | LAST_REQUEST = datetime.now() 88 | logging.debug('Get query to {url}'.format(url=url.split('?')[0])) 89 | headers = helpers.get_cache('headers') 90 | cookies = helpers.get_cache('cookies') 91 | data= {} 92 | try: 93 | with aiohttp.Timeout(10): 94 | async with session.get(url, headers=headers) as response: 95 | data = await response.json() 96 | except (asyncio.TimeoutError, aiohttp.errors.ServerDisconnectedError, aiohttp.errors.ClientOSError, 97 | aiohttp.errors.ClientResponseError): 98 | logging.error('Query Error. Level {}'.format(recursion_level)) 99 | await asyncio.sleep(2*recursion_level) 100 | except concurrent.futures._base.TimeoutError: 101 | logging.error('Query Error.') 102 | # return await get(session, url, json_flag=json_flag, recursion_level=(recursion_level +1 )) 103 | except json.decoder.JSONDecodeError: 104 | logging.error('Json decode error. Level {}. Url: {}'.format(recursion_level, url)) 105 | await asyncio.sleep(2*recursion_level) 106 | # return await get(session, url, json_flag=json_flag, recursion_level=(recursion_level +1 )) 107 | return data 108 | 109 | async def close_order(session, position_id, price=None, demo=True): 110 | logging.info('Order was closed. Price: {}'.format(price)) 111 | account_type = 'demo' if demo else 'real' 112 | headers = helpers.get_cache('headers') 113 | if price is None: 114 | url = 'https://www.etoro.com/sapi/trade-{account_type}/exit-orders?client_request_id={client_id}'.format( 115 | client_id=helpers.device_id(), account_type=account_type 116 | ) 117 | payload = { 118 | 'PendingClosePositionID': position_id 119 | } 120 | async with session.post(url, data=json.dumps(payload), headers=headers) as response: 121 | resp = await response.json() 122 | else: 123 | url = 'https://www.etoro.com/sapi/trade-{account_type}/positions/{position_id}?' \ 124 | 'client_request_id={client_id}&ClientViewRate={price}' \ 125 | '&PositionID={position_id}'.format(position_id=position_id, client_id=helpers.device_id(), price=price, 126 | account_type=account_type) 127 | async with session.delete(url, headers=headers) as response: 128 | resp = await response.json() 129 | return resp 130 | 131 | 132 | async def order(session, InstrumentID, ClientViewRate, IsBuy=True, IsTslEnabled=False, Leverage=1, Amount=25, demo=True): 133 | logging.info('Order is opened. Instrument: {}. IsBuy: {}'.format(InstrumentID, IsBuy)) 134 | url = 'https://www.etoro.com/sapi/trade-{account_type}/positions?client_request_id={}'.format(helpers.device_id(), 135 | account_type='demo' if demo else 'real') 136 | stop_loss = (ClientViewRate * 1.4) if not IsBuy else (ClientViewRate * 0.6) 137 | take_profit = (ClientViewRate * 1.4) if IsBuy else (ClientViewRate * 0.6) 138 | headers = helpers.get_cache('headers') 139 | payload = { 140 | 'Amount': Amount, 141 | 'ClientViewRate': ClientViewRate, 142 | 'InstrumentID': InstrumentID, 143 | 'IsBuy': IsBuy, 144 | 'IsTslEnabled': IsTslEnabled, 145 | 'Leverage': Leverage, 146 | 'StopLossRate': stop_loss, 147 | 'TakeProfitRate': take_profit, 148 | } 149 | async with session.post(url, data=json.dumps(payload), headers=headers) as response: 150 | resp = await response.json() 151 | return resp 152 | 153 | 154 | async def login(session, account_type='Demo', only_info=False): 155 | url = 'https://www.etoro.com/api/sts/v2/login/?client_request_id={}'.format(helpers.device_id()) 156 | payload = settings.payload 157 | params = {'client_request_id': helpers.device_id(), 158 | 'conditionIncludeDisplayableInstruments': False, 159 | 'conditionIncludeMarkets': False, 160 | 'conditionIncludeMetadata': False, 161 | } 162 | headers = {'content-type': 'application/json;charset=UTF-8', 163 | 'AccountType': account_type, 164 | 'ApplicationIdentifier': 'ReToro', 165 | 'ApplicationVersion': 'vp3079', 166 | 'X-CSRF-TOKEN': 'k%7cuGYP%7ci9dVOhujYx0ZsTw%5f%5f', 167 | 'X-DEVICE-ID': helpers.device_id() 168 | } 169 | helpers.set_cache('headers', headers) 170 | if not only_info: 171 | with aiohttp.Timeout(10): 172 | async with session.post(url, 173 | data=json.dumps(payload), 174 | headers=headers) as response: 175 | if response.status == 201: 176 | login_content_josn = await response.read() 177 | else: 178 | return {} 179 | login_content = json.loads(login_content_josn.decode('utf-8')) 180 | headers['Authorization'] = login_content['accessToken'] 181 | helpers.set_cache('headers', headers) 182 | cookies_dict = helpers.cookies_parse(response.cookies) 183 | helpers.set_cache('cookies', cookies_dict) 184 | with aiohttp.Timeout(10): 185 | try: 186 | async with session.get('https://www.etoro.com/api/logininfo/v1.1/logindata' ,params=params, 187 | headers=headers) as response: 188 | login_info = await response.json() 189 | except (asyncio.TimeoutError, aiohttp.errors.ServerDisconnectedError, aiohttp.errors.ClientOSError, 190 | aiohttp.errors.ClientResponseError): 191 | logging.error('Query Error.') 192 | except concurrent.futures._base.TimeoutError: 193 | logging.error('Query Error.') 194 | except json.decoder.JSONDecodeError: 195 | logging.error('Json decode error.') 196 | return login_info 197 | -------------------------------------------------------------------------------- /advisors/etoro_advisor.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import asyncio 3 | 4 | import random 5 | import json 6 | import operator 7 | import datetime 8 | 9 | import helpers 10 | import etoro 11 | from my_logging import logger as logging 12 | from interfaces.advisor import ABCAdvisor 13 | import settings 14 | 15 | 16 | class EtoroAdvisor(ABCAdvisor): 17 | 18 | def __init__(self, in_loop, **kwargs): 19 | super().__init__(in_loop, **kwargs) 20 | self.aggregate_data = {} 21 | self.trade = {} 22 | self.time_out = 5 23 | self.cache_time_value = 60 24 | self.aviable_limit = 50 25 | self.user_portfolio = {} 26 | self.instruments = {} 27 | self.instruments_rate = {} 28 | self.instruments_instrument = {} 29 | self.my_portfolio = {} 30 | self.time_out *= 60 31 | # self.messenger = self._messageManager(in_loop) 32 | self.account_type = settings.account_type 33 | self.last_run = None 34 | self.last_send_message = None 35 | self._message = None 36 | 37 | @property 38 | def cache_time(self): 39 | return random.randint(self.cache_time_value-30, self.cache_time_value) 40 | 41 | def do_somesing(self): 42 | while True: 43 | pass 44 | 45 | async def traders_info(self, entire_balance=True): 46 | list_traders = helpers.get_cache('list_traders', self.cache_time) 47 | if not list_traders: 48 | list_traders = await etoro.trader_list(self.session, gainmin=0, profitablemonthspctmin=35) 49 | helpers.set_cache('list_traders', list_traders) 50 | logging.debug('Traders was found: {}'.format(list_traders['TotalRows'])) 51 | traders = helpers.get_cache('traders', self.cache_time * 10) 52 | if not traders: 53 | traders = [] 54 | for trader in list_traders['Items']: 55 | trader_info = helpers.get_cache('trader_portfolios/trader_info_{}'.format(trader['UserName']), 56 | self.cache_time) 57 | if not trader_info: 58 | trader_info = await etoro.user_info(self.session, trader['UserName']) 59 | helpers.set_cache('trader_portfolios/trader_info_{}'.format(trader['UserName']), trader_info) 60 | traders.append(trader_info) 61 | helpers.set_cache('traders', traders) 62 | 63 | for trader in traders: 64 | portfolio = helpers.get_cache('trader_portfolios/{}'.format(trader['realCID']), (random.randint(5, self.cache_time))) 65 | if not portfolio: 66 | portfolio = await etoro.user_portfolio(self.session, trader['realCID']) 67 | if portfolio: 68 | helpers.set_cache('trader_portfolios/{}'.format(trader['realCID']), portfolio) 69 | if portfolio: 70 | balance = 9999999 if entire_balance else self.user_portfolio["Credit"] 71 | if 'AggregatedPositions' in portfolio: 72 | for position in portfolio['AggregatedPositions']: 73 | if position['Direction'] in self.aggregate_data \ 74 | and self.instruments_instrument[position['InstrumentID']]['MinPositionAmount'] <= self.aviable_limit\ 75 | and self.instruments_instrument[position['InstrumentID']]['MinPositionAmount'] <= balance: 76 | if position['InstrumentID'] not in self.aggregate_data[position['Direction']]: 77 | self.aggregate_data[position['Direction']][position['InstrumentID']] = 0 78 | self.aggregate_data[position['Direction']][position['InstrumentID']] += 1 79 | break 80 | return True 81 | 82 | def full_my_porfolio(self): 83 | for position in self.user_portfolio["Positions"]: 84 | self.my_portfolio[position["InstrumentID"]] = { 85 | 'IsBuy': position["IsBuy"], 86 | 'Amount': position["Amount"], 87 | 'CID': position["CID"], 88 | 'PositionID': position["PositionID"] 89 | } 90 | logging.debug('My order: {}. My price: {}. Current Ask: {}. Direct: {}'.format( 91 | self.instruments[position["InstrumentID"]]['SymbolFull'], position["OpenRate"], 92 | self.instruments_rate[position["InstrumentID"]]["Ask"], 'Byu' if position["IsBuy"] else 'Sell')) 93 | 94 | async def check_instruments(self): 95 | self.instruments = helpers.get_cache('instruments', self.cache_time) 96 | if not self.instruments: 97 | self.instruments = await etoro.instruments(self.session) 98 | helpers.set_cache('instruments', self.instruments) 99 | self.instruments = {instrument['InstrumentID']: instrument for instrument in 100 | self.instruments['InstrumentDisplayDatas']} 101 | self.instruments_rate = helpers.get_cache('instruments_rate', self.cache_time) 102 | if not self.instruments_rate: 103 | self.instruments_rate = await etoro.instruments_rate(self.session) 104 | helpers.set_cache('instruments_rate', self.instruments_rate) 105 | self.instruments_instrument = {instrument['InstrumentID']: instrument for instrument in 106 | self.instruments_rate['Instruments']} 107 | self.instruments_rate = {instrument['InstrumentID']: instrument for instrument in self.instruments_rate['Rates']} 108 | self.full_my_porfolio() 109 | 110 | 111 | async def check_my_order(self, buy_max, sell_max): 112 | for instrument_id in self.my_portfolio: 113 | if instrument_id not in buy_max['ids'] and instrument_id not in sell_max['ids'] or \ 114 | (instrument_id in buy_max['ids'] and buy_max['count'] <= 1) or \ 115 | (instrument_id in sell_max['ids'] and sell_max['count'] <= 1): 116 | logging.debug('Deleting of instrument {}'.format(self.instruments[instrument_id]['SymbolFull'])) 117 | price_view = self.instruments_rate[instrument_id]['Ask'] if self.my_portfolio[instrument_id]['IsBuy'] else \ 118 | self.instruments_rate[instrument_id]['Bid'] 119 | 120 | await etoro.close_order(self.session, self.my_portfolio[instrument_id]['PositionID'], price_view) 121 | 122 | 123 | async def login(self): 124 | if self.session.closed: 125 | self.session.close() 126 | content = await etoro.login(self.session, account_type=self.account_type) 127 | return content 128 | 129 | async def loop(self): 130 | if self.last_run is not None: 131 | dif_time = datetime.datetime.now() - self.last_run 132 | if dif_time.seconds > 0 and dif_time.seconds < 300: 133 | return False 134 | async def trading(store_max, is_buy=True, demo=True): 135 | if is_buy: 136 | rate_type = 'Ask' 137 | else: 138 | rate_type = 'Bid' 139 | for instr_id in store_max['ids']: 140 | if instr_id in self.instruments: 141 | if instr_id in self.my_portfolio and ((is_buy and self.my_portfolio[instr_id]['IsBuy']) or 142 | (not is_buy and not self.my_portfolio[instr_id]['IsBuy'])): 143 | logging.debug('You have {} in your portfolio'.format(self.instruments[instr_id]['SymbolFull'])) 144 | elif instr_id in self.my_portfolio and ((is_buy and not self.my_portfolio[instr_id]['IsBuy']) or 145 | (not is_buy and self.my_portfolio[instr_id]['IsBuy'])): 146 | logging.debug('You have backward {} in portfolio'.format(self.instruments[instr_id]['SymbolFull'])) 147 | 148 | if instr_id in self.instruments_rate: 149 | await etoro.close_order(self.session, self.my_portfolio[instr_id]['PositionID'], 150 | self.instruments_rate[instr_id][rate_type], demo=demo) 151 | else: 152 | logging.debug('You didn\'t have {} in portfolio'.format(self.instruments[instr_id]['SymbolFull'])) 153 | 154 | # if instr_id in self.instruments_rate: 155 | # await etoro.order(self.session, instr_id, self.instruments_rate[instr_id][rate_type], 156 | # Amount=self.instruments_instrument[instr_id]['MinPositionAmount'], 157 | # Leverage=self.instruments_instrument[instr_id]['Leverages'][0], IsBuy=is_buy, 158 | # demo=demo) 159 | 160 | self.aggregate_data = {'Buy': {}, 'Sell': {}} 161 | content = await etoro.login(self.session, only_info=True) 162 | 163 | helpers.set_cache('login_info', content) 164 | 165 | while "AggregatedResult" not in content: 166 | logging.debug('Login fail') 167 | content = await self.login() 168 | 169 | 170 | self.user_portfolio = content["AggregatedResult"]["ApiResponses"]["PrivatePortfolio"]["Content"][ 171 | "ClientPortfolio"] 172 | logging.debug('Balance: {}'.format(self.user_portfolio["Credit"])) 173 | 174 | await self.check_instruments() 175 | trader_info_status = await self.traders_info() 176 | buy_list = {self.instruments[inst_id]['SymbolFull']:self.aggregate_data['Buy'][inst_id] 177 | for inst_id in self.aggregate_data['Buy']} 178 | buy_list = sorted(buy_list.items(), key=operator.itemgetter(1), reverse=True) 179 | sell_list = {self.instruments[inst_id]['SymbolFull']:self.aggregate_data['Sell'][inst_id] 180 | for inst_id in self.aggregate_data['Sell']} 181 | sell_list = sorted(sell_list.items(), key=operator.itemgetter(1), reverse=True) 182 | 183 | self._message = 'Баланс: {}\r\n\r\n'.format(self.user_portfolio["Credit"]) 184 | self._message += 'Мое портфолио: \r\n' 185 | for position in self.user_portfolio["Positions"]: 186 | self._message += 'My order: {}. My price: {}. Current Ask: {}. Direct: {}\r\n'.format( 187 | self.instruments[position["InstrumentID"]]['SymbolFull'], position["OpenRate"], 188 | self.instruments_rate[position["InstrumentID"]]["Ask"], 'Byu' if position["IsBuy"] else 'Sell') 189 | self._message += '\r\nПокупка: \r\n' 190 | for tuple_item in buy_list: 191 | self._message += '{}: {}\r\n'.format(tuple_item[0], tuple_item[1]) 192 | self._message += '\r\nПродажа: \r\n' 193 | for tuple_item in sell_list: 194 | self._message += '{}: {}\r\n'.format(tuple_item[0], tuple_item[1]) 195 | close_orders = helpers.set_cache('close_orders', 0) 196 | if close_orders: 197 | self._message += '\r\nОрдера, закрытые роботом: \r\n' 198 | for close_order_key in close_orders: 199 | self._message += '{}: {}'.format(close_order_key, close_orders[close_order_key]) 200 | if trader_info_status: 201 | buy_max = helpers.get_list_instruments(self.aggregate_data) 202 | sell_max = helpers.get_list_instruments(self.aggregate_data, type='Sell') 203 | if not buy_max and not sell_max: 204 | return False 205 | 206 | logging.debug('buy info {}, sell info {}'.format(buy_max, sell_max)) 207 | if buy_max['count'] > 2: 208 | # await trading(buy_max, is_buy=True, demo=True) 209 | pass 210 | 211 | if sell_max['count'] > 2: 212 | # await trading(sell_max, is_buy=False, demo=True) 213 | pass 214 | 215 | # await self.check_my_order(buy_max, sell_max) 216 | 217 | datetime_obj = datetime.datetime.now() 218 | current_time = datetime_obj.today() 219 | if self.last_run is not None: 220 | if str(current_time).find(settings.strtime_send_message) != 0: 221 | return None 222 | if self.last_send_message is not None and current_time.hour == self.last_send_message.hour and \ 223 | current_time.second == self.last_send_message.second: 224 | return None 225 | self.message = self._message 226 | self.last_run = datetime.datetime.today() 227 | self.last_send_message = datetime.datetime.today() 228 | -------------------------------------------------------------------------------- /advisors/strategy_advisor.py: -------------------------------------------------------------------------------- 1 | import settings 2 | import etoro 3 | from interfaces.advisor import ABCAdvisor 4 | from strategy import StrategyManager 5 | from my_logging import logger as logging 6 | import datetime 7 | from collections import deque 8 | 9 | 10 | DEMO = True 11 | 12 | class StrategyAdvisor(ABCAdvisor): 13 | 14 | def __init__(self, loop, **kwargs): 15 | super().__init__(loop, **kwargs) 16 | self.objloop = loop 17 | self.swop_buy = 0.0003 18 | self.total_marg = 0 19 | self.account_type = settings.account_type 20 | self.user_portfolio = {} 21 | self.instruments = {} 22 | self.instruments_rate = {} 23 | self.instruments_instrument = {} 24 | self.object_strategy = StrategyManager(0, '', buy=self.buy, sell=self.sell) 25 | self.object_strategy.start() 26 | self.ask = 0 27 | self.bid = 0 28 | self.exit_orders = [] 29 | self.close_orders = {} 30 | self.fine_orders = {} 31 | self.fast_deals = {} 32 | self.watch_instuments_id = {} 33 | 34 | async def loop(self): 35 | datetime_obj = datetime.datetime.now() 36 | week_day = datetime_obj.weekday() 37 | if week_day == 6 and week_day == 5: 38 | return False 39 | await self.build_data() 40 | 41 | async def build_data(self): 42 | history_items = await etoro.get_history(count=2) 43 | self.close_orders = etoro.helpers.get_cache('close_orders', 0) 44 | self.fine_orders = etoro.helpers.get_cache('fine_orders', 0) 45 | if not self.close_orders: 46 | self.close_orders = {} 47 | if not self.fine_orders: 48 | self.fine_orders = {} 49 | if 'Candles' in history_items and history_items['Candles'] is not None: 50 | self.ask = history_items['Candles'][0]['Candles'][0]['Close'] 51 | self.bid = self.ask + self.swop_buy 52 | self.object_strategy.tick(self.ask, self.bid, history_items['Candles'][0]['Candles'][0]['FromDate']) 53 | content = await etoro.login(self.session, only_info=True) 54 | if "AggregatedResult" not in content: 55 | content = await etoro.login(self.session, account_type=self.account_type) 56 | 57 | try: 58 | self.user_portfolio = content["AggregatedResult"]["ApiResponses"]["PrivatePortfolio"]["Content"][ 59 | "ClientPortfolio"] 60 | except KeyError: 61 | logging.warning('Key Error') 62 | return False 63 | 64 | self.instruments_rate = etoro.helpers.get_cache('instruments_rate', (1/4)) 65 | if not self.instruments_rate: 66 | self.instruments_rate = await etoro.instruments_rate(self.session) 67 | if not self.instruments_rate: 68 | return False 69 | etoro.helpers.set_cache('instruments_rate', self.instruments_rate) 70 | self.instruments_instrument = {instrument['InstrumentID']: instrument for instrument in 71 | self.instruments_rate['Instruments']} 72 | self.instruments_rate = {instrument['InstrumentID']: instrument for instrument in 73 | self.instruments_rate['Rates']} 74 | self.instruments = etoro.helpers.get_cache('instruments', 20) 75 | if not self.instruments: 76 | self.instruments = await etoro.instruments(self.session) 77 | if not self.instruments: 78 | return False 79 | etoro.helpers.set_cache('instruments', self.instruments) 80 | self.instruments = {instrument['InstrumentID']: instrument for instrument in 81 | self.instruments['InstrumentDisplayDatas']} 82 | self.exit_orders = [order['InstrumentID'] for order in self.user_portfolio['ExitOrders']] 83 | await self.check_position() 84 | 85 | async def check_position(self): 86 | for position in self.user_portfolio['Positions']: 87 | if position['InstrumentID'] in self.instruments and position['InstrumentID'] in self.instruments_rate: 88 | position_id = position['PositionID'] 89 | instrument_name = self.instruments[position['InstrumentID']]['SymbolFull'] 90 | instrument_current_price = self.instruments_rate[position['InstrumentID']]['LastExecution'] 91 | instrument_my_price = position['OpenRate'] 92 | instrument_is_buy = position["IsBuy"] 93 | if instrument_name in self.close_orders: 94 | try: 95 | if (self.close_orders[instrument_name]['price'] > instrument_current_price and 96 | self.close_orders[instrument_name]['is_buy'] == True) or \ 97 | (self.close_orders[instrument_name]['price'] < instrument_current_price and 98 | self.close_orders[instrument_name]['is_buy'] == True): 99 | self.message = 'Insrument {} now is fine'.format(instrument_name) 100 | logging.debug('Insrument {} now is fine'.format(instrument_name)) 101 | del self.close_orders[instrument_name] 102 | etoro.helpers.set_cache('close_orders', self.close_orders) 103 | except KeyError: 104 | logging.error('Key error {}'.format(instrument_name)) 105 | if not instrument_is_buy: 106 | fee_relative = (instrument_my_price*100/instrument_current_price) - 100 107 | fee_absolute = instrument_my_price-instrument_current_price 108 | else: 109 | fee_relative = (instrument_current_price*100/instrument_my_price) - 100 110 | fee_absolute = instrument_current_price-instrument_my_price 111 | logging.debug('{}: {}'.format(instrument_name, fee_relative)) 112 | if fee_relative < (-1*settings.fee_relative['first_case']) and position['InstrumentID'] not in self.exit_orders: 113 | self.message = 'Firs case. I have tried your order. {}'.format(instrument_name) 114 | await self.close_order(position_id, instrument_name=instrument_name, 115 | instrument_current_price=instrument_current_price) 116 | if fee_relative > settings.fee_relative['second_case'] and instrument_name not in self.fine_orders: 117 | self.fine_orders[instrument_name] = fee_relative 118 | if instrument_name in self.fine_orders: 119 | if fee_relative > self.fine_orders[instrument_name]: 120 | self.fine_orders[instrument_name] = fee_relative 121 | if (self.fine_orders[instrument_name] - fee_relative) >= settings.fee_relative['second_case']: 122 | self.message = 'Second case. I have tried your order. {}'.format(instrument_name) 123 | await self.close_order(position_id, instrument_name=instrument_name, 124 | instrument_current_price=instrument_current_price) 125 | del self.fine_orders[instrument_name] 126 | 127 | 128 | async def fast_change_detect(self): 129 | if not self.instruments: 130 | return False 131 | lists = etoro.helpers.get_cache('watch_list', 10) 132 | if not lists: 133 | lists = await etoro.watch_list(self.session) 134 | if 'Watchlists' in lists: 135 | etoro.helpers.set_cache('watch_list', lists) 136 | else: 137 | return False 138 | for watch_list in lists['Watchlists']: 139 | for item_list in watch_list['Items']: 140 | if item_list['ItemType'] == 'Instrument' and item_list['ItemId'] not in self.watch_instuments_id: 141 | self.watch_instuments_id[item_list['ItemId']] = deque([]) 142 | if not self.instruments_rate: 143 | self.instruments_rate = await etoro.instruments_rate(self.session) 144 | if not self.instruments_rate: 145 | return False 146 | self.instruments_rate = {instrument['InstrumentID']: instrument for instrument in 147 | self.instruments_rate['Rates']} 148 | for key in self.instruments_rate: 149 | if key in self.watch_instuments_id: 150 | self.watch_instuments_id[key].append(self.instruments_rate[key]['LastExecution']) 151 | if len(self.watch_instuments_id[key]) > 10: 152 | changing = self.watch_instuments_id[key][0]/self.watch_instuments_id[key][-1] 153 | if changing > 1: 154 | changing = (1.0 - 1/changing)*-1 155 | else: 156 | changing = 1.0 - changing 157 | if changing > settings.fast_grow_points or changing < (-1*settings.fast_grow_points): 158 | logging.info('Changing for {} is {}'.format(self.instruments[key]['SymbolFull'], str(changing))) 159 | await self.fast_deal(changing, key) 160 | # self.message = 'Changing {} is {}'.format(self.instruments[key]['SymbolFull'], 161 | # str(changing)) 162 | self.watch_instuments_id[key].popleft() 163 | 164 | async def fast_deal(self, changing, key): 165 | if not self.fast_deals: 166 | self.fast_deals = etoro.helpers.get_cache('fast_deals') 167 | if not self.fast_deals: 168 | self.fast_deals = {} 169 | if key in self.fast_deals: 170 | return False 171 | min_amount = self.instruments_instrument[key]['MinPositionAmount'] 172 | min_leverage = self.instruments_instrument[key]['Leverages'][0] 173 | if changing > 0: 174 | await self.buy(key, self.instruments_rate[key]['LastExecution'], min_amount, min_leverage) 175 | else: 176 | await self.sell(key, self.instruments_rate[key]['LastExecution'], min_amount, min_leverage) 177 | 178 | 179 | async def check_fast_orders(self): 180 | if 'Positions' in self.user_portfolio: 181 | for position in self.user_portfolio['Positions']: 182 | if position['InstrumentID'] in self.fast_deals: 183 | date_time_current = self.fast_deals[position['InstrumentID']]['date'] 184 | dif_time = datetime.datetime.now() - date_time_current 185 | if dif_time.seconds > 5: 186 | await etoro.close_order(self.session, position['PositionID'], demo=DEMO) 187 | del self.fast_deals[position['InstrumentID']] 188 | 189 | 190 | async def make_business(self, key, last_execution, is_buy, min_amount, min_leverage): 191 | response = await etoro.order(self.session, key, last_execution, IsBuy=is_buy, 192 | Amount=min_amount, Leverage=min_leverage) 193 | if 'Token' in response: 194 | self.fast_deals[key] = { 195 | 'id': key, 196 | 'date': datetime.datetime.now().strftime("%s") 197 | } 198 | etoro.helpers.set_cache('fast_deals', self.fast_deals) 199 | else: 200 | await etoro.login(self.session, account_type=self.account_type) 201 | await self.make_business(key, last_execution, is_buy, min_amount, min_leverage) 202 | return response 203 | 204 | async def buy(self, key, last_execution, min_amount, min_leverage): 205 | await self.make_business(key, last_execution, True, min_amount, min_leverage) 206 | 207 | async def sell(self, key, last_execution, min_amount, min_leverage): 208 | await self.make_business(key, last_execution, False, min_amount, min_leverage) 209 | 210 | async def close_order(self, position_id, instrument_name='', instrument_current_price=0.0): 211 | await etoro.close_order(self.session, position_id, demo=DEMO) 212 | for position in self.user_portfolio['Positions']: 213 | if position['InstrumentID'] in self.instruments and position['InstrumentID'] in self.instruments_rate: 214 | if instrument_name == self.instruments[position['InstrumentID']]['SymbolFull']: 215 | self.close_orders[instrument_name] = {'price': instrument_current_price, 216 | 'is_buy': position["IsBuy"]} 217 | etoro.helpers.set_cache('close_orders', self.close_orders) --------------------------------------------------------------------------------