Browse Source

More signals and filters

master
Denis Tereshkin 8 years ago
parent
commit
a984e7fb6e
  1. 491
      data/signal.py
  2. 10
      execution/executor.py
  3. 4
      execution/trade.py
  4. 47
      solver/solver.py
  5. 72
      ui/mainwindow.py
  6. 173
      ui/mainwindow.ui
  7. 75
      ui/ui_mainwindow.py

491
data/signal.py

@ -1,9 +1,11 @@
''' '''
''' '''
import copy
import random import random
import talib
import numpy import numpy
import talib
class Signal: class Signal:
@ -16,6 +18,9 @@ class Signal:
def get_text(self): def get_text(self):
pass pass
def mutate(self, factor):
return copy.deepcopy(self)
class PriceComparisonSignalGenerator: class PriceComparisonSignalGenerator:
def __init__(self): def __init__(self):
@ -59,7 +64,7 @@ class PriceComparisonSignal(Signal):
elif self.lhs == PriceComparisonSignal.CLOSE: elif self.lhs == PriceComparisonSignal.CLOSE:
lhs = series.get_close(index - self.lhs_shift) lhs = series.get_close(index - self.lhs_shift)
else: else:
raise Exception('Invalid lhs type') raise Exception('Invalid lhs type: ' + str(self.lhs))
if self.rhs == PriceComparisonSignal.OPEN: if self.rhs == PriceComparisonSignal.OPEN:
rhs = series.get_open(index - self.rhs_shift) rhs = series.get_open(index - self.rhs_shift)
@ -92,6 +97,18 @@ class PriceComparisonSignal(Signal):
else: else:
return "??" return "??"
def mutate(self, factor):
mutation_type = random.randint(0, 3)
if mutation_type == 0:
self.lhs = random.randint(PriceComparisonSignal.OPEN, PriceComparisonSignal.CLOSE)
elif mutation_type == 1:
self.rhs = random.randint(PriceComparisonSignal.OPEN, PriceComparisonSignal.CLOSE)
elif mutation_type == 2:
self.lhs_shift = max(0, self.lhs_shift + random.randint(-int(10 * factor + 1), int(10 * factor + 1)))
elif mutation_type == 3:
self.rhs_shift = max(0, self.rhs_shift + random.randint(-int(10 * factor + 1), int(10 * factor + 1)))
class RsiSignalGenerator: class RsiSignalGenerator:
@ -99,20 +116,22 @@ class RsiSignalGenerator:
pass pass
def generate(self): def generate(self):
shift = random.randint(0, 5)
period = random.randint(2, 30) period = random.randint(2, 30)
threshold = random.randrange(1, 9) * 10 threshold = random.randrange(1, 9) * 10
ineq_type = random.randint(RsiSignal.LT, RsiSignal.GT) ineq_type = random.randint(RsiSignal.LT, RsiSignal.GT)
return RsiSignal(period, threshold, ineq_type) return RsiSignal(period, threshold, ineq_type, shift)
class RsiSignal(Signal): class RsiSignal(Signal):
LT = 0 LT = 0
GT = 1 GT = 1
def __init__(self, period, threshold, inequality_type): def __init__(self, period, threshold, inequality_type, shift):
self.period = period self.period = period
self.threshold = threshold self.threshold = threshold
self.inequality_type = inequality_type self.inequality_type = inequality_type
self.shift = shift
if inequality_type == RsiSignal.LT: if inequality_type == RsiSignal.LT:
self.inequality_sign_str = '<' self.inequality_sign_str = '<'
else: else:
@ -124,7 +143,10 @@ class RsiSignal(Signal):
rsi = talib.RSI(closes, self.period) rsi = talib.RSI(closes, self.period)
result = [self.calc_signal(v) for v in rsi] result = [self.calc_signal(v) for v in rsi]
return result if self.shift == 0:
return result
else:
return [False] * self.shift + result[:-self.shift]
def calc_signal(self, value): def calc_signal(self, value):
if self.inequality_type == RsiSignal.LT: if self.inequality_type == RsiSignal.LT:
@ -133,9 +155,16 @@ class RsiSignal(Signal):
return value > self.threshold return value > self.threshold
def get_text(self): def get_text(self):
return "rsi(c, " + str(self.period) + ') ' + self.inequality_sign_str + ' ' + str(self.threshold) return "rsi(c, {:d})[{:d}] {:s} {:d}".format(self.period, self.shift, self.inequality_sign_str, int(self.threshold))
def mutate(self, factor):
mutation_type = random.randint(0, 2)
if mutation_type == 0:
self.period = max(2, self.period + random.randint(-int(10 * factor + 1), int(10 * factor + 1)))
elif mutation_type == 1:
self.threshold = random.randrange(1, 9) * 10
elif mutation_type == 2:
random.randint(RsiSignal.LT, RsiSignal.GT)
class AtrSignalGenerator: class AtrSignalGenerator:
@ -157,7 +186,7 @@ class AtrSignal(Signal):
self.period = period self.period = period
self.threshold_factor = threshold_factor self.threshold_factor = threshold_factor
self.inequality_type = inequality_type self.inequality_type = inequality_type
if inequality_type == RsiSignal.LT: if inequality_type == AtrSignal.LT:
self.inequality_sign_str = '<' self.inequality_sign_str = '<'
else: else:
self.inequality_sign_str = '>' self.inequality_sign_str = '>'
@ -181,6 +210,452 @@ class AtrSignal(Signal):
def get_text(self): def get_text(self):
return "atr(" + str(self.period) + ') ' + self.inequality_sign_str + ' close[0] * ' + "{:.3f}".format(self.threshold_factor) return "atr(" + str(self.period) + ') ' + self.inequality_sign_str + ' close[0] * ' + "{:.3f}".format(self.threshold_factor)
def mutate(self, factor):
mutation_type = random.randint(0, 2)
if mutation_type == 0:
self.period = max(2, self.period + random.randint(-int(10 * factor + 1), int(10 * factor + 1)))
elif mutation_type == 1:
self.threshold_factor = random.randint(1, 30) * 0.001
elif mutation_type == 2:
random.randint(AtrSignal.LT, AtrSignal.GT)
class AtrDeltaSignalGenerator:
def __init__(self):
pass
def generate(self):
period = random.randint(2, 30)
threshold = random.randint(1, 15) * 0.2
ineq_type = random.randint(AtrDeltaSignal.LT, AtrDeltaSignal.GT)
sign = random.randint(AtrDeltaSignal.PLUS, AtrDeltaSignal.MINUS)
return AtrDeltaSignal(period, threshold, ineq_type, sign)
class AtrDeltaSignal(Signal):
LT = 0
GT = 1
PLUS = 0
MINUS = 1
def __init__(self, period, threshold_factor, inequality_type, sign):
self.period = period
self.threshold_factor = threshold_factor
self.inequality_type = inequality_type
if inequality_type == AtrDeltaSignal.LT:
self.inequality_sign_str = '<'
else:
self.inequality_sign_str = '>'
self.sign = sign
if self.sign == AtrDeltaSignal.PLUS:
self.sign_str = '+'
else:
self.sign_str = '-'
def calculate(self, series):
closes = numpy.array([series.get_close(i) for i in range(0, series.length())])
highs = numpy.array([series.get_high(i) for i in range(0, series.length())])
lows = numpy.array([series.get_low(i) for i in range(0, series.length())])
atr = talib.ATR(highs, lows, closes, self.period)
result = [False]
for i in range(1, len(closes)):
result.append(self.calc_signal(atr[i], closes[i], closes[i - 1]))
return result
def calc_signal(self, value, c1, c2):
if self.sign == AtrDeltaSignal.PLUS:
if self.inequality_type == AtrDeltaSignal.LT:
return c1 < c2 + value * self.threshold_factor
else:
return c1 > c2 + value * self.threshold_factor
else:
if self.inequality_type == AtrDeltaSignal.LT:
return c1 < c2 - value * self.threshold_factor
else:
return c1 > c2 - value * self.threshold_factor
def get_text(self):
return 'close[0] {:s} close[1] {:s} atr({:d}) * {:f}'.format(self.inequality_sign_str, self.sign_str, self.period, self.threshold_factor)
def mutate(self, factor):
mutation_type = random.randint(0, 2)
if mutation_type == 0:
self.period = max(2, self.period + random.randint(-int(10 * factor + 1), int(10 * factor + 1)))
elif mutation_type == 1:
self.threshold_factor = random.randint(1, 30) * 0.001
elif mutation_type == 2:
random.randint(AtrDeltaSignal.LT, AtrDeltaSignal.GT)
class DayOfWeekSignalGenerator:
def __init__(self):
pass
def generate(self):
dow = random.randint(0, 6)
return DayOfWeekSignal(dow)
class DayOfWeekSignal(Signal):
def __init__(self, day_of_week):
self.day_of_week = day_of_week
def calculate(self, series):
result = []
for i in range(0, series.length()):
result.append(series.get_dt(i).date().weekday() == self.day_of_week)
return result
def get_text(self):
return "day_of_week == " + self.dow_str(self.day_of_week)
def dow_str(self, dow):
return ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'][dow]
def mutate(self, factor):
self.day_of_week = random.randint(0, 6)
class DayOfMonthSignalGenerator:
def __init__(self):
pass
def generate(self):
month_day = random.randint(1, 31)
ineq_type = random.randint(DayOfMonthSignal.LT, DayOfMonthSignal.GT)
return DayOfMonthSignal(month_day, ineq_type)
class DayOfMonthSignal(Signal):
LT = 0
GT = 1
def __init__(self, day_of_month, inequality_type):
self.day_of_month = day_of_month
self.inequality_type = inequality_type
if inequality_type == CrtdrSignal.LT:
self.inequality_sign_str = '<'
else:
self.inequality_sign_str = '>'
def calculate(self, series):
result = []
for i in range(0, series.length()):
if self.inequality_type == DayOfMonthSignal.LT:
result.append(series.get_dt(i).date().day < self.day_of_month)
else:
result.append(series.get_dt(i).date().day > self.day_of_month)
return result
def get_text(self):
return "day_of_month {:s} {:d}".format(self.inequality_sign_str, self.day_of_month)
def mutate(self, factor):
self.day_of_month = random.randint(1, 31)
class CrtdrSignalGenerator:
def __init__(self):
pass
def generate(self):
shift = random.randint(0, 10)
ineq_type = random.randint(CrtdrSignal.LT, CrtdrSignal.GT)
threshold = 0.05 * random.randint(1, 19)
return CrtdrSignal(shift, threshold, ineq_type)
class CrtdrSignal(Signal):
LT = 0
GT = 1
def __init__(self, shift, threshold, inequality_type):
self.shift = shift
self.threshold = threshold
self.inequality_type = inequality_type
if inequality_type == CrtdrSignal.LT:
self.inequality_sign_str = '<'
else:
self.inequality_sign_str = '>'
def calculate(self, series):
result = []
for i in range(0, series.length()):
try:
h = series.get_high(i - self.shift)
l = series.get_low(i - self.shift)
c = series.get_close(i - self.shift)
if h > l:
if self.inequality_type == CrtdrSignal.LT:
result.append((c - l) / (h - l) < self.threshold)
else:
result.append((c - l) / (h - l) > self.threshold)
else:
result.append(False)
except IndexError:
result.append(False)
return result
def get_text(self):
return 'crtdr[{:d}] {:s} {:.2f}'.format(self.shift, self.inequality_sign_str, self.threshold)
def mutate(self, factor):
mutation_type = random.randint(0, 2)
if mutation_type == 0:
self.shift = max(0, self.shift + random.randint(-int(10 * factor + 1), int(10 * factor + 1)))
elif mutation_type == 1:
self.threshold = 0.05 * random.randint(1, 19)
elif mutation_type == 2:
random.randint(CrtdrSignal.LT, CrtdrSignal.GT)
class SmaSignalGenerator:
def __init__(self):
pass
def generate(self):
lhs = random.randint(SmaSignal.OPEN, SmaSignal.CLOSE)
period = random.randint(2, 30)
rhs = random.randint(SmaSignal.OPEN, SmaSignal.CLOSE)
ineq_sign = random.randint(SmaSignal.LT, SmaSignal.GT)
return SmaSignal(period, lhs, rhs, ineq_sign)
class SmaSignal(Signal):
OPEN = 0
HIGH = 1
LOW = 2
CLOSE = 3
LT = 0
GT = 1
def __init__(self, period, lhs, rhs, inequality_type):
self.period = period
self.lhs = lhs
self.rhs = rhs
self.inequality_type = inequality_type
if inequality_type == SmaSignal.LT:
self.inequality_sign_str = '<'
else:
self.inequality_sign_str = '>'
def calculate(self, series):
if self.lhs == SmaSignal.OPEN:
lhs = numpy.array([series.get_open(i) for i in range(0, series.length())])
elif self.lhs == SmaSignal.HIGH:
lhs = numpy.array([series.get_high(i) for i in range(0, series.length())])
elif self.lhs == SmaSignal.LOW:
lhs = numpy.array([series.get_low(i) for i in range(0, series.length())])
elif self.lhs == SmaSignal.CLOSE:
lhs = numpy.array([series.get_close(i) for i in range(0, series.length())])
if self.rhs == SmaSignal.OPEN:
rhs = numpy.array([series.get_open(i) for i in range(0, series.length())])
elif self.rhs == SmaSignal.HIGH:
rhs = numpy.array([series.get_high(i) for i in range(0, series.length())])
elif self.rhs == SmaSignal.LOW:
rhs = numpy.array([series.get_low(i) for i in range(0, series.length())])
elif self.rhs == SmaSignal.CLOSE:
rhs = numpy.array([series.get_close(i) for i in range(0, series.length())])
rhs_sma = talib.SMA(rhs, self.period)
result = [self.calc_signal(l, r) for (l, r) in zip(lhs, rhs_sma)]
return result
def calc_signal(self, l, r):
if self.inequality_type == SmaSignal.LT:
return l < r
else:
return l > r
def get_text(self):
return '{:s}[0] {:s} sma({:s}, {:d})'.format(self.component_to_str(self.lhs), self.inequality_sign_str, self.component_to_str(self.rhs), self.period)
def component_to_str(self, component):
if component == SmaSignal.OPEN:
return "open"
elif component == SmaSignal.HIGH:
return "high"
elif component == SmaSignal.LOW:
return "low"
elif component == SmaSignal.CLOSE:
return "close"
else:
return "??"
class CciSignalGenerator:
def __init__(self):
pass
def generate(self):
period = random.randint(2, 30)
shift = random.randint(0, 5)
threshold = random.randint(1, 30) * 10
ineq_type = random.randint(CciSignal.LT, CciSignal.GT)
return CciSignal(shift, period, threshold, ineq_type)
class CciSignal(Signal):
LT = 0
GT = 1
def __init__(self, shift, period, threshold, inequality_type):
self.shift = shift
self.period = period
self.threshold = threshold
self.inequality_type = inequality_type
if inequality_type == CciSignal.LT:
self.inequality_sign_str = '<'
else:
self.inequality_sign_str = '>'
def calculate(self, series):
closes = numpy.array([series.get_close(i) for i in range(0, series.length())])
highs = numpy.array([series.get_high(i) for i in range(0, series.length())])
lows = numpy.array([series.get_low(i) for i in range(0, series.length())])
cci = talib.CCI(highs, lows, closes, self.period)
result = [self.calc_signal(v) for v in cci]
if self.shift == 0:
return result
else:
return [False] * self.shift + result[:-self.shift]
def calc_signal(self, value):
if self.inequality_type == CciSignal.LT:
return value < self.threshold
else:
return value > self.threshold
def get_text(self):
return "cci({:d})[{:d}] {:s} {:f}".format(self.period, self.shift, self.inequality_sign_str, self.threshold)
def mutate(self, factor):
mutation_type = random.randint(0, 2)
if mutation_type == 0:
self.period = max(2, self.period + random.randint(-int(10 * factor + 1), int(10 * factor + 1)))
elif mutation_type == 1:
self.threshold_factor = random.randint(1, 30) * 0.001
elif mutation_type == 2:
random.randint(AtrSignal.LT, AtrSignal.GT)
class BbandsSignalGenerator:
def __init__(self):
pass
def generate(self):
lhs = random.randint(BbandsSignal.OPEN, BbandsSignal.CLOSE)
period = random.randint(2, 30)
rhs = random.randint(BbandsSignal.OPEN, BbandsSignal.CLOSE)
ineq_sign = random.randint(BbandsSignal.LT, BbandsSignal.GT)
dev = random.randint(1, 6) * 0.5
band_type = random.randint(BbandsSignal.UP, BbandsSignal.DOWN)
return BbandsSignal(period, lhs, rhs, dev, ineq_sign, band_type)
class BbandsSignal(Signal):
OPEN = 0
HIGH = 1
LOW = 2
CLOSE = 3
LT = 0
GT = 1
UP = 0
DOWN = 1
def __init__(self, period, lhs, rhs, dev, inequality_type, band_type):
self.period = period
self.lhs = lhs
self.rhs = rhs
self.dev = dev
self.inequality_type = inequality_type
self.band_type = band_type
if inequality_type == SmaSignal.LT:
self.inequality_sign_str = '<'
else:
self.inequality_sign_str = '>'
if inequality_type == BbandsSignal.UP:
self.band_type_str = 'up'
else:
self.band_type_str = 'down'
def calculate(self, series):
if self.lhs == BbandsSignal.OPEN:
lhs = numpy.array([series.get_open(i) for i in range(0, series.length())])
elif self.lhs == BbandsSignal.HIGH:
lhs = numpy.array([series.get_high(i) for i in range(0, series.length())])
elif self.lhs == BbandsSignal.LOW:
lhs = numpy.array([series.get_low(i) for i in range(0, series.length())])
elif self.lhs == BbandsSignal.CLOSE:
lhs = numpy.array([series.get_close(i) for i in range(0, series.length())])
if self.rhs == BbandsSignal.OPEN:
rhs = numpy.array([series.get_open(i) for i in range(0, series.length())])
elif self.rhs == BbandsSignal.HIGH:
rhs = numpy.array([series.get_high(i) for i in range(0, series.length())])
elif self.rhs == BbandsSignal.LOW:
rhs = numpy.array([series.get_low(i) for i in range(0, series.length())])
elif self.rhs == BbandsSignal.CLOSE:
rhs = numpy.array([series.get_close(i) for i in range(0, series.length())])
(upband, _, downband) = talib.BBANDS(rhs, self.period, self.dev, self.dev)
if self.band_type == BbandsSignal.UP:
band = upband
else:
band = downband
result = [self.calc_signal(l, r) for (l, r) in zip(lhs, band)]
return result
def calc_signal(self, l, r):
if self.inequality_type == BbandsSignal.LT:
return l < r
else:
return l > r
def get_text(self):
return '{:s}[0] {:s} bband({:s}, {:s}, {:.2f}, {:d})'.format(self.component_to_str(self.lhs), self.inequality_sign_str, self.component_to_str(self.rhs), self.band_type_str, self.dev, self.period)
def component_to_str(self, component):
if component == BbandsSignal.OPEN:
return "open"
elif component == BbandsSignal.HIGH:
return "high"
elif component == BbandsSignal.LOW:
return "low"
elif component == BbandsSignal.CLOSE:
return "close"
else:
return "??"

10
execution/executor.py

@ -15,7 +15,7 @@ class Executor(object):
self.series = series self.series = series
self.max_hold_bars = 1 self.max_hold_bars = 1
def execute(self, signals): def execute(self, signals, long=True):
self.trades = [] self.trades = []
sig_vectors = [] sig_vectors = []
vec_length = 0 vec_length = 0
@ -30,6 +30,7 @@ class Executor(object):
in_trade = False in_trade = False
current_entry_price = None current_entry_price = None
bar_counter = 0 bar_counter = 0
entry_bar = 0
for i in range(0, vec_length): for i in range(0, vec_length):
if not in_trade: if not in_trade:
@ -42,11 +43,16 @@ class Executor(object):
if has_signal and i + 1 < vec_length: if has_signal and i + 1 < vec_length:
in_trade = True in_trade = True
current_entry_price = self.series.get_open(i + 1) current_entry_price = self.series.get_open(i + 1)
entry_bar = i + 1
bar_counter = 0 bar_counter = 0
else: else:
bar_counter += 1 bar_counter += 1
if bar_counter >= self.max_hold_bars: if bar_counter >= self.max_hold_bars:
in_trade = False in_trade = False
self.trades.append(Trade(current_entry_price, self.series.get_close(i))) if long:
trade_dir = Trade.LONG
else:
trade_dir = Trade.SHORT
self.trades.append(Trade(current_entry_price, self.series.get_close(i), entry_bar, i, trade_dir))
return self.trades return self.trades

4
execution/trade.py

@ -8,7 +8,7 @@ class Trade:
LONG = 1 LONG = 1
SHORT = 2 SHORT = 2
def __init__(self, entry_price, exit_price, direction = LONG): def __init__(self, entry_price, exit_price, entry_bar, exit_bar, direction = LONG):
''' '''
Constructor Constructor
''' '''
@ -16,6 +16,8 @@ class Trade:
self.direction = direction self.direction = direction
self.entry_price = entry_price self.entry_price = entry_price
self.exit_price = exit_price self.exit_price = exit_price
self.entry_bar = entry_bar
self.exit_bar = exit_bar
def pnl(self): def pnl(self):

47
solver/solver.py

@ -6,17 +6,23 @@ from execution.executor import Executor
import random import random
from math import inf from math import inf
import numpy import numpy
from PyQt5.Qt import pyqtSignal, QObject
from PyQt5 import QtCore
import talib
class Solver(): class Solver(QObject):
''' '''
''' '''
progress = pyqtSignal(int, int, name='progress')
done = pyqtSignal(list, name='done')
def __init__(self, series): def __init__(self, series):
''' '''
Constructor Constructor
''' '''
super().__init__(None)
self.series = series self.series = series
self.executor = Executor(series) self.executor = Executor(series)
@ -25,29 +31,48 @@ class Solver():
def add_generator(self, generator): def add_generator(self, generator):
self.generators.append(generator) self.generators.append(generator)
def solve(self): @QtCore.pyqtSlot(dict)
max_signals = 3 def solve(self, params):
max_strategies = 1000 max_signals = 5
self.results = [] max_strategies = params.get('num_strategies', 1000)
results = []
for x in range(0, max_strategies): min_trades = params.get('min_trades', 0)
min_win_rate = params.get('min_win_rate', 0)
min_sharpe = params.get('min_sharpe', 0)
is_long = params.get('direction', 'long') == 'long'
print(is_long)
while len(results) < max_strategies:
sig_num = random.randint(1, max_signals) sig_num = random.randint(1, max_signals)
strategy = [] strategy = []
for i in range(0, sig_num): for i in range(0, sig_num):
strategy.append(random.choice(self.generators).generate()) strategy.append(random.choice(self.generators).generate())
trades = self.executor.execute(strategy) trades = self.executor.execute(strategy, is_long)
result = self.evaluate_trades(trades) if len(trades) >= min_trades:
result['display_name'] = ' && '.join([signal.get_text() for signal in strategy]) result = self.evaluate_trades(trades)
self.results.append(result) if result['win_percentage'] > min_win_rate and result['sharpe'] > min_sharpe:
result['strategy'] = strategy
result['display_name'] = ' && '.join([signal.get_text() for signal in strategy])
result['trades'] = trades
results.append(result)
self.progress.emit(len(results), max_strategies)
return self.results self.done.emit(results)
def evaluate_trades(self, trades): def evaluate_trades(self, trades):
result = {} result = {}
profits = [x.pnl() for x in trades] profits = [x.pnl() for x in trades]
total_won = len(list(filter(lambda x: x.pnl() > 0, trades)))
if len(trades) > 0:
result['win_percentage'] = total_won / len(trades) * 100
else:
result['win_percentage'] = 0
result['trades_number'] = len(trades) result['trades_number'] = len(trades)
result['total_pnl'] = sum(profits) result['total_pnl'] = sum(profits)

72
ui/mainwindow.py

@ -7,8 +7,13 @@ from .ui_mainwindow import Ui_MainWindow
from solver.solver import Solver from solver.solver import Solver
from data.series import Series from data.series import Series
from data.signal import PriceComparisonSignalGenerator, RsiSignalGenerator,\ from data.signal import PriceComparisonSignalGenerator, RsiSignalGenerator,\
AtrSignalGenerator AtrSignalGenerator, DayOfWeekSignalGenerator, CrtdrSignalGenerator,\
from PyQt5.Qt import Qt AtrDeltaSignalGenerator, SmaSignalGenerator, DayOfMonthSignalGenerator,\
CciSignalGenerator, BbandsSignalGenerator
from PyQt5.Qt import Qt, QFileDialog, QThread, Q_ARG, QMetaObject
import pyqtgraph
import numpy
class MainWindow(QMainWindow, Ui_MainWindow): class MainWindow(QMainWindow, Ui_MainWindow):
''' '''
@ -20,24 +25,79 @@ class MainWindow(QMainWindow, Ui_MainWindow):
''' '''
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.work_thread = QThread()
self.work_thread.start()
def browse(self):
fname = QFileDialog.getOpenFileName(self, 'Open file')
if fname[0] != '':
self.e_filename.setText(fname[0])
def go(self):
self.tw_strategies.clear()
self.series = Series() self.series = Series()
self.series.load_from_finam_csv('/home/asakul/tmp/daily/RTSI_20000101_20171231_daily.csv') self.series.load_from_finam_csv(self.e_filename.text())
self.solver = Solver(self.series) self.solver = Solver(self.series)
self.solver.add_generator(PriceComparisonSignalGenerator()) self.solver.add_generator(PriceComparisonSignalGenerator())
self.solver.add_generator(RsiSignalGenerator()) self.solver.add_generator(RsiSignalGenerator())
self.solver.add_generator(AtrSignalGenerator()) self.solver.add_generator(AtrSignalGenerator())
results = self.solver.solve() self.solver.add_generator(AtrDeltaSignalGenerator())
self.solver.add_generator(DayOfWeekSignalGenerator())
self.solver.add_generator(DayOfMonthSignalGenerator())
self.solver.add_generator(SmaSignalGenerator())
self.solver.add_generator(CrtdrSignalGenerator())
self.solver.add_generator(CciSignalGenerator())
self.solver.add_generator(BbandsSignalGenerator())
params = { 'num_strategies' : self.sb_strategiesNum.value() }
if self.cb_minTradesFilter.isChecked():
params['min_trades'] = self.sb_minTrades.value()
if self.cb_minWinRate.isChecked():
params['min_win_rate'] = self.sb_minWinRate.value()
if self.cb_minSharpe.isChecked():
params['min_sharpe'] = self.sb_minSharpe.value()
if self.rb_long.isChecked():
params['direction'] = 'long'
else:
params['direction'] = 'short'
self.solver.done.connect(self.done)
self.solver.progress.connect(self.progress)
self.solver.moveToThread(self.work_thread)
QMetaObject.invokeMethod(self.solver, 'solve', Q_ARG(dict, params))
#results = self.solver.solve(params)
def done(self, results):
for result in results: for result in results:
item = QTreeWidgetItem(self.tw_strategies) item = QTreeWidgetItem(self.tw_strategies)
item.setText(0, result['display_name']) item.setText(0, result['display_name'])
item.setText(1, str(result['trades_number'])) item.setText(1, str(result['trades_number']))
item.setText(2, str(result['total_pnl'])) item.setText(2, "{:.4f}".format(result['total_pnl']))
item.setText(3, "{:.2f}".format(result['profit_factor'])) item.setText(3, "{:.2f}".format(result['profit_factor']))
item.setText(4, "{:.2f}".format(result['sharpe'])) item.setText(4, "{:.2f}".format(result['sharpe']))
item.setText(5, "{:.2f}%".format(result['avg_percentage'])) item.setText(5, "{:.2f}%".format(result['avg_percentage']))
item.setText(6, "{:.2f}%".format(result['win_percentage']))
item.setData(0, Qt.UserRole + 1, result) item.setData(0, Qt.UserRole + 1, result)
for i in range(0, 7):
self.tw_strategies.resizeColumnToContents(i)
def progress(self, current, total):
if current < total:
self.pb_progress.setValue(float(current) / total * 100)
else:
self.pb_progress.setValue(100)
def strategyClicked(self, item, column): def strategyClicked(self, item, column):
result = item.getData(0, Qt.UserRole + 1) result = item.data(0, Qt.UserRole + 1)
pnl = numpy.cumsum([trade.pnl() for trade in result['trades']])
xs = [trade.entry_bar for trade in result['trades']]
pyqtgraph.plot(xs, pnl)
for trade in result['trades']:
print(trade.entry_bar, trade.entry_price, trade.exit_price)

173
ui/mainwindow.ui

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>1041</width>
<height>600</height> <height>600</height>
</rect> </rect>
</property> </property>
@ -27,7 +27,87 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>2</number> <number>2</number>
</property> </property>
<item row="0" column="0"> <item row="1" column="6">
<widget class="QCheckBox" name="cb_minSharpe">
<property name="text">
<string>Min. sharpe</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="10">
<widget class="QProgressBar" name="pb_progress">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QSpinBox" name="sb_minWinRate">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item row="0" column="1" colspan="8">
<widget class="QLineEdit" name="e_filename"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>#Strategies to generate:</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QSpinBox" name="sb_minTrades">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item row="1" column="9">
<widget class="QPushButton" name="e_go">
<property name="text">
<string>Go</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="sb_strategiesNum">
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
<item row="1" column="8">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="10">
<widget class="QTreeWidget" name="tw_strategies"> <widget class="QTreeWidget" name="tw_strategies">
<property name="sortingEnabled"> <property name="sortingEnabled">
<bool>true</bool> <bool>true</bool>
@ -62,6 +142,59 @@
<string>Avg. %</string> <string>Avg. %</string>
</property> </property>
</column> </column>
<column>
<property name="text">
<string>Win %</string>
</property>
</column>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="rb_long">
<property name="text">
<string>Long</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Data source:</string>
</property>
</widget>
</item>
<item row="0" column="9">
<widget class="QPushButton" name="b_browse">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="cb_minTradesFilter">
<property name="text">
<string>Min. trades:</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QCheckBox" name="cb_minWinRate">
<property name="text">
<string>Min. win rate</string>
</property>
</widget>
</item>
<item row="1" column="7">
<widget class="QDoubleSpinBox" name="sb_minSharpe"/>
</item>
<item row="3" column="0">
<widget class="QRadioButton" name="rb_short">
<property name="text">
<string>Short</string>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -71,7 +204,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>1041</width>
<height>27</height> <height>27</height>
</rect> </rect>
</property> </property>
@ -96,8 +229,42 @@
</hint> </hint>
</hints> </hints>
</connection> </connection>
<connection>
<sender>b_browse</sender>
<signal>clicked()</signal>
<receiver>MainWindow</receiver>
<slot>browse()</slot>
<hints>
<hint type="sourcelabel">
<x>776</x>
<y>42</y>
</hint>
<hint type="destinationlabel">
<x>706</x>
<y>56</y>
</hint>
</hints>
</connection>
<connection>
<sender>e_go</sender>
<signal>clicked()</signal>
<receiver>MainWindow</receiver>
<slot>go()</slot>
<hints>
<hint type="sourcelabel">
<x>768</x>
<y>69</y>
</hint>
<hint type="destinationlabel">
<x>680</x>
<y>84</y>
</hint>
</hints>
</connection>
</connections> </connections>
<slots> <slots>
<slot>strategyClicked(QTreeWidgetItem*,int)</slot> <slot>strategyClicked(QTreeWidgetItem*,int)</slot>
<slot>browse()</slot>
<slot>go()</slot>
</slots> </slots>
</ui> </ui>

75
ui/ui_mainwindow.py

@ -11,18 +11,75 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object): class Ui_MainWindow(object):
def setupUi(self, MainWindow): def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow") MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600) MainWindow.resize(1041, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget") self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setContentsMargins(2, 2, 2, 2) self.gridLayout.setContentsMargins(2, 2, 2, 2)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.cb_minSharpe = QtWidgets.QCheckBox(self.centralwidget)
self.cb_minSharpe.setObjectName("cb_minSharpe")
self.gridLayout.addWidget(self.cb_minSharpe, 1, 6, 1, 1)
self.pb_progress = QtWidgets.QProgressBar(self.centralwidget)
self.pb_progress.setProperty("value", 0)
self.pb_progress.setObjectName("pb_progress")
self.gridLayout.addWidget(self.pb_progress, 5, 0, 1, 10)
self.sb_minWinRate = QtWidgets.QSpinBox(self.centralwidget)
self.sb_minWinRate.setMaximum(100)
self.sb_minWinRate.setProperty("value", 50)
self.sb_minWinRate.setObjectName("sb_minWinRate")
self.gridLayout.addWidget(self.sb_minWinRate, 1, 5, 1, 1)
self.e_filename = QtWidgets.QLineEdit(self.centralwidget)
self.e_filename.setObjectName("e_filename")
self.gridLayout.addWidget(self.e_filename, 0, 1, 1, 8)
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
self.sb_minTrades = QtWidgets.QSpinBox(self.centralwidget)
self.sb_minTrades.setMinimum(1)
self.sb_minTrades.setMaximum(10000)
self.sb_minTrades.setProperty("value", 100)
self.sb_minTrades.setObjectName("sb_minTrades")
self.gridLayout.addWidget(self.sb_minTrades, 1, 3, 1, 1)
self.e_go = QtWidgets.QPushButton(self.centralwidget)
self.e_go.setObjectName("e_go")
self.gridLayout.addWidget(self.e_go, 1, 9, 1, 1)
self.sb_strategiesNum = QtWidgets.QSpinBox(self.centralwidget)
self.sb_strategiesNum.setMinimum(100)
self.sb_strategiesNum.setMaximum(10000)
self.sb_strategiesNum.setProperty("value", 1000)
self.sb_strategiesNum.setObjectName("sb_strategiesNum")
self.gridLayout.addWidget(self.sb_strategiesNum, 1, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem, 1, 8, 1, 1)
self.tw_strategies = QtWidgets.QTreeWidget(self.centralwidget) self.tw_strategies = QtWidgets.QTreeWidget(self.centralwidget)
self.tw_strategies.setObjectName("tw_strategies") self.tw_strategies.setObjectName("tw_strategies")
self.gridLayout.addWidget(self.tw_strategies, 0, 0, 1, 1) self.gridLayout.addWidget(self.tw_strategies, 4, 0, 1, 10)
self.rb_long = QtWidgets.QRadioButton(self.centralwidget)
self.rb_long.setChecked(True)
self.rb_long.setObjectName("rb_long")
self.gridLayout.addWidget(self.rb_long, 2, 0, 1, 1)
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.b_browse = QtWidgets.QPushButton(self.centralwidget)
self.b_browse.setObjectName("b_browse")
self.gridLayout.addWidget(self.b_browse, 0, 9, 1, 1)
self.cb_minTradesFilter = QtWidgets.QCheckBox(self.centralwidget)
self.cb_minTradesFilter.setObjectName("cb_minTradesFilter")
self.gridLayout.addWidget(self.cb_minTradesFilter, 1, 2, 1, 1)
self.cb_minWinRate = QtWidgets.QCheckBox(self.centralwidget)
self.cb_minWinRate.setObjectName("cb_minWinRate")
self.gridLayout.addWidget(self.cb_minWinRate, 1, 4, 1, 1)
self.sb_minSharpe = QtWidgets.QDoubleSpinBox(self.centralwidget)
self.sb_minSharpe.setObjectName("sb_minSharpe")
self.gridLayout.addWidget(self.sb_minSharpe, 1, 7, 1, 1)
self.rb_short = QtWidgets.QRadioButton(self.centralwidget)
self.rb_short.setObjectName("rb_short")
self.gridLayout.addWidget(self.rb_short, 3, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 27)) self.menubar.setGeometry(QtCore.QRect(0, 0, 1041, 27))
self.menubar.setObjectName("menubar") self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar) MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar = QtWidgets.QStatusBar(MainWindow)
@ -31,11 +88,16 @@ class Ui_MainWindow(object):
self.retranslateUi(MainWindow) self.retranslateUi(MainWindow)
self.tw_strategies.itemClicked['QTreeWidgetItem*','int'].connect(MainWindow.strategyClicked) self.tw_strategies.itemClicked['QTreeWidgetItem*','int'].connect(MainWindow.strategyClicked)
self.b_browse.clicked.connect(MainWindow.browse)
self.e_go.clicked.connect(MainWindow.go)
QtCore.QMetaObject.connectSlotsByName(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow): def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.cb_minSharpe.setText(_translate("MainWindow", "Min. sharpe"))
self.label_2.setText(_translate("MainWindow", "#Strategies to generate:"))
self.e_go.setText(_translate("MainWindow", "Go"))
self.tw_strategies.setSortingEnabled(True) self.tw_strategies.setSortingEnabled(True)
self.tw_strategies.headerItem().setText(0, _translate("MainWindow", "Strategy")) self.tw_strategies.headerItem().setText(0, _translate("MainWindow", "Strategy"))
self.tw_strategies.headerItem().setText(1, _translate("MainWindow", "Trades #")) self.tw_strategies.headerItem().setText(1, _translate("MainWindow", "Trades #"))
@ -43,4 +105,11 @@ class Ui_MainWindow(object):
self.tw_strategies.headerItem().setText(3, _translate("MainWindow", "PF")) self.tw_strategies.headerItem().setText(3, _translate("MainWindow", "PF"))
self.tw_strategies.headerItem().setText(4, _translate("MainWindow", "Sharpe")) self.tw_strategies.headerItem().setText(4, _translate("MainWindow", "Sharpe"))
self.tw_strategies.headerItem().setText(5, _translate("MainWindow", "Avg. %")) self.tw_strategies.headerItem().setText(5, _translate("MainWindow", "Avg. %"))
self.tw_strategies.headerItem().setText(6, _translate("MainWindow", "Win %"))
self.rb_long.setText(_translate("MainWindow", "Long"))
self.label.setText(_translate("MainWindow", "Data source:"))
self.b_browse.setText(_translate("MainWindow", "Browse"))
self.cb_minTradesFilter.setText(_translate("MainWindow", "Min. trades:"))
self.cb_minWinRate.setText(_translate("MainWindow", "Min. win rate"))
self.rb_short.setText(_translate("MainWindow", "Short"))

Loading…
Cancel
Save