From 1673e6f693b4394ca81f9bd014ff212b7f930394 Mon Sep 17 00:00:00 2001 From: Denis Tereshkin Date: Sun, 14 Nov 2021 15:17:28 +0700 Subject: [PATCH] Work (yes) --- cuda/__init__.py | 2 + cuda/signalcalc.py | 72 +++++++++++++ data/signal.py | 234 +++++++++++++++++++++++++++++++----------- execution/executor.py | 66 +++++++----- solver/cudasolver.py | 110 ++++++++++++++++++++ solver/solver.py | 46 ++++++++- ui/mainwindow.py | 28 ++++- ui/mainwindow.ui | 34 ++++++ ui/ui_mainwindow.py | 18 ++++ 9 files changed, 518 insertions(+), 92 deletions(-) create mode 100644 cuda/__init__.py create mode 100644 cuda/signalcalc.py create mode 100644 solver/cudasolver.py diff --git a/cuda/__init__.py b/cuda/__init__.py new file mode 100644 index 0000000..35350f6 --- /dev/null +++ b/cuda/__init__.py @@ -0,0 +1,2 @@ +''' +''' diff --git a/cuda/signalcalc.py b/cuda/signalcalc.py new file mode 100644 index 0000000..a6c95d6 --- /dev/null +++ b/cuda/signalcalc.py @@ -0,0 +1,72 @@ +''' +''' + +import pycuda.driver as cuda +import pycuda.autoinit + +from pycuda.compiler import SourceModule + + +class SignalCalculator(): + ''' + ''' + + + def __init__(self): + ''' + ''' + self.generators = [] + + def add_signal_generator(self, gen): + self.generators.append(gen) + + def make_cuda_kernel(self): + src = """ + #include + + const int nstates = %(NGENERATORS)s; + __device__ curandState_t* states[nstates]; + + __global__ void initkernel(int seed) + { + int tidx = threadIdx.x + blockIdx.x * blockDim.x; + + if (tidx < nstates) { + curandState_t* s = new curandState_t; + if (s != 0) { + curand_init(seed, tidx, 0, s); + } + + states[tidx] = s; + } + } + + __global__ void calc_signal(float* open, float* high, float* low, float* close, size_t points, int8_t* signals, char* sig_names, float* scratch) + { + int tidx = threadIdx.x + blockIdx.x * blockDim.x; + curandState_t s = *states[tidx]; + int signals = 5 * curand_uniform(&s); + float* tScratch = &scratch[tidx * points]; + int8_t* tSignals = &signals[tidx * points]; + char* tSigName = &signames[tidx * 256]; + + for(int i = 0; i < signals; i++) + { + int sigtype = curand_uniform(&s); + switch(sigtype) + { + """ + + i = 0 + for gen in self.generators: + src += gen.make_signal_kernel(i) + i += 1 + + src += """ + } + } + } + """ + + + \ No newline at end of file diff --git a/data/signal.py b/data/signal.py index fb8bb73..11d2ff8 100644 --- a/data/signal.py +++ b/data/signal.py @@ -5,7 +5,6 @@ import random import numpy import talib -from scipy.integrate.tests.test_odeint_jac import rhs class Signal: @@ -21,6 +20,9 @@ class Signal: def mutate(self, factor): return copy.deepcopy(self) + def cuda_kernel(self): + pass + class PriceComparisonSignalGenerator: def __init__(self): @@ -36,6 +38,54 @@ class PriceComparisonSignalGenerator: def id(self): return 'Price' + def make_signal_kernel(self, ix): + return (""" + case %(IX): + { + int lhs = curand_uniform(&s) * 4; + int rhs = curand_uniform(&s) * 4; + int lhs_shift = curand_uniform(&s) * 10; + int rhs_shift = curand_uniform(&s) * 10; + float* lx = open; + if(lhs == 1) + { + lx = high; + } + else if(lhs == 2) + { + lx = low; + } + else if(lhs == 3) + { + lx = close; + } + float* rx = open; + if(rhs == 1) + { + rx = high; + } + else if(rhs == 2) + { + rx = low; + } + else if(rhs == 3) + { + rx = close; + } + for(int x = 0; x < points; x++) + { + if((x < lhs_shift) || (x < rhs_shift)) + { + tSignals[x] = 0; + } + else + { + tSignals[x] &= (lx[x - lhs_shift] < rx[x - rhs_shift]); + } + } + } + """) + class PriceComparisonSignal(Signal): OPEN = 0 @@ -50,9 +100,9 @@ class PriceComparisonSignal(Signal): self.rhs_shift = rhs_shift def calculate(self, series): - result = [] + result = numpy.zeros(series.length()) for i in range(0, series.length()): - result.append(self.calculate_at_index(series, i)) + result[i] = self.calculate_at_index(series, i) return result @@ -110,9 +160,7 @@ class PriceComparisonSignal(Signal): 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: def __init__(self): @@ -148,11 +196,13 @@ class RsiSignal(Signal): rsi = talib.RSI(closes, self.period) - result = [self.calc_signal(v) for v in rsi] - if self.shift == 0: - return result - else: - return [False] * self.shift + result[:-self.shift] + result = numpy.zeros(len(rsi)) + pos = 0 + for i in range(self.shift, len(result)): + result[i] = self.calc_signal(rsi[pos]) + pos += 1 + + return result def calc_signal(self, value): if self.inequality_type == RsiSignal.LT: @@ -205,9 +255,11 @@ class AtrSignal(Signal): 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) + atr = numpy.nan_to_num(talib.ATR(highs, lows, closes, self.period)) - result = [self.calc_signal(v, c) for (v, c) in zip(atr, closes)] + result = numpy.zeros(len(atr)) + for i in range(0, len(atr)): + result[i] = self.calc_signal(atr[i], closes[i]) return result def calc_signal(self, value, close): @@ -271,13 +323,12 @@ class AtrDeltaSignal(Signal): 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) + atr = numpy.nan_to_num(talib.ATR(highs, lows, closes, self.period)) - result = [False] + result = numpy.zeros(len(atr)) for i in range(1, len(closes)): - result.append(self.calc_signal(atr[i], closes[i], closes[i - 1])) - + result[i] = self.calc_signal(atr[i], closes[i], closes[i - 1]) return result def calc_signal(self, value, c1, c2): @@ -324,9 +375,9 @@ class DayOfWeekSignal(Signal): self.day_of_week = day_of_week def calculate(self, series): - result = [] + result = numpy.zeros(series.length()) for i in range(0, series.length()): - result.append(series.get_dt(i).date().weekday() == self.day_of_week) + result[i] = series.get_dt(i).date().weekday() == self.day_of_week return result @@ -367,12 +418,12 @@ class DayOfMonthSignal(Signal): self.inequality_sign_str = '>' def calculate(self, series): - result = [] + result = numpy.zeros(series.length()) 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) + result[i] = series.get_dt(i).date().day < self.day_of_month else: - result.append(series.get_dt(i).date().day > self.day_of_month) + result[i] = series.get_dt(i).date().day > self.day_of_month return result @@ -412,7 +463,7 @@ class CrtdrSignal(Signal): self.inequality_sign_str = '>' def calculate(self, series): - result = [] + result = numpy.zeros(series.length()) for i in range(0, series.length()): try: h = series.get_high(i - self.shift) @@ -421,13 +472,13 @@ class CrtdrSignal(Signal): if h > l: if self.inequality_type == CrtdrSignal.LT: - result.append((c - l) / (h - l) < self.threshold) + result[i] = (c - l) / (h - l) < self.threshold else: - result.append((c - l) / (h - l) > self.threshold) + result[i] = (c - l) / (h - l) > self.threshold else: - result.append(False) + result[i] = False except IndexError: - result.append(False) + result[i] = False return result @@ -498,9 +549,11 @@ class SmaSignal(Signal): 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) + rhs_sma = numpy.nan_to_num(talib.SMA(rhs, self.period)) - result = [self.calc_signal(l, r) for (l, r) in zip(lhs, rhs_sma)] + result = numpy.zeros(series.length()) + for i in range(0, len(result)): + result[i] = self.calc_signal(lhs[i], rhs_sma[i]) return result def calc_signal(self, l, r): @@ -560,14 +613,15 @@ class CciSignal(Signal): 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] + cci = numpy.nan_to_num(talib.CCI(highs, lows, closes, self.period)) - if self.shift == 0: - return result - else: - return [False] * self.shift + result[:-self.shift] + result = numpy.zeros(len(cci)) + pos = 0 + for i in range(self.shift, len(result)): + result[i] = cci[pos] + pos += 1 + + return result def calc_signal(self, value): if self.inequality_type == CciSignal.LT: @@ -655,16 +709,16 @@ class BbandsSignal(Signal): 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) - - + (downband, _, upband) = talib.BBANDS(rhs, self.period, self.dev, self.dev) if self.band_type == BbandsSignal.UP: - band = upband + band = numpy.nan_to_num(upband) else: - band = downband - - result = [self.calc_signal(l, r) for (l, r) in zip(lhs, band)] + band = numpy.nan_to_num(downband) + + result = numpy.zeros(len(band)) + for i in range(0, len(result)): + result[i] = self.calc_signal(lhs[i], band[i]) return result def calc_signal(self, l, r): @@ -727,14 +781,12 @@ class PivotPointsSignal(Signal): self.rhs_shift = rhs_shift def calculate(self, series): - result = [] + result = numpy.zeros(series.length()) for i in range(0, series.length()): lhs = self.calculate_signal(series, self.lhs, i - self.lhs_shift) rhs = self.calculate_signal(series, self.rhs, i - self.rhs_shift) if i - self.lhs_shift >= 0 and i - self.rhs_shift >= 0: - result.append(lhs < rhs) - else: - result.append(False) + result[i] = lhs < rhs return result @@ -836,11 +888,17 @@ class StochasticSignal(Signal): stoch_k, stoch_d = talib.STOCHF(highs, lows, closes, self.period, self.period2) - result = [self.calc_signal(v) for v in stoch_k] - if self.shift == 0: - return result - else: - return [False] * self.shift + result[:-self.shift] + stoch_k = numpy.nan_to_num(stoch_k) + + result = numpy.zeros(len(stoch_k)) + pos = 0 + for i in range(self.shift, len(result)): + result[i] = self.calc_signal(stoch_k[pos]) + pos += 1 + + + + return result def calc_signal(self, value): if self.inequality_type == StochasticSignal.LT: @@ -849,7 +907,7 @@ class StochasticSignal(Signal): return value > self.threshold def get_text(self): - return "stoch(c, {:d})[{:d}] {:s} {:d}".format(self.period, self.shift, self.inequality_sign_str, int(self.threshold)) + return "stoch(c, {:d}, {:d})[{:d}] {:s} {:d}".format(self.period, self.period2, self.shift, self.inequality_sign_str, int(self.threshold)) class IntradayBarNumberSignalGenerator: @@ -882,7 +940,7 @@ class IntradayBarNumberSignal(Signal): self.inequality_sign_str = '==' def calculate(self, series): - ibn = [] + ibn = numpy.zeros(series.length()) cur_date = None for i in range(0, series.length()): if series.get_dt(i).date() != cur_date: @@ -890,11 +948,11 @@ class IntradayBarNumberSignal(Signal): ctr = 0 else: ctr += 1 - ibn.append(ctr) + ibn[i] = ctr - result = [] - for i in ibn: - result.append(self.calc_signal(i)) + result = numpy.zeros(len(ibn)) + for i in range(0, len(result)): + result[i] = self.calc_signal(ibn[i]) return result def calc_signal(self, ibn): @@ -907,4 +965,64 @@ class IntradayBarNumberSignal(Signal): def get_text(self): return "ibn {:s} {:d}".format(self.inequality_sign_str, self.ibn) + +class GainPercentrankSignalGenerator: + + def __init__(self): + pass + def generate(self): + period = random.randint(2, 120) + threshold = random.choice([1, 5, 10, 20, 25, 50, 75, 80, 90, 95, 99]) + ineq_type = random.randint(GainPercentrankSignal.LT, GainPercentrankSignal.GT) + return GainPercentrankSignal(period, threshold, ineq_type) + + def id(self): + return 'IBN' + + +class GainPercentrankSignal(Signal): + + LT = 0 + GT = 1 + + def __init__(self, period, threshold, inequality_type): + self.period = period + self.threshold = threshold + self.inequality_type = inequality_type + if inequality_type == GainPercentrankSignal.LT: + self.inequality_sign_str = '<' + elif inequality_type == GainPercentrankSignal.GT: + self.inequality_sign_str = '>' + else: + self.inequality_sign_str = '==' + + def calculate(self, series): + gains = numpy.zeros(series.length()) + for i in range(0, len(gains)): + gains[i] = series.get_close(i) / series.get_open(i) + + result = numpy.zeros(series.length()) + for i in range(0, len(result)): + result[i] = self.calc_signal(gains[i-self.period+1:i+1]) + return result + + def calc_signal(self, gains): + if len(gains) == 0: + return False + current_gain = gains[-1] + less = 1 + for g in gains: + if g < current_gain: + less += 1 + + rank = 100 * (float(less) / len(gains)) + + if self.inequality_type == GainPercentrankSignal.LT: + return rank < self.threshold + elif self.inequality_type == GainPercentrankSignal.GT: + return rank > self.threshold + + def get_text(self): + return "%rank(return, {:d}) {:s} {:d}".format(self.period, self.inequality_sign_str, self.threshold) + diff --git a/execution/executor.py b/execution/executor.py index 7d5502b..6ef938e 100644 --- a/execution/executor.py +++ b/execution/executor.py @@ -2,6 +2,7 @@ ''' from .trade import Trade +import numpy class Executor(object): ''' @@ -15,44 +16,59 @@ class Executor(object): self.series = series self.max_hold_bars = max_hold_bars - def execute(self, signals, long=True): + def execute(self, sig_vectors, long=True, stop=None, tp=None): self.trades = [] - sig_vectors = [] - vec_length = 0 - for signal in signals: - vec = signal.calculate(self.series) - sig_vectors.append(vec) - if vec_length == 0: - vec_length = len(vec) - else: - assert(vec_length == len(vec)) - + in_trade = False current_entry_price = None bar_counter = 0 entry_bar = 0 + stop_price = 0 + tp_price = 0 - for i in range(0, vec_length): + for i in range(0, self.series.length()): if not in_trade: - has_signal = True - for vec in sig_vectors: - if vec[i] is None or vec[i] == False: - has_signal = False - break + has_signal = sig_vectors[i] - if has_signal and i + 1 < vec_length: + if has_signal and i + 1 < self.series.length(): in_trade = True current_entry_price = self.series.get_open(i + 1) + if stop is not None: + if long: + stop_price = current_entry_price * (1 - stop) + else: + stop_price = current_entry_price * (1 + stop) + if tp is not None: + if long: + tp_price = current_entry_price * (1 + tp) + else: + tp_price = current_entry_price * (1 - tp) entry_bar = i + 1 bar_counter = 0 else: bar_counter += 1 - if bar_counter >= self.max_hold_bars: - in_trade = False - 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)) + if long: + if stop is not None and self.series.get_low(i) < stop_price: + self.trades.append(Trade(current_entry_price, stop_price, entry_bar, i, Trade.LONG)) + in_trade = False + elif tp is not None and self.series.get_high(i) > tp_price: + self.trades.append(Trade(current_entry_price, tp_price, entry_bar, i, Trade.LONG)) + in_trade = False + else: + if stop is not None and self.series.get_high(i) > stop_price: + self.trades.append(Trade(current_entry_price, stop_price, entry_bar, i, Trade.SHORT)) + in_trade = False + elif tp is not None and self.series.get_low(i) < tp_price: + self.trades.append(Trade(current_entry_price, tp_price, entry_bar, i, Trade.SHORT)) + in_trade = False + + if in_trade: + if bar_counter >= self.max_hold_bars: + in_trade = False + 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 \ No newline at end of file diff --git a/solver/cudasolver.py b/solver/cudasolver.py new file mode 100644 index 0000000..c83fd49 --- /dev/null +++ b/solver/cudasolver.py @@ -0,0 +1,110 @@ +''' +''' +from PyQt5.Qt import pyqtSignal +from PyQt5 import QtCore +import random + +class CudaSolver(): + ''' + ''' + + + progress = pyqtSignal(int, int, name='progress') + done = pyqtSignal(list, name='done') + + def __init__(self, calc, series): + ''' + Constructor + ''' + super().__init__(None) + + self.calc = calc + + self.series = series + self.generators = [] + self.counter = 0 + self.total_counter = 0 + + def add_generator(self, generator): + self.generators.append(generator) + + @QtCore.pyqtSlot(dict) + def solve(self, params): + + max_signals = 5 + max_hold_bars = params.get('max_hold_bars', 1) + #self.executor = Executor(self.series, max_hold_bars) + max_strategies = params.get('num_strategies', 1000) + results = [] + + min_trades = params.get('min_trades', 0) + min_win_rate = params.get('min_win_rate', 0) + min_sharpe = params.get('min_sharpe', 0) + + stop = params.get('stop_loss', None) + tp = params.get('take_profit', None) + + is_long = params.get('direction', 'long') == 'long' + while len(results) < max_strategies: + sig_num = random.randint(1, max_signals) + strategy = [] + for i in range(0, sig_num): + strategy.append(random.choice(self.generators).generate()) + + trades = self.executor.execute(strategy, is_long, stop, tp) + if len(trades) >= min_trades: + result = self.evaluate_trades(trades) + 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) + + self.done.emit([result]) + + self.counter += 1 + self.total_counter += 1 + + + def evaluate_trades(self, trades): + result = {} + + 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['total_pnl'] = sum(profits) + + if len(trades) > 0: + result['avg_percentage'] = sum([trade.pnl_percentage() for trade in trades]) / len(trades) + else: + result['avg_percentage'] = 0 + + gross_profit = sum([max(0, x.pnl()) for x in trades]) + gross_loss = sum([min(0, x.pnl()) for x in trades]) + + if gross_loss != 0: + result['profit_factor'] = gross_profit / (-gross_loss) + else: + result['profit_factor'] = inf + + if len(profits) > 0: + mean = numpy.mean(profits) + stddev = numpy.std(profits) + if stddev != 0: + result['sharpe'] = mean / stddev + else: + result['sharpe'] = 0 + else: + result['sharpe'] = 0 + + return result + + \ No newline at end of file diff --git a/solver/solver.py b/solver/solver.py index 4a27a16..412c470 100644 --- a/solver/solver.py +++ b/solver/solver.py @@ -9,6 +9,9 @@ import numpy from PyQt5.Qt import pyqtSignal, QObject from PyQt5 import QtCore import talib +from data.signal import PriceComparisonSignal, DayOfMonthSignal, DayOfWeekSignal,\ + StochasticSignal, IntradayBarNumberSignal, BbandsSignal,\ + GainPercentrankSignal class Solver(QObject): ''' @@ -26,12 +29,15 @@ class Solver(QObject): self.series = series self.generators = [] + self.counter = 0 + self.total_counter = 0 def add_generator(self, generator): self.generators.append(generator) @QtCore.pyqtSlot(dict) - def solve(self, params): + def solve(self, params): + max_signals = 5 max_hold_bars = params.get('max_hold_bars', 1) self.executor = Executor(self.series, max_hold_bars) @@ -42,15 +48,33 @@ class Solver(QObject): min_win_rate = params.get('min_win_rate', 0) min_sharpe = params.get('min_sharpe', 0) + stop = params.get('stop_loss', None) + tp = params.get('take_profit', None) + is_long = params.get('direction', 'long') == 'long' - print(is_long) + + strategy = [GainPercentrankSignal(107, 1, GainPercentrankSignal.LT)] + signals = self.make_signals(strategy) + + trades = self.executor.execute(signals, is_long, stop, tp) + result = self.evaluate_trades(trades) + 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) + + self.done.emit([result]) + while len(results) < max_strategies: sig_num = random.randint(1, max_signals) strategy = [] for i in range(0, sig_num): strategy.append(random.choice(self.generators).generate()) - trades = self.executor.execute(strategy, is_long) + signals = self.make_signals(strategy) + + trades = self.executor.execute(signals, is_long, stop, tp) if len(trades) >= min_trades: result = self.evaluate_trades(trades) if result['win_percentage'] > min_win_rate and result['sharpe'] > min_sharpe: @@ -59,8 +83,20 @@ class Solver(QObject): result['trades'] = trades results.append(result) self.progress.emit(len(results), max_strategies) + + self.done.emit([result]) + + self.counter += 1 + self.total_counter += 1 - self.done.emit(results) + def make_signals(self, strategy): + sig_vectors = numpy.ones(self.series.length(), dtype=int) + for signal in strategy: + vec = signal.calculate(self.series) + assert(len(vec) == self.series.length()) + sig_vectors = numpy.logical_and(sig_vectors, vec) + + return sig_vectors def evaluate_trades(self, trades): result = {} @@ -102,4 +138,4 @@ class Solver(QObject): return result - \ No newline at end of file + diff --git a/ui/mainwindow.py b/ui/mainwindow.py index 84fcbc0..e94bf01 100644 --- a/ui/mainwindow.py +++ b/ui/mainwindow.py @@ -10,11 +10,14 @@ from data.signal import PriceComparisonSignalGenerator, RsiSignalGenerator,\ AtrSignalGenerator, DayOfWeekSignalGenerator, CrtdrSignalGenerator,\ AtrDeltaSignalGenerator, SmaSignalGenerator, DayOfMonthSignalGenerator,\ CciSignalGenerator, BbandsSignalGenerator, PivotPointsSignalGenerator,\ - StochasticSignalGenerator, IntradayBarNumberSignalGenerator + StochasticSignalGenerator, IntradayBarNumberSignalGenerator,\ + GainPercentrankSignalGenerator from PyQt5.Qt import Qt, QFileDialog, QThread, Q_ARG, QMetaObject +from PyQt5 import QtCore import pyqtgraph import numpy +from backtrader import strategies class MainWindow(QMainWindow, Ui_MainWindow): ''' @@ -28,6 +31,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.setupUi(self) self.work_thread = QThread() self.work_thread.start() + + self.timer = QtCore.QTimer(self) + self.timer.timeout.connect(self.speed) + def browse(self): fname = QFileDialog.getOpenFileName(self, 'Open file') @@ -51,7 +58,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.solver.add_generator(BbandsSignalGenerator()) self.solver.add_generator(PivotPointsSignalGenerator()) self.solver.add_generator(StochasticSignalGenerator()) - self.solver.add_generator(IntradayBarNumberSignalGenerator(14)) + self.solver.add_generator(IntradayBarNumberSignalGenerator(60)) + self.solver.add_generator(GainPercentrankSignalGenerator()) + + self.timer.start(10000.0) params = { 'num_strategies' : self.sb_strategiesNum.value(), 'max_hold_bars' : self.sb_maxHoldBars.value() } @@ -68,6 +78,11 @@ class MainWindow(QMainWindow, Ui_MainWindow): else: params['direction'] = 'short' + if self.cb_stopLoss.isChecked(): + params['stop_loss'] = self.sb_stopLoss.value() / 100.; + if self.cb_takeProfit.isChecked(): + params['take_profit'] = self.sb_takeProfit.value() / 100.; + self.solver.done.connect(self.done) self.solver.progress.connect(self.progress) @@ -95,6 +110,11 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.pb_progress.setValue(float(current) / total * 100) else: self.pb_progress.setValue(100) + + def speed(self): + print(self.solver.counter) + self.statusbar.showMessage("{:.2f} sps, {:d} total ({:.2f}% yield)".format(self.solver.counter / 10., self.solver.total_counter, 100. * float(self.tw_strategies.topLevelItemCount()) / self.solver.total_counter)) + self.solver.counter = 0 def strategyClicked(self, item, column): @@ -103,6 +123,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): 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) + #for trade in result['trades']: + # print(trade.entry_bar, trade.entry_price, trade.exit_price) diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 614de87..ebfb516 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -211,6 +211,40 @@ + + + + Take profit + + + + + + + % + + + 1.000000000000000 + + + + + + + Stop loss + + + + + + + % + + + 1.000000000000000 + + + diff --git a/ui/ui_mainwindow.py b/ui/ui_mainwindow.py index cb87711..6b76403 100644 --- a/ui/ui_mainwindow.py +++ b/ui/ui_mainwindow.py @@ -84,6 +84,20 @@ class Ui_MainWindow(object): self.sb_maxHoldBars.setMinimum(1) self.sb_maxHoldBars.setObjectName("sb_maxHoldBars") self.gridLayout.addWidget(self.sb_maxHoldBars, 2, 3, 1, 1) + self.cb_takeProfit = QtWidgets.QCheckBox(self.centralwidget) + self.cb_takeProfit.setObjectName("cb_takeProfit") + self.gridLayout.addWidget(self.cb_takeProfit, 2, 4, 1, 1) + self.sb_takeProfit = QtWidgets.QDoubleSpinBox(self.centralwidget) + self.sb_takeProfit.setProperty("value", 1.0) + self.sb_takeProfit.setObjectName("sb_takeProfit") + self.gridLayout.addWidget(self.sb_takeProfit, 2, 5, 1, 1) + self.cb_stopLoss = QtWidgets.QCheckBox(self.centralwidget) + self.cb_stopLoss.setObjectName("cb_stopLoss") + self.gridLayout.addWidget(self.cb_stopLoss, 3, 4, 1, 1) + self.sb_stopLoss = QtWidgets.QDoubleSpinBox(self.centralwidget) + self.sb_stopLoss.setProperty("value", 1.0) + self.sb_stopLoss.setObjectName("sb_stopLoss") + self.gridLayout.addWidget(self.sb_stopLoss, 3, 5, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 1041, 27)) @@ -120,4 +134,8 @@ class Ui_MainWindow(object): self.cb_minWinRate.setText(_translate("MainWindow", "Min. win rate")) self.rb_short.setText(_translate("MainWindow", "Short")) self.label_3.setText(_translate("MainWindow", "Max hold bars")) + self.cb_takeProfit.setText(_translate("MainWindow", "Take profit")) + self.sb_takeProfit.setSuffix(_translate("MainWindow", " %")) + self.cb_stopLoss.setText(_translate("MainWindow", "Stop loss")) + self.sb_stopLoss.setSuffix(_translate("MainWindow", " %"))