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 %}
+
+ {{ api_error }}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+ | Status |
+
+
+
+
+ | {{ status }} |
+
+
+
+
+
+
+
+ | Instrument |
+ Epic |
+ Size |
+ Buy Price |
+ Limit Level |
+ Date |
+ Deal ID (IG) |
+ Deal Ref (ours) |
+
+
+
+
+ {% for transaction in transactions %}
+
+ | {{ transaction.market.instrumentName }} |
+ {{ transaction.market.epic }} |
+ {{ transaction.position.size }} |
+ {{ transaction.position.level }} |
+ {{ transaction.position.limitLevel }} |
+ {{ transaction.position.createdDate }} |
+ {{ transaction.position.dealId }} |
+ {{ transaction.position.dealReference }} |
+
+ {% endfor %}
+
+
+{% 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 %}
-
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