Not only am I new to Python on Quantopian, this is a first post.
Below is a simple algorithm / trading system based on RSI2. It runs on a series of symbols / sids.
My questions are as follows:
1. I'd like to port this algorithm to run in research but have struggled doing so for several days.
2. In research, the list of symbols hard-coded in the algorithm are sitting in a flat-file in data (though it keeps disappearing :) ). Is there a simple way to read a universe of symbols / sids from a flat file maintained outside of quantopian?
3. For my uses, the Quantopian Position object is missing some essential data (eg: Date position was established for example, Tags to log other data to store when the position is entered, among others). I've tried extending the Position object by adding my own position object in context, Context.Positions -- which I tried to create as a pandas dataframe. The code is commented out in the attached code; it compiles but generates a runtime error (TypeError: 'DataFrame' objects are mutable, thus they cannot be hashed).
Any help with the above items would be greatly appreciated.
Thanks
# This is meant to be a toy algorithm to learn quantopian. Changes I'm trying to learn to make are # 1) not hard-coding the sids but instead reading them from a flat-file. This sid list was produced by running a program in RESEARCH that reads a flat file of symbols, converts them to sids and then outputs this list to be pasted into the code.
# 2) I want to convert this simple algorithm into a backtest that I can run in RESEARCH; I spent several days trying to understand how to do that directly and could not figure out how.
import pandas as pd
import numpy as np
#import matplotlib.pyplot as plt
import talib
from zipline.api import get_environment
def initialize(context):
schedule_function(entry_exit_rules, date_rules.every_day(), time_rules.market_close())
set_commission(commission.PerShare(cost=0.005, min_trade_cost=1.00)) #IB commissions
set_slippage(slippage.FixedSlippage(spread=0.001)) #NOT USING SLIPPAGE RIGHT NOW
set_benchmark(symbol('SPY'))
context.Mode = "SSB" # Allowed == SSB: Single Symbol Backtest or MSB: Multi Symbol Backtest
# IF SSB, record single name dataseries if MSB only portfolio stats
context.ssid = [sid(19920)]
context.sids = [sid(25485), sid(41013), sid(25801), sid(32406), sid(35793), sid(23881)] #sid(24744), sid(32888), sid(21518), sid(42041), sid(21517), sid(45555), sid(21519), sid(41382), sid(44849), sid(26981), sid(39941), sid(33652), sid(32505), sid(33127), sid(35071), sid(28054), sid(34385), sid(19920), sid(46302), sid(35175), sid(34831), sid(35323), sid(42039), sid(24705), sid(27100), sid(41603), sid(14520), sid(25098), sid(14519), sid(8554), sid(14517), sid(23911), sid(14516), sid(14518), sid(27536), sid(21757), sid(21491), sid(22972), sid(14526), sid(14530), sid(14529), sid(21619), sid(41178), sid(40107), sid(21513), sid(27102), sid(21785), sid(23134), sid(25910), sid(34022), sid(26807), sid(34648), sid(21512), sid(33651), sid(38901), sid(12915), sid(38985), sid(33486), sid(23921), sid(35970), sid(41553), sid(43548), sid(38987), sid(33697), sid(35971), sid(26432), sid(23870), sid(43549), sid(33655), sid(28320), sid(42633), sid(2174), sid(46300), sid(33541), sid(22739), sid(33650), sid(42413), sid(35072), sid(28368), sid(39116), sid(25909), sid(26703), sid(42524), sid(38297), sid(48049), sid(21508), sid(32620), sid(21758), sid(33748), sid(34164), sid(21507), sid(39080)]
if context.Mode == "SSB":
context.sids = context.ssid
context.RsiPeriod = 2
context.OB = 100 #Overbought threshold
context.OS = 5 #Oversold threshold
context.ExitRSI = 50 #RSI Level to exit positions
context.MaxHoldDays = 5
context.TradeAmount = 100000.0
context.longs = 0
context.shorts = 0
context.netposn = 0
context.WinningTrades = 0
context.LosingTrades = 0
context.WinningLongTrades = 0
context.LosingLongTrades = 0
context.WinningShortTrades = 0
context.LosingShortTrades = 0
context.EndDate = get_environment('end').date()
ids = []
for s in context.sids:
ids.append(s.sid)
context.Positions = pd.DataFrame(index=ids, columns=['ActivePosition','EnterDate','EnterPrice', 'Amount', 'Shares', 'ExitDate','ExitPrice', 'PL'])
log.info(context.Positions.to_string())
#def handle_data(context, data):
#not implemented since nothing happens on a minute basis
#def before_trading_start(context, data):
#not implemented since nothing needs to happen before the market opens each day
def calculate_trade_stats(context, cur_sid):
cur_posn = context.portfolio.positions[cur_sid]
if cur_posn.amount > 0: # Long Posn
if cur_posn.last_sale_price >= cur_posn.cost_basis:
context.WinningTrades += 1
context.WinningLongTrades += 1
else:
context.LosingTrades += 1
context.LosingLongTrades += 1
else: # Short Position
if cur_posn.last_sale_price <= cur_posn.cost_basis:
context.WinningTrades += 1
context.WinningShortTrades += 1
else:
context.LosingTrades += 1
context.LosingShortTrades += 1
def execute_order(cur_sid, OrderType, Amount, CalcTradeStats, context):
cur_posn = context.portfolio.positions[cur_sid].amount
s = cur_sid.sid
if cur_posn <> 0:
ExistingPosition = True
cur_price = context.portfolio.positions[cur_sid].last_sale_price
cost_basis = context.portfolio.positions[cur_sid].cost_basis
else:
ExistingPosition = False
if OrderType == "order_value":
order_value(cur_sid, Amount)
elif OrderType == "order_target_value":
order_target_value(cur_sid, Amount)
new_posn = context.portfolio.positions[cur_sid].amount
# this code does not work; i get a runtime error:
# "TypeError: 'DataFrame' objects are mutable, thus they cannot be hashed"
# I also tried using context.Positions.set_value(sid, 'ActivePosition', False)
# which also raised the same TypeError above
#
# --------------------------------------------------------
# I want to create my own position object to track more data about a position than
# Quantopian tracks. For example, Data position was initiated so that I can easily determine
# How many days I've held a position. Also, I want to make it easier to sum up P&L
# --------------------------------------------------------
#
# if new_posn == 0:
# context.Positions.loc[sid]['ActivePosition']=False
# context.Positions.loc[sid]['ExitDate']=get_datetime().date()
# context.Positions.loc[sid]['ExitPrice']=cur_price
# context.Positions.loc[sid]['PL']=(cur_price-cost_basis)*cur_posn
# else:
# context.Positions.loc[sid]['ActivePosition']=True
# context.Positions.loc[sid]['EnterDate']=get_datetime().date()
# context.Positions.loc[sid]['EnterPrice']=context.portfolio.positions[cur_sid].cost_basis
# context.Positions.loc[sid]['Shares']=new_posn
# context.Positions.loc[sid]['Amount']=new_posn * context.portfolio.positions[cur_sid].cost_basis
# log.info(str(context.Positions.ix['ActivePosition', sid]))
if CalcTradeStats == True:
if Amount > 0:
context.longs +=1
context.netposn +=1
elif Amount < 0:
context.shorts += 1
context.netposn -=1
else: #Amount == 0 means to exit the position
if ExistingPosition and cur_posn > 0: #we're selling out of a long
context.longs -=1
context.netposn -=1
calculate_trade_stats(context, sid)
if ExistingPosition and cur_posn < 0: #we're selling out of a long
context.shorts -=1
context.netposn +=1
calculate_trade_stats(context, sid)
def entry_exit_rules(context, data):
hist = data.history(context.sids, 'price', 3, '1d') # get 3 days of px history for universee
# rsis = talib.RSI(hist, context.rsi_period) <-- this doesn't work would be great if you could calculate technical indicators for your entire universe at once instead of looping
for cur_sid in context.sids: # for each instrument in my universe
rsi_c = talib.RSI(hist[cur_sid], context.RsiPeriod)
#
# DO WE HAVE A POSITION? IF SO, APPLY EXIT RULES FIRST
#
if context.portfolio.positions[cur_sid].amount > 0: # EXISTING LONG POSITION
# DaysInPosition = context.portfolio.positions[cur_sid].
if rsi_c[-1] > context.ExitRSI:
execute_order(cur_sid, 'order_target_value', 0, True, context)
if context.portfolio.positions[cur_sid].amount < 0: # EXISTING SHORT POSITION
if rsi_c[-1] < context.ExitRSI:
execute_order(cur_sid, 'order_target_value', 0, True, context)
#
# NOW CHECK ENTRY RULES
#
if context.portfolio.positions[cur_sid].amount == 0: # NO POSITION CHECK ENTRY RULES
if rsi_c[-1] < context.OS and data.can_trade(cur_sid): # LONG ENTRY
execute_order(cur_sid, 'order_value', context.TradeAmount, True, context)
elif rsi_c[-1] > context.OB and data.can_trade(cur_sid): # SHORT ENTRY
execute_order(cur_sid, 'order_value', context.TradeAmount, True, context)
record(long=context.longs, short=context.shorts, net=context.netposn)
if context.Mode == "SSB":
record(rsi=rsi_c[-1])
if get_datetime().date() == context.EndDate:
log.info("Total Trades: " + str(context.WinningTrades + context.LosingTrades))
log.info("Total Winnning Trades: " + str(context.WinningTrades))
log.info("Total Losing Trades: " + str(context.LosingTrades))
log.info(" ")
log.info("Total Long Trades: " + str(context.WinningLongTrades + context.LosingLongTrades))
log.info("Total Winnning Long Trades: " + str(context.WinningLongTrades))
log.info("Total Losing Long Trades: " + str(context.LosingLongTrades))
log.info(" ")
log.info("Total Short Trades: " + str(context.WinningShortTrades + context.LosingShortTrades))
log.info("Total Winnning Short Trades: " + str(context.WinningShortTrades))
log.info("Total Losing Short Trades: " + str(context.LosingShortTrades))