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 7bf39fb..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,6 +224,29 @@ def open_position_wrapper(self, payload: dict): open_position_data = self.open_position(deal_id) return open_position_data + 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 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 new file mode 100644 index 0000000..6460eb5 --- /dev/null +++ b/letsberich/ig/strategy.py @@ -0,0 +1,126 @@ +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): + + 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='CHECK'): + 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() + 30 + price_feed = [] + + while time() < end_time: + 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 + + if self.previous_position_meets_criteria[0]['epic'] != "": #skip if it iś initial iteration# + + 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']) + + 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': + # This closes all positions and stops monitoring. + response_dict = ig_api.close_position_wrapper() + self.status = response_dict['status'] + return {'transactions': self.transaction_history, 'status': self.status} + + if status_request == 'CHECK': + response_dict = ig_api.get_account_useful_data() + return {'transactions': response_dict, 'status': self.status} + + 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 = [] + + 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 new file mode 100644 index 0000000..3d73545 --- /dev/null +++ b/letsberich/ig/templates/ig/auto_trade_launch_interface.html @@ -0,0 +1,73 @@ +{% extends 'ig/base.html' %} +{% load crispy_forms_tags %} + +{% block title %}Operate Auto Trade Tool{% endblock %} + +{% block content %} +
+
+ {% if api_error %} + + {% endif %} +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + + + + + + + + + +
Status
{{ status }}
+ + + + + + + + + + + + + + + + + {% 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 c96ed71..0ceaa92 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='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 49a8515..663c426 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,33 @@ 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['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 = {} + 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): + ig_api = get_ig_api() + + def get(self, request): + 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