From bcc4a485251a520016e901ebefab2fb81ad4e04a Mon Sep 17 00:00:00 2001 From: Denis Tereshkin Date: Tue, 11 Dec 2018 06:39:22 +0700 Subject: [PATCH] Analyzers --- examples/multiple_assets.py | 2 +- src/naiback/analyzers/analyzer.py | 3 + src/naiback/analyzers/statsanalyzer.py | 5 + src/naiback/analyzers/tradeslistanalyzer.py | 16 ++++ src/naiback/data/bars.py | 10 ++ src/naiback/strategy/strategy.py | 31 +++++- tests/test_strategy.py | 101 +++++++++++++++++--- 7 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 src/naiback/analyzers/tradeslistanalyzer.py diff --git a/examples/multiple_assets.py b/examples/multiple_assets.py index 11abaeb..14d0e7d 100644 --- a/examples/multiple_assets.py +++ b/examples/multiple_assets.py @@ -13,7 +13,7 @@ class MyStrategy(Strategy): rsi1 = RSI(self.bars.close, 2) self.set_current_ticker('GAZP') rsi2 = RSI(self.bars.close, 2) - for i in self.bars.index[200:]: + for i in self.bars.index[200:-1]: if self.last_position_is_active(): if i - self.last_position().entry_bar() > 3: for position in self.all_positions(): diff --git a/src/naiback/analyzers/analyzer.py b/src/naiback/analyzers/analyzer.py index 7429d7b..7f8b1a2 100644 --- a/src/naiback/analyzers/analyzer.py +++ b/src/naiback/analyzers/analyzer.py @@ -7,3 +7,6 @@ class Analyzer: def generate_plain_text(self): pass + def get_result(self): + pass + diff --git a/src/naiback/analyzers/statsanalyzer.py b/src/naiback/analyzers/statsanalyzer.py index 9aa849a..3db768f 100644 --- a/src/naiback/analyzers/statsanalyzer.py +++ b/src/naiback/analyzers/statsanalyzer.py @@ -39,6 +39,11 @@ class StatsAnalyzer(Analyzer): return table.get_string() + def get_result(self): + positions = self.strategy.broker.retired_positions() # TODO also add open positions + stats = self.calc_stats(positions) + return stats + def calc_stats(self, positions): longs = list(filter(lambda x: x.is_long(), positions)) shorts = list(filter(lambda x: x.is_short(), positions)) diff --git a/src/naiback/analyzers/tradeslistanalyzer.py b/src/naiback/analyzers/tradeslistanalyzer.py new file mode 100644 index 0000000..c4b49a8 --- /dev/null +++ b/src/naiback/analyzers/tradeslistanalyzer.py @@ -0,0 +1,16 @@ + +from .analyzer import Analyzer + +class TradesListAnalyzer(Analyzer): + + def __init__(self, strategy): + self.strategy = strategy + + def get_result(self): + positions = self.strategy.broker.retired_positions() + return [self.make_trade(pos) for pos in positions] + + def make_trade(self, pos): + return { 'entry_price' : pos.entry_price(), + 'exit_price' : pos.exit_price(), + 'pnl' : pos.pnl() } diff --git a/src/naiback/data/bars.py b/src/naiback/data/bars.py index e53f34e..e4a2bec 100644 --- a/src/naiback/data/bars.py +++ b/src/naiback/data/bars.py @@ -45,3 +45,13 @@ class Bars: for bar in feed.items(): bars.append_bar(bar.open, bar.high, bar.low, bar.close, bar.volume, bar.timestamp) return bars + + @classmethod + def from_feed_filter(cls, feed, from_time, to_time): + if feed.type() != 'bars': + raise NaibackException('Invalid feed type: "{}", should be "bars"'.format(feed.type())) + bars = Bars(feed.ticker()) + for bar in feed.items(): + if bar.timestamp >= from_time and bar.timestamp <= to_time: + bars.append_bar(bar.open, bar.high, bar.low, bar.close, bar.volume, bar.timestamp) + return bars diff --git a/src/naiback/strategy/strategy.py b/src/naiback/strategy/strategy.py index 75dde54..cfe97e4 100644 --- a/src/naiback/strategy/strategy.py +++ b/src/naiback/strategy/strategy.py @@ -3,6 +3,7 @@ from naiback.broker.position import Position from naiback.broker.broker import Broker from naiback.data.bars import Bars from naiback.analyzers.statsanalyzer import StatsAnalyzer +from naiback.analyzers.tradeslistanalyzer import TradesListAnalyzer class Strategy: """ @@ -13,10 +14,11 @@ class Strategy: self.all_bars = [] self.broker = Broker() self.bars = None - self.analyzer = StatsAnalyzer(self) + self.analyzers = { 'stats' : StatsAnalyzer(self), + 'tradeslist' : TradesListAnalyzer(self) } def get_analyzer(self, analyzer_id): - return self.analyzer + return self.analyzers[analyzer_id] def add_feed(self, feed): """ @@ -35,19 +37,22 @@ class Strategy: """ By default, just calls execute. """ - self._prepare_bars() + self._prepare_bars(from_time, to_time) self.execute() def set_current_ticker(self, ticker): self.bars = self._get_bars(ticker) - def _prepare_bars(self): + def _prepare_bars(self, from_time, to_time): if len(self.feeds) == 0: raise NaibackException('No feeds added to strategy') self.all_bars.clear() for feed in self.feeds: - self.all_bars.append(Bars.from_feed(feed)) + if from_time is None or to_time is None: + self.all_bars.append(Bars.from_feed(feed)) + else: + self.all_bars.append(Bars.from_feed_filter(feed, from_time, to_time)) all_dates = list(sorted(self._combine_dates())) @@ -90,10 +95,14 @@ class Strategy: return dates def buy_at_open(self, bar, ticker): + if isinstance(ticker, int): + ticker = self.all_bars[ticker].ticker bars = self._get_bars(ticker) return self.broker.add_position(ticker, bars.open[bar], 1, bar) def buy_at_limit(self, bar, price, ticker): + if isinstance(ticker, int): + ticker = self.all_bars[ticker].ticker bars = self._get_bars(ticker) if bars.low[bar] <= price: if bars.open[bar] > price: @@ -104,6 +113,8 @@ class Strategy: return None def buy_at_stop(self, bar, price, ticker): + if isinstance(ticker, int): + ticker = self.all_bars[ticker].ticker bars = self._get_bars(ticker) if bars.high[bar] >= price: if bars.open[bar] < price: @@ -114,14 +125,20 @@ class Strategy: return None def buy_at_close(self, bar, ticker): + if isinstance(ticker, int): + ticker = self.all_bars[ticker].ticker bars = self._get_bars(ticker) return self.broker.add_position(ticker, bars.close[bar], 1, bar) def short_at_open(self, bar, ticker): + if isinstance(ticker, int): + ticker = self.all_bars[ticker].ticker bars = self._get_bars(ticker) return self.broker.add_position(ticker, bars.open[bar], -1, bar) def short_at_limit(self, bar, price, ticker): + if isinstance(ticker, int): + ticker = self.all_bars[ticker].ticker bars = self._get_bars(ticker) if bars.high[bar] >= price: if bars.open[bar] < price: @@ -132,6 +149,8 @@ class Strategy: return None def short_at_stop(self, bar, price, ticker): + if isinstance(ticker, int): + ticker = self.all_bars[ticker].ticker bars = self._get_bars(ticker) if bars.low[bar] <= price: if bars.open[bar] > price: @@ -142,6 +161,8 @@ class Strategy: return None def short_at_close(self, bar, ticker): + if isinstance(ticker, int): + ticker = self.all_bars[ticker].ticker bars = self._get_bars(ticker) return self.broker.add_position(ticker, bars.close[bar], -1, bar) diff --git a/tests/test_strategy.py b/tests/test_strategy.py index df39f02..e04330b 100644 --- a/tests/test_strategy.py +++ b/tests/test_strategy.py @@ -20,74 +20,80 @@ MICEX,D,20100114,000000,1439.5500000,1456.2700000,1439.5500000,1455.6500000,4437 class BuyAndSell(Strategy): - def __init__(self, buy_bar, sell_bar): + def __init__(self, buy_bar, sell_bar, ticker='MICEX'): super().__init__() self.buy_bar = buy_bar self.sell_bar = sell_bar + self.ticker = ticker def execute(self): - pos = self.buy_at_open(self.buy_bar, 'MICEX') + pos = self.buy_at_open(self.buy_bar, self.ticker) self.exit_at_close(self.sell_bar, pos) class BuyAndSellLimit(Strategy): - def __init__(self, buy_bar, buy_price, sell_bar, sell_price): + def __init__(self, buy_bar, buy_price, sell_bar, sell_price, ticker='MICEX'): super().__init__() self.buy_bar = buy_bar self.buy_price = buy_price self.sell_bar = sell_bar self.sell_price = sell_price + self.ticker = ticker def execute(self): - pos = self.buy_at_limit(self.buy_bar, self.buy_price, 'MICEX') + pos = self.buy_at_limit(self.buy_bar, self.buy_price, self.ticker) self.exit_at_limit(self.sell_bar, self.sell_price, pos) class BuyOpenAndSellStop(Strategy): - def __init__(self, buy_bar, sell_bar, sell_price): + def __init__(self, buy_bar, sell_bar, sell_price, ticker='MICEX'): super().__init__() self.buy_bar = buy_bar self.sell_bar = sell_bar self.sell_price = sell_price + self.ticker = ticker def execute(self): - pos = self.buy_at_open(self.buy_bar, 'MICEX') + pos = self.buy_at_open(self.buy_bar, self.ticker) self.exit_at_stop(self.sell_bar, self.sell_price, pos) class ShortAndCover(Strategy): - def __init__(self, short_bar, cover_bar): + def __init__(self, short_bar, cover_bar, ticker='MICEX'): super().__init__() self.short_bar = short_bar self.cover_bar = cover_bar + self.ticker = ticker def execute(self): - pos = self.short_at_open(self.short_bar, 'MICEX') + pos = self.short_at_open(self.short_bar, self.ticker) self.exit_at_close(self.cover_bar, pos) class ShortAndCoverLimit(Strategy): - def __init__(self, short_bar, short_price, cover_bar, cover_price): + def __init__(self, short_bar, short_price, cover_bar, cover_price, ticker='MICEX'): super().__init__() self.short_bar = short_bar self.short_price = short_price self.cover_bar = cover_bar self.cover_price = cover_price + self.ticker = ticker def execute(self): - pos = self.short_at_limit(self.short_bar, self.short_price, 'MICEX') + pos = self.short_at_limit(self.short_bar, self.short_price, self.ticker) self.exit_at_limit(self.cover_bar, self.cover_price, pos) class ShortOpenAndCoverStop(Strategy): - def __init__(self, short_bar, cover_bar, cover_price): + def __init__(self, short_bar, cover_bar, cover_price, ticker='MICEX'): super().__init__() self.short_bar = short_bar self.cover_bar = cover_bar self.cover_price = cover_price + self.ticker = ticker def execute(self): - pos = self.short_at_open(self.short_bar, 'MICEX') + pos = self.short_at_open(self.short_bar, self.ticker) self.exit_at_stop(self.cover_bar, self.cover_price, pos) def test_buy_and_sell_1(feed): @@ -123,6 +129,17 @@ def test_buy_and_sell_3(feed): ending_cash = s.broker.cash() assert(ending_cash == (initial_cash + 1435.01 - 1444.78)) +def test_buy_and_sell_1_index(feed): + s = BuyAndSell(0, 0, 0) + s.add_feed(feed) + + initial_cash = s.broker.cash() + + s.run() + + ending_cash = s.broker.cash() + assert(ending_cash == (initial_cash + 1444.78 - 1411.37)) + def test_buy_and_sell_limit_1(feed): s = BuyAndSellLimit(0, 1412, 1, 1445) s.add_feed(feed) @@ -159,6 +176,18 @@ def test_buy_and_sell_limit_3(feed): assert(ending_cash == (initial_cash + 1417.80 - 1444.78)) assert(not s.last_position_is_active()) +def test_buy_and_sell_limit_1_index(feed): + s = BuyAndSellLimit(0, 1412, 1, 1445, 0) + s.add_feed(feed) + + initial_cash = s.broker.cash() + + s.run() + + ending_cash = s.broker.cash() + assert(ending_cash == (initial_cash + 1445 - 1411.37)) + assert(not s.last_position_is_active()) + def test_buy_and_sell_stop_1(feed): s = BuyOpenAndSellStop(0, 1, 1430) s.add_feed(feed) @@ -183,6 +212,18 @@ def test_buy_and_sell_stop_2(feed): assert(ending_cash == (initial_cash + 1444.78 - 1411.37)) assert(not s.last_position_is_active()) +def test_buy_and_sell_stop_1_index(feed): + s = BuyOpenAndSellStop(0, 1, 1430, 0) + s.add_feed(feed) + + initial_cash = s.broker.cash() + + s.run() + + ending_cash = s.broker.cash() + assert(ending_cash == (initial_cash + 1430 - 1411.37)) + assert(not s.last_position_is_active()) + def test_short_and_cover_1(feed): s = ShortAndCover(0, 0) s.add_feed(feed) @@ -205,6 +246,17 @@ def test_short_and_cover_2(feed): ending_cash = s.broker.cash() assert(ending_cash == (initial_cash + 1411.37 - 1427.67)) +def test_short_and_cover_1_index(feed): + s = ShortAndCover(0, 0, 0) + s.add_feed(feed) + + initial_cash = s.broker.cash() + + s.run() + + ending_cash = s.broker.cash() + assert(ending_cash == (initial_cash + 1411.37 - 1444.78)) + def test_short_and_cover_limit_1(feed): s = ShortAndCoverLimit(0, 1450, 1, 1430) s.add_feed(feed) @@ -244,6 +296,19 @@ def test_short_and_cover_limit_3(feed): assert(ending_cash == (initial_cash + 1430 - 1444.78)) assert(not s.last_position_is_active()) +def test_short_and_cover_limit_1_index(feed): + s = ShortAndCoverLimit(0, 1450, 1, 1430, 0) + s.add_feed(feed) + + initial_cash = s.broker.cash() + + s.run() + + ending_cash = s.broker.cash() + + assert(ending_cash == (initial_cash + 1450 - 1430)) + assert(not s.last_position_is_active()) + def test_short_and_cover_stop_1(feed): s = ShortOpenAndCoverStop(0, 1, 1445) s.add_feed(feed) @@ -268,3 +333,15 @@ def test_short_and_cover_stop_2(feed): assert(ending_cash == (initial_cash + 1411.37 - 1444.78)) assert(not s.last_position_is_active()) +def test_short_and_cover_stop_1_index(feed): + s = ShortOpenAndCoverStop(0, 1, 1445) + s.add_feed(feed) + + initial_cash = s.broker.cash() + + s.run() + + ending_cash = s.broker.cash() + assert(ending_cash == (initial_cash + 1411.37 - 1445)) + assert(not s.last_position_is_active()) +