2015-06-30 09:48:25 +01:00
|
|
|
import copy
|
|
|
|
|
|
2015-02-03 13:33:33 +00:00
|
|
|
from qsforex.event.event import SignalEvent
|
|
|
|
|
|
|
|
|
|
|
2015-03-06 09:57:15 +00:00
|
|
|
class TestStrategy(object):
|
2015-04-21 13:01:20 +01:00
|
|
|
"""
|
|
|
|
|
A testing strategy that alternates between buying and selling
|
|
|
|
|
a currency pair on every 5th tick. This has the effect of
|
|
|
|
|
continuously "crossing the spread" and so will be loss-making
|
|
|
|
|
strategy.
|
|
|
|
|
|
|
|
|
|
It is used to test that the backtester/live trading system is
|
|
|
|
|
behaving as expected.
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, pairs, events):
|
|
|
|
|
self.pairs = pairs
|
2015-02-03 13:33:33 +00:00
|
|
|
self.events = events
|
|
|
|
|
self.ticks = 0
|
2015-03-06 09:57:15 +00:00
|
|
|
self.invested = False
|
2015-02-03 13:33:33 +00:00
|
|
|
|
|
|
|
|
def calculate_signals(self, event):
|
2015-07-13 16:30:55 +01:00
|
|
|
if event.type == 'TICK' and event.instrument == self.pairs[0]:
|
2015-04-17 12:34:31 +01:00
|
|
|
if self.ticks % 5 == 0:
|
2015-03-06 09:57:15 +00:00
|
|
|
if self.invested == False:
|
2015-04-21 13:01:20 +01:00
|
|
|
signal = SignalEvent(self.pairs[0], "market", "buy", event.time)
|
2015-03-06 09:57:15 +00:00
|
|
|
self.events.put(signal)
|
|
|
|
|
self.invested = True
|
|
|
|
|
else:
|
2015-04-21 13:01:20 +01:00
|
|
|
signal = SignalEvent(self.pairs[0], "market", "sell", event.time)
|
2015-03-06 09:57:15 +00:00
|
|
|
self.events.put(signal)
|
|
|
|
|
self.invested = False
|
2015-04-17 12:34:31 +01:00
|
|
|
self.ticks += 1
|
2015-04-21 13:01:20 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class MovingAverageCrossStrategy(object):
|
|
|
|
|
"""
|
|
|
|
|
A basic Moving Average Crossover strategy that generates
|
|
|
|
|
two simple moving averages (SMA), with default windows
|
|
|
|
|
of 500 ticks for the short SMA and 2,000 ticks for the
|
|
|
|
|
long SMA.
|
|
|
|
|
|
|
|
|
|
The strategy is "long only" in the sense it will only
|
|
|
|
|
open a long position once the short SMA exceeds the long
|
|
|
|
|
SMA. It will close the position (by taking a corresponding
|
|
|
|
|
sell order) when the long SMA recrosses the short SMA.
|
|
|
|
|
|
|
|
|
|
The strategy uses a rolling SMA calculation in order to
|
|
|
|
|
increase efficiency by eliminating the need to call two
|
|
|
|
|
full moving average calculations on each tick.
|
|
|
|
|
"""
|
|
|
|
|
def __init__(
|
|
|
|
|
self, pairs, events,
|
|
|
|
|
short_window=500, long_window=2000
|
|
|
|
|
):
|
|
|
|
|
self.pairs = pairs
|
2015-06-30 09:48:25 +01:00
|
|
|
self.pairs_dict = self.create_pairs_dict()
|
|
|
|
|
self.events = events
|
2015-04-21 13:01:20 +01:00
|
|
|
self.short_window = short_window
|
|
|
|
|
self.long_window = long_window
|
2015-06-30 09:48:25 +01:00
|
|
|
|
|
|
|
|
def create_pairs_dict(self):
|
|
|
|
|
attr_dict = {
|
|
|
|
|
"ticks": 0,
|
|
|
|
|
"invested": False,
|
|
|
|
|
"short_sma": None,
|
|
|
|
|
"long_sma": None
|
|
|
|
|
}
|
|
|
|
|
pairs_dict = {}
|
|
|
|
|
for p in self.pairs:
|
|
|
|
|
pairs_dict[p] = copy.deepcopy(attr_dict)
|
|
|
|
|
return pairs_dict
|
2015-04-21 13:01:20 +01:00
|
|
|
|
|
|
|
|
def calc_rolling_sma(self, sma_m_1, window, price):
|
|
|
|
|
return ((sma_m_1 * (window - 1)) + price) / window
|
|
|
|
|
|
|
|
|
|
def calculate_signals(self, event):
|
|
|
|
|
if event.type == 'TICK':
|
2015-06-30 09:48:25 +01:00
|
|
|
pair = event.instrument
|
2015-04-21 13:01:20 +01:00
|
|
|
price = event.bid
|
2015-06-30 09:48:25 +01:00
|
|
|
pd = self.pairs_dict[pair]
|
|
|
|
|
if pd["ticks"] == 0:
|
|
|
|
|
pd["short_sma"] = price
|
|
|
|
|
pd["long_sma"] = price
|
2015-04-21 13:01:20 +01:00
|
|
|
else:
|
2015-06-30 09:48:25 +01:00
|
|
|
pd["short_sma"] = self.calc_rolling_sma(
|
|
|
|
|
pd["short_sma"], self.short_window, price
|
2015-04-21 13:01:20 +01:00
|
|
|
)
|
2015-06-30 09:48:25 +01:00
|
|
|
pd["long_sma"] = self.calc_rolling_sma(
|
|
|
|
|
pd["long_sma"], self.long_window, price
|
2015-04-21 13:01:20 +01:00
|
|
|
)
|
|
|
|
|
# Only start the strategy when we have created an accurate short window
|
2015-06-30 09:48:25 +01:00
|
|
|
if pd["ticks"] > self.short_window:
|
|
|
|
|
if pd["short_sma"] > pd["long_sma"] and not pd["invested"]:
|
|
|
|
|
signal = SignalEvent(pair, "market", "buy", event.time)
|
2015-04-21 13:01:20 +01:00
|
|
|
self.events.put(signal)
|
2015-06-30 09:48:25 +01:00
|
|
|
pd["invested"] = True
|
|
|
|
|
if pd["short_sma"] < pd["long_sma"] and pd["invested"]:
|
|
|
|
|
signal = SignalEvent(pair, "market", "sell", event.time)
|
2015-04-21 13:01:20 +01:00
|
|
|
self.events.put(signal)
|
2015-06-30 09:48:25 +01:00
|
|
|
pd["invested"] = False
|
|
|
|
|
pd["ticks"] += 1
|