Browse Source

Basic analyzer

master
Denis Tereshkin 8 years ago
parent
commit
a1c35f034e
  1. 2
      src/naiback/analyzers/__init__.py
  2. 9
      src/naiback/analyzers/analyzer.py
  3. 96
      src/naiback/analyzers/statsanalyzer.py
  4. 9
      src/naiback/broker/broker.py
  5. 22
      src/naiback/broker/position.py
  6. 5
      src/naiback/strategy/strategy.py
  7. 9
      tests/test_broker.py

2
src/naiback/analyzers/__init__.py

@ -0,0 +1,2 @@

9
src/naiback/analyzers/analyzer.py

@ -0,0 +1,9 @@
class Analyzer:
def __init__(self):
pass
def generate_plain_text(self):
pass

96
src/naiback/analyzers/statsanalyzer.py

@ -0,0 +1,96 @@
from .analyzer import Analyzer
from prettytable import PrettyTable
def render_float(a):
return "{:.3f}".format(a)
def render_ratio(a, b):
if b != 0:
return a / b
else:
return ""
class StatsAnalyzer(Analyzer):
def __init__(self, strategy):
self.strategy = strategy
def generate_plain_text(self):
positions = self.strategy.broker.retired_positions() # TODO also add open positions
stats = self.calc_stats(positions)
table = PrettyTable()
table.field_names = ["", "All positions", "Long only", "Short only"]
table.add_row(["Net profit", render_float(stats['all']['net_profit']), render_float(stats['long']['net_profit']), render_float(stats['short']['net_profit'])])
table.add_row(["Bars in trade", stats['all']['bars_in_trade'], stats['long']['bars_in_trade'], stats['short']['bars_in_trade']])
table.add_row(["Profit per bar", render_float(stats['all']['profit_per_bar']), render_float(stats['long']['profit_per_bar']), render_float(stats['short']['profit_per_bar'])])
table.add_row(["Number of trades", stats['all']['number_of_trades'], stats['long']['number_of_trades'], stats['short']['number_of_trades']])
table.add_row(["Avg. profit", render_float(stats['all']['avg']), render_float(stats['long']['avg']), render_float(stats['short']['avg'])])
table.add_row(["Avg. profit, %", render_float(stats['all']['avg_percentage']), render_float(stats['long']['avg_percentage']), render_float(stats['short']['avg_percentage'])])
table.add_row(["Avg. bars in trade", render_float(stats['all']['avg_bars']), render_float(stats['long']['avg_bars']), render_float(stats['short']['avg_bars'])])
table.add_row(["Winning trades", stats['all']['won'], stats['long']['won'], stats['short']['won']])
table.add_row(["Gross profit", render_float(stats['all']['total_won']), render_float(stats['long']['total_won']), render_float(stats['short']['total_won'])])
table.add_row(["Losing trades", stats['all']['lost'], stats['long']['lost'], stats['short']['lost']])
table.add_row(["Gross loss", render_float(stats['all']['total_lost']), render_float(stats['long']['total_lost']), render_float(stats['short']['total_lost'])])
table.add_row(["Profit factor", render_float(stats['all']['profit_factor']), render_float(stats['long']['profit_factor']), render_float(stats['short']['profit_factor'])])
return table.get_string()
def calc_stats(self, positions):
longs = list(filter(lambda x: x.is_long(), positions))
shorts = list(filter(lambda x: x.is_short(), positions))
result = { 'all' : {}, 'long' : {}, 'short' : {} }
result['all']['net_profit'] = sum([pos.pnl() for pos in positions])
result['long']['net_profit'] = sum([pos.pnl() for pos in longs])
result['short']['net_profit'] = sum([pos.pnl() for pos in shorts])
result['all']['bars_in_trade'] = sum([pos.bars_in_trade() for pos in positions])
result['long']['bars_in_trade'] = sum([pos.bars_in_trade() for pos in longs])
result['short']['bars_in_trade'] = sum([pos.bars_in_trade() for pos in shorts])
result['all']['profit_per_bar'] = render_ratio(result['all']['net_profit'], result['all']['bars_in_trade'])
result['long']['profit_per_bar'] = render_ratio(result['long']['net_profit'], result['long']['bars_in_trade'])
result['short']['profit_per_bar'] = render_ratio(result['short']['net_profit'], result['short']['bars_in_trade'])
result['all']['number_of_trades'] = len(positions)
result['long']['number_of_trades'] = len(list(longs))
result['short']['number_of_trades'] = len(list(shorts))
result['all']['avg'] = render_ratio(result['all']['net_profit'], result['all']['number_of_trades'])
result['long']['avg'] = render_ratio(result['long']['net_profit'], result['long']['number_of_trades'])
result['short']['avg'] = render_ratio(result['short']['net_profit'], result['short']['number_of_trades'])
result['all']['avg_percentage'] = render_ratio(sum([pos.profit_percentage() for pos in positions]), result['all']['number_of_trades'])
result['long']['avg_percentage'] = render_ratio(sum([pos.profit_percentage() for pos in longs]), result['long']['number_of_trades'])
result['short']['avg_percentage'] = render_ratio(sum([pos.profit_percentage() for pos in shorts]), result['short']['number_of_trades'])
result['all']['avg_bars'] = render_ratio(result['all']['bars_in_trade'], result['all']['number_of_trades'])
result['long']['avg_bars'] = render_ratio(result['long']['bars_in_trade'], result['long']['number_of_trades'])
result['short']['avg_bars'] = render_ratio(result['short']['bars_in_trade'], result['short']['number_of_trades'])
result['all']['won'] = len(list(filter(lambda pos: pos.pnl() > 0, positions)))
result['long']['won'] = len(list(filter(lambda pos: pos.pnl() > 0, longs)))
result['short']['won'] = len(list(filter(lambda pos: pos.pnl() > 0, shorts)))
result['all']['lost'] = len(list(filter(lambda pos: pos.pnl() <= 0, positions)))
result['long']['lost'] = len(list(filter(lambda pos: pos.pnl() <= 0, longs)))
result['short']['lost'] = len(list(filter(lambda pos: pos.pnl() <= 0, shorts)))
result['all']['total_won'] = sum(map(lambda pos: pos.pnl(), filter(lambda pos: pos.pnl() > 0, positions)))
result['long']['total_won'] = sum(map(lambda pos: pos.pnl(), filter(lambda pos: pos.pnl() > 0, longs)))
result['short']['total_won'] = sum(map(lambda pos: pos.pnl(), filter(lambda pos: pos.pnl() > 0, shorts)))
result['all']['total_lost'] = sum(map(lambda pos: pos.pnl(), filter(lambda pos: pos.pnl() <= 0, positions)))
result['long']['total_lost'] = sum(map(lambda pos: pos.pnl(), filter(lambda pos: pos.pnl() <= 0, longs)))
result['short']['total_lost'] = sum(map(lambda pos: pos.pnl(), filter(lambda pos: pos.pnl() <= 0, shorts)))
result['all']['profit_factor'] = render_ratio(result['all']['total_won'], -result['all']['total_lost'])
result['long']['profit_factor'] = render_ratio(result['long']['total_won'], -result['long']['total_lost'])
result['short']['profit_factor'] = render_ratio(result['short']['total_won'], -result['short']['total_lost'])
return result

9
src/naiback/broker/broker.py

@ -13,6 +13,7 @@ class Broker:
def __init__(self, initial_cash=100000.): def __init__(self, initial_cash=100000.):
self.cash_ = initial_cash self.cash_ = initial_cash
self.positions = [] self.positions = []
self.retired_positions_ = []
self.commission_percentage = 0 self.commission_percentage = 0
def cash(self): def cash(self):
@ -35,6 +36,9 @@ class Broker:
size = pos.size() size = pos.size()
pos.exit(price, bar=bar_index) pos.exit(price, bar=bar_index)
self.retired_positions_.append(pos)
self.positions.remove(pos)
self.cash_ += price * size self.cash_ += price * size
self.cash_ -= volume * 0.01 * self.commission_percentage self.cash_ -= volume * 0.01 * self.commission_percentage
return True return True
@ -46,7 +50,10 @@ class Broker:
return self.positions[-1] return self.positions[-1]
def all_positions(self): def all_positions(self):
return self.positions return self.positions[:]
def retired_positions(self):
return self.retired_positions_
def last_position_is_active(self): def last_position_is_active(self):
if len(self.positions) == 0: if len(self.positions) == 0:

22
src/naiback/broker/position.py

@ -8,6 +8,7 @@ class Position:
self.exit_price_ = None self.exit_price_ = None
self.exit_metadata = {} self.exit_metadata = {}
self.size_ = None self.size_ = None
self.original_size_ = None
self.total_pnl = 0 self.total_pnl = 0
def entry_price(self): def entry_price(self):
@ -19,8 +20,14 @@ class Position:
def size(self): def size(self):
return self.size_ return self.size_
def original_size(self):
return self.orignal_size_
def is_long(self): def is_long(self):
return self.size_ > 0 return self.original_size_ > 0
def is_short(self):
return self.original_size_ < 0
def entry_commission(self): def entry_commission(self):
return self.entry_metadata['commission'] return self.entry_metadata['commission']
@ -28,12 +35,25 @@ class Position:
def entry_bar(self): def entry_bar(self):
return self.entry_metadata['bar'] return self.entry_metadata['bar']
def exit_bar(self):
return self.exit_metadata['bar']
def bars_in_trade(self):
return self.exit_bar() - self.entry_bar()
def pnl(self): def pnl(self):
return self.total_pnl return self.total_pnl
def profit_percentage(self):
if self.is_long():
return (self.exit_price() / self.entry_price() - 1) * 100.
else:
return -(self.exit_price() / self.entry_price() - 1) * 100.
def enter(self, price, amount, **kwargs): def enter(self, price, amount, **kwargs):
self.entry_price_ = price self.entry_price_ = price
self.size_ = amount self.size_ = amount
self.original_size_ = amount
for k, v in kwargs.items(): for k, v in kwargs.items():
self.entry_metadata[k] = v self.entry_metadata[k] = v

5
src/naiback/strategy/strategy.py

@ -2,6 +2,7 @@ from abc import abstractmethod
from naiback.broker.position import Position from naiback.broker.position import Position
from naiback.broker.broker import Broker from naiback.broker.broker import Broker
from naiback.data.bars import Bars from naiback.data.bars import Bars
from naiback.analyzers.statsanalyzer import StatsAnalyzer
class Strategy: class Strategy:
""" """
@ -12,6 +13,10 @@ class Strategy:
self.all_bars = [] self.all_bars = []
self.broker = Broker() self.broker = Broker()
self.bars = None self.bars = None
self.analyzer = StatsAnalyzer(self)
def get_analyzer(self, analyzer_id):
return self.analyzer
def add_feed(self, feed): def add_feed(self, feed):
""" """

9
tests/test_broker.py

@ -59,3 +59,12 @@ def test_broker_close_position_with_commission():
assert broker.cash() == 100 + 2 - (10 + 12) * 0.01 assert broker.cash() == 100 + 2 - (10 + 12) * 0.01
def test_broker_close_position_places_it_in_retired_list():
broker = Broker(initial_cash=100)
pos = broker.add_position('FOO', price=10, amount=1, bar_index=0)
broker.close_position(pos, price=12, bar_index=1)
assert pos not in broker.all_positions()
assert pos in broker.retired_positions()

Loading…
Cancel
Save