def passive_roll_child_order( data: dataBlob, trade: int, instrument_order: instrumentOrder, ) -> list: log = instrument_order.log_with_attributes(data.log) diag_positions = diagPositions(data) instrument_code = instrument_order.instrument_code diag_contracts = diagContracts(data) current_contract = diag_contracts.get_priced_contract_id(instrument_code) next_contract = diag_contracts.get_forward_contract_id(instrument_code) position_current_contract = ( diag_positions.get_position_for_instrument_and_contract_date( instrument_code, current_contract)) # Break out because so darn complicated if position_current_contract == 0: # Passive roll and no position in the current contract, start trading # the next contract log.msg( "Passive roll handling order %s, no position in current contract, entire trade in next contract %s" % (str(instrument_order), next_contract)) return [contractIdAndTrade(next_contract, trade)] # ok still have a position in the current contract increasing_trade = sign(trade) == sign(position_current_contract) if increasing_trade: # Passive roll and increasing trade # Do it all in next contract log.msg( "Passive roll handling order %s, increasing trade, entire trade in next contract %s" % (str(instrument_order), next_contract)) return [contractIdAndTrade(next_contract, trade)] # ok a reducing trade new_position = position_current_contract + trade sign_of_position_is_unchanged = sign(position_current_contract) == sign( new_position) if new_position == 0 or sign_of_position_is_unchanged: # A reducing trade that we can do entirely in the current contract log.msg( "Passive roll handling order %s, reducing trade, entire trade in next contract %s" % (str(instrument_order), next_contract)) return [contractIdAndTrade(current_contract, trade)] # OKAY to recap: it's a passive roll, but the trade will be split between # current and next list_of_child_contract_dates_and_trades = \ passive_trade_split_over_two_contracts(trade=trade, current_contract=current_contract, next_contract=next_contract, position_current_contract=position_current_contract) log.msg( "Passive roll handling order %s, reducing trade, split trade between contract %s and %s" % (str(instrument_order), current_contract, next_contract)) return list_of_child_contract_dates_and_trades
def passive_roll_child_order(position_current_contract, current_contract, next_contract, trade, log, instrument_order): # Break out because so darn complicated if position_current_contract == 0: # Passive roll and no position in the current contract, start trading the next log.msg("Passive roll handling order %s, no position in current contract, entire trade in next contract %s" % (str(instrument_order), next_contract)) return [(next_contract, trade)] # ok still have a position in the current contract increasing_trade = sign(trade) == sign(position_current_contract) if increasing_trade: # Passive roll and increasing trade # Do it all in next contract log.msg("Passive roll handling order %s, increasing trade, entire trade in next contract %s" % (str(instrument_order), next_contract)) return [(next_contract, trade)] # ok a reducing trade new_position = position_current_contract + trade sign_of_position_is_unchanged = sign(position_current_contract) == sign(new_position) if new_position == 0 or sign_of_position_is_unchanged: # A reducing trade that we can do entirely in the current contract log.msg("Passive roll handling order %s, reducing trade, entire trade in next contract %s" % (str(instrument_order), next_contract)) return [(current_contract, trade)] ## OKAY to recap: it's a passive roll, but the trade will be split between current and next log.msg("Passive roll handling order %s, reducing trade, split trade between contract %s and %s" % (str(instrument_order), current_contract, next_contract)) trade_in_current_contract = - position_current_contract trade_in_next_contract = trade - trade_in_current_contract return [(current_contract, trade_in_current_contract), (next_contract, trade_in_next_contract)]
def resolveBS_for_list(trade_list): ## result is always positive trade = highest_common_factor_for_list(trade_list) trade = sign(trade_list[0]) * trade return resolveBS(trade)
def cut_down_proposed_instrument_trade_okay(self, instrument_trade): strategy_name = instrument_trade.strategy_name instrument_code = instrument_trade.instrument_code proposed_trade = instrument_trade.trade.as_int() ## want to CUT DOWN rather than bool possible trades ## underneath should be using tradeQuantity and position objects ## these will handle abs cases plus legs if required in future max_trade_ok_against_strategy_instrument = \ self.check_if_proposed_trade_okay_against_strategy_instrument_constraint(strategy_name, instrument_code, proposed_trade) max_trade_ok_against_instrument = \ self.check_if_proposed_trade_okay_against_instrument_constraint(instrument_code, proposed_trade) mini_max_trade = sign(proposed_trade) * \ min([abs(max_trade_ok_against_instrument), abs(max_trade_ok_against_strategy_instrument)]) instrument_trade = instrument_trade.replace_trade_only_use_for_unsubmitted_trades( mini_max_trade) return instrument_trade
def expiry_diff(carry_row, floor_date_diff=20): """ Given a pandas row containing CARRY_CONTRACT and PRICE_CONTRACT, both of which represent dates Return the annualised difference between the dates :param carry_row: object with attributes CARRY_CONTRACT and PRICE_CONTRACT :type carry_row: pandas row, or something that quacks like it :param floor_date_diff: If date resolves to less than this, floor here (*default* 20) :type carry_row: pandas row, or something that quacks like it :returns: datetime.datetime or datetime.date """ if carry_row.PRICE_CONTRACT == "" or carry_row.CARRY_CONTRACT == "": return np.nan ans = float((expiry_date(carry_row.CARRY_CONTRACT) - expiry_date(carry_row.PRICE_CONTRACT)).days) if abs(ans) < floor_date_diff: ans = sign(ans) * floor_date_diff ans = ans / CALENDAR_DAYS_IN_YEAR return ans
def cut_down_proposed_instrument_trade_okay(self, instrument_trade): strategy_name = instrument_trade.strategy_name instrument_code = instrument_trade.instrument_code proposed_trade = instrument_trade.trade.as_int() ## want to CUT DOWN rather than bool possible trades ## FIXME: ## underneath should be using tradeQuantity and position objects ## these will handle abs cases plus legs if required in future # :FIXME instrument_strategy = instrumentStrategy( strategy_name=strategy_name, instrument_code=instrument_code) max_trade_ok_against_instrument_strategy = \ self.check_if_proposed_trade_okay_against_instrument_strategy_constraint(instrument_strategy, proposed_trade) max_trade_ok_against_instrument = \ self.check_if_proposed_trade_okay_against_instrument_constraint(instrument_code, proposed_trade) ## FIXME THIS IS UGLY WOULD BE BETTER IF DONE INSIDE TRADE SIZE OBJECT mini_max_trade = sign(proposed_trade) * \ min([abs(max_trade_ok_against_instrument), abs(max_trade_ok_against_instrument_strategy)]) instrument_trade = instrument_trade.replace_trade_only_use_for_unsubmitted_trades( mini_max_trade) return instrument_trade
def map_forecast_value_scalar(x, threshold, capped_value, a_param, b_param): """ Non linear mapping of x value; replaces forecast capping; with defaults will map 1 for 1 We want to end up with a function like this, for raw forecast x and mapped forecast m, capped_value c and threshold_value t: if -t < x < +t: m=0 if abs(x)>c: m=sign(x)*c*a if c < x < -t: (x+t)*b if t < x < +c: (x-t)*b :param x: value to map :param threshold: value below which map to zero :param capped_value: maximum value we want x to take (without non linear mapping) :param a_param: slope :param b_param: :return: mapped x """ x = float(x) if np.isnan(x): return x if abs(x) < threshold: return 0.0 if x >= -capped_value and x <= -threshold: return b_param * (x + threshold) if x >= threshold and x <= capped_value: return b_param * (x - threshold) if abs(x) > capped_value: return sign(x) * capped_value * a_param raise Exception("This should cover all conditions!")
def _DEPRECATE_apply_floor_to_date_differential( fraction_of_year_between_expiries: float, floor_date_diff: float): if abs(fraction_of_year_between_expiries) < floor_date_diff: fraction_of_year_between_expiries = \ sign(fraction_of_year_between_expiries) * floor_date_diff return fraction_of_year_between_expiries
def what_trade_is_possible(self, proposed_trade: int): abs_proposed_trade = abs(proposed_trade) possible_abs_trade = self.what_abs_trade_is_possible( abs_proposed_trade) # convert to same sign as proposed possible_trade = possible_abs_trade * sign(proposed_trade) return possible_trade
def _apply_reduce_only(self, original_position_no_override, proposed_trade): desired_new_position = original_position_no_override + proposed_trade.trade if sign(desired_new_position) != sign(original_position_no_override): # Closing trade only; don't allow sign to change new_trade_value = -original_position_no_override elif abs(desired_new_position) > abs(original_position_no_override): # Increasing trade not allowed new_trade_value = 0 else: # Reducing trade and sign not changing, we'll allow new_trade_value = proposed_trade.trade proposed_trade.replace_trade_only_use_for_unsubmitted_trades( new_trade_value) return proposed_trade
def _apply_reduce_only(original_position_no_override: int, proposed_trade: Order) -> Order: proposed_trade_value = proposed_trade.trade.as_single_trade_qty_or_error() desired_new_position = original_position_no_override + proposed_trade_value if sign(desired_new_position) != sign(original_position_no_override): # Wants sign to change, we convert into a pure closing trade new_trade_value = -original_position_no_override elif abs(desired_new_position) > abs(original_position_no_override): # Increasing trade not allowed, zero trade new_trade_value = 0 else: # Reducing trade and sign not changing, we'll allow new_trade_value = proposed_trade_value proposed_trade.replace_required_trade_size_only_use_for_unsubmitted_trades( tradeQuantity(new_trade_value)) return proposed_trade
def apply_fixed_position(position): """ Turn a variable position into one that is held constant regardless of changes in volatility :param position: Raw position series, without stoploss or entry / exit logic :return: New position series """ # assume all lined up current_position = 0.0 new_position = [] for iday in range(len(position)): original_position_now = position[iday] if current_position == 0.0: # no position, check for signal if np.isnan(original_position_now): # no signal new_position.append(0.0) continue if original_position_now > 0.0 or original_position_now < 0.0: # go long or short current_position = original_position_now new_position.append(current_position) continue if sign(current_position) != sign(original_position_now): # changed sign current_position = original_position_now new_position.append(current_position) continue # already holding a position of same sign new_position.append(current_position) new_position = pd.DataFrame(new_position, position.index) return new_position
def calculate_direction( optimum_weight: float, minimum: float = -A_VERY_LARGE_NUMBER, maximum: float = A_VERY_LARGE_NUMBER, ) -> float: if minimum >= 0: return 1 if maximum <= 0: return -1 if np.isnan(optimum_weight): return 1 return sign(optimum_weight)
def get_spread_price(self, list_of_prices): assert len(self._trade_or_fill_qty) == len(list_of_prices) if len(self._trade_or_fill_qty) == 1: return list_of_prices[0] # spread price won't make sense otherwise assert sum(self._trade_or_fill_qty) == 0 sign_to_adjust = sign(self._trade_or_fill_qty[0]) multiplied_prices = [ x * y * sign_to_adjust for x, y in zip(self._trade_or_fill_qty, list_of_prices) ] return sum(multiplied_prices)
def fraction_of_year_between_price_and_carry_expiries(carry_row, floor_date_diff: int = 1 ) -> float: """ Given a pandas row containing CARRY_CONTRACT and PRICE_CONTRACT, both of which represent dates Return the difference between the dates as a fraction Positive means PRICE BEFORE CARRY, negative means CARRY BEFORE PRICE :param carry_row: object with attributes CARRY_CONTRACT and PRICE_CONTRACT :type carry_row: pandas row, or something that quacks like it :param floor_date_diff: If date resolves to less than this, floor here (*default* 20) :type int :returns: float >>> import pandas as pd >>> carry_df = pd.DataFrame(dict(PRICE_CONTRACT =["20200601", "20200601", "20200601"],\ CARRY_CONTRACT = ["20200303", "20200905", "20200603"])) >>> fraction_of_year_between_price_and_carry_expiries(carry_df.iloc[0]) -0.2464065708418891 >>> fraction_of_year_between_price_and_carry_expiries(carry_df.iloc[1]) 0.26283367556468173 >>> fraction_of_year_between_price_and_carry_expiries(carry_df.iloc[2], floor_date_diff= 50) 0.13689253935660506 """ if carry_row.PRICE_CONTRACT == "" or carry_row.CARRY_CONTRACT == "": return np.nan period_between_expiries = get_datetime_from_datestring(carry_row.CARRY_CONTRACT) \ - get_datetime_from_datestring(carry_row.PRICE_CONTRACT) days_between_expiries = period_between_expiries.days if abs(days_between_expiries) < floor_date_diff: days_between_expiries = sign(days_between_expiries) * floor_date_diff ## Annualise, ensuring float output fraction_of_year_between_expiries = float( days_between_expiries) / CALENDAR_DAYS_IN_YEAR return fraction_of_year_between_expiries
def get_spread_price(self, list_of_prices): if list_of_prices is None: return None if isinstance(list_of_prices, int) or isinstance( list_of_prices, float): list_of_prices = [list_of_prices] assert len(self._trade_or_fill_qty) == len(list_of_prices) if len(self._trade_or_fill_qty) == 1: return list_of_prices[0] # spread price won't make sense otherwise assert sum(self._trade_or_fill_qty) == 0 sign_to_adjust = sign(self._trade_or_fill_qty[0]) multiplied_prices = [ x * y * sign_to_adjust for x, y in zip(self._trade_or_fill_qty, list_of_prices) ] return sum(multiplied_prices)
def what_trade_is_possible(self, trade: int) -> int: if self.position_limit is NO_LIMIT: return trade position = self.position new_position = position + trade # position limit should be abs, but just in case... abs_position_limit = abs(self.position_limit) signed_position_limit = int(abs_position_limit * sign(new_position)) if abs(new_position) <= abs_position_limit: possible_trade = trade elif trade > 0 and new_position >= 0: possible_trade = max(0, signed_position_limit - position) elif trade < 0 and new_position < 0: possible_trade = min(0, signed_position_limit - position) else: possible_trade = 0 return int(possible_trade)
def stoploss(price, vol, position, Xfactor=4): """ Apply trailing stoploss :param price: :param vol: eg system.rawdata.daily_returns_volatility("SP500") :param position: Raw position series, without stoploss or entry / exit logic :return: New position series """ # assume all lined up current_position = 0.0 previous_position = 0.0 new_position = [] price_list_since_position_held = [] for iday in range(len(price)): current_price = price[iday] if current_position == 0.0: # no position, check for signal original_position_now = position[iday] if np.isnan(original_position_now): # no signal new_position.append(0.0) continue if original_position_now > 0.0 or original_position_now < 0.0: # potentially going long / short # check last position to avoid whipsaw if previous_position == 0.0 or sign( original_position_now) != sign(previous_position): # okay to do this - we don't want to enter a new position unless sign changed # we set the position at the sized position at moment of inception current_position = original_position_now price_list_since_position_held.append(current_price) new_position.append(current_position) continue # if we've made it this far then: # no signal new_position.append(0.0) continue # already holding a position # calculate HWM sign_position = sign(current_position) price_list_since_position_held.append(current_price) current_vol = vol[iday] trailing_factor = current_vol * Xfactor if sign_position == 1: # long hwm = np.nanmax(price_list_since_position_held) threshold = hwm - trailing_factor close_trade = current_price < threshold else: # short hwm = np.nanmin(price_list_since_position_held) threshold = hwm + trailing_factor close_trade = current_price > threshold if close_trade: previous_position = copy(current_position) current_position = 0.0 # note if we don't close the current position is maintained price_list_since_position_held = [] new_position.append(current_position) new_position = pd.DataFrame(new_position, price.index) return new_position
def sign_equal(self, other): return all([sign(x) == sign(y) for x, y in zip(self, other)])
def buy_or_sell(self) -> int: # sign of trade quantity return sign(self[0])
def resolveBS_for_calendar_spread(trade_list): trade = highest_common_factor_for_list(trade_list) trade = sign(trade_list[0]) * trade return resolveBS(trade)
def apply_floor_to_date_differential(days_between_expiries: float, floor_date_diff: float): if abs(days_between_expiries) < floor_date_diff: days_between_expiries = sign(days_between_expiries) * floor_date_diff return days_between_expiries
def buy_or_sell(self): return sign(self.qty[0])
def what_trade_is_possible_single_leg_trade(position: int, position_limit: int, proposed_trade: int)-> int: """ >>> what_trade_is_possible(1, 1, 0) 0 >>> what_trade_is_possible(5, 1, 0) 0 >>> what_trade_is_possible(0, 3, 1) 1 >>> what_trade_is_possible(0, 3, 2) 2 >>> what_trade_is_possible(0, 3, 3) 3 >>> what_trade_is_possible(0, 3, 4) 3 >>> what_trade_is_possible(0, 3, -1) -1 >>> what_trade_is_possible(0, 3, -2) -2 >>> what_trade_is_possible(0, 3, -3) -3 >>> what_trade_is_possible(0, 3, -4) -3 >>> what_trade_is_possible(2, 3, 1) 1 >>> what_trade_is_possible(2, 3, 2) 1 >>> what_trade_is_possible(2, 3, -2) -2 >>> what_trade_is_possible(2, 3, -4) -4 >>> what_trade_is_possible(2, 3, -5) -5 >>> what_trade_is_possible(2, 3, -6) -5 >>> what_trade_is_possible(5, 3, 2) 0 >>> what_trade_is_possible(5, 3, -1) -1 >>> what_trade_is_possible(5, 3, -2) -2 >>> what_trade_is_possible(5, 3, -9) -8 >>> what_trade_is_possible(2, 3, -4) -4 >>> what_trade_is_possible(2, 3, -5) -5 >>> what_trade_is_possible(2, 3, -6) -5 >>> what_trade_is_possible(0, 3, 1) 1 >>> what_trade_is_possible(0, 3, -4) -3 >>> what_trade_is_possible(-2, 3, -1) -1 >>> what_trade_is_possible(-2, 3, -2) -1 >>> what_trade_is_possible(-5, 3, -2) 0 >>> what_trade_is_possible(-5, 3, 1) 1 >>> what_trade_is_possible(-5, 3, 2) 2 >>> what_trade_is_possible(-5, 3, 2) 2 >>> what_trade_is_possible(-5, 3, 7) 7 >>> what_trade_is_possible(-5, 3, 9) 8 >>> what_trade_is_possible(-3, 3, 7) 6 >>> what_trade_is_possible(3, 3, -7) -6 """ if proposed_trade==0: return proposed_trade new_position = position + proposed_trade # position limit should be abs, but just in case... abs_position_limit = abs(position_limit) signed_position_limit = int(abs_position_limit * sign(new_position)) if abs(new_position)<=abs_position_limit: ## new position is within limits return proposed_trade if position>=0 and abs(position)<=abs_position_limit: ## Was okay, but won't be after trade ## We move to the limit, either long or short depending on what the trade wanted to do possible_new_position = signed_position_limit possible_trade = possible_new_position - position return possible_trade if position>=0 and abs(position)>abs_position_limit: ## Was already too big if proposed_trade>=0: # want to buy when already too big return 0 if new_position>0: ## selling, but sell isn't big enough to get within limits ## we don't increase the size of a trade return proposed_trade else: ## selling and gone out the other side possible_new_position = signed_position_limit possible_trade = possible_new_position - position return possible_trade if position<0 and abs(position)<=abs_position_limit: ## Was okay, but won't be after trade ## We move to the limit, either long or short depending on what the trade wanted to do possible_new_position = signed_position_limit possible_trade = possible_new_position - position return possible_trade if position<0 and abs(position)>abs_position_limit: ## Was already too big if proposed_trade<0: # want to sell when already too big return 0 if new_position<0: ## buying but not big enough to get within limits ## we don't increase the size of a trade return proposed_trade else: # buying and gone out the other side possible_new_position = signed_position_limit possible_trade = possible_new_position - position return possible_trade raise Exception("Don't know how to handle original position %f proposed trade %f limit %f" % (position, proposed_trade, abs_position_limit))