コード例 #1
0
    def test_to_string_should_return_date_string(self) -> None:
        # ARRANGE
        none_case: date = None
        invalid_type_case: int = 1
        date_case: date = date(2000, 1, 1)

        # ACT
        ret_none_case: str = DateUtils.to_string(none_case)
        ret_invalid_type_case: str = DateUtils.to_string(invalid_type_case)
        ret_date_case: str = DateUtils.to_string(date_case)

        # ASSERT
        self.assertEqual('', ret_none_case)
        self.assertEqual('', ret_invalid_type_case)
        self.assertEqual('2000-01-01', ret_date_case)
コード例 #2
0
 def is_tmrw_valid(self) -> bool:
     tmrw = date.today() + timedelta(days=1)
     tmrw_as_string: str = DateUtils.to_string(tmrw)
     calendars: List[Calendar] = self.__api.get_calendar(
         tmrw_as_string, tmrw_as_string)
     LogUtils.debug(calendars)
     return (calendars[0].date.to_pydatetime().date() == tmrw)
コード例 #3
0
    def run(self, req: BackTestRunRequest) -> BackTestRunResponse:
        if not req or not req.is_valid_model():
            raise BadRequestException()
        response: BackTestRunResponse = BackTestRunResponse(req)

        # Init Symbols
        symbols: List[SymbolMaster] = self.__get_symbols__(req)

        # Init Prices
        prices: DataFrame = self.__get_prices__(req, symbols)

        # Do Base Preparation
        prices[AppConsts.CUSTOM_COL_PV] = prices[
            AppConsts.PRICE_COL_CLOSE] * prices[AppConsts.PRICE_COL_VOLUME]
        for s in symbols:
            prices = self.__calc_service.append_sma(
                prices=prices,
                index=[s.id],
                sma_period=AppConsts.ADV_PERIOD_DFLT,
                sma_column_name=AppConsts.CUSTOM_COL_ADV,
                target_column=AppConsts.PRICE_COL_VOLUME)
            prices = self.__calc_service.append_sma(
                prices=prices,
                index=[s.id],
                sma_period=AppConsts.ADPV_PERIOD_DFLT,
                sma_column_name=AppConsts.CUSTOM_COL_ADPV,
                target_column=AppConsts.CUSTOM_COL_PV)

        LogUtils.debug('Prices shape after base preparation={0}'.format(
            prices.shape))

        # region Init Service
        strategy_service: Any = self.__stock_service.get_strategy_service(
            req.strategy_type, req.strategy_request, symbols, prices)
        if not strategy_service or not strategy_service._is_valid_request():
            raise BadRequestException()
        strategy_service._do_preparations()

        LogUtils.debug('Prices shape after strategy preparation={0}'.format(
            prices.shape))
        # endregion

        LogUtils.debug(prices.info())

        # region Init Dates
        start_date: date = DateUtils.add_business_days(req.date_from_obj, -1)
        start_date = DateUtils.add_business_days(start_date, 1)
        start_date_str: str = DateUtils.to_string(start_date)
        end_date: date = DateUtils.add_business_days(req.date_to_obj, -1)
        dates: DataFrame = self.__stock_service.get_dates(
            prices, start_date, end_date)

        LogUtils.debug(
            'Dates actual_start={0}, actual_end={1}, shape={2}'.format(
                start_date, end_date, dates.shape))
        # endregion

        # region Loop Dates
        strategy_item: BackTestResultItem = next(
            b for b in response.back_test_result_items
            if b.target == req.strategy_type)
        strategy_item.capital[start_date_str] = req.start_capital
        strategy_item.capital_available[start_date_str] = req.start_capital
        portfolio: Dict = {}

        for i, date_row in dates.iterrows():
            current_date = date_row[AppConsts.PRICE_COL_DATE]
            current_date_str: str = DateUtils.to_string(current_date)
            next_date = date_row[AppConsts.CUSTOM_COL_NEXT_DATE]
            next_date_str = DateUtils.to_string(next_date)
            next_next_date = date_row[AppConsts.CUSTOM_COL_NEXT_NEXT_DATE]

            shuffle(symbols)
            for symbol in symbols:
                has_price: bool = (symbol.id, current_date) in prices.index
                if not has_price:
                    continue
                price: Series = prices.loc[symbol.id, current_date]

                if symbol.instrument == AppConsts.INSTRUMENT_ETF:

                    # region Benchmark
                    b_result_item: BackTestResultItem = next(
                        b for b in response.back_test_result_items
                        if b.target == symbol.symbol)
                    if not b_result_item:
                        continue

                    if not b_result_item.transactions:
                        no_of_shares: int = self.__stock_service.get_no_of_shares(
                            req.start_capital, req.pct_risk_per_trade,
                            req.volume_limit, price, req.slippage)
                        if no_of_shares == 0:
                            LogUtils.warning('0 shares for ETF={0}'.format(
                                symbol.symbol))
                            continue

                        b_transaction: Transaction = Transaction()
                        b_transaction.symbol_master = symbol
                        b_transaction.action = AppConsts.ACTION_BUY
                        b_transaction.start_date = current_date
                        b_transaction.start_price = price.loc[
                            AppConsts.PRICE_COL_OPEN]
                        b_transaction.no_of_shares = no_of_shares
                        b_result_item.transactions.append(b_transaction)
                        b_result_item.capital[
                            current_date_str] = req.start_capital
                    else:
                        b_transaction: Transaction = b_result_item.transactions[
                            0]
                        b_transaction.end_date = current_date
                        b_transaction.end_price = price.loc[
                            AppConsts.PRICE_COL_CLOSE]
                        b_transaction.set_readonly_props()
                        b_result_item.capital[
                            current_date_str] = self.__calc_benchmark_capital(
                                req, b_transaction.start_price,
                                b_transaction.end_price,
                                b_transaction.no_of_shares)
                    b_result_item.ttl_no_days += 1
                    # endregion

                else:

                    # region Strategy
                    strategy_service._do_calculations(symbol.id, current_date)
                    action: str = strategy_service._get_action()

                    is_in_position: bool = symbol.id in portfolio
                    if not is_in_position:

                        if len(portfolio
                               ) == req.portfolio_max:  # todo: prioritize?
                            continue
                        if current_date == end_date or next_date >= end_date:
                            continue
                        has_next_price: bool = (symbol.id,
                                                next_date) in prices.index
                        has_next_next_price: bool = (
                            symbol.id, next_next_date) in prices.index
                        if not has_next_price or not has_next_next_price:
                            continue
                        adv: float = price.loc[
                            AppConsts.CUSTOM_COL_ADV] if price.loc[
                                AppConsts.CUSTOM_COL_ADV] > 0 else price.loc[
                                    AppConsts.PRICE_COL_VOLUME]
                        if adv < req.adv_min:
                            continue
                        adpv: float = price.loc[
                            AppConsts.CUSTOM_COL_ADPV] if price.loc[
                                AppConsts.CUSTOM_COL_ADPV] > 0 else price.loc[
                                    AppConsts.CUSTOM_COL_PV]
                        if adpv < req.adpv_min:
                            continue

                        next_price: Series = prices.loc[symbol.id, next_date]

                        has_entry_conditions: bool = strategy_service._has_entry_conditions(
                            symbol.id, current_date)
                        if has_entry_conditions:

                            no_of_shares: int = self.__stock_service.get_no_of_shares(
                                strategy_item.
                                capital_available[current_date_str],
                                req.pct_risk_per_trade, req.volume_limit,
                                next_price, req.slippage,
                                action == AppConsts.ACTION_BUY)
                            if no_of_shares == 0:
                                continue

                            trans: Transaction = Transaction()
                            trans.symbol_master = symbol
                            trans.action = action
                            trans.start_date = next_date
                            trans.start_price = next_price.loc[
                                AppConsts.PRICE_COL_OPEN]
                            trans.no_of_shares = no_of_shares

                            trans_amount: float = NumberUtils.round(
                                trans.start_price * no_of_shares)
                            strategy_item.capital_available[
                                current_date_str] -= trans_amount

                            # Add to portfolio
                            portfolio[symbol.id] = trans

                    elif is_in_position:

                        has_exit_conditions: bool = strategy_service._has_exit_conditions(
                            symbol.id, current_date)
                        has_next_next_price: bool = (
                            symbol.id, next_next_date) in prices.index
                        if next_date == end_date or not has_next_next_price or has_exit_conditions:

                            next_price: Series = prices.loc[symbol.id,
                                                            next_date]
                            next_open_price: float = next_price.loc[
                                AppConsts.PRICE_COL_OPEN]
                            slippage_price: float = 0
                            if action == AppConsts.ACTION_BUY:
                                slippage_price: float = NumberUtils.round(
                                    next_open_price -
                                    (next_open_price * AppConsts.BASIS_POINT *
                                     req.slippage))
                            else:
                                slippage_price: float = NumberUtils.round(
                                    next_open_price +
                                    (next_open_price * AppConsts.BASIS_POINT *
                                     req.slippage))
                            trans: Transaction = portfolio.get(symbol.id)
                            trans.end_date = next_date
                            trans.end_price = slippage_price
                            trans.set_readonly_props()
                            strategy_item.transactions.append(trans)

                            if action == AppConsts.ACTION_BUY:
                                trans_amount = NumberUtils.round(
                                    trans.end_price * trans.no_of_shares)
                                strategy_item.capital_available[
                                    current_date_str] += trans_amount
                            else:
                                init_trans_amount = NumberUtils.round(
                                    trans.start_price * trans.no_of_shares)
                                strategy_item.capital_available[
                                    current_date_str] += init_trans_amount
                                strategy_item.capital_available[
                                    current_date_str] += trans.change_in_capital

                            # Remove from portfolio
                            portfolio.pop(symbol.id, None)

                    # endregion

            # capital = capital available + capital in portfolio
            capital: float = strategy_item.capital_available[current_date_str]
            for key, val in portfolio.items():
                price: Series = prices.loc[key, current_date]
                capital += price.loc[
                    AppConsts.PRICE_COL_CLOSE] * val.no_of_shares
            strategy_item.capital[current_date_str] = NumberUtils.round(
                capital)
            strategy_item.ttl_no_days += 1
            strategy_item.capital[next_date_str] = strategy_item.capital[
                current_date_str]
            strategy_item.capital_available[
                next_date_str] = strategy_item.capital_available[
                    current_date_str]

        # endregion

        for result_item in response.back_test_result_items:
            result_item.set_readonly_props()
        return response
コード例 #4
0
    def close_positions(self) -> int:
        errors: List[Exception] = []
        try:
            is_tmrw_valid: bool = self.__alpaca_client.is_tmrw_valid()
            if not is_tmrw_valid:
                LogUtils.warning('Tmrw is not a valid trade date')
                raise BadRequestException('Date',
                                          DateUtils.to_string(date.today()))

            req: GetTradeOrdersRequest = GetTradeOrdersRequest()
            req.exact_status = AppConsts.ORDER_STATUS_IN_POSITION
            orders: List[TradeOrderCustom] = self.get_trade_orders(req)

            if not orders:
                LogUtils.debug('No positions')

            for order in orders:
                try:
                    LogUtils.debug('Check position for = {0}'.format(
                        order.symbol_master.symbol))
                    spd: StockPriceDaily = self.__stock_service.get_last_single_stock_price_daily(
                        order.symbol_master.id)
                    if not spd:
                        LogUtils.warning('No Stock Price Found')
                        raise NotFoundException('SPD', 'symbol_id',
                                                order.symbol_master.id)

                    LogUtils.debug('Last close price = {0}'.format(
                        spd.close_price))
                    # is_exit: bool = (spd.close_price > order.trade_order.target_price
                    #                  or spd.close_price < order.trade_order.stop_loss)
                    if True:  # To-do: fix this to use strategy service. is_exit:

                        LogUtils.debug('Close position for = {0}'.format(
                            order.symbol_master.symbol))

                        resp: Order = self.__alpaca_client.submit_order(
                            symbol=order.symbol_master.symbol,
                            qty=order.trade_order.actual_qty,
                            action=AppConsts.ACTION_SELL
                            if order.trade_order.action == AppConsts.ACTION_BUY
                            else AppConsts.ACTION_BUY)
                        if resp:
                            org: TradeOrder = BaseService._get_by_id(
                                TradeOrder, order.trade_order.id)
                            if not org:
                                LogUtils.warning('Order not found.')
                                raise NotFoundException(
                                    'TradeOrder', 'id', order.trade_order.id)
                            org.exit_alpaca_id = resp.id
                            org.status = AppConsts.ORDER_STATUS_SUBMITTED_EXIT
                            org.modified = datetime.now()
                            BaseService._update()
                        else:
                            raise Exception('Close Position Error.')

                except Exception as ex:
                    LogUtils.error('Close Position Error', ex)
                    errors.append(ex)

        except Exception as ex:
            LogUtils.error('Close Position Error', ex)
            errors.append(ex)
        finally:
            self.__email_client.send_html(
                subject=AppConsts.EMAIL_SUBJECT_CLOSE_POSITIONS,
                template_path=AppConsts.TEMPLATE_PATH_CLOSE_POSITIONS,
                model={'errors': errors})
            return 1
コード例 #5
0
    def queue_positions(self) -> int:
        errors: List[Exception] = []
        try:
            is_tmrw_valid: bool = self.__alpaca_client.is_tmrw_valid()
            if not is_tmrw_valid:
                LogUtils.warning('Tmrw is not a valid trade date')
                raise BadRequestException('Date',
                                          DateUtils.to_string(date.today()))

            req: GetTradeOrdersRequest = GetTradeOrdersRequest()

            # If Sunday, check Friday's price.
            today: date = date.today()
            if today.weekday() == AppConsts.WEEKDAY_IDX_SUN:
                today = DateUtils.add_business_days(today, -1)
            req.created = today.strftime('%Y-%m-%d')

            req.exact_status = AppConsts.ORDER_STATUS_INIT
            orders: List[TradeOrderCustom] = self.get_trade_orders(req)

            req_to_ignore: GetTradeOrdersRequest = GetTradeOrdersRequest()
            req_to_ignore.status = [
                AppConsts.ORDER_STATUS_SUBMITTED_ENTRY,
                AppConsts.ORDER_STATUS_IN_POSITION,
                AppConsts.ORDER_STATUS_SUBMITTED_EXIT,
                AppConsts.ORDER_STATUS_CANCELLED_EXIT
            ]
            orders_to_ignore: List[TradeOrderCustom] = self.get_trade_orders(
                req_to_ignore)
            symbols_to_ignore: List[str] = [
                o.symbol_master.symbol for o in orders_to_ignore
            ] if orders_to_ignore else []

            LogUtils.debug('symbols_to_ignore = {0}'.format(symbols_to_ignore))

            if not orders:
                LogUtils.debug('No orders suggested')

            shuffle(orders)
            prioritized_orders: List[TradeOrderCustom] = []
            for order in orders:
                if order.trade_order.strategy == AppConsts.STRATEGY_DOUBLE_BOTTOMS:
                    prioritized_orders.append(order)
            for order in orders:
                if order.trade_order.strategy == AppConsts.STRATEGY_DOUBLE_TOPS:
                    prioritized_orders.append(order)

            accnt: Account = self.__alpaca_client.get_account()
            capital: float = NumberUtils.to_floor(
                NumberUtils.to_float(accnt._raw['buying_power']) /
                2)  # 2 to trade everyday
            for order in prioritized_orders:
                try:
                    LogUtils.debug('Try symbol = {0}'.format(
                        order.symbol_master.symbol))

                    if order.symbol_master.symbol in symbols_to_ignore:
                        LogUtils.debug('Ignore for = {0}'.format(
                            order.symbol_master.symbol))
                        continue

                    cost: float = NumberUtils.to_float(
                        order.stock_price_daily.close_price *
                        order.trade_order.qty)

                    if cost > capital:
                        LogUtils.debug('Too expensive for = {0}'.format(
                            order.symbol_master.symbol))
                        continue
                    capital = capital - cost

                    resp: Order = self.__alpaca_client.submit_order(
                        symbol=order.symbol_master.symbol,
                        qty=order.trade_order.qty,
                        action=order.trade_order.action)
                    if resp:
                        org: TradeOrder = BaseService._get_by_id(
                            TradeOrder, order.trade_order.id)
                        if not org:
                            raise NotFoundException('TradeOrder', 'id',
                                                    order.trade_order.id)
                        org.alpaca_id = resp.id
                        org.status = AppConsts.ORDER_STATUS_SUBMITTED_ENTRY
                        org.order_type = AppConsts.ORDER_TYPE_MARKET
                        org.time_in_force = AppConsts.TIME_IN_FORCE_DAY
                        org.modified = datetime.now()
                        BaseService._update()

                except Exception as ex:
                    LogUtils.error('Queue Position Error', ex)
                    errors.append(ex)

        except Exception as ex:
            LogUtils.error('Queue Position Error', ex)
            errors.append(ex)
        finally:
            self.__email_client.send_html(
                subject=AppConsts.EMAIL_SUBJECT_QUEUE_POSITIONS,
                template_path=AppConsts.TEMPLATE_PATH_QUEUE_POSITIONS,
                model={'errors': errors})
            return 1