Skip to content

Instantly share code, notes, and snippets.

@cs224
Last active June 14, 2023 15:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cs224/4f8624cc78ddd94f5aa0061600559803 to your computer and use it in GitHub Desktop.
Save cs224/4f8624cc78ddd94f5aa0061600559803 to your computer and use it in GitHub Desktop.
Trading Evolved: Futures Trend Following
import futures_trading
import os
from zipline.utils.run_algo import load_extensions
load_extensions(
default=True,
extensions=['/home/xxx/.zipline/extension.py'],
strict=True,
environ=os.environ,
)
from IPython.display import display
import ipywidgets as widgets
out = widgets.HTML()
display(out)
start_date='2003-01-01'
end_date='2020-10-01'
file_name = '{}-trendmodel-{}-{}.dill'.format(datetime.datetime.now().strftime('%Y-%m-%d-%H%M'), pd.to_datetime(start_date).strftime('%Y%m%d'), pd.to_datetime(end_date).strftime('%Y%m%d'))
print(file_name)
#widgets_html_out=out
algo = futures_trading.FuturesTrendAlgorithm(enable_commission=True, enable_slippage=True, start_date=start_date, end_date=end_date).execute()
print(algo.quick_report_result())
# algo.save_perf(file_name)
algo.analyze()
import zipline
import zipline.api
import datetime
import pytz
import matplotlib, matplotlib.pyplot as plt
import pyfolio as pf
import pandas as pd
import numpy as np
import numba, numba.extending
import zipline.finance.commission
import zipline.finance.constants
import zipline.finance.slippage
import trading_calendars
import empyrical as em
import yfinance as yf
import multiprocessing
import tqdm
import re
from IPython.core.display import display, HTML
import csi_futures_data.csi_futures_data as cfd
cfd.futures_lookup_
import warnings, dill
import os, threading
import collections
from zipline.utils.run_algo import load_extensions
load_extensions(
default=True,
extensions=['/home/xxx/.zipline/extension.py'],
strict=True,
environ=os.environ,
)
def create_closure(instance, func):
def fn(*args):
return func(instance, *args)
return fn
root_symbol_to_η = collections.defaultdict(lambda: zipline.finance.constants.DEFAULT_ETA)
root_symbol_to_η.update(zipline.finance.constants.ROOT_SYMBOL_TO_ETA)
bundle_name = 'csi_futures_data'
class FuturesAlgorithm():
def __init__(self, starting_portfolio=50000000, bundle_name = bundle_name, enable_commission=False, enable_slippage=False, start_date=None, end_date=None, risk_factor = 0.001):
self.bundle_name = bundle_name
self.risk_factor = risk_factor
self.starting_portfolio = starting_portfolio
# Default values for futures constants : FUTURE_EXCHANGE_FEES_BY_SYMBOL and ROOT_SYMBOL_TO_ETA : https://github.com/quantopian/zipline/issues/2340
self.enable_commission = enable_commission
self.enable_slippage = enable_slippage
self.widgets_html_out = None
self.start_date = start_date
self.end_date = end_date
self.fig_ax = None
self.markets = None
def save_perf(self, file_name):
if not hasattr(self, 'perf') or self.perf is None:
raise RuntimeError('You need to first run the algorithm before you can save the state')
with open(file_name, 'wb') as file:
dill.dump(self.perf, file)
def quick_report_result(self):
return self.perf.portfolio_value[-1] / self.perf.portfolio_value[0]
def report_result(self, context, data,):
context.months += 1
today = zipline.api.get_datetime().date()
# Calculate annualized return so far
ann_ret = np.power(context.portfolio.portfolio_value / self.starting_portfolio, 12 / context.months) - 1
# Update the text
if self.widgets_html_out is not None:
self.widgets_html_out.value = """{} We have traded <b>{}</b> months and the annualized return is <b>{:.2%}</b>""".format(today, context.months, ann_ret)
else:
return today, context.months, ann_ret
def update_chart(self, context, data):
if self.fig_ax is None:
return
fig, ax, ax2 = None, None, None
if len(self.fig_ax) == 2:
fig, ax = self.fig_ax
else:
fig, ax, ax2 = self.fig_ax
# This function continuously update the graph during the backtest
today = data.current_session.date()
pv = context.portfolio.portfolio_value
exp = context.portfolio.positions_exposure
self.dynamic_results.loc[today, 'PortfolioValue'] = pv
if ax.lines: # Update existing line
ax.lines[0].set_xdata(self.dynamic_results.index)
ax.lines[0].set_ydata(self.dynamic_results.PortfolioValue)
else: # Create new line
ax.semilogy(self.dynamic_results)
# Update scales min/max
min = self.dynamic_results.PortfolioValue.min()
max = self.dynamic_results.PortfolioValue.max()
if min <= 0:
min = 0.1
if max <= min:
max = min + 1
ax.set_ylim(min,max)
min = self.dynamic_results.index.min()
max = self.dynamic_results.index.max()
if max <= min:
max = min + pd.DateOffset(days=1)
ax.set_xlim(min, max)
if ax2 is not None:
drawdown = (pv / self.dynamic_results['PortfolioValue'].max()) - 1
exposure = exp / pv
self.dynamic_results.loc[today, 'Drawdown'] = drawdown
if ax2.lines:
ax2.lines[0].set_xdata(self.dynamic_results.index)
ax2.lines[0].set_ydata(self.dynamic_results.Drawdown)
else:
ax2.plot(self.dynamic_results.Drawdown)
min = self.dynamic_results.Drawdown.min()
max = self.dynamic_results.Drawdown.max()
if min <= 0:
min = 0.1
if max <= min:
max = min + 1
ax2.set_ylim(min, max)
min = self.dynamic_results.index.min()
max = self.dynamic_results.index.max()
if max <= min:
max = min + pd.DateOffset(days=1)
ax2.set_xlim(min, max)
# Redraw the graph
fig.canvas.draw()
def analyze(self, perf=None):
if perf is None:
perf = self.perf
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
pf.create_returns_tear_sheet(returns, benchmark_rets=None)
def set_up_commission_and_slippage(self, context):
"""
Cost Settings
"""
context.enable_commission = self.enable_commission
context.enable_slippage = self.enable_slippage
if self.enable_commission:
comm_model = zipline.finance.commission.PerContract(cost=0.85, exchange_fee=1.5)
else:
comm_model = zipline.finance.commission.PerTrade(cost=0.0)
self.comm_model = comm_model
zipline.api.set_commission(us_futures=comm_model)
if self.enable_slippage:
slippage_model = zipline.finance.slippage.VolatilityVolumeShare(volume_limit=0.2) # 0.3
else:
slippage_model = zipline.finance.slippage.FixedSlippage(spread=0.0)
self.slippage_model = slippage_model
zipline.api.set_slippage(us_futures=slippage_model)
def convertContractName(self, contract):
root_symbol = contract.symbol[0:2]
month_letter = contract.symbol[2:3]
year_digits = contract.symbol[3:5]
lexically_sortable_name = root_symbol + year_digits + month_letter
return lexically_sortable_name
def order_target(self, asset, amount):
open_orders = zipline.api.get_open_orders(asset)
for order in open_orders:
# if order.sid.symbol == 'PAH19':
# print(order)
zipline.api.cancel_order(order)
return zipline.api.order_target(asset, amount)
def roll_futures(self, context, data):
today = data.current_session.date()
open_orders = zipline.api.get_open_orders()
for held_contract in context.portfolio.positions:
# don't roll positions that are set to change by core logic
if held_contract in open_orders:
continue
# Save some time by only checking rolls for
# contracts stopping trading in the next days
days_to_auto_close = (held_contract.auto_close_date.date() - today).days
if days_to_auto_close > 20:
continue
# Make a continuation
continuation = zipline.api.continuous_future(
held_contract.root_symbol,
offset=0,
roll='volume',
adjustment='mul'
)
# Get the current contract of the continuation
continuation_contract = data.current(continuation, 'contract')
if days_to_auto_close <= 10:
current_chain = data.current_chain(continuation)
if len(current_chain) <= 1:
print("{}: The current_chain for continuation: {} is empty??".format(today, held_contract.root_symbol))
else:
try:
next_contract = current_chain[1]
continuation_contract = next_contract
except:
print("{}: Ran into exception, even after length check: {}, The current_chain for continuation: {} is empty??".format(today, len(current_chain), held_contract.root_symbol))
# if held_contract.symbol in ['CLK20'] and days_to_auto_close <= 10:
# current_chain = data.current_chain(continuation)
# next_contract = current_chain[1]
# print('CLK20, {}, {}'.format(data.current_session.date(), next_contract))
# continuation_contract = next_contract
if self.convertContractName(continuation_contract) > self.convertContractName(held_contract):
# Check how many contracts we hold
pos_size = context.portfolio.positions[held_contract].amount
# Close current position
self.order_target(held_contract, 0)
# Open new position
self.order_target(continuation_contract, pos_size)
def position_size(self, portfolio_value, std, point_value, avg_volume=None):
target_variation = portfolio_value * self.risk_factor
contract_variation = std * point_value
contracts = target_variation / contract_variation
return int(np.nan_to_num(contracts))
def initialize(self, context):
# Schedule daily roll check
zipline.api.schedule_function(create_closure(self, self.roll_futures.__func__), zipline.api.date_rules.every_day(), zipline.api.time_rules.market_close())
context.pbar_month_count = 0
zipline.api.schedule_function(create_closure(self, self.update_pbar.__func__), zipline.api.date_rules.month_start(), zipline.api.time_rules.market_close())
# Schedule charting
if self.fig_ax is not None:
self.dynamic_results = pd.DataFrame()
zipline.api.schedule_function(create_closure(self, self.update_chart.__func__), zipline.api.date_rules.month_start(), zipline.api.time_rules.market_close())
# Schedule monthly report output
if self.widgets_html_out is not None:
context.months = 0
zipline.api.schedule_function(func=create_closure(self, self.report_result.__func__), date_rule=zipline.api.date_rules.month_start(), time_rule=zipline.api.time_rules.market_open())
self.set_up_commission_and_slippage(context)
# Make a list of all continuations
context.universe = [zipline.api.continuous_future(market, offset=0, roll='volume', adjustment='mul') for market in self.markets]
def daily_trade(self, context, data):
pass
def configure(self, start_date, end_date):
self.start_date = start_date
self.end_date = end_date
return self
def update_pbar(self, context, data):
context.pbar_month_count += 1
self.pbar.update(1)
def execute(self, start_date=None, end_date=None, widgets_html_out=None, fig_ax=None):
self.widgets_html_out = widgets_html_out
self.fig_ax = fig_ax
if start_date is not None:
self.start_date = start_date
if end_date is not None:
self.end_date = end_date
# https://pvlib-python.readthedocs.io/en/stable/timetimezones.html
start = pd.Timestamp(self.start_date, tz=pytz.UTC)
end = pd.Timestamp(self.end_date, tz=pytz.UTC)
self.month_delta = (end.year - start.year)*12 + end.month - start.month
with warnings.catch_warnings():
warnings.filterwarnings("ignore", module='empyrical')
with tqdm.tqdm(total=self.month_delta) as pbar:
self.pbar = pbar
self.perf = zipline.run_algorithm(
start=start, end=end,
initialize=create_closure(self, self.initialize.__func__),
capital_base=self.starting_portfolio,
data_frequency='daily',
# benchmark_returns=zipline.utils.run_algo.BenchmarkSpec._zero_benchmark_returns(start_date=start,end_date=end),
bundle=self.bundle_name)
return self
class FuturesTrendAlgorithm(FuturesAlgorithm):
def __init__(self, starting_portfolio=50000000, bundle_name = bundle_name, enable_commission=False, enable_slippage=False, start_date=None, end_date=None, risk_factor = 0.0015):
super().__init__(starting_portfolio=starting_portfolio, bundle_name = bundle_name, enable_commission=enable_commission, enable_slippage=enable_slippage, start_date=start_date, end_date=end_date, risk_factor = risk_factor)
self.stop_distance = 3
self.breakout_window = 50
self.vola_window = 40
self.slow_ma = 80
self.fast_ma = 40
self.markets = cfd.get_bundle_market_symbols('trend_following_markets')
def initialize(self, context):
super().initialize(context)
# We'll use these to keep track of best position reading
# Used to calculate stop points.
context.highest_in_position = {market: 0 for market in self.markets}
context.lowest_in_position = {market: 0 for market in self.markets}
# Schedule the daily trading
zipline.api.schedule_function(create_closure(self, self.daily_trade.__func__), zipline.api.date_rules.every_day(), zipline.api.time_rules.market_close())
def exit_position(self, context, data, open_pos, continuation):
# if data.current(continuation, 'contract').symbol == 'CTH19':
# #print(data.current(continuation, 'contract').symbol)
# pass
root = continuation.root_symbol
contract = open_pos[root]
self.order_target(contract, 0)
context.highest_in_position[root] = 0
context.lowest_in_position[root] = 0
def daily_trade(self, context, data):
# Get continuation data
hist = data.history(
context.universe,
fields=['close', 'volume'],
frequency='1d',
bar_count=250,
)
# Calculate trend
hist['trend'] = hist['close'].ewm(span=self.fast_ma).mean() > hist['close'].ewm(span=self.slow_ma).mean()
# Make dictionary of open positions
open_pos = {
pos.root_symbol: pos for pos in context.portfolio.positions
}
# Iterate markets, check for trades
for continuation in context.universe:
if data.current(continuation, 'contract') is None:
continue
# if data.current(continuation, 'contract').symbol == 'CTH19':
# #print(data.current(continuation, 'contract').symbol)
# pass
# Get root symbol of continuation
root = continuation.root_symbol
# Slice off history for just this market
h = hist.xs(continuation, 2)
# Get standard deviation
std = h.close.diff()[-self.vola_window:].std()
if root in open_pos: # Position is open
# Get position
p = context.portfolio.positions[open_pos[root]]
if p.amount > 0: # Position is long
if context.highest_in_position[root] == 0: # First day holding the position
context.highest_in_position[root] = p.last_sale_price
else:
context.highest_in_position[root] = max(
h['close'].iloc[-1], context.highest_in_position[root]
)
# Calculate stop point
stop = context.highest_in_position[root] - (std * self.stop_distance)
# Check if stop is hit
if h.iloc[-1]['close'] < stop:
self.exit_position(context, data, open_pos, continuation)
# contract = open_pos[root]
# self.order_target(contract, 0)
# context.highest_in_position[root] = 0
# Check if trend has flipped
elif h['trend'].iloc[-1] == False:
self.exit_position(context, data, open_pos, continuation)
# contract = open_pos[root]
# self.order_target(contract, 0)
# context.highest_in_position[root] = 0
else: # Position is short
#print(context.lowest_in_position[root])
if context.lowest_in_position[root] == 0: # First day holding the position
context.lowest_in_position[root] = p.last_sale_price
else:
context.lowest_in_position[root] = min(
h['close'].iloc[-1], context.lowest_in_position[root]
)
# Calculate stop point
stop = context.lowest_in_position[root] + (std * self.stop_distance)
# Check if stop is hit
if h.iloc[-1]['close'] > stop:
self.exit_position(context, data, open_pos, continuation)
# contract = open_pos[root]
# self.order_target(contract, 0)
# context.lowest_in_position[root] = 0
# Check if trend has flipped
elif h['trend'].iloc[-1] == True:
self.exit_position(context, data, open_pos, continuation)
# contract = open_pos[root]
# self.order_target(contract, 0)
# context.lowest_in_position[root] = 0
else: # No position on
if h['trend'].iloc[-1]: # Bull trend
# Check if we just made a new high
if h['close'][-1] == h[-self.breakout_window:]['close'].max():
contract = data.current(continuation, 'contract')
contracts_to_trade = self.position_size(context.portfolio.portfolio_value, std, contract.price_multiplier) #, h['volume'][-20:].mean()
# Limit size to 20% of avg. daily volume
contracts_cap = int(h['volume'][-20:].mean() * 0.2)
contracts_to_trade = min(contracts_to_trade, contracts_cap)
# Place the order
self.order_target(contract, contracts_to_trade)
else: # Bear trend
# Check if we just made a new low
if h['close'][-1] == h[-self.breakout_window:]['close'].min():
contract = data.current(continuation, 'contract')
contracts_to_trade = self.position_size(context.portfolio.portfolio_value,std, contract.price_multiplier) # , h['volume'][-20:].mean()
# Limit size to 20% of avg. daily volume
contracts_cap = int(h['volume'][-20:].mean() * 0.2)
contracts_to_trade = min(contracts_to_trade, contracts_cap)
# Place the order
self.order_target(contract, -1 * contracts_to_trade)
class FuturesTimeReturnAlgorithm(FuturesAlgorithm):
def __init__(self, starting_portfolio=10000000, bundle_name = bundle_name, enable_commission=False, enable_slippage=False, start_date=None, end_date=None, risk_factor = 0.001):
super().__init__(starting_portfolio=starting_portfolio, bundle_name = bundle_name, enable_commission=enable_commission, enable_slippage=enable_slippage, start_date=start_date, end_date=end_date, risk_factor = risk_factor)
self.vola_window = 60
self.short_trend_window = 125
self.long_trend_window = 250
self.markets = cfd.get_bundle_market_symbols('time_return_markets')
def initialize(self, context):
super().initialize(context)
# Schedule daily trading
zipline.api.schedule_function(create_closure(self, self.rebalance.__func__), zipline.api.date_rules.month_start(), zipline.api.time_rules.market_close())
def rebalance(self, context, data):
# Get the history
hist = data.history(
context.universe,
fields=['close', 'volume'],
frequency='1d',
bar_count=self.long_trend_window,
)
# Make a dictionary of open positions
open_pos = {pos.root_symbol: pos for pos in context.portfolio.positions}
# Loop all markets
for continuation in context.universe:
# Slice off history for this market
h = hist.xs(continuation, 2)
root = continuation.root_symbol
# Calculate volatility
std = h.close.diff()[-self.vola_window:].std()
if root in open_pos: # Position is already open
p = context.portfolio.positions[open_pos[root]]
if p.amount > 0: # Long position
if h.close[-1] < h.close[-self.long_trend_window]:
# Lost slow trend, close position
self.order_target(open_pos[root], 0)
elif h.close[-1] < h.close[-self.short_trend_window]:
# Lost fast trend, close position
self.order_target(open_pos[root], 0)
else: # Short position
if h.close[-1] > h.close[-self.long_trend_window]:
# Lost slow trend, close position
self.order_target(open_pos[root], 0)
elif h.close[-1] > h.close[-self.short_trend_window]:
# Lost fast trend, close position
self.order_target(open_pos[root], 0)
else: # No position open yet.
if (h.close[-1] > h.close[-self.long_trend_window]) and (h.close[-1] > h.close[-self.short_trend_window]):
# Buy new position
contract = data.current(continuation, 'contract')
contracts_to_trade = self.position_size(context.portfolio.portfolio_value, std, contract.price_multiplier) # , h['volume'][-20:].mean()
self.order_target(contract, contracts_to_trade)
elif (h.close[-1] < h.close[-self.long_trend_window]) \
and \
(h.close[-1] < h.close[-self.short_trend_window]):
# New short position
contract = data.current(continuation, 'contract')
contracts_to_trade = self.position_size(context.portfolio.portfolio_value, std, contract.price_multiplier) # , h['volume'][-20:].mean()
self.order_target(contract, contracts_to_trade * -1)
class FuturesCounterTrendTradingAlgorithm(FuturesAlgorithm):
def __init__(self, starting_portfolio=20000000, bundle_name = bundle_name, enable_commission=False, enable_slippage=False, start_date=None, end_date=None, risk_factor = 0.0015):
super().__init__(starting_portfolio=starting_portfolio, bundle_name = bundle_name, enable_commission=enable_commission, enable_slippage=enable_slippage, start_date=start_date, end_date=end_date, risk_factor = risk_factor)
self.vola_window = 40
self.slow_ma = 80
self.fast_ma = 40
self.high_window = 20
self.days_to_hold = 20
self.dip_buy = -3
self.markets = cfd.get_bundle_market_symbols('counter_trend_markets')
def initialize(self, context):
super().initialize(context)
# Dictionary used for keeping track of how many days a position has been open.
context.bars_held = {market.root_symbol: 0 for market in context.universe}
zipline.api.schedule_function(create_closure(self, self.daily_trade.__func__), zipline.api.date_rules.every_day(), zipline.api.time_rules.market_close())
def daily_trade(self, context, data):
open_pos = {pos.root_symbol: pos for pos in context.portfolio.positions}
hist = data.history(
context.universe,
fields=['close', 'volume'],
frequency='1d',
bar_count=250,
)
# Calculate the trend
hist['trend'] = hist['close'].ewm(span=self.fast_ma).mean() > hist['close'].ewm(span=self.slow_ma).mean()
for continuation in context.universe:
root = continuation.root_symbol
# Slice off history for this market
h = hist.xs(continuation, 2)
# Calculate volatility
std = h.close.diff()[-self.vola_window:].std()
if root in open_pos: # Check open positions first.
context.bars_held[root] += 1 # One more day held
if context.bars_held[root] >= 20:
# Held for a month, exit
contract = open_pos[root]
self.order_target(contract, 0)
elif h['trend'].iloc[-1] == False:
# Trend changed, exit.
contract = open_pos[root]
self.order_target(contract, 0)
else: # Check for new entries
if h['trend'].iloc[-1]:
# Calculate the pullback
pullback = (
h['close'].values[-1] - np.max(h['close'].values[-self.high_window:])
) / std
if pullback < self.dip_buy:
# Get the current contract
contract = data.current(continuation, 'contract')
# Calculate size
contracts_to_trade = self.position_size(context.portfolio.portfolio_value, std, contract.price_multiplier)
# Trade
self.order_target(contract, contracts_to_trade)
# Reset bar count to zero
context.bars_held[root] = 0
class FuturesCurveTradingAlgorithm(FuturesAlgorithm):
def __init__(self, starting_portfolio=1000000, bundle_name = bundle_name, enable_commission=False, enable_slippage=False, start_date=None, end_date=None, risk_factor = 0.0015):
super().__init__(starting_portfolio=starting_portfolio, bundle_name = bundle_name, enable_commission=enable_commission, enable_slippage=enable_slippage, start_date=start_date, end_date=end_date, risk_factor = risk_factor)
self.spread_months = 12
self.pos_per_side = 5
self.target_exposure_per_side = 1.5
self.volume_order_cap = 0.25
self.markets = cfd.get_bundle_market_symbols('curve_trading_markets')
def initialize(self, context):
super().initialize(context)
zipline.api.schedule_function(create_closure(self, self.weekly_trade.__func__), zipline.api.date_rules.week_start(), zipline.api.time_rules.market_close())
def weekly_trade(self, context, data):
# Empty DataFrame to be filled in later.
carry_df = pd.DataFrame(index=context.universe)
today = data.current_session.date()
for continuation in context.universe:
# Get the chain
chain = data.current_chain(continuation)
# Transform the chain into dataframe
df = pd.DataFrame(index=chain)
for contract in chain:
df.loc[contract, 'future'] = contract
df.loc[contract, 'expiration_date'] = contract.expiration_date
# Locate the contract closest to the target date.
# X months out from the front contract.
closest_expiration_date = df.iloc[0].expiration_date
target_expiration_date = closest_expiration_date + pd.DateOffset(months=self.spread_months)
df['days_to_target'] = abs(df.expiration_date - target_expiration_date)
target_contract = df.loc[df.days_to_target == df.days_to_target.min()]
# Get prices for front contract and target contract
prices = data.current(
[
df.index[0],
target_contract.index[0]
],
'close'
)
# Check the exact day difference between the contracts
days_to_front = int(
(target_contract.expiration_date - closest_expiration_date)[0].days
)
# Calculate the annualized carry
try:
annualized_carry = (np.power(
(prices[0] / prices[1]), (365 / days_to_front))
) - 1
except:
print('{}: {} / {}: Problem in calculating annualized_carry: target_contract_price: {}, days_to_front: {}'.format(today, df.iloc[0].future, target_contract.index[0], prices[1], days_to_front))
annualized_carry = 0.0
carry_df.loc[continuation, 'front'] = df.iloc[0].future
carry_df.loc[continuation, 'next'] = target_contract.index[0]
carry_df.loc[continuation, 'carry'] = annualized_carry
# Sort on carry
carry_df.sort_values('carry', inplace=True, ascending=False)
carry_df.dropna(inplace=True)
new_portfolio = []
new_longs = []
new_shorts = []
# Contract Selection
for i in np.arange(0, self.pos_per_side):
j = -(i + 1)
# Buy top, short bottom
long_contract = carry_df.iloc[i].next
short_contract = carry_df.iloc[j].next
new_longs.append(long_contract)
new_shorts.append(short_contract)
# Get data for the new portfolio
new_portfolio = new_longs + new_shorts
hist = data.history(new_portfolio, fields=['close', 'volume'], frequency='1d', bar_count=10,)
# Simple Equal Weighted
target_weight = (
self.target_exposure_per_side * context.portfolio.portfolio_value
) / self.pos_per_side
# Trading
for contract in new_portfolio:
# Slice history for contract
h = hist.xs(contract, 2)
# Equal weighted, with volume based cap.
contracts_to_trade = target_weight / contract.price_multiplier / h.close[-1]
# Position size cap
contracts_cap = int(h['volume'].mean() * self.volume_order_cap)
# Limit trade size to position size cap.
contracts_to_trade = min(contracts_to_trade, contracts_cap)
# Negative position for shorts
if contract in new_shorts:
contracts_to_trade *= -1
# Execute
self.order_target(contract, contracts_to_trade)
# Close any other open position
for pos in context.portfolio.positions:
if pos not in new_portfolio:
self.order_target(pos, 0.0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment