Browse Source

Initial commit

master
Denis Tereshkin 8 years ago
commit
8969a3d9ea
  1. 3
      .hgignore
  2. 0
      data/__init__.py
  3. 53
      data/series.py
  4. 186
      data/signal.py
  5. 17
      edgeyopt.py
  6. 0
      execution/__init__.py
  7. 52
      execution/executor.py
  8. 31
      execution/trade.py
  9. 0
      solver/__init__.py
  10. 79
      solver/solver.py
  11. 0
      ui/__init__.py
  12. 43
      ui/mainwindow.py
  13. 103
      ui/mainwindow.ui
  14. 46
      ui/ui_mainwindow.py

3
.hgignore

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
syntax: glob
.*
*/__pycache__/*

0
data/__init__.py

53
data/series.py

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
'''
'''
import csv
import datetime
class Series:
def __init__(self):
self.data = []
def load_from_finam_csv(self, filepath):
with open(filepath) as fp:
self.ticker_ = None
reader = csv.reader(fp, delimiter=',')
next(reader)
for row in reader:
try:
self.ticker_ = row[0]
open_ = float(row[4])
high = float(row[5])
low = float(row[6])
close = float(row[7])
volume = int(row[8])
date = row[2]
time = row[3]
dt = datetime.datetime.strptime(date + "_" + time, "%Y%m%d_%H%M%S")
self.append_bar(dt, open_, high, low, close, volume)
except IndexError:
pass
def append_bar(self, dt, open_, high, low, close, volume):
self.data.append((dt, open_, high, low, close, volume))
def get_dt(self, index):
return self.data[index][0]
def get_open(self, index):
return self.data[index][1]
def get_high(self, index):
return self.data[index][2]
def get_low(self, index):
return self.data[index][3]
def get_close(self, index):
return self.data[index][4]
def get_volume(self, index):
return self.data[index][5]
def length(self):
return len(self.data)

186
data/signal.py

@ -0,0 +1,186 @@ @@ -0,0 +1,186 @@
'''
'''
import random
import talib
import numpy
class Signal:
def __init__(self):
pass
def calculate(self, series):
pass
def get_text(self):
pass
class PriceComparisonSignalGenerator:
def __init__(self):
pass
def generate(self):
lhs = random.randint(PriceComparisonSignal.OPEN, PriceComparisonSignal.CLOSE)
lhs_shift = random.randint(0, 10)
rhs = random.randint(PriceComparisonSignal.OPEN, PriceComparisonSignal.CLOSE)
rhs_shift = random.randint(0, 10)
return PriceComparisonSignal(lhs, lhs_shift, rhs, rhs_shift)
class PriceComparisonSignal(Signal):
OPEN = 0
HIGH = 1
LOW = 2
CLOSE = 3
def __init__(self, lhs, lhs_shift, rhs, rhs_shift):
self.lhs = lhs
self.lhs_shift = lhs_shift
self.rhs = rhs
self.rhs_shift = rhs_shift
def calculate(self, series):
result = []
for i in range(0, series.length()):
result.append(self.calculate_at_index(series, i))
return result
def calculate_at_index(self, series, index):
try:
if self.lhs == PriceComparisonSignal.OPEN:
lhs = series.get_open(index - self.lhs_shift)
elif self.lhs == PriceComparisonSignal.HIGH:
lhs = series.get_high(index - self.lhs_shift)
elif self.lhs == PriceComparisonSignal.LOW:
lhs = series.get_low(index - self.lhs_shift)
elif self.lhs == PriceComparisonSignal.CLOSE:
lhs = series.get_close(index - self.lhs_shift)
else:
raise Exception('Invalid lhs type')
if self.rhs == PriceComparisonSignal.OPEN:
rhs = series.get_open(index - self.rhs_shift)
elif self.rhs == PriceComparisonSignal.HIGH:
rhs = series.get_high(index - self.rhs_shift)
elif self.rhs == PriceComparisonSignal.LOW:
rhs = series.get_low(index - self.rhs_shift)
elif self.rhs == PriceComparisonSignal.CLOSE:
rhs = series.get_close(index - self.rhs_shift)
else:
raise Exception('Invalid lhs type')
return lhs < rhs
except IndexError:
return False
def get_text(self):
return self.component_to_str(self.lhs) + '[' + str(self.lhs_shift) + '] < ' + self.component_to_str(self.rhs) + '[' + str(self.rhs_shift) + ']'
def component_to_str(self, component):
if component == PriceComparisonSignal.OPEN:
return "open"
elif component == PriceComparisonSignal.HIGH:
return "high"
elif component == PriceComparisonSignal.LOW:
return "low"
elif component == PriceComparisonSignal.CLOSE:
return "close"
else:
return "??"
class RsiSignalGenerator:
def __init__(self):
pass
def generate(self):
period = random.randint(2, 30)
threshold = random.randrange(1, 9) * 10
ineq_type = random.randint(RsiSignal.LT, RsiSignal.GT)
return RsiSignal(period, threshold, ineq_type)
class RsiSignal(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 == RsiSignal.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())])
rsi = talib.RSI(closes, self.period)
result = [self.calc_signal(v) for v in rsi]
return result
def calc_signal(self, value):
if self.inequality_type == RsiSignal.LT:
return value < self.threshold
else:
return value > self.threshold
def get_text(self):
return "rsi(c, " + str(self.period) + ') ' + self.inequality_sign_str + ' ' + str(self.threshold)
class AtrSignalGenerator:
def __init__(self):
pass
def generate(self):
period = random.randint(2, 30)
threshold = random.randint(1, 30) * 0.001
ineq_type = random.randint(AtrSignal.LT, AtrSignal.GT)
return AtrSignal(period, threshold, ineq_type)
class AtrSignal(Signal):
LT = 0
GT = 1
def __init__(self, period, threshold_factor, inequality_type):
self.period = period
self.threshold_factor = threshold_factor
self.inequality_type = inequality_type
if inequality_type == RsiSignal.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())])
atr = talib.ATR(highs, lows, closes, self.period)
result = [self.calc_signal(v, c) for (v, c) in zip(atr, closes)]
return result
def calc_signal(self, value, close):
if self.inequality_type == AtrSignal.LT:
return value < self.threshold_factor * close
else:
return value > self.threshold_factor * close
def get_text(self):
return "atr(" + str(self.period) + ') ' + self.inequality_sign_str + ' close[0] * ' + "{:.3f}".format(self.threshold_factor)

17
edgeyopt.py

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
'''
'''
import sys
from PyQt5.QtWidgets import QApplication
from ui.mainwindow import MainWindow
if __name__ == '__main__':
app = QApplication(sys.argv)
wnd = MainWindow()
wnd.show()
app.exec_()

0
execution/__init__.py

52
execution/executor.py

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
'''
'''
from .trade import Trade
class Executor(object):
'''
'''
def __init__(self, series):
'''
Constructor
'''
self.series = series
self.max_hold_bars = 1
def execute(self, signals):
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
for i in range(0, vec_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
if has_signal and i + 1 < vec_length:
in_trade = True
current_entry_price = self.series.get_open(i + 1)
bar_counter = 0
else:
bar_counter += 1
if bar_counter >= self.max_hold_bars:
in_trade = False
self.trades.append(Trade(current_entry_price, self.series.get_close(i)))
return self.trades

31
execution/trade.py

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
'''
'''
class Trade:
'''
'''
LONG = 1
SHORT = 2
def __init__(self, entry_price, exit_price, direction = LONG):
'''
Constructor
'''
self.direction = direction
self.entry_price = entry_price
self.exit_price = exit_price
def pnl(self):
if self.direction == Trade.LONG:
return self.exit_price - self.entry_price
else:
return self.entry_price - self.exit_price
def pnl_percentage(self):
if self.direction == Trade.LONG:
return (self.exit_price - self.entry_price) / self.entry_price * 100
else:
return (self.entry_price - self.exit_price) / self.entry_price * 100

0
solver/__init__.py

79
solver/solver.py

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
'''
'''
from data import series
from execution.executor import Executor
import random
from math import inf
import numpy
class Solver():
'''
'''
def __init__(self, series):
'''
Constructor
'''
self.series = series
self.executor = Executor(series)
self.generators = []
def add_generator(self, generator):
self.generators.append(generator)
def solve(self):
max_signals = 3
max_strategies = 1000
self.results = []
for x in range(0, 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)
result = self.evaluate_trades(trades)
result['display_name'] = ' && '.join([signal.get_text() for signal in strategy])
self.results.append(result)
return self.results
def evaluate_trades(self, trades):
result = {}
profits = [x.pnl() for x in trades]
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

0
ui/__init__.py

43
ui/mainwindow.py

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
'''
'''
from PyQt5.QtWidgets import QMainWindow, QTreeWidgetItem
from .ui_mainwindow import Ui_MainWindow
from solver.solver import Solver
from data.series import Series
from data.signal import PriceComparisonSignalGenerator, RsiSignalGenerator,\
AtrSignalGenerator
from PyQt5.Qt import Qt
class MainWindow(QMainWindow, Ui_MainWindow):
'''
'''
def __init__(self, parent=None):
'''
Constructor
'''
super().__init__(parent)
self.setupUi(self)
self.series = Series()
self.series.load_from_finam_csv('/home/asakul/tmp/daily/RTSI_20000101_20171231_daily.csv')
self.solver = Solver(self.series)
self.solver.add_generator(PriceComparisonSignalGenerator())
self.solver.add_generator(RsiSignalGenerator())
self.solver.add_generator(AtrSignalGenerator())
results = self.solver.solve()
for result in results:
item = QTreeWidgetItem(self.tw_strategies)
item.setText(0, result['display_name'])
item.setText(1, str(result['trades_number']))
item.setText(2, str(result['total_pnl']))
item.setText(3, "{:.2f}".format(result['profit_factor']))
item.setText(4, "{:.2f}".format(result['sharpe']))
item.setText(5, "{:.2f}%".format(result['avg_percentage']))
item.setData(0, Qt.UserRole + 1, result)
def strategyClicked(self, item, column):
result = item.getData(0, Qt.UserRole + 1)

103
ui/mainwindow.ui

@ -0,0 +1,103 @@ @@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item row="0" column="0">
<widget class="QTreeWidget" name="tw_strategies">
<property name="sortingEnabled">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Strategy</string>
</property>
</column>
<column>
<property name="text">
<string>Trades #</string>
</property>
</column>
<column>
<property name="text">
<string>Total PnL</string>
</property>
</column>
<column>
<property name="text">
<string>PF</string>
</property>
</column>
<column>
<property name="text">
<string>Sharpe</string>
</property>
</column>
<column>
<property name="text">
<string>Avg. %</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>27</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections>
<connection>
<sender>tw_strategies</sender>
<signal>itemClicked(QTreeWidgetItem*,int)</signal>
<receiver>MainWindow</receiver>
<slot>strategyClicked(QTreeWidgetItem*,int)</slot>
<hints>
<hint type="sourcelabel">
<x>106</x>
<y>103</y>
</hint>
<hint type="destinationlabel">
<x>800</x>
<y>33</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>strategyClicked(QTreeWidgetItem*,int)</slot>
</slots>
</ui>

46
ui/ui_mainwindow.py

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setContentsMargins(2, 2, 2, 2)
self.gridLayout.setObjectName("gridLayout")
self.tw_strategies = QtWidgets.QTreeWidget(self.centralwidget)
self.tw_strategies.setObjectName("tw_strategies")
self.gridLayout.addWidget(self.tw_strategies, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 27))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.tw_strategies.itemClicked['QTreeWidgetItem*','int'].connect(MainWindow.strategyClicked)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.tw_strategies.setSortingEnabled(True)
self.tw_strategies.headerItem().setText(0, _translate("MainWindow", "Strategy"))
self.tw_strategies.headerItem().setText(1, _translate("MainWindow", "Trades #"))
self.tw_strategies.headerItem().setText(2, _translate("MainWindow", "Total PnL"))
self.tw_strategies.headerItem().setText(3, _translate("MainWindow", "PF"))
self.tw_strategies.headerItem().setText(4, _translate("MainWindow", "Sharpe"))
self.tw_strategies.headerItem().setText(5, _translate("MainWindow", "Avg. %"))
Loading…
Cancel
Save