def idx_of_first_trade_next_30days(self, tradetomatch): ## Return index of first trade done after this trade, with opposite sign, and within 30 days self.date_sort() tradedatetime = tradetomatch.Date listdatetimes = [trade.Date for trade in self] listdates = [x.date() for x in listdatetimes] tradedate30daysafter = tradetomatch.Date.date() + datetime.timedelta( 30) listsignquant = [trade.SignQuantity for trade in self] ## trades are in next 30 days or today, but not in the past ## and with opposite sign next_30days_trades = [ idx for idx in range(len(listdates)) if listdates[idx] <= tradedate30daysafter and listdatetimes[idx] > tradedatetime and not signs_match(listsignquant[idx], tradetomatch.SignQuantity) ] if len(next_30days_trades) == 0: return None ## Return the first trade return next_30days_trades[0]
def spawn_pseudo_trades(self, tradetoclose): """ Returns a new clone trade with tradetoclose, and a residual clone trade """ assert self._ready_for_split() assert not self.pseudotrade oldquantity=copy(self.SignQuantity) residualtrade=oldquantity - tradetoclose assert type(tradetoclose) is float assert signs_match(tradetoclose, oldquantity) assert abs(tradetoclose)<=abs(oldquantity) assert abs(tradetoclose)>0.0 neworder=self._share_of_trade(share=tradetoclose) changedorder=self._share_of_trade(share=residualtrade) neworder.modify(tradetype="Close") changedorder.modify(tradetype="Open") neworder.modify(pseudotrade=True) changedorder.modify(pseudotrade=True) oldtradeid=self.TradeID neworder.modify(TradeID=oldtradeid+":1") changedorder.modify(TradeID=oldtradeid+":2") ## To avoid duplications we make the opening order a second after the old one newdate=changedorder.Date+datetime.timedelta(seconds=1) changedorder.modify(Date=newdate) return [changedorder, neworder]
def idx_of_last_trade_same_day(self, tradetomatch): ## Return indices of trades with same date, executed prior to this trade, with opposite sign tradedatetime = tradetomatch.Date self.date_sort() tradedate = tradedatetime.date() listdatetimes = [trade.Date for trade in self] listdates = [x.date() for x in listdatetimes] listsignquant = [trade.SignQuantity for trade in self] ## done on same day, but not in future ## Future trades on same day will be picked up in 'within 30 days' rule same_day_trades = [ idx for idx in range(len(listdates)) if listdates[idx] == tradedate and listdatetimes[idx] < tradedatetime and not signs_match(listsignquant[idx], tradetomatch.SignQuantity) ] if len(same_day_trades) == 0: return None return same_day_trades[-1]
def idx_of_first_trade_next_30days(self, tradetomatch): ## Return index of first trade done after this trade, with opposite sign, and within 30 days self.date_sort() tradedatetime=tradetomatch.Date listdatetimes=[trade.Date for trade in self] listdates=[x.date() for x in listdatetimes] tradedate30daysafter=trade.Date.date()+datetime.timedelta(30) listsignquant=[trade.SignQuantity for trade in self] ## trades are in next 30 days or today, but not in the past ## and with opposite sign next_30days_trades=[idx for idx in range(len(listdates)) if listdates[idx]<=tradedate30daysafter and listdatetimes[idx]>tradedatetime and not signs_match(listsignquant[idx], tradetomatch.SignQuantity)] if len(next_30days_trades)==0: return None ## Return the first trade return next_30days_trades[0]
def _share_of_trade(self, share=None, pro_rata=None): """ Returns a trade, a clones of self, with quantity share or a pro_rata proportion """ assert not (share is None and pro_rata is None) newtrade=copy(self) oldquantity=copy(self.SignQuantity) if pro_rata is None: assert type(share) is float assert signs_match(share, oldquantity) assert abs(share)<=abs(oldquantity) pro_rata=share/oldquantity if share is None: assert type(pro_rata) is float assert pro_rata>=0.0 and pro_rata<=1.0 share=oldquantity*pro_rata if pro_rata==0.0: newtrade.modify(Value=0.0, Commission=0.0, Tax=0.0, SignQuantity=0.0, Quantity=0.0, ) else: newtrade.modify(Value=self.Value*pro_rata, Commission=self.Commission*pro_rata, Tax=self.Tax*pro_rata, SignQuantity=share, Quantity=abs(share), ) return newtrade
def _proportionate_pop_idx(self, tradeidxlist, totaltopop): """ Reduce all trades in tradeidxlist by a proportion """ old_final_position=copy(self.final_position()) old_trade_count=copy(len(self)) if not all([trade._has_allocation_data() for trade in self]): raise Exception("You can't add spawn pseudo trades without _add_cumulative_data first") original_trades_to_trim=TradeList([self[idx] for idx in tradeidxlist]) total_in_list=original_trades_to_trim.final_position() assert signs_match(totaltopop, total_in_list) pro_rata=totaltopop/total_in_list residual = 1.0 - pro_rata if abs(residual)<THRESHOLD: residual=0.0 pro_rata=1.0 ## Returns list of tuples (parent, child) tradetuplelist=TradeList([tradetopop.spawn_child_trade(pro_rata=pro_rata) for tradetopop in original_trades_to_trim]) ## remove original trades for trade in original_trades_to_trim: ## find matching trade (can't use original indices since will change with size of list) trade_idx=[idx for idx in range(len(self)) if self[idx]==trade] assert len(trade_idx)==1 self.pop(trade_idx[0]) popped_trades=TradeList([tradetuple[1] for tradetuple in tradetuplelist]) assert abs(totaltopop - popped_trades.final_position())<THRESHOLD ## Residual left behind... if residual>0.0: ## Put residual parent trades back in [self.append(tradetuple[0]) for tradetuple in tradetuplelist] assert len(self) == old_trade_count else: ## No residual trades, just pop in their entirity ## We've permanently lost these trades assert (len(self) + len(popped_trades)) == old_trade_count self.date_sort() assert abs(self.final_position()+totaltopop - old_final_position)<THRESHOLD return popped_trades
def _partial_pop_idx(self, tradeidx, maxtopop): """ Pop the trade tradeidx, up to a limit of maxtopop If this trade is too big then leave behind a residual trade Returns the trade """ old_final_position = copy(self.final_position()) old_trade_count = copy(len(self)) if not all([trade._has_allocation_data() for trade in self]): raise Exception( "You can't add spawn pseudo trades without _add_cumulative_data first" ) tradetopop = self[tradeidx] assert signs_match(tradetopop.SignQuantity, maxtopop) if abs(tradetopop.SignQuantity) <= abs(maxtopop): ## Pop the entire trade finaltradetopop = self.pop(tradeidx) assert (len(self) + 1) == old_trade_count else: ## Pop part of the trade, by spawing a child order (parent_trade, finaltradetopop) = tradetopop.spawn_child_trade(share=maxtopop) ## Remove the original trade self.pop(tradeidx) ## Add the residual trade self.append(parent_trade) assert len(self) == old_trade_count self.date_sort() assert abs(self.final_position() + finaltradetopop.SignQuantity - old_final_position) < THRESHOLD return finaltradetopop
def _partial_pop_idx(self, tradeidx, maxtopop): """ Pop the trade tradeidx, up to a limit of maxtopop If this trade is too big then leave behind a residual trade Returns the trade """ old_final_position=copy(self.final_position()) old_trade_count=copy(len(self)) if not all([trade._has_allocation_data() for trade in self]): raise Exception("You can't add spawn pseudo trades without _add_cumulative_data first") tradetopop=self[tradeidx] assert signs_match(tradetopop.SignQuantity, maxtopop) if abs(tradetopop.SignQuantity)<=abs(maxtopop): ## Pop the entire trade finaltradetopop=self.pop(tradeidx) assert (len(self)+1) == old_trade_count else: ## Pop part of the trade, by spawing a child order (parent_trade, finaltradetopop)=tradetopop.spawn_child_trade(share=maxtopop) ## Remove the original trade self.pop(tradeidx) ## Add the residual trade self.append(parent_trade) assert len(self) == old_trade_count self.date_sort() assert abs(self.final_position()+finaltradetopop.SignQuantity - old_final_position)<THRESHOLD return finaltradetopop
def _check_inputs(self): if self.Commission<0.0: raise Exception("can't have negative commssion") if self.Tax<0.0: raise Exception("can't have negative tax") if self.Quantity<0: raise Exception("Quantity can't be negative (you're confusing with SignQuantity") if "SignQuantity" in self.argsused: if "Quantity" in self.argsused and "BS" in self.argsused: checksignquant = self._signed_quantity() if checksignquant!=self.SignQuantity: raise Exception("Signed quantity of %d not consistent with quantity of %d and BS of %s" % (self.SignQuantity, self.Quantity, self.BS)) if "typestring" in self.argsused: assert self.typestring in ["Open", "Close", "OverClose"] if "Value" in self.argsused: if not self.Value==0.0 and self.SignQuantity==0.0: assert not signs_match(self.Value, self.SignQuantity)
def idx_of_last_trade_same_day(self, tradetomatch): ## Return indices of trades with same date, executed prior to this trade, with opposite sign tradedatetime=tradetomatch.Date self.date_sort() tradedate=tradedatetime.date() listdatetimes=[trade.Date for trade in self] listdates=[x.date() for x in listdatetimes] listsignquant=[trade.SignQuantity for trade in self] ## done on same day, but not in future ## Future trades on same day will be picked up in 'within 30 days' rule same_day_trades=[idx for idx in range(len(listdates)) if listdates[idx]==tradedate and listdatetimes[idx]<tradedatetime and not signs_match(listsignquant[idx], tradetomatch.SignQuantity)] if len(same_day_trades)==0: return None return same_day_trades[-1]
def _last_child(self,share=None, pro_rata=None): oldquantity=self.SignQuantity if pro_rata is not None: assert type(pro_rata) is float assert pro_rata>=0.0 and pro_rata<=1.0 if abs(pro_rata - 1.0)<THRESHOLD: return True else: return False if share is not None: assert type(share) is float assert signs_match(share, oldquantity) assert abs(share)<=abs(oldquantity) if abs(share - oldquantity)<THRESHOLD: return True else: return False raise Exception("no share or pro rata")
def _proportionate_pop_idx(self, tradeidxlist, totaltopop): """ Reduce all trades in tradeidxlist by a proportion """ old_final_position = copy(self.final_position()) old_trade_count = copy(len(self)) if not all([trade._has_allocation_data() for trade in self]): raise Exception( "You can't add spawn pseudo trades without _add_cumulative_data first" ) original_trades_to_trim = TradeList( [self[idx] for idx in tradeidxlist]) total_in_list = original_trades_to_trim.final_position() assert signs_match(totaltopop, total_in_list) pro_rata = totaltopop / total_in_list residual = 1.0 - pro_rata if abs(residual) < THRESHOLD: residual = 0.0 pro_rata = 1.0 ## Returns list of tuples (parent, child) tradetuplelist = TradeList([ tradetopop.spawn_child_trade(pro_rata=pro_rata) for tradetopop in original_trades_to_trim ]) ## remove original trades for trade in original_trades_to_trim: ## find matching trade (can't use original indices since will change with size of list) trade_idx = [idx for idx in range(len(self)) if self[idx] == trade] assert len(trade_idx) == 1 self.pop(trade_idx[0]) popped_trades = TradeList( [tradetuple[1] for tradetuple in tradetuplelist]) assert abs(totaltopop - popped_trades.final_position()) < THRESHOLD ## Residual left behind... if residual > 0.0: ## Put residual parent trades back in [self.append(tradetuple[0]) for tradetuple in tradetuplelist] assert len(self) == old_trade_count else: ## No residual trades, just pop in their entirity ## We've permanently lost these trades assert (len(self) + len(popped_trades)) == old_trade_count self.date_sort() assert abs(self.final_position() + totaltopop - old_final_position) < THRESHOLD return popped_trades