def sortino_ratio(algorithm_returns, algorithm_period_return, mar): """ http://en.wikipedia.org/wiki/Sortino_ratio Args: algorithm_returns (np.array-like): Returns from algorithm lifetime. algorithm_period_return (float): Algorithm return percentage from latest period. mar (float): Minimum acceptable return. Returns: float. The Sortino ratio. """ if len(algorithm_returns) == 0: return 0.0 rets = algorithm_returns downside = (rets[rets < mar] - mar)**2 dr = np.sqrt(downside.sum() / len(rets)) if zp_math.tolerant_equals(dr, 0): return 0.0 return (algorithm_period_return - mar) / dr
def sharpe_ratio(algorithm_volatility, annualized_return, treasury_return): """ http://en.wikipedia.org/wiki/Sharpe_ratio Args: algorithm_volatility (float): Algorithm volatility. algorithm_return (float): Algorithm return percentage. treasury_return (float): Treasury return percentage. Returns: float. The Sharpe ratio. """ if zp_math.tolerant_equals(algorithm_volatility, 0): return np.nan return ( (annualized_return - treasury_return) # The square of the annualization factor is in the volatility, # because the volatility is also annualized, # i.e. the sqrt(annual factor) is in the volatility's numerator. # So to have the the correct annualization factor for the # Sharpe value's numerator, which should be the sqrt(annual factor). # The square of the sqrt of the annual factor, i.e. the annual factor # itself, is needed in the numerator to factor out the division by # its square root. / algorithm_volatility)
def information_ratio(algo_volatility, algorithm_return, benchmark_return): """ http://en.wikipedia.org/wiki/Information_ratio Args: algorithm_returns (np.array-like): All returns during algorithm lifetime. benchmark_returns (np.array-like): All benchmark returns during algo lifetime. Returns: float. Information ratio. """ if zp_math.tolerant_equals(algo_volatility, 0): return np.nan return ( (algorithm_return - benchmark_return) # The square of the annualization factor is in the volatility, # because the volatility is also annualized, # i.e. the sqrt(annual factor) is in the volatility's numerator. # So to have the the correct annualization factor for the # Sharpe value's numerator, which should be the sqrt(annual factor). # The square of the sqrt of the annual factor, i.e. the annual factor # itself, is needed in the numerator to factor out the division by # its square root. / algo_volatility)
def process_trade(self, trade_event): if trade_event.type != zp.DATASOURCE_TYPE.TRADE: return if zp_math.tolerant_equals(trade_event.volume, 0): # there are zero volume trade_events bc some stocks trade # less frequently than once per minute. return if 'contract' in trade_event.__dict__: sid = (trade_event.sid, trade_event.contract) else: sid = trade_event.sid if sid in self.open_orders: orders = self.open_orders[sid] orders = sorted(orders, key=lambda o: o.dt) # Only use orders for the current day or before current_orders = filter(lambda o: o.dt <= trade_event.dt, orders) else: return for order, txn in self.transact(trade_event, current_orders): if txn.type == zp.DATASOURCE_TYPE.COMMISSION: yield txn, order continue if 'contract' in order.__dict__: self.futures_handle_leverage(txn, order) else: self.handle_leverage(txn, order) transaction_cost = txn.amount * txn.price self.portfolio.cash -= transaction_cost self.portfolio.positions_value += transaction_cost self.portfolio.portfolio_value = self.portfolio.cash + self.portfolio.positions_value # if txn.amount == 0: # raise alephnull.errors.TransactionWithNoAmount(txn=txn) if math.copysign(1, txn.amount) != order.direction: raise alephnull.errors.TransactionWithWrongDirection( txn=txn, order=order) if abs(txn.amount) > abs(self.orders[txn.order_id].amount): raise alephnull.errors.TransactionVolumeExceedsOrder( txn=txn, order=order) order.filled += txn.amount # mark the date of the order to match the transaction # that is filling it. order.dt = txn.dt yield txn, order # update the open orders for the trade_event's sid self.open_orders[sid] = \ [order for order in self.open_orders[sid] if order.open]
def information_ratio(algorithm_returns, benchmark_returns): """ http://en.wikipedia.org/wiki/Information_ratio Args: algorithm_returns (np.array-like): All returns during algorithm lifetime. benchmark_returns (np.array-like): All benchmark returns during algo lifetime. Returns: float. Information ratio. """ relative_returns = algorithm_returns - benchmark_returns relative_deviation = relative_returns.std(ddof=1) if ( zp_math.tolerant_equals(relative_deviation, 0) or np.isnan(relative_deviation) ): return 0.0 return np.mean(relative_returns) / relative_deviation
def sortino_ratio(algorithm_returns, algorithm_period_return, mar): """ http://en.wikipedia.org/wiki/Sortino_ratio Args: algorithm_returns (np.array-like): Returns from algorithm lifetime. algorithm_period_return (float): Algorithm return percentage from latest period. mar (float): Minimum acceptable return. Returns: float. The Sortino ratio. """ if len(algorithm_returns) == 0: return 0.0 rets = algorithm_returns downside = (rets[rets < mar] - mar) ** 2 dr = np.sqrt(downside.sum() / len(rets)) if zp_math.tolerant_equals(dr, 0): return 0.0 return (algorithm_period_return - mar) / dr
def transact_stub(slippage, commission, event, open_orders): """ This is intended to be wrapped in a partial, so that the slippage and commission models can be enclosed. """ for order, transaction in slippage(event, open_orders): if (transaction and not zp_math.tolerant_equals(transaction.amount, 0)): direction = math.copysign(1, transaction.amount) per_share, total_commission = commission.calculate(transaction) transaction.price = transaction.price + (per_share * direction) transaction.commission = total_commission yield order, transaction
def get_stddev(self): # Sample standard deviation is undefined for a single event or # no events. if len(self) <= 1: return None else: average = self.sum / len(self) s_squared = (self.sum_sqr - self.sum * average) \ / (len(self) - 1) if zp_math.tolerant_equals(0, s_squared): return 0.0 stddev = sqrt(s_squared) return stddev
def sharpe_ratio(algorithm_volatility, algorithm_return, treasury_return): """ http://en.wikipedia.org/wiki/Sharpe_ratio Args: algorithm_volatility (float): Algorithm volatility. algorithm_return (float): Algorithm return percentage. treasury_return (float): Treasury return percentage. Returns: float. The Sharpe ratio. """ if zp_math.tolerant_equals(algorithm_volatility, 0): return 0.0 return (algorithm_return - treasury_return) / algorithm_volatility
def transact_stub(slippage, commission, event, open_orders): """ This is intended to be wrapped in a partial, so that the slippage and commission models can be enclosed. """ for order, transaction in slippage(event, open_orders): if ( transaction and not zp_math.tolerant_equals(transaction.amount, 0) ): direction = math.copysign(1, transaction.amount) per_share, total_commission = commission.calculate(transaction) transaction.price = transaction.price + (per_share * direction) transaction.commission = total_commission yield order, transaction
def sortino_ratio(annualized_algorithm_return, treasury_return, downside_risk): """ http://en.wikipedia.org/wiki/Sortino_ratio Args: algorithm_returns (np.array-like): Returns from algorithm lifetime. algorithm_period_return (float): Algorithm return percentage from latest period. mar (float): Minimum acceptable return. Returns: float. The Sortino ratio. """ if np.isnan(downside_risk) or zp_math.tolerant_equals(downside_risk, 0): return 0.0 return (annualized_algorithm_return - treasury_return) / downside_risk
def simulate(self, event, current_orders): self._volume_for_bar = 0 for order in current_orders: open_amount = order.amount - order.filled if zp_math.tolerant_equals(open_amount, 0): continue order.check_triggers(event) if not order.triggered: continue txn = self.process_order(event, order) if txn: self._volume_for_bar += abs(txn.amount) yield order, txn
def information_ratio(algorithm_returns, benchmark_returns): """ http://en.wikipedia.org/wiki/Information_ratio Args: algorithm_returns (np.array-like): All returns during algorithm lifetime. benchmark_returns (np.array-like): All benchmark returns during algo lifetime. Returns: float. Information ratio. """ relative_returns = algorithm_returns - benchmark_returns relative_deviation = relative_returns.std(ddof=1) if (zp_math.tolerant_equals(relative_deviation, 0) or np.isnan(relative_deviation)): return 0.0 return np.mean(relative_returns) / relative_deviation
def process_trade(self, trade_event): if trade_event.type != zp.DATASOURCE_TYPE.TRADE: return if zp_math.tolerant_equals(trade_event.volume, 0): # there are zero volume trade_events bc some stocks trade # less frequently than once per minute. return if 'contract' in trade_event.__dict__: sid = (trade_event.sid, trade_event.contract) else: sid = trade_event.sid if sid in self.open_orders: orders = self.open_orders[sid] orders = sorted(orders, key=lambda o: o.dt) # Only use orders for the current day or before current_orders = filter( lambda o: o.dt <= trade_event.dt, orders) else: return for order, txn in self.transact(trade_event, current_orders): if txn.type == zp.DATASOURCE_TYPE.COMMISSION: yield txn, order continue if 'contract' in order.__dict__: self.futures_handle_leverage(txn, order) else: self.handle_leverage(txn, order) transaction_cost = txn.amount * txn.price self.portfolio.cash -= transaction_cost self.portfolio.positions_value += transaction_cost self.portfolio.portfolio_value = self.portfolio.cash + self.portfolio.positions_value # if txn.amount == 0: # raise alephnull.errors.TransactionWithNoAmount(txn=txn) if math.copysign(1, txn.amount) != order.direction: raise alephnull.errors.TransactionWithWrongDirection( txn=txn, order=order) if abs(txn.amount) > abs(self.orders[txn.order_id].amount): raise alephnull.errors.TransactionVolumeExceedsOrder( txn=txn, order=order) order.filled += txn.amount # mark the date of the order to match the transaction # that is filling it. order.dt = txn.dt yield txn, order # update the open orders for the trade_event's sid self.open_orders[sid] = \ [order for order in self.open_orders[sid] if order.open]
def round_for_minimum_price_variation(x, is_buy, diff=(0.0095 - .005)): # relies on rounding half away from zero, unlike numpy's bankers' rounding rounded = round(x - (diff if is_buy else -diff), 2) if zp_math.tolerant_equals(rounded, 0.0): return 0.0 return rounded