diff --git a/src/naiback/analyzers/statsanalyzer.py b/src/naiback/analyzers/statsanalyzer.py index 4bfde57..a2ff50e 100644 --- a/src/naiback/analyzers/statsanalyzer.py +++ b/src/naiback/analyzers/statsanalyzer.py @@ -16,6 +16,25 @@ def render_ratio(a, b): else: return 0 +def calculate_kelly(pos): + winning = list(filter(lambda x: x.pnl() >= 0, pos)) + losing = list(filter(lambda x: x.pnl() < 0, pos)) + + won = len(winning) + lost = len(losing) + + if won == 0: + return 0 + elif lost == 0: + return 1 + + avg_winning = sum(map(lambda x: x.pnl(), winning)) / won + avg_losing = sum(map(lambda x: x.pnl(), losing)) / lost + + p = float(won) / (won + lost) + + return p + (1 - p)/(avg_winning / avg_losing) + class StatsAnalyzer(Analyzer): def __init__(self, strategy): @@ -57,6 +76,10 @@ class StatsAnalyzer(Analyzer): result['long']['net_profit'] = sum([pos.pnl() for pos in longs]) result['short']['net_profit'] = sum([pos.pnl() for pos in shorts]) + result['all']['total_commission'] = sum([pos.total_commission() for pos in positions]) + result['long']['total_commission'] = sum([pos.total_commission() for pos in longs]) + result['short']['total_commission'] = sum([pos.total_commission() 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]) @@ -103,24 +126,27 @@ class StatsAnalyzer(Analyzer): mean = np.mean(list(map(lambda x: x.pnl(), positions))) stddev = np.std(list(map(lambda x: x.pnl(), positions))) - sharpe = mean / stddev - tstat = sharpe * math.sqrt(len(positions)) - result['all']['sharpe_ratio'] = sharpe + z_score = mean / stddev + tstat = z_score * math.sqrt(len(positions)) + result['all']['z_score'] = z_score result['all']['t_stat'] = tstat + result['all']['kelly'] = calculate_kelly(positions) mean = np.mean(list(map(lambda x: x.pnl(), longs))) stddev = np.std(list(map(lambda x: x.pnl(), longs))) - sharpe = mean / stddev - tstat = sharpe * math.sqrt(len(longs)) - result['long']['sharpe_ratio'] = sharpe + z_score = mean / stddev + tstat = z_score * math.sqrt(len(longs)) + result['long']['z_score'] = z_score result['long']['t_stat'] = tstat + result['long']['kelly'] = calculate_kelly(longs) mean = np.mean(list(map(lambda x: x.pnl(), shorts))) stddev = np.std(list(map(lambda x: x.pnl(), shorts))) - sharpe = mean / stddev - tstat = sharpe * math.sqrt(len(shorts)) - result['short']['sharpe_ratio'] = sharpe + z_score = mean / stddev + tstat = z_score * math.sqrt(len(shorts)) + result['short']['z_score'] = z_score result['short']['t_stat'] = tstat + result['short']['kelly'] = calculate_kelly(shorts) return result diff --git a/src/naiback/broker/broker.py b/src/naiback/broker/broker.py index 652b905..71c8a1b 100644 --- a/src/naiback/broker/broker.py +++ b/src/naiback/broker/broker.py @@ -15,6 +15,7 @@ class Broker: self.positions = [] self.retired_positions_ = [] self.commission_percentage = 0 + self.commission_fixed = 0 self.timestamp = None def cash(self): @@ -28,27 +29,30 @@ class Broker: #if amount > 0: # if volume * (1 + 0.01 * self.commission_percentage) > self.cash_: # return None + commission = volume * 0.01 * self.commission_percentage + abs(amount) * self.commission_fixed pos = Position(ticker) - pos.enter(price, amount, bar=bar_index, timestamp=self.timestamp) + pos.enter(price, amount, bar=bar_index, timestamp=self.timestamp, commission=commission) self.cash_ -= price * amount - self.cash_ -= volume * 0.01 * self.commission_percentage + self.cash_ -= commission self.positions.append(pos) return pos def close_position(self, pos, price, bar_index): volume = abs(price * pos.size()) size = pos.size() - pos.exit(price, bar=bar_index, timestamp=self.timestamp) + commission = volume * 0.01 * self.commission_percentage + abs(size) * self.commission_fixed + pos.exit(price, bar=bar_index, timestamp=self.timestamp, commission=commission) self.retired_positions_.append(pos) self.positions.remove(pos) self.cash_ += price * size - self.cash_ -= volume * 0.01 * self.commission_percentage + self.cash_ -= commission return True - def set_commission(self, percentage): + def set_commission(self, percentage, fixed): self.commission_percentage = percentage + self.commission_fixed = fixed def last_position(self): return self.positions[-1] diff --git a/src/naiback/broker/position.py b/src/naiback/broker/position.py index 401446f..daf78b5 100644 --- a/src/naiback/broker/position.py +++ b/src/naiback/broker/position.py @@ -30,7 +30,10 @@ class Position: return self.original_size_ < 0 def entry_commission(self): - return self.entry_metadata['commission'] + try: + return self.entry_metadata['commission'] + except KeyError: + return 0 def entry_bar(self): return self.entry_metadata['bar'] @@ -44,17 +47,35 @@ class Position: def exit_time(self): return self.exit_metadata['timestamp'] + def exit_commission(self): + try: + return self.exit_metadata['commission'] + except KeyError: + return 0 + def bars_in_trade(self): return self.exit_bar() - self.entry_bar() + def total_commission(self): + commission = 0 + try: + commission += self.entry_commission() + except KeyError: + pass + try: + commission += self.exit_commission() + except KeyError: + pass + return commission + def pnl(self): - return self.total_pnl + return self.total_pnl - self.total_commission() def profit_percentage(self): if self.is_long(): - return (self.exit_price() / self.entry_price() - 1) * 100. + return (self.exit_price() / self.entry_price() - 1 - (self.total_commission()) / self.entry_price()) * 100. else: - return -(self.exit_price() / self.entry_price() - 1) * 100. + return (1 - self.exit_price() / self.entry_price() - (self.total_commission()) / self.entry_price()) * 100. def enter(self, price, amount, **kwargs): self.entry_price_ = price @@ -66,7 +87,7 @@ class Position: def exit(self, price, **kwargs): self.exit_price_ = price - self.total_pnl += (self.exit_price() - self.entry_price()) * self.size() + self.total_pnl += (self.exit_price() - self.entry_price()) * self.size() - self.total_commission() self.size_ = 0 for k, v in kwargs.items(): diff --git a/src/naiback/data/feeds/yahoofeed.py b/src/naiback/data/feeds/yahoofeed.py new file mode 100644 index 0000000..58cef09 --- /dev/null +++ b/src/naiback/data/feeds/yahoofeed.py @@ -0,0 +1,35 @@ + +from naiback.data.feed import Feed, Bar +import csv +import datetime +import itertools + +class YahooCSVFeed(Feed): + + def __init__(self, fp): + self.bars = [] + self.ticker_ = None + reader = csv.reader(fp, delimiter=',') + next(reader) + for row in reader: + try: + self.ticker_ = row[0] + open_ = float(row[1]) + high = float(row[2]) + low = float(row[3]) + close = float(row[4]) + volume = int(row[6]) + date = row[0] + dt = datetime.datetime.strptime(date, "%Y-%m-%d") + self.bars.append(Bar(open_, high, low, close, volume, dt)) + except IndexError: + pass + + def type(self): + return 'bars' + + def items(self): + return self.bars + + def ticker(self): + return self.ticker_ diff --git a/src/naiback/indicators/__init__.py b/src/naiback/indicators/__init__.py index 6f02e63..51600a5 100644 --- a/src/naiback/indicators/__init__.py +++ b/src/naiback/indicators/__init__.py @@ -5,5 +5,7 @@ from .rsi import RSI from .intradaybarnumber import IntradayBarNumber from .highest import Highest,HighestValue from .lowest import Lowest,LowestValue -from .atr import ATR +from .atr import ATR, ATR_minus, ATR_plus from .bollinger import BollingerBands +from .adx import ADX +from .smma import SMMA diff --git a/src/naiback/indicators/adx.py b/src/naiback/indicators/adx.py new file mode 100644 index 0000000..ce54068 --- /dev/null +++ b/src/naiback/indicators/adx.py @@ -0,0 +1,33 @@ + +import numpy as np + +from .atr import ATR +from .ema import EMA +from .smma import SMMA + +def ADX(bars, period): + dm_minus = np.zeros(len(bars.close)) + dm_plus = np.zeros(len(bars.close)) + adx = np.zeros(len(bars.close)) + atr = ATR(bars, period) + + if len(bars.close) == 0: + return np.array([]) + + for i in range(1, len(bars.close)): + plus = bars.high[i] - bars.high[i - 1] + minus = -bars.low[i] + bars.low[i - 1] + + if plus > 0 and plus > minus: + dm_plus[i] = plus + + if minus > 0 and minus > plus: + dm_minus[i] = minus + + di_plus = np.asarray(SMMA(dm_plus, period)) * 100 / atr + di_minus = np.asarray(SMMA(dm_minus, period)) * 100 / atr + + preprocessed_adx = np.abs(np.nan_to_num(np.divide(di_plus - di_minus, di_plus + di_minus))) + adx = 100 * np.asarray(SMMA(preprocessed_adx, period)) + + return (adx, di_plus, di_minus) diff --git a/src/naiback/indicators/atr.py b/src/naiback/indicators/atr.py index e99c816..c47aee7 100644 --- a/src/naiback/indicators/atr.py +++ b/src/naiback/indicators/atr.py @@ -18,3 +18,27 @@ def ATR(bars, period): for i in range(period, len(bars.close)): atr[i] = (atr[i - 1] * (period - 1) + tr[i]) / period return atr + +def ATR_filtered(bars, period, f): + tr = np.zeros(len(bars.close)) + + if len(bars.close) == 0: + return np.array([]) + + tr[0] = bars.high[0] - bars.low[0] + for i in range(1, len(bars.close)): + tr[i] = max(bars.high[i] - bars.low[i], abs(bars.high[i] - bars.close[i - 1]), abs(bars.low[i] - bars.close[i - 1])) + + atr = np.zeros(len(bars.close)) + if len(bars.close) <= period: + return atr + atr[period - 1] = sum(filter(f, tr[0:period])) / period + for i in range(period, len(bars.close)): + atr[i] = (atr[i - 1] * (period - 1) + tr[i]) / period + return atr + +def ATR_plus(bars, period): + return ATR_filtered(bars, period, lambda x : x > 0) + +def ATR_minus(bars, period): + return ATR_filtered(bars, period, lambda x : x < 0) diff --git a/src/naiback/indicators/bollinger.py b/src/naiback/indicators/bollinger.py index 8657c10..c6d646c 100644 --- a/src/naiback/indicators/bollinger.py +++ b/src/naiback/indicators/bollinger.py @@ -9,7 +9,8 @@ def BollingerBands(values, period, stddevs): ma = SMA(values, period) diffs = ma - np.array(values) for i in range(period, len(values)): - sigma = np.std(diffs[i-period+1:i+1]) + #sigma = np.std(diffs[i-period+1:i+1]) + sigma = np.std(values[i-period+1:i+1]) lower[i] = ma[i] - stddevs * sigma higher[i] = ma[i] + stddevs * sigma diff --git a/src/naiback/indicators/ema.py b/src/naiback/indicators/ema.py index 691b36f..6abdbe0 100644 --- a/src/naiback/indicators/ema.py +++ b/src/naiback/indicators/ema.py @@ -6,7 +6,7 @@ def EMA(data, period, alpha=None): if alpha is None: alpha = 2. / (period + 1) result = [] - v = None + v = 0 for d in data: if d is None: result.append(None) diff --git a/src/naiback/indicators/smma.py b/src/naiback/indicators/smma.py new file mode 100644 index 0000000..0ae75bf --- /dev/null +++ b/src/naiback/indicators/smma.py @@ -0,0 +1,13 @@ + +import numpy as np + +def SMMA(values, period): + smma = np.zeros(len(values)) + alpha = (period - 1) / period + + smma[0] = values[0] + for i in range(1, len(values)): + smma[i] = smma[i - 1] * alpha + values[i] * (1 - alpha) + + return smma +