Example #1
0
 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'))
Example #2
0
    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()
Example #3
0
 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
Example #4
0
    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()
Example #5
0
 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
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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
Example #9
0
    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
Example #10
0
    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
Example #11
0
    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