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 @@ @@ -0,0 +1,2 @@

9
src/naiback/analyzers/analyzer.py

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

22
src/naiback/broker/position.py

@ -8,6 +8,7 @@ class Position: @@ -8,6 +8,7 @@ class Position:
self.exit_price_ = None
self.exit_metadata = {}
self.size_ = None
self.original_size_ = None
self.total_pnl = 0
def entry_price(self):
@ -19,8 +20,14 @@ class Position: @@ -19,8 +20,14 @@ class Position:
def size(self):
return self.size_
def original_size(self):
return self.orignal_size_
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):
return self.entry_metadata['commission']
@ -28,12 +35,25 @@ class Position: @@ -28,12 +35,25 @@ class Position:
def entry_bar(self):
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):
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):
self.entry_price_ = price
self.size_ = amount
self.original_size_ = amount
for k, v in kwargs.items():
self.entry_metadata[k] = v

5
src/naiback/strategy/strategy.py

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

9
tests/test_broker.py

@ -59,3 +59,12 @@ def test_broker_close_position_with_commission(): @@ -59,3 +59,12 @@ def test_broker_close_position_with_commission():
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