Browse Source

Initial commit

master
Denis Tereshkin 8 years ago
commit
d7f8cbdc7d
  1. 0
      CHANGELOG.rst
  2. 0
      README.rst
  3. 30
      examples/multiple_assets.py
  4. 29
      examples/single_asset.py
  5. 69
      setup.py
  6. 0
      src/naiback/__init__.py
  7. 0
      src/naiback/broker/__init__.py
  8. 14
      src/naiback/broker/broker.py
  9. 34
      src/naiback/broker/position.py
  10. 0
      src/naiback/data/__init__.py
  11. 37
      src/naiback/data/bars.py
  12. 8
      src/naiback/data/feed.py
  13. 3
      src/naiback/exceptions.py
  14. 3
      src/naiback/strategy/__init__.py
  15. 20
      src/naiback/strategy/singleassetstrategy.py
  16. 28
      src/naiback/strategy/strategy.py
  17. 16
      tests/test_bars.py
  18. 47
      tests/test_position.py

0
CHANGELOG.rst

0
README.rst

30
examples/multiple_assets.py

@ -0,0 +1,30 @@
from naiback.strategy import Strategy
from naiback.data.feeds import FinamCSVFeed
from naiback.indicators import SMA, RSI
class MyStrategy(BarStrategy):
def __init__(self):
super().__init__()
def execute(self):
self.set_context('FOO')
rsi1 = RSI(self.bars.close, 2)
self.set_context('BAR')
rsi2 = RSI(self.bars.close, 2)
for i in self.bars.index[200:]:
if self.last_position_is_active():
if i - self.last_position().entry_bar > 3:
for position in self.all_positions():
position.exit_at_close(i)
else:
if rsi1[i] < 20 and rsi2[i] > 80:
self.buy_at_open(i + 1, 'FOO')
self.short_at_open(i + 1, 'BAR')
if __name__ == "__main__":
strategy = MyStrategy()
strategy.add_feed(FinamCSVFeed('data/SBER_20100101_20161231_daily.csv'))
strategy.run(from_time='2012-01-01', to_time='2016-12-31')
print(strategy.get_analyzer('stats').generate_plain_text())

29
examples/single_asset.py

@ -0,0 +1,29 @@
from naiback.strategy import SingleAssetStrategy
from naiback.data.feeds import FinamCSVFeed
from naiback.indicators import SMA, RSI
class MyStrategy(SingleAssetStrategy):
def __init__(self):
super().__init__()
def execute(self):
exit_sma = SMA(self.bars.close, 22)
sma = SMA(self.bars.close, 200)
rsi = RSI(self.bars.close, 2)
stop = 0
for i, bar in self.bars[200:]:
if self.last_position_is_active():
if not self.exit_at_stop(i, self.last_position(), stop):
if self.bars.close[i] < exit_sma[i]:
self.exit_at_close(i, self.last_position())
else:
if self.bars.close[i] > exit_sma[i] and self.bars.close[i] > sma[i] and rsi[i] < 20:
self.buy_at_open(i + 1)
if __name__ == "__main__":
strategy = MyStrategy()
strategy.add_feed(FinamCSVFeed('data/SBER_20100101_20161231_daily.csv'))
strategy.run(from_time='2012-01-01', to_time='2016-12-31')
print(strategy.get_analyzer('stats').generate_plain_text())

69
setup.py

@ -0,0 +1,69 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from __future__ import absolute_import
from __future__ import print_function
import io
import re
from glob import glob
from os.path import basename
from os.path import dirname
from os.path import join
from os.path import splitext
from setuptools import find_packages
from setuptools import setup
def read(*names, **kwargs):
return io.open(
join(dirname(__file__), *names),
encoding=kwargs.get('encoding', 'utf8')
).read()
setup(
name='naiback',
version='0.1.0',
license='BSD 2-Clause License',
description='Naive backtester',
long_description='%s\n%s' % (
re.compile('^.. start-badges.*^.. end-badges', re.M | re.S).sub('', read('README.rst')),
re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst'))
),
author='Denis Tereshkin',
author_email='denis@kasan.ws',
url='https://github.com/asakul/naiback',
packages=find_packages('src'),
package_dir={'': 'src'},
py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')],
include_package_data=True,
zip_safe=False,
classifiers=[
# complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers
'Development Status :: 1 - Planning',
'Intended Audience :: Financial and Insurance Industry',
'License :: OSI Approved :: MIT License',
'Operating System :: Unix',
'Operating System :: POSIX',
'Operating System :: Microsoft :: Windows',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Office/Business :: Financial'
],
keywords=[
'backtesting'
],
install_requires=[
],
extras_require={
},
entry_points={
},
)

0
src/naiback/__init__.py

0
src/naiback/broker/__init__.py

14
src/naiback/broker/broker.py

@ -0,0 +1,14 @@
class Broker:
"""
Broker has several responsibilities (so called SRP, or several responsibilities principle):
1) Track money amount on trading account
2) Track active positions
3) Subtract commissions/slippage
4) Validate issued orders and reject them if needed
"""
def __init__(self, initial_cash=100000.):
self.cash = initial_cash
self.positions = []

34
src/naiback/broker/position.py

@ -0,0 +1,34 @@
class Position:
def __init__(self, ticker):
self.ticker = ticker
self.entry_price = None
self.entry_metadata = {}
self.exit_price = None
self.exit_metadata = {}
self.size = None
self.total_pnl = 0
def entry_commission(self):
return self.entry_metadata['commission']
def entry_bar(self):
return self.entry_metadata['bar']
def pnl(self):
return self.total_pnl
def enter(self, price, amount, **kwargs):
self.entry_price = price
self.size = amount
for k, v in kwargs.items():
self.entry_metadata[k] = v
def exit(self, price):
self.exit_price = price
self.total_pnl += (self.exit_price - self.entry_price) * self.size
self.size = 0

0
src/naiback/data/__init__.py

37
src/naiback/data/bars.py

@ -0,0 +1,37 @@
from ..exceptions import NaibackException
class Bars:
"""
Basic bar series structure
"""
def __init__(self):
self.index = []
self.open = []
self.high = []
self.low = []
self.close = []
self.volume = []
self.timestamp = []
def append_bar(self, open_, high, low, close, volume, timestamp):
"""
Appends OHLCV data
"""
self.index.append(len(self.open))
self.open.append(open_)
self.high.append(high)
self.low.append(low)
self.close.append(close)
self.volume.append(volume)
self.timestamp.append(timestamp)
@classmethod
def from_feed(feed):
if feed.type() != 'bars':
raise NaibackException('Invalid feed type: "{}", should be "bars"'.format(feed.type()))
bars = Bars()
for bar in feed.items():
bars.append_bar(bar.open, bar.high, bar.low, bar.close, bar.volume, bar.timestamp)
return bars

8
src/naiback/data/feed.py

@ -0,0 +1,8 @@
class Feed:
"""
Interface for data source
"""
def __init__(self):
pass

3
src/naiback/exceptions.py

@ -0,0 +1,3 @@
class NaibackException(Exception):
pass

3
src/naiback/strategy/__init__.py

@ -0,0 +1,3 @@
__all__ = ['strategy']
from .singleassetstrategy import SingleAssetStrategy
from .strategy import Strategy

20
src/naiback/strategy/singleassetstrategy.py

@ -0,0 +1,20 @@
from .strategy import Strategy
from ..data.bars import Bars
from ..exceptions import NaibackException
class SingleAssetStrategy(Strategy):
def __init__(self):
super.__init__()
self.bars = None
def run(self, from_time=None, to_time=None):
self._prepare_bars()
super.run(from_time, to_time)
def _prepare_bars(self):
if len(self.feeds) == 0:
raise NaibackException('No feeds added to strategy')
self.bars = list(Bars.from_feed(self.feeds[0]))

28
src/naiback/strategy/strategy.py

@ -0,0 +1,28 @@
from abc import abstractmethod
class Strategy:
"""
Internal base class for strategies. User should use it's subclasses (i.e. SingleAssetStrategy)
"""
def __init__(self):
self.feeds = []
def add_feed(self, feed):
"""
Adds feed to feeds list.
"""
self.feeds.append(feed)
@abstractmethod
def execute(self):
"""
Will be called by 'run'
"""
pass
def run(self, from_time=None, to_time=None):
"""
By default, just calls execute.
"""
self.execute()

16
tests/test_bars.py

@ -0,0 +1,16 @@
import pytest
import datetime
from naiback.data.bars import Bars
def test_bar_append():
bars = Bars()
bars.append_bar(10, 20, 5, 11, 100, datetime.datetime(2017, 1, 1))
assert bars.open[0] == 10
assert bars.high[0] == 20
assert bars.low[0] == 5
assert bars.close[0] == 11
assert bars.volume[0] == 100
assert bars.timestamp[0] == datetime.datetime(2017, 1, 1)

47
tests/test_position.py

@ -0,0 +1,47 @@
import pytest
import datetime
from naiback.broker.position import Position
@pytest.fixture
def position():
return Position('FOO')
def test_position_enter(position):
position.enter(3.50, 10)
assert position.entry_price == 3.50
assert position.size == 10
def test_position_enter_metadata(position):
position.enter(3.50, 10, commission=0.1, bar=42)
assert position.entry_metadata['commission'] == 0.1
assert position.entry_metadata['bar'] == 42
def test_position_metadata_helpers(position):
position.enter(3.50, 10, commission=0.1, bar=42)
assert position.entry_commission() == 0.1
assert position.entry_bar() == 42
def test_position_exit(position):
position.enter(3.50, 10)
position.exit(4.50)
assert position.exit_price == 4.50
assert position.size == 0
def test_position_enter_short(position):
position.enter(3.50, -10)
assert position.entry_price == 3.50
assert position.size == -10
def test_position_exit_pnl(position):
position.enter(3.50, 10)
position.exit(4.00)
assert position.pnl() == 5
Loading…
Cancel
Save