From b588b8f1b51ba369207f9bfb76b4368b7cddad57 Mon Sep 17 00:00:00 2001 From: jdom32 Date: Tue, 9 Jun 2020 20:41:14 +0100 Subject: [PATCH 1/2] save the world --- letsberich/ig/ig_services.py | 6 ++ letsberich/ig/strategy.py | 64 +++++++++++++++++++ .../ig/auto_trade_launch_interface.html | 48 ++++++++++++++ letsberich/ig/urls.py | 5 +- letsberich/ig/views.py | 26 ++++++++ 5 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 letsberich/ig/strategy.py create mode 100644 letsberich/ig/templates/ig/auto_trade_launch_interface.html diff --git a/letsberich/ig/ig_services.py b/letsberich/ig/ig_services.py index 7bf39fb..2f4d16a 100644 --- a/letsberich/ig/ig_services.py +++ b/letsberich/ig/ig_services.py @@ -222,6 +222,12 @@ def open_position_wrapper(self, payload: dict): open_position_data = self.open_position(deal_id) return open_position_data + def get_instrument(self): + return 1 + def get_live_data(self): + return 1 + + def get_ig_api() -> IGService: return IGService() diff --git a/letsberich/ig/strategy.py b/letsberich/ig/strategy.py new file mode 100644 index 0000000..a1efdd2 --- /dev/null +++ b/letsberich/ig/strategy.py @@ -0,0 +1,64 @@ +from letsberich.ig.ig_services import get_ig_api +from time import time, sleep + +# This method manages strategy and returns strategy status +class StrategyOne(object): + + def __init__(self): + self.status = 'OFF' + self.account_statistics ={} + + def get_status(self, status_request): + ig_api = get_ig_api() + # This starts monitoring and buys when signals are right + + if status_request=='ON': + # Firstly, select instrument of complete FTSE, DOW30 and S&P to trade automatically depending on parameters + # Need to find a way to keep the while loop running in the background so that we can stop it whenever we want + self.status = status_request + end_time = time() + 8 * 3600 + previous_position_meets_criteria = {} + + while time() < end_time: + instrument_list = ig_api.get_instrument() # instrument_list is a dict with all instrument IDs + data_feed = ig_api.get_live_data(instrument_list) # data_feed contains all relevant data for the instrument_list + position_meets_criteria = self.strategy(data_feed) # position_meets_criteria will be a dict of boolean values with True=open trade or False=Close trade + + for instrument in data_feed: + #this for loop execute buy or sell depending on what strat is telling + pos_st = position_meets_criteria[instrument['EPIC']]['status'] + prev_pos_st = previous_position_meets_criteria[instrument['EPIC']]['status'] + + if pos_st == 'true' and pos_st != prev_pos_st: + ig_api.open_position_wrapper(data_feed) + if pos_st == 'false' and pos_st != prev_pos_st: + ig_api.close_position_wrapper(instrument['EPIC']) + + previous_position_meets_criteria = position_meets_criteria + self.account_statistics = previous_position_meets_criteria + sleep(10) + + + + if status_request=='OFF': + # This closes all positions and stops monitoring. + response_dict = ig_api.close_position_wrapper() + self.status = response_dict['status'] + return self.status + + if status_request=='CHECK': + # Check strategy status + return self.status + + def strategy(self, data_feed: dict): + # this method contains the strategy. I have split this from main method to facilitate changing or plugging in a different strategy + # + # /// HERE: code strategy that will come up with update list of boolean to decide whether to buy or sell. + # strategy example: Calculate average over past 100 prices and if current price in data_feed goes through + # downwards, then sell (boolean set to false). If goes through upwards, then buy (blooean set to true). + # Finally, return dict with boole to buy or sell/// + position_meets_criteria={} + return position_meets_criteria + +def get_strategy() -> StrategyOne: + return StrategyOne() \ No newline at end of file diff --git a/letsberich/ig/templates/ig/auto_trade_launch_interface.html b/letsberich/ig/templates/ig/auto_trade_launch_interface.html new file mode 100644 index 0000000..168b5f5 --- /dev/null +++ b/letsberich/ig/templates/ig/auto_trade_launch_interface.html @@ -0,0 +1,48 @@ +{% extends 'ig/base.html' %} +{% load crispy_forms_tags %} + +{% block title %}Operate Auto Trade Tool{% endblock %} + +{% block content %} +
+
+ {% if api_error %} + + {% endif %} +
+
+
+
+ {% csrf_token %} + {{ form_auto_trade|crispy }} + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+ + + + + + + + + + + + +
Auto Trade Status
{{ status }}
+{% endblock %} diff --git a/letsberich/ig/urls.py b/letsberich/ig/urls.py index c96ed71..7bee1d6 100644 --- a/letsberich/ig/urls.py +++ b/letsberich/ig/urls.py @@ -22,5 +22,8 @@ name='node-navigation' ), path('account_summary/', views.IGAccountSummary.as_view(), name='account-summary'), - path('open_position/', views.IGOpenPosition.as_view(), name='open-position') + path('open_position/', views.IGOpenPosition.as_view(), name='open-position'), + path('start_auto_trade/', views.IGAutoTradeStart.as_view(), name='start-auto-trade'), + path('pause_auto_trade/', views.IGAutoTradePause.as_view(), name='start-auto-trade'), + path('auto_trade_status/', views.IGAutoTradeStatus.as_view(), name='start-auto-trade') ] diff --git a/letsberich/ig/views.py b/letsberich/ig/views.py index 49a8515..7bf2414 100644 --- a/letsberich/ig/views.py +++ b/letsberich/ig/views.py @@ -3,6 +3,7 @@ from letsberich.ig.exceptions import IGServiceError from letsberich.ig.ig_services import get_ig_api +from letsberich.ig.strategy import get_strategy from letsberich.ig.forms import OpenPositionForm @@ -121,3 +122,28 @@ def post(self, request): context['created_position_data'] = created_position_data return render(request, 'ig/open_position.html', context) + + +class IGAutoTradeStart(generic.View): + + def get(self, request): + day_strat = get_strategy() + context = {} + context['status'] = day_strat.get_status('ON') + return render(request, 'ig/auto_trade_launch_interface.html', context) + +class IGAutoTradePause(generic.View): + + def get(self, request): + context = {} + context['status'] = day_strat.get_status('OFF') + return render(request, 'ig/auto_trade_launch_interface.html', context) + + +class IGAutoTradeStatus(generic.View): + ig_api = get_ig_api() + + def get(self, request): + context = {} + context['status'] = strategy_one() + return render(request, 'ig/auto_trade_launch_interface.html', context) From a388b86fb325967feb160c25810f618ee9073ca4 Mon Sep 17 00:00:00 2001 From: jdom32 Date: Sun, 28 Jun 2020 12:23:13 +0100 Subject: [PATCH 2/2] auto trade tool, although issue with historical price 10k week api limit --- letsberich/ig/forms.py | 12 +- letsberich/ig/ig_services.py | 39 ++++-- letsberich/ig/models.py | 6 + letsberich/ig/strategy.py | 114 ++++++++++++++---- .../ig/auto_trade_launch_interface.html | 59 ++++++--- .../ig/templates/ig/ig_popular_markets.html | 2 +- letsberich/ig/urls.py | 4 +- letsberich/ig/views.py | 17 ++- letsberich/settings.py | 4 +- requirements.txt | 39 +++++- 10 files changed, 226 insertions(+), 70 deletions(-) diff --git a/letsberich/ig/forms.py b/letsberich/ig/forms.py index d2636ec..225fad2 100644 --- a/letsberich/ig/forms.py +++ b/letsberich/ig/forms.py @@ -1,6 +1,6 @@ from django import forms -from letsberich.ig.models import Position +from letsberich.ig.models import Position, Autotrade class OpenPositionForm(forms.ModelForm): @@ -11,3 +11,13 @@ def __init__(self, *args, **kwargs): class Meta: model = Position exclude = ("created_by",) + + +class AutoTradeForm(forms.ModelForm): + + def __init__(self, *args, **kwargs): + super(AutoTradeForm, self).__init__(*args, **kwargs) + + class Meta: + model = Autotrade + fields = ['status'] \ No newline at end of file diff --git a/letsberich/ig/ig_services.py b/letsberich/ig/ig_services.py index 2f4d16a..a2e8845 100644 --- a/letsberich/ig/ig_services.py +++ b/letsberich/ig/ig_services.py @@ -141,15 +141,17 @@ def get_node_list(self, node_id) -> dict: else: raise IGServiceError - def get_prices(self): - # TODO - + def get_instrument_characteristics(self, epic: str): self.get_token() - - url = self._get_endpoint('PRICES').format('KA.D.VOD.CASH.IP') - + url = self._get_endpoint('GET_INSTRUMENT_CHARACTERISTICS').format(epic) response = self._make_request(url, version='3') + if response.status_code < 300: + response_dict = json.loads(response.content.decode('utf-8')) + return response_dict + else: + raise IGServiceError + def get_account_useful_data(self): self.get_token() url = self._get_endpoint('ACCOUNT_USEFUL_DATA') @@ -222,12 +224,29 @@ def open_position_wrapper(self, payload: dict): open_position_data = self.open_position(deal_id) return open_position_data - def get_instrument(self): - return 1 + def get_instrument_list(self) -> dict: + self.get_token() + url = self._get_endpoint('SPECIFIC_WATCHLIST').format('12047692') #weekend_watch is 12047692; pm_watchlist id is 11899563. ftse_watchlist is 11899563 + response = self._make_request(url, version='1') + + if response.status_code < 300: + response_dict = json.loads(response.content.decode('utf-8')) + return response_dict['markets'] + raise IGServiceError("Error getting watchlist: {}".format(response.content)) + + def get_past_price(self, instrument: dict, data_points: int): + self.get_token() + url = self._get_endpoint('GET_HISTORICAL_PRICES').format(instrument['epic'], 'MINUTE', data_points) + response = self._make_request(url, version='2') + + if response.status_code < 300: + response_dict = json.loads(response.content.decode('utf-8')) + return response_dict['prices'] + + raise IGServiceError("Error getting past price: {}".format(response.content)) - def get_live_data(self): + def close_position_wrapper(self): #TODO return 1 - def get_ig_api() -> IGService: return IGService() diff --git a/letsberich/ig/models.py b/letsberich/ig/models.py index 110b819..9c78685 100644 --- a/letsberich/ig/models.py +++ b/letsberich/ig/models.py @@ -41,3 +41,9 @@ class Position(models.Model): ) +class Autotrade(models.Model): + status = models.CharField( + max_length=7, + help_text="This activates the Auto Trade tool", + choices=(("ON", "TURN ON"), ("OFF", "TURN OFF"), ("STATUS", "STATUS")), + ) diff --git a/letsberich/ig/strategy.py b/letsberich/ig/strategy.py index a1efdd2..6460eb5 100644 --- a/letsberich/ig/strategy.py +++ b/letsberich/ig/strategy.py @@ -1,5 +1,9 @@ from letsberich.ig.ig_services import get_ig_api from time import time, sleep +import pandas as pd +import numpy as np +import random + # This method manages strategy and returns strategy status class StrategyOne(object): @@ -7,58 +11,116 @@ class StrategyOne(object): def __init__(self): self.status = 'OFF' self.account_statistics ={} + self.previous_position_meets_criteria = [{'epic': 'CS.D.BITCOIN.TODAY.IP', 'status': 'false'}, + {'epic': 'CS.D.LTCUSD.TODAY.IP', 'status': 'false'}, + {'epic': 'CS.D.BCHXBT.TODAY.IP', 'status': 'true'}] + self.position_meets_criteria = [] + self.transaction_history = [] - def get_status(self, status_request): + def get_status(self, status_request='CHECK'): ig_api = get_ig_api() # This starts monitoring and buys when signals are right - if status_request=='ON': + if status_request == 'ON': # Firstly, select instrument of complete FTSE, DOW30 and S&P to trade automatically depending on parameters # Need to find a way to keep the while loop running in the background so that we can stop it whenever we want self.status = status_request - end_time = time() + 8 * 3600 - previous_position_meets_criteria = {} + end_time = time() + 30 + price_feed = [] while time() < end_time: - instrument_list = ig_api.get_instrument() # instrument_list is a dict with all instrument IDs - data_feed = ig_api.get_live_data(instrument_list) # data_feed contains all relevant data for the instrument_list - position_meets_criteria = self.strategy(data_feed) # position_meets_criteria will be a dict of boolean values with True=open trade or False=Close trade + instrument_list = ig_api.get_instrument_list() + + for instrument in instrument_list: + price_feed_dict = {} + price_feed_dict.update({'epic': instrument['epic']}) + price_feed_dict.update({'currentPrice': instrument['offer']}) # offer is buy price ['EPIC1': ] data_feed is a time series that contains all relevant data for the instrument_list + parse_data = ig_api.get_past_price(instrument, data_points=200) # time series containing past price data. For now we will get this from the API but it could be more efficient to have this info in our DB? + parse_data_closePrice = [parse_data_element.get('closePrice').get('ask') for parse_data_element in parse_data] + # import ipdb; ipdb.set_trace() + # parse_data is a list and needs to be accessed element by element. + price_feed_dict.update({'pastClosePrice': parse_data_closePrice}) + price_feed.append(price_feed_dict) + + self.position_meets_criteria = self._strategy_200movingaverage(price_feed, + self.previous_position_meets_criteria) # position_meets_criteria will be a dict of boolean values with True=open trade or False=Close trade and EPIC - for instrument in data_feed: - #this for loop execute buy or sell depending on what strat is telling - pos_st = position_meets_criteria[instrument['EPIC']]['status'] - prev_pos_st = previous_position_meets_criteria[instrument['EPIC']]['status'] + if self.previous_position_meets_criteria[0]['epic'] != "": #skip if it iś initial iteration# - if pos_st == 'true' and pos_st != prev_pos_st: - ig_api.open_position_wrapper(data_feed) - if pos_st == 'false' and pos_st != prev_pos_st: - ig_api.close_position_wrapper(instrument['EPIC']) + for position in self.position_meets_criteria: + #this for loop execute buy or sell depending on what strat is telling + pos_st = position['status'] + prev_pos_st = next((element['status'] for element in self.previous_position_meets_criteria + if element['epic'] == position['epic']), None) + instrument = ig_api.get_instrument_characteristics(position['epic']) - previous_position_meets_criteria = position_meets_criteria - self.account_statistics = previous_position_meets_criteria - sleep(10) + if pos_st == 'true' and pos_st != prev_pos_st: + data_for_position = { + 'currency_code': 'GBP', + 'deal_reference': 'TESTPos' + str(random.randint(1,100)), + 'direction': 'BUY', + 'epic': position['epic'], + 'expiry': 'DFB', + 'force_open': 'True', + 'guaranteed_stop': 'False', + 'order_type': 'MARKET', + 'size': '0.5', + 'stop_level': str(next((element['currentPrice'] for element in price_feed if element['epic'] + == position['epic'])) * 0.97) + } + open_position_details = ig_api.open_position_wrapper(data_for_position) + self.transaction_history.append(open_position_details) + if pos_st == 'false' and pos_st != prev_pos_st: + closed_position_details = ig_api.close_position_wrapper(position['epic']) + self.transaction_history.append(closed_position_details) + self.previous_position_meets_criteria = self.position_meets_criteria + sleep(10) + return {'transactions': self.transaction_history, 'status': self.status} - if status_request=='OFF': + if status_request == 'OFF': # This closes all positions and stops monitoring. response_dict = ig_api.close_position_wrapper() self.status = response_dict['status'] - return self.status + return {'transactions': self.transaction_history, 'status': self.status} - if status_request=='CHECK': - # Check strategy status - return self.status + if status_request == 'CHECK': + response_dict = ig_api.get_account_useful_data() + return {'transactions': response_dict, 'status': self.status} - def strategy(self, data_feed: dict): + def _strategy_200movingaverage(self, price_feed: list, previous_position_meets_criteria: list): # this method contains the strategy. I have split this from main method to facilitate changing or plugging in a different strategy # # /// HERE: code strategy that will come up with update list of boolean to decide whether to buy or sell. # strategy example: Calculate average over past 100 prices and if current price in data_feed goes through # downwards, then sell (boolean set to false). If goes through upwards, then buy (blooean set to true). - # Finally, return dict with boole to buy or sell/// - position_meets_criteria={} + # Finally, return dict with boole to buy or sell/ + position_meets_criteria = [] + + for instrument in price_feed: + pos_meet_cri_dict = {} + pos_meet_cri_dict.update({'epic': instrument['epic']}) + + past_data_feed_numpy = np.array(instrument['pastClosePrice']) + moving_avg_200 = np.sum(past_data_feed_numpy[1:200])/200 #pandas rolling method could do this + if previous_position_meets_criteria[0]['epic'] != '': + prev_pos_elem_status = next((element['status'] for element in previous_position_meets_criteria + if element['epic'] == instrument['epic']), None) + #prev_pos_elem_status = [element.get('epic', instrument['epic']).get('status') for element in previous_position_meets_criteria] + else: + prev_pos_elem_status = '' + + if instrument['currentPrice'] >= moving_avg_200 and (prev_pos_elem_status != 'true' or prev_pos_elem_status != '' ): + pos_meet_cri_dict.update({'status': 'true'}) + position_meets_criteria.append(pos_meet_cri_dict) + + if instrument['currentPrice'] < moving_avg_200 and ( prev_pos_elem_status != 'false' or prev_pos_elem_status != '' ): + pos_meet_cri_dict.update({'status': 'false'}) + position_meets_criteria.append(pos_meet_cri_dict) + return position_meets_criteria + def get_strategy() -> StrategyOne: return StrategyOne() \ No newline at end of file diff --git a/letsberich/ig/templates/ig/auto_trade_launch_interface.html b/letsberich/ig/templates/ig/auto_trade_launch_interface.html index 168b5f5..3d73545 100644 --- a/letsberich/ig/templates/ig/auto_trade_launch_interface.html +++ b/letsberich/ig/templates/ig/auto_trade_launch_interface.html @@ -13,30 +13,25 @@ {% endif %}
-
-
- {% csrf_token %} - {{ form_auto_trade|crispy }} - -
-
+ + + +
-
-
- -
+ + +
-
-
- -
+ + +
- +
- + @@ -45,4 +40,34 @@
Auto Trade StatusStatus
+ + + + + + + + + + + + + + + + + {% for transaction in transactions %} + + + + + + + + + + + {% endfor %} + +
InstrumentEpicSizeBuy PriceLimit LevelDateDeal ID (IG)Deal Ref (ours)
{{ transaction.market.instrumentName }}{{ transaction.market.epic }}{{ transaction.position.size }}{{ transaction.position.level }}{{ transaction.position.limitLevel }}{{ transaction.position.createdDate }}{{ transaction.position.dealId }}{{ transaction.position.dealReference }}
{% endblock %} diff --git a/letsberich/ig/templates/ig/ig_popular_markets.html b/letsberich/ig/templates/ig/ig_popular_markets.html index 089e05b..8afd79a 100644 --- a/letsberich/ig/templates/ig/ig_popular_markets.html +++ b/letsberich/ig/templates/ig/ig_popular_markets.html @@ -7,7 +7,7 @@ {% if api_error %}
{{ api_error }}
{% endif %} -
+ {% csrf_token %}
diff --git a/letsberich/ig/urls.py b/letsberich/ig/urls.py index 7bee1d6..0ceaa92 100644 --- a/letsberich/ig/urls.py +++ b/letsberich/ig/urls.py @@ -24,6 +24,6 @@ path('account_summary/', views.IGAccountSummary.as_view(), name='account-summary'), path('open_position/', views.IGOpenPosition.as_view(), name='open-position'), path('start_auto_trade/', views.IGAutoTradeStart.as_view(), name='start-auto-trade'), - path('pause_auto_trade/', views.IGAutoTradePause.as_view(), name='start-auto-trade'), - path('auto_trade_status/', views.IGAutoTradeStatus.as_view(), name='start-auto-trade') + path('pause_auto_trade/', views.IGAutoTradePause.as_view(), name='pause-auto-trade'), + path('status_auto_trade/', views.IGAutoTradeStatus.as_view(), name='status-auto-trade') ] diff --git a/letsberich/ig/views.py b/letsberich/ig/views.py index 7bf2414..663c426 100644 --- a/letsberich/ig/views.py +++ b/letsberich/ig/views.py @@ -129,15 +129,18 @@ class IGAutoTradeStart(generic.View): def get(self, request): day_strat = get_strategy() context = {} - context['status'] = day_strat.get_status('ON') - return render(request, 'ig/auto_trade_launch_interface.html', context) + context['data'] = day_strat.get_status('ON') + return render(request, 'ig/auto_trade_launch_interface.html', {'transactions': context['data']['transactions'], + 'status': context['data']['status']}) class IGAutoTradePause(generic.View): def get(self, request): context = {} - context['status'] = day_strat.get_status('OFF') - return render(request, 'ig/auto_trade_launch_interface.html', context) + day_strat = get_strategy() + context['data'] = day_strat.get_status('OFF') + return render(request, 'ig/auto_trade_launch_interface.html', {'transactions': context['data']['transactions'], + 'status': context['data']['status']}) class IGAutoTradeStatus(generic.View): @@ -145,5 +148,7 @@ class IGAutoTradeStatus(generic.View): def get(self, request): context = {} - context['status'] = strategy_one() - return render(request, 'ig/auto_trade_launch_interface.html', context) + day_strat = get_strategy() + context['data'] = day_strat.get_status() + return render(request, 'ig/auto_trade_launch_interface.html', {'transactions': context['data']['transactions'], + 'status': context['data']['status']}) diff --git a/letsberich/settings.py b/letsberich/settings.py index 6c988ac..0439214 100644 --- a/letsberich/settings.py +++ b/letsberich/settings.py @@ -143,7 +143,9 @@ 'ACCOUNT_USEFUL_DATA': '/positions', 'CREATE_POSITION': '/positions/otc', 'CONFIRM_POSITION': '/confirms/{}', - 'OPEN_POSITION': '/positions/{}' + 'OPEN_POSITION': '/positions/{}', + 'GET_HISTORICAL_PRICES': '/prices/{}/{}/{}', + 'GET_INSTRUMENT_CHARACTERISTICS': '/markets/{}' } } diff --git a/requirements.txt b/requirements.txt index 66c55b3..b66ec3e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,33 @@ -Django==3.0.7 # web framework for Python -django-crispy-forms==1.9.1 # django forms with bootstrap -python-decouple==3.3 # keeps your secrets safe -requests==2.23.0 # module to perform url calls - -ipdb==0.13.2 # pdb on steroids +asgiref==3.2.7 +backcall==0.1.0 +certifi==2020.4.5.1 +chardet==3.0.4 +decorator==4.4.2 +Django==3.0.7 +django-crispy-forms==1.9.1 +get==2019.4.13 +idna==2.9 +ipdb==0.13.2 +ipython==7.15.0 +ipython-genutils==0.2.0 +jedi==0.17.0 +numpy==1.18.5 +pandas==1.0.4 +parso==0.7.0 +pexpect==4.8.0 +pickleshare==0.7.5 +post==2019.4.13 +prompt-toolkit==3.0.5 +ptyprocess==0.6.0 +public==2019.4.13 +Pygments==2.6.1 +python-dateutil==2.8.1 +python-decouple==3.3 +pytz==2020.1 +query-string==2019.4.13 +requests==2.23.0 +six==1.15.0 +sqlparse==0.3.1 +traitlets==4.3.3 +urllib3==1.25.9 +wcwidth==0.1.9