def update_position_values(self): ''' update position cost and market value given the quantities and cursor bar price info ''' # remove all rows with 0 holding quantity (and nothing on the way) self.cursor_positions = self.cursor_positions.loc[(self.cursor_positions['quantity']!=0) | (self.cursor_positions['unsettled']!=0)\ |(self.cursor_positions['ts_code']=='cash')] # cash value of cash is the quatity of cash self.cursor_positions['value'] = np.where( self.cursor_positions['ts_code'] == 'cash', self.cursor_positions['quantity'], self.cursor_positions['value']) # latest market value of the holdings is calculated and updated for symbol in self.cursor_positions['ts_code']: if symbol != 'cash': try: price = libs.toPrec( self.bars.cursorBar.loc[self.bars.cursorBar['ts_code'] == symbol]['close'].values[0], 2, 'r') except: price = np.nan if price is not np.nan: # self.cursor_positions.loc[self.cursor_positions['ts_code']==symbol,'value'] = \ # libs.toPrec(price * self.cursor_positions.loc[self.cursor_positions['ts_code']==symbol]['quantity'],2,'r') self.cursor_positions.loc[self.cursor_positions['ts_code']==symbol,'value'] = \ price * self.cursor_positions.loc[self.cursor_positions['ts_code']==symbol]['quantity'] self.cursor_positions['value'] = self.cursor_positions[ 'value'].apply(lambda x: libs.toPrec(x, 2, 'r'))
def update_positions_from_fill(self, fill): # determine buy or sell coefficient, buy is + sell is minus, naive strategy will have a coeffecient of 1 fill_dir = 0 if fill.direction == 'BUY': fill_dir = 1 self.sizing['crypto_locked'] = 0 if fill.direction == 'SELL': fill_dir = -1 delta_cash = fill_dir * fill.fill_cost # if there is already a holding pisition in the portfolio if len(self.cursor_positions.loc[self.cursor_positions['ts_code'] == fill.symbol]) > 0: quantity_held = self.cursor_positions.loc[ self.cursor_positions['ts_code'] == fill.symbol]['quantity'].values[0] cost_held = self.cursor_positions.loc[ self.cursor_positions['ts_code'] == fill.symbol]['cost'].values[0] delta_quantity = libs.toPrec(fill_dir * fill.quantity, fill.fill_quantprec, 'd') tobe_quantity = quantity_held + delta_quantity new_cost = (cost_held * quantity_held + delta_cash ) / tobe_quantity if tobe_quantity != 0 else None new_cost = libs.toPrec(new_cost, 2, 'u') if fill.direction == 'BUY': self.cursor_positions.loc[self.cursor_positions['ts_code'] == 'cash', 'quantity'] += -delta_cash self.cursor_positions.loc[self.cursor_positions['ts_code'] == fill.symbol, 'quantity'] += delta_quantity # settling holding quanty self.cursor_positions.loc[self.cursor_positions['ts_code'] == fill.symbol, 'unsettled'] += -delta_quantity self.cursor_positions.loc[self.cursor_positions['ts_code'] == fill.symbol, 'available'] += delta_quantity if fill.direction == 'SELL': self.cursor_positions.loc[self.cursor_positions['ts_code'] == 'cash', 'quantity'] += -delta_cash self.cursor_positions.loc[self.cursor_positions['ts_code'] == 'cash', 'available'] += -delta_cash # settling holding quanty self.cursor_positions.loc[self.cursor_positions['ts_code'] == fill.symbol, 'quantity'] += delta_quantity self.cursor_positions.loc[self.cursor_positions['ts_code'] == fill.symbol, 'unsettled'] += -delta_quantity else: # new line item should have already been added to portfolio when order generated pass self.update_position_values()
def calculate_signals(self, marketevent) -> list: listOfEvent = [] if marketevent.type == 'MARKET' or marketevent.type == 'POLL': for secObj in self.sec_pool: if secObj.cursorBar['vol'].values[0] != 0: '''-----conditions for LONG-----''' if 1 == 1: # if secObj.cursorBar['open'].values[0] < secObj.cursorBar['pre_close'].values[0]: price = libs.toPrec(secObj.cursorBar['open'].values[0], 2) # 开盘价买入 assert self.position[0]['sec_obj'].ts_code == 'cash' cash_available = self.position[0]['available'] mkt_quantity = cash_available / ( price * (1 + secObj.commission) ) if cash_available > 0 else 0 mkt_quantity = libs.toPrec(mkt_quantity, secObj.tradeunit, 'd') if mkt_quantity > 0: listOfEvent.append( SignalEvent(datetime=marketevent.datetime, secObj=secObj, position=self.position, signal_type='LONG:{}@{}'.format( mkt_quantity, price), signal_src=self.name)) print( 'signal generated by {} to LONG:{}@{}'.format( self.name, mkt_quantity, price)) '''-----conditions for SHORT-----''' if 1 == 2: price = libs.toPrec( secObj.cursorBar['close'].values[0], 2) sec_available = 0 for holding in self.position: if holding['sec_obj'].ts_code == secObj.ts_code: sec_available = holding['available'] if sec_available > 0: listOfEvent.append( SignalEvent(datetime=marketevent.datetime, secObj=secObj, position=self.position, signal_type='SHORT:{}@{}'.format( sec_available, price), signal_src=self.name)) print( 'signal generated by {} to SHORT:{}@{}'.format( self.name, sec_available, price)) break # only react on the 1st sec in the sec pool if the pool has more than 1 else: print('{} skipped reaction on {} as non trade date:{}'. format(self.name, secObj.sec_name, marketevent.datetime)) return listOfEvent
def _update_allbars(self): if self.exists == False: return None if self.allBars is None: return self._get_all_bars() else: # print(self.allBars.tail(1)['unit_intdate']) assert self.allBars.tail(1)['unit_intdate'].values[ 0] == self._heartbeat._df_unitAxis.tail( 1)['unit_intdate'].values[0] # 输入时间序列中带有未来日期,则取当下能取到的最新online数据模拟 if self.cursorTick['timeAxis'] > self.cursorTick[ 'timeAxis'].replace( hour=0, minute=0, second=0, microsecond=0): # if self.cursorTick['unit_intdate'] >= self._heartbeat._df_unitAxis.tail(1)['unit_intdate'].values[0]: df_liveQuote = self.get_livebar( assign_timestamp=self.cursorTick['timeAxis'], assign_unitdate=self._heartbeat._df_unitAxis.tail( 1)['unit_intdate'].values[0]) if len(df_liveQuote) > 0 and self._heartbeat.freq == '1d': for col in ['open', 'high', 'low', 'close' ]: # convert price columns to Decimal type df_liveQuote[col] = df_liveQuote[col].apply( lambda x: libs.toPrec(x, 2)) self.allBars = self.allBars[:-1].append(df_liveQuote, ignore_index=True, sort=False) # if freq is not 1d, then the whole allBars table is to be regenerated because resampling is needed if self._heartbeat.freq != '1d': self.allBars = self._get_all_bars()
def execute_order(self, event): if event.type == 'ORDER': if event.order_type == 'MKT': unit_price = libs.toPrec( self.bars.cursorBar.loc[self.bars.cursorBar['ts_code'] == event.symbol]['close'].values[0], 2, 'r') fill_cost = libs.toPrec( event.quantity * unit_price * (1 + self.commisions['crypto']), 2, 'r') # print('debug: code={}, unit_price={}, quantity={}'.format(event.symbol,unit_price,event.quantity)) fill_event = FillEvent(datetime.utcnow(), event.symbol, 'crypto', event.quantity, event.direction, fill_cost, self.quant_prec['crypto']) self.events.put(fill_event) print('MKT order of {} - {} {} executed by broker...'.format( event.symbol, event.direction, event.quantity)) elif event.order_type == 'LMT': pass
def score_abs_gain(self, target_pos): def popValues(pos): if pos.fname is not None: fname = pos.fname else: fname = '{}{}.csv'.format(cfg.PATH_POSFILE, pos.name) df = pd.read_csv(fname, encoding=cfg.FILE_ENCODE) df = df.groupby('timeindex').agg({'market_value': 'sum'}) lst_values = df['market_value'].values.tolist() return lst_values lst_targetvalues = popValues(target_pos) lst_result = list( map(lambda x: Decimal(str(x)) - target_pos.initialcash, lst_targetvalues)) gainPerDay = libs.toPrec(sum(lst_result) / len(lst_result), 0) return gainPerDay
def score_relative_gain(self, benchmark_pos, target_pos): def popValues(pos): if pos.fname is not None: fname = pos.fname else: fname = '{}{}.csv'.format(cfg.PATH_POSFILE, pos.name) df = pd.read_csv(fname, encoding=cfg.FILE_ENCODE) df = df.groupby('timeindex').agg({'market_value': 'sum'}) lst_values = df['market_value'].values.tolist() return lst_values lst_benchvalues = popValues(benchmark_pos) lst_targetvalues = popValues(target_pos) assert len(lst_benchvalues) == len(lst_targetvalues) lst_result = list( map(lambda x, y: x - y, lst_targetvalues, lst_benchvalues)) gainPerDay = libs.toPrec(sum(lst_result) / len(lst_result), 0) return gainPerDay
def calculate_signals(self, marketevent) -> list: listOfEvent = [] # supporting conditions if self.sec_pool[0]._heartbeat.freq != '1d': print(f'{self.name} works only with freq=1d, skipping...') return listOfEvent if marketevent.type == 'MARKET' or marketevent.type == 'POLL': camparedate = self.sec_pool[0]._dataagent.int_findOffsetDate( self.sec_pool[0].cursorTick['unit_intdate'], self.period, self.sec_pool[0]._heartbeat._df_unitAxis) if camparedate is None: print( f'not on trade on {camparedate} from {self.period} trade days ago' ) return listOfEvent for secObj in self.sec_pool: # 如果无n日前的数据 if not self.dict_comparegrowth: break if secObj.cursorBar['vol'].values[ 0] == 0: # if the sec is not on trade today # input(f'!!!!!!! {secObj.ts_code} is not on trade today, press enter to skip signal generation...') print( f'!!!!!!! {secObj.ts_code} is not on trade today, press enter to skip signal generation...' ) continue '''-----conditions for SHORT-----''' if self.dict_comparegrowth[ secObj.ts_code][1] != 1 and self.dict_comparegrowth[ secObj.ts_code][0] < self.neg_threshold: price = libs.toPrec(secObj.cursorBar['open'].values[0], 2) # 开盘价 sec_available = 0 for holding in self.position: if holding['sec_obj'].ts_code == secObj.ts_code: sec_available = holding['available'] if sec_available > 0: listOfEvent.append( SignalEvent(datetime=marketevent.datetime, secObj=secObj, position=self.position, signal_type='SHORT:{}@{}'.format( sec_available, price), signal_src=self.name)) print('signal generated by {} to SHORT:{}@{}'.format( self.name, sec_available, price)) '''-----conditions for LONG-----''' if self.dict_comparegrowth[ secObj.ts_code][1] == 1 and self.dict_comparegrowth[ secObj.ts_code][0] > self.pos_threshold: price = libs.toPrec(secObj.cursorBar['open'].values[0], 2) # 开盘价买入 if price == 0: # means the ETF product still not available yet despite of the existence of anchoring index print( 'cannot LONG {} because the ETF does not exist yet...' .format(secObj.ts_code)) continue assert self.position[0]['sec_obj'].ts_code == 'cash' cash_available = self.position[0]['available'] mkt_quantity = cash_available / ( price * (1 + secObj.commission)) if cash_available > 0 else 0 mkt_quantity = libs.toPrec(mkt_quantity, secObj.tradeunit, 'd') if mkt_quantity > 0: listOfEvent.append( SignalEvent(datetime=marketevent.datetime, secObj=secObj, position=self.position, signal_type='LONG:{}@{}'.format( mkt_quantity, price), signal_src=self.name)) print('signal generated by {} to LONG:{}@{}'.format( self.name, mkt_quantity, price)) # if Poll event, no need to update dict_comparegrowth as it is meant to be used in next trade dau if marketevent.type != 'POLL': valuelist = [] self.dict_comparegrowth = None for secObj in self.sec_pool: if secObj.anchorObj is None: secObj.anchorObj = secObj print( f'no achoring intrument found for sec {secObj.sec_name}, using itself to simulate...' ) currentprice = secObj.anchorObj.cursorBar['close'].values[ 0] compareprice = secObj.anchorObj.allBars.loc[ secObj.anchorObj.allBars['unit_intdate'] == camparedate]['close'].values[0] if compareprice == 0: # if any of the n-days-ago prices is 0, means the index did not existed hence the whole strategy is invalid print( 'anchor sec {} did not exist on {}, 28Turn strategy skipped reaction' .format(secObj.anchorObj.sec_name, camparedate)) break else: growth_rate = (currentprice - compareprice) / compareprice valuelist.append([secObj.ts_code, growth_rate]) else: valuelist.sort(key=lambda x: x[1], reverse=True) rank = 1 valuelist[0].append(1) for i in range(1, len(valuelist)): if valuelist[i][1] != valuelist[i - 1][1]: rank += 1 valuelist[i].append(rank) self.dict_comparegrowth = {x[0]: x[1:] for x in valuelist} print(f'{self.name} calculated ranking is:', self.dict_comparegrowth) return listOfEvent
def _get_all_bars(self): if self.exists == False: return None # getallbar必须获取所有历史数据而不是时间轴参数所涵盖时间,因为processbar方法中有些指标需要全历史才可计算,如有效突破点等 # load up all bars from local repository df_result = self._dataagent.query('load_alldaily',ts_code=self.ts_code,tpl_dateindex=None) df_result['timeAxis'] = pd.to_datetime(df_result['trade_date'], format='%Y%m%d') latestTimeTick = self._heartbeat._df_unitAxis[-1:] lastBarData = df_result.loc[df_result['timeAxis']==df_result['timeAxis'].max()] # 输入时间序列中带有未来日期,则取当下能取到的最新online数据模拟 if latestTimeTick['timeAxis'].values[0] > lastBarData['timeAxis'].values[0]: # df_liveQuote = self.get_livebar(assign_timestamp=self.cursorTick['timeAxis']) df_liveQuote = self.get_livebar(assign_timestamp=pd.to_datetime(latestTimeTick['timeAxis'].values[0])) if len(df_liveQuote) > 0: df_result = df_result.append(df_liveQuote, ignore_index = True, sort=False) # resample the dataset if time unit is not 1 day if self._heartbeat.freq != '1d': df_result = df_result.resample(self._heartbeat.freq,on='timeAxis').agg({'trade_date':'last','ts_code':'first', 'open':'first','high':'max','low':'min','close':'last','vol':'sum','amount':'sum'}).reset_index() df_result['pre_close'] = df_result['close'].shift(1) df_result['pre_close'].fillna(df_result['close'],inplace=True) df_result = pd.merge(self._heartbeat.df_unitAxis, df_result,left_on=['timeAxis'], right_on=['timeAxis'],how='left',suffixes=['','_1']) df_result['sec_type'] = self.sec_type df_result['sec_market'] = self.ts_market # normalising missing intervals after left join with calendar dates by filling in the blanks df_result['ts_code'].fillna(value=self.ts_code,inplace=True) df_result['trade_date'].fillna(method='ffill',inplace=True) df_result['close'].fillna(method='ffill',inplace=True) # poplulate all remaining price columns with close price, note the very first row will be all 0's if lies on a non-trade date df_result[['close','open','high','low']] = df_result[['close','open','high','low']].ffill(axis=1) # fill all NA's in value column with 0, this is to ensure time index bars from before the security existed populate with data # if the sec did not exist yet on the time index, trade date and all value column will show 0, which is more logical for valueCol in ['trade_date','open','high','low','close','vol','amount','pre_close']: df_result[valueCol].fillna(value=0,inplace=True) # libs.df_csv(cfg.PATH_BUGFILE,(df_result,)) df_result = df_result.bfill(axis=1).ffill(axis=1) df_result = df_result.astype(dtype= {'unit_intdate':'int64','trade_date':'int64','high':'float', 'low':'float','open':'float','close':'float'}) df_result['candle_size'] = (df_result['high'] - df_result['low'])/df_result['close'].shift(1) df_result['box_size'] = (df_result['close'] - df_result['open'])/df_result['close'].shift(1) df_result['upper_stick'] = (df_result['high'] - df_result[['open', 'close']].max(axis=1))/df_result['close'].shift(1) df_result['lower_stick'] = (df_result[['open', 'close']].min(axis=1)-df_result['low'])/df_result['close'].shift(1) roundCol = ['candle_size','box_size','upper_stick','box_size','lower_stick'] df_result[roundCol] = df_result[roundCol].round(3) # 对全集数据按证券类型切片并赋值 df_result['klineSML']=self._dataagent.classifyKline(df_result,self._dataagent.binRangeidx,'idx') '''价格及交易额均线''' df_result['MA_S'] = df_result['close'].rolling(window=5).mean() df_result['MA_M'] = df_result['close'].rolling(window=10).mean() df_result['MA_L'] = df_result['close'].rolling(window=21).mean() df_result['MA_Y'] = df_result['close'].rolling(window=250).mean() df_result['VolMA_S'] = df_result['vol'].rolling(window=5).mean() df_result['VolMA_M'] = df_result['vol'].rolling(window=10).mean() df_result['VolMA_L'] = df_result['vol'].rolling(window=21).mean() '''''' # libs.df_csv(cfg.PATH_BUGFILE,(df_result,)) for col in ['open','high','low','close']: # convert price columns to Decimal type df_result[col] = df_result[col].apply(lambda x: libs.toPrec(x,2)) return df_result
def execute_order(self, orderevent) -> FillEvent: if orderevent.type == 'ORDER': fill_event = FillEvent( datetime=orderevent.datetime, secObj=orderevent.secObj, position=orderevent.position, quantity=orderevent.quantity, direction=orderevent.direction, fill_cost=None) # cost none means fill order failed assert orderevent.secObj.cursorBar['vol'].values[ 0] > 0 # if equals 0 means it is not a trade day high = libs.toPrec(orderevent.secObj.cursorBar['high'].values[0], 2) low = libs.toPrec(orderevent.secObj.cursorBar['low'].values[0], 2) open = libs.toPrec(orderevent.secObj.cursorBar['open'].values[0], 2) close = libs.toPrec(orderevent.secObj.cursorBar['close'].values[0], 2) pre_close = libs.toPrec( orderevent.secObj.cursorBar['pre_close'].values[0], 2) isceiling = all([high == low, high > pre_close]) #判断是否涨停一字板 isfloor = all([high == low, high < pre_close]) #判断师傅跌停一字板 # ordertime = datetime.now().astimezone(timezone(cfg.STR_TIMEZONE)).replace(tzinfo=None) if orderevent.order_type == 'MKT': if all([not (isceiling), orderevent.direction == 'BUY']): fill_cost = libs.toPrec( orderevent.quantity * high * (1 + orderevent.secObj.commision), 2, 'r' ) # aggressive buy default to use highest price of the day fill_event = FillEvent(datetime=orderevent.datetime, secObj=orderevent.secObj, position=orderevent.position, quantity=orderevent.quantity, direction=orderevent.direction, fill_cost=fill_cost) elif all([not (isfloor), orderevent.direction == 'SELL']): fill_cost = libs.toPrec( orderevent.quantity * low * (1 - orderevent.secObj.commision), 2, 'r' ) # aggressive sell default to use lowest price of the day fill_event = FillEvent(datetime=orderevent.datetime, secObj=orderevent.secObj, position=orderevent.position, quantity=orderevent.quantity, direction=orderevent.direction, fill_cost=fill_cost) else: print('MKT order cannot be fulfilled...') elif orderevent.order_type == 'LMT': if all([ not (isceiling), orderevent.direction == 'BUY', orderevent.price >= low ]): fill_cost = libs.toPrec( orderevent.quantity * orderevent.price * (1 + orderevent.secObj.commission), 2, 'r') fill_event = FillEvent(datetime=orderevent.datetime, secObj=orderevent.secObj, position=orderevent.position, quantity=orderevent.quantity, direction=orderevent.direction, fill_cost=fill_cost) elif all([ not (isfloor), orderevent.direction == 'SELL', orderevent.price <= high ]): fill_cost = libs.toPrec( orderevent.quantity * orderevent.price * (1 - orderevent.secObj.commission), 2, 'r') fill_event = FillEvent(datetime=orderevent.datetime, secObj=orderevent.secObj, position=orderevent.position, quantity=orderevent.quantity, direction=orderevent.direction, fill_cost=fill_cost) else: print('LMT order cannot be fulfilled...') return fill_event self.timeindex = timeindex self.secObj = secObj self.quantity = quantity self.direction = direction self.fill_cost = fill_cost
def generate_order(self, signal, order_type='MKT'): order = None curPrice = libs.toPrec( self.bars.cursorBar.loc[self.bars.cursorBar['ts_code'] == signal.symbol]['close'].values[0], 2, 'r') secType = self.bars.cursorBar.loc[self.bars.cursorBar['ts_code'] == signal.symbol]['sec_type'].values[0] commissionfee = Brokerage.commisions[secType] quantPrec = Brokerage.quant_prec[secType] symbol = signal.symbol direction = signal.signal_type # calculate valid sizing based on locked percentage sizing = self.sizing[ 'crypto'] - self.sizing['crypto'] * self.sizing['crypto_locked'] # sizing = self.sizing['crypto'] cash_position = self.cursor_positions.loc[ self.cursor_positions['ts_code'] == 'cash']['quantity'].values[0] cash_available = self.cursor_positions.loc[ self.cursor_positions['ts_code'] == 'cash']['available'].values[0] crypto_position = self.cursor_positions.loc[ self.cursor_positions['ts_code'] != 'cash']['value'].sum() total_position = cash_position + crypto_position if direction == 'LONG': sizing_residual = max( libs.toPrec( (total_position * sizing), 2, 'd') - crypto_position, 0) cash_tospend = min(sizing_residual, cash_available) mkt_quantity = cash_tospend / ( curPrice * (1 + commissionfee)) if cash_tospend > 0 else 0 mkt_quantity = libs.toPrec(mkt_quantity, quantPrec, 'd') # print('debug: code={}, unit_price={}, quantity={}'.format(symbol,curPrice,mkt_quantity)) if mkt_quantity > 0: self.cursor_positions.loc[self.cursor_positions['ts_code'] == 'cash', 'available'] += -cash_tospend if len(self.cursor_positions.loc[ self.cursor_positions['ts_code'] == symbol]) == 0: record = pd.DataFrame.from_dict({ 'ts_code': [symbol], 'quantity': [0], 'available': [0], 'unsettled': [mkt_quantity], 'cost': [0], 'value': [0], 'trade_date': [self.bars.cursorTime.trade_date] }) self.cursor_positions = pd.concat( [self.cursor_positions, record]) else: self.cursor_positions.loc[ self.cursor_positions['ts_code'] == symbol, 'unsettled'] += mkt_quantity # locking the sizing, to unlock/release it when BUY order is fullfiled self.sizing['crypto_locked'] = 1 order = OrderEvent(symbol, order_type, mkt_quantity, 'BUY') print('order generated to BUY {} using cash {}'.format( symbol, cash_tospend)) else: print( 'cannot LONG {} because - sizing remaining: {}, cash remaining: {}' .format(signal.symbol, sizing_residual, cash_available)) elif direction == 'SHORT': try: mkt_quantity = self.cursor_positions.loc[ self.cursor_positions['ts_code'] == signal.symbol]['available'].values[0] except: mkt_quantity = 0 if mkt_quantity > 0: self.cursor_positions.loc[self.cursor_positions['ts_code'] == signal.symbol, 'available'] += -mkt_quantity self.cursor_positions.loc[self.cursor_positions['ts_code'] == signal.symbol, 'unsettled'] += -mkt_quantity order = OrderEvent(symbol, order_type, mkt_quantity, 'SELL') print('order generated to SELL {} of {}'.format( symbol, mkt_quantity)) else: print('cannot SHORT {} because no holding available'.format( signal.symbol)) return order