Пример #1
0
    def test_get_date_from_epoch_should_return_appropriately(self) -> None:
        # ARRANGE
        date_2020_07_15_4_utc: datetime = datetime(2020,
                                                   7,
                                                   15,
                                                   4,
                                                   tzinfo=tz.tzutc())
        date_2020_07_16_4_utc: datetime = datetime(2020,
                                                   7,
                                                   16,
                                                   4,
                                                   tzinfo=tz.tzutc())
        date_2020_07_16_0_ny: datetime = datetime(2020,
                                                  7,
                                                  16,
                                                  0,
                                                  tzinfo=tz.gettz(
                                                      AppConsts.TZ_NY))
        seconds_2020_07_15_4_utc: int = 1594785600
        seconds_2020_07_16_4_utc: int = 1594872000

        # ACT
        ret_2020_07_15_4_utc: datetime = DateUtils.get_date_from_epoch(
            seconds_2020_07_15_4_utc)
        ret_2020_07_16_4_utc: datetime = DateUtils.get_date_from_epoch(
            seconds_2020_07_16_4_utc)
        ret_2020_07_16_0_ny: datetime = DateUtils.get_date_from_epoch(
            seconds_2020_07_16_4_utc, AppConsts.TZ_NY)

        # ASSERT
        self.assertEqual(date_2020_07_15_4_utc, ret_2020_07_15_4_utc)
        self.assertEqual(date_2020_07_16_4_utc, ret_2020_07_16_4_utc)
        self.assertEqual(date_2020_07_16_0_ny, ret_2020_07_16_0_ny)
Пример #2
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)
Пример #3
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)
Пример #4
0
    def import_from_csv_yahoo(self) -> str:
        BaseService._truncate(EPD)

        files: List[str] = FileUtils.get_files(AppConsts.ETF_PRICE_FOLDER,
                                               is_full=True)
        for file in files:
            symbol: str = FileUtils.get_wo_ext(FileUtils.get_base_name(file))
            org_symbol: SM = self.__stock_service.get_symbol(
                symbol, AppConsts.INSTRUMENT_ETF)
            if not org_symbol:
                continue
            records: List[Dict] = CsvUtils.parse_as_dict(file)
            models: List[Any] = []
            for record in records:
                models.append((
                    org_symbol.id,
                    DateUtils.get_date(record.get(AppConsts.YAHOO_KEY_DATE),
                                       AppConsts.YAHOO_DATE_FORMAT),
                    NumberUtils.to_float(record.get(AppConsts.YAHOO_KEY_OPEN)),
                    NumberUtils.to_float(record.get(AppConsts.YAHOO_KEY_HIGH)),
                    NumberUtils.to_float(record.get(AppConsts.YAHOO_KEY_LOW)),
                    NumberUtils.to_float(record.get(
                        AppConsts.YAHOO_KEY_CLOSE)),
                    NumberUtils.to_int(record.get(AppConsts.YAHOO_KEY_VOLUME)),
                ))
            BaseService._insert_bulk(EPD, models)
        return "1"
Пример #5
0
 def import_from_csv_filedates(self) -> str:
     file: str = FileUtils.get_file(AppConsts.INCOME_STMT_FILE)
     records: List[Dict] = CsvUtils.parse_as_dict(file)
     for record in records:
         if not self.__is_valid_intrinio_record_for_filedates(record):
             continue
         symbol: str = record.get(AppConsts.INTRINIO_KEY_INC_STMT_TICKER)
         fiscal_period: str = record.get(
             AppConsts.INTRINIO_KEY_INC_STMT_FISC_PD)
         year: int = NumberUtils.to_int(
             record.get(AppConsts.INTRINIO_KEY_INC_STMT_FISC_YR))
         quarter: int = self.__get_quarter(fiscal_period)
         file_date: date = DateUtils.get_date(
             record.get(AppConsts.INTRINIO_KEY_INC_STMT_FILE_DTE),
             AppConsts.INTRINIO_FILE_DTE_FMT)
         if not file_date or quarter == 4:
             continue
         org_symbol: SM = self.__stock_service.get_symbol(
             symbol, AppConsts.INSTRUMENT_STOCK)
         if not org_symbol:
             continue
         org_fn: FN = self.__stock_service.get_financial(
             org_symbol.id, year, quarter)
         if not org_fn:
             continue
         org_fn.file_date = file_date
         BaseService._update()
     return "1"
Пример #6
0
    def test_get_date_should_not_return_date_if_invalid_request(self) -> None:
        # ARRANGE
        none_datestr_case: str = None
        empty_datestr_case: str = ''
        whitespace_datestr_case: str = ' '
        invalid_datestr_case: str = 'foo'
        invalid_datestr_type_case: int = 1
        none_fmt_case: str = None
        empty_fmt_case: str = ''
        whitespace_fmt_case: str = ' '
        invalid_fmt_case: str = 'bar'
        invalid_fmt_type_case: int = 1

        # ACT
        ret_none_datestr_case: date = DateUtils.get_date(
            none_datestr_case, 'fake')
        ret_empty_datestr_case: date = DateUtils.get_date(
            empty_datestr_case, 'fake')
        ret_whitespace_datestr_case: date = DateUtils.get_date(
            whitespace_datestr_case, 'fake')
        ret_invalid_datestr_case: date = DateUtils.get_date(
            invalid_datestr_case, '%Y-%m-%d')
        ret_invalid_datestr_type_case: date = DateUtils.get_date(
            invalid_datestr_type_case, '%Y-%m-%d')
        ret_none_fmt_case: date = DateUtils.get_date('fake', none_fmt_case)
        ret_empty_fmt_case: date = DateUtils.get_date('fake', empty_fmt_case)
        ret_whitespace_fmt_case: date = DateUtils.get_date(
            'fake', whitespace_fmt_case)
        ret_invalid_fmt_case: date = DateUtils.get_date(
            '2001-01-01', invalid_fmt_case)
        ret_invalid_fmt_type_case: date = DateUtils.get_date(
            '2001-01-01', invalid_fmt_type_case)

        # ASSERT
        self.assertIsNone(ret_none_datestr_case)
        self.assertIsNone(ret_empty_datestr_case)
        self.assertIsNone(ret_whitespace_datestr_case)
        self.assertIsNone(ret_invalid_datestr_case)
        self.assertIsNone(ret_invalid_datestr_type_case)
        self.assertIsNone(ret_none_fmt_case)
        self.assertIsNone(ret_empty_fmt_case)
        self.assertIsNone(ret_whitespace_fmt_case)
        self.assertIsNone(ret_invalid_fmt_case)
        self.assertIsNone(ret_invalid_fmt_type_case)
Пример #7
0
    def test_get_diff_should_return_timedelta_if_valid_request(self) -> None:
        # ARRANGE
        first_date: date = date(2001, 1, 1)
        second_date: date = date(2003, 1, 1)
        expected_timedelta: timedelta = first_date - second_date

        # ACT
        ret_actual: timedelta = DateUtils.get_diff(first_date, second_date)

        # ASSERT
        self.assertEqual(expected_timedelta, ret_actual)
Пример #8
0
    def test_get_diff_should_return_none_if_invalid_request(self) -> None:
        # ARRANGE
        normal_date: date = date(2001, 1, 1)
        none_first_date: date = None
        none_second_date: date = None
        invalid_type_first_date: int = 1
        invalid_type_second_date: int = 1

        # ACT
        ret_both_none_case: date = DateUtils.get_diff(none_first_date,
                                                      none_second_date)
        ret_first_none_case: date = DateUtils.get_diff(none_first_date,
                                                       normal_date)
        ret_second_none_case: date = DateUtils.get_diff(
            normal_date, none_second_date)
        ret_both_invalid_type_case: date = DateUtils.get_diff(
            invalid_type_first_date, invalid_type_second_date)
        ret_invalid_type_first_case: date = DateUtils.get_diff(
            invalid_type_first_date, normal_date)
        ret_invalid_type_second_case: date = DateUtils.get_diff(
            normal_date, invalid_type_second_date)

        # ASSERT
        self.assertIsNone(ret_both_none_case)
        self.assertIsNone(ret_first_none_case)
        self.assertIsNone(ret_second_none_case)
        self.assertIsNone(ret_invalid_type_first_case)
        self.assertIsNone(ret_invalid_type_second_case)
Пример #9
0
    def import_from_csv_incomestatements(self) -> str:
        BaseService._truncate(FN)

        file: str = FileUtils.get_file(AppConsts.INCOME_STMT_FILE)
        records: List[Dict] = CsvUtils.parse_as_dict(file)
        models: List[Any] = []
        for record in records:
            if not self.__is_valid_intrinio_record(record):
                continue
            symbol: str = record.get(AppConsts.INTRINIO_KEY_INC_STMT_TICKER)
            fiscal_period: str = record.get(
                AppConsts.INTRINIO_KEY_INC_STMT_FISC_PD)
            org_symbol: SM = self.__stock_service.get_symbol(
                symbol, AppConsts.INSTRUMENT_STOCK)
            if not org_symbol:
                continue
            quarter_end_dte: date = DateUtils.get_date(
                record.get(AppConsts.INTRINIO_KEY_INC_STMT_END_DTE),
                AppConsts.INTRINIO_END_DTE_FMT)
            file_date: date = DateUtils.get_date(
                record.get(AppConsts.INTRINIO_KEY_INC_STMT_FILE_DTE),
                AppConsts.INTRINIO_FILE_DTE_FMT)
            models.append(
                (org_symbol.id,
                 record.get(AppConsts.INTRINIO_KEY_INC_STMT_FISC_YR),
                 self.__get_quarter(fiscal_period), quarter_end_dte, file_date,
                 None,
                 NumberUtils.to_float(
                     record.get(AppConsts.INTRINIO_KEY_INC_STMT_TTLREV)),
                 NumberUtils.to_float(
                     record.get(AppConsts.INTRINIO_KEY_INC_STMT_TTLPROF)),
                 NumberUtils.to_float(
                     record.get(AppConsts.INTRINIO_KEY_INC_STMT_TTLOPINC)),
                 NumberUtils.to_float(
                     record.get(AppConsts.INTRINIO_KEY_INC_STMT_NETINC)), None,
                 None, None, None, None, None, None, None, None, None, None,
                 None, None, None, None, None, None, None, None))
        BaseService._insert_bulk(FN, models)
        return "1"
Пример #10
0
 def set_readonly_props(self) -> None:
     if not hasattr(self, 'start_date') \
             or not hasattr(self, 'end_date') \
             or not hasattr(self, 'start_price') \
             or not hasattr(self, 'end_price') \
             or not hasattr(self, 'no_of_shares') \
             or not hasattr(self, 'action'):
         return
     delta: timedelta = DateUtils.get_diff(self._end_date, self._start_date)
     if self._action == AppConsts.ACTION_BUY:
         self._net_change_in_price = NumberUtils.round(self._end_price -
                                                       self._start_price)
     elif self._action == AppConsts.ACTION_SELL:
         self._net_change_in_price = NumberUtils.round(self._start_price -
                                                       self._end_price)
     self._year = self._end_date.year
     self._quarter = DateUtils.get_quarter(self._end_date.month)
     self._month = self._end_date.month
     self._hold_length_days = delta.days if delta else 0
     self._change_in_capital = NumberUtils.round(self._net_change_in_price *
                                                 self._no_of_shares)
     self._has_profit = (self._change_in_capital > 0)
Пример #11
0
    def test_get_date_should_return_date_if_valid_request(self) -> None:
        # ARRANGE
        test_date: str = '2001-01-01'
        fmt: str = '%Y-%m-%d'

        # ACT
        ret_test_date: date = DateUtils.get_date(test_date, fmt)

        # ASSERT
        self.assertIsNotNone(ret_test_date)
        self.assertEqual(2001, ret_test_date.year)
        self.assertEqual(1, ret_test_date.month)
        self.assertEqual(1, ret_test_date.day)
Пример #12
0
    def test_add_business_days_should_validate_input(self) -> None:
        # ARRANGE
        none_date: date = None
        invalid_type_date: str = 'foo'
        none_days: int = None
        invalid_type_days: str = 'bar'
        normal_date: date = date(2000, 1, 1)
        zero_date: int = 0

        # ACT
        ret_none_date_case: date = DateUtils.add_business_days(none_date, 1)
        ret_invalid_type_date_case: date = DateUtils.add_business_days(
            invalid_type_date, 1)
        ret_none_days_case: date = DateUtils.add_business_days(
            normal_date, none_days)
        ret_invalid_type_days_case: date = DateUtils.add_business_days(
            normal_date, invalid_type_days)

        # ASSERT
        self.assertIsNone(ret_none_date_case)
        self.assertIsNone(ret_invalid_type_date_case)
        self.assertEqual(normal_date, ret_none_days_case)
        self.assertEqual(normal_date, ret_invalid_type_days_case)
Пример #13
0
    def test_add_business_days_should_return_appropriately(self) -> None:
        # ARRANGE
        monday: date = date(2000, 1, 3)
        tuesday: date = date(2000, 1, 4)
        friday: date = date(2000, 1, 7)
        saturday: date = date(2000, 1, 8)
        sunday: date = date(2000, 1, 9)
        next_monday: date = date(2000, 1, 10)
        next_tuesday: date = date(2000, 1, 11)
        friday_before_christmas: date = date(2000, 12, 22)
        tuesday_after_christmas: date = date(2000, 12, 26)

        # ACT
        add_to_monday_case: date = DateUtils.add_business_days(monday, 1)
        add_to_friday_case: date = DateUtils.add_business_days(friday, 1)
        add_to_saturday_case: date = DateUtils.add_business_days(saturday, 1)
        add_to_sunday_case: date = DateUtils.add_business_days(sunday, 1)
        subtract_from_next_monday_case: date = DateUtils.add_business_days(
            next_monday, -1)
        subtract_from_next_tuesday_case: date = DateUtils.add_business_days(
            next_tuesday, -1)
        subtract_from_saturday_case: date = DateUtils.add_business_days(
            saturday, -1)
        subtract_from_sunday_case: date = DateUtils.add_business_days(
            sunday, -1)
        add_to_friday_before_christmas: date = DateUtils.add_business_days(
            friday_before_christmas, 1)

        # ASSERT
        self.assertEqual(tuesday, add_to_monday_case)
        self.assertEqual(next_monday, add_to_friday_case)
        self.assertEqual(next_monday, add_to_saturday_case)
        self.assertEqual(next_monday, add_to_sunday_case)
        self.assertEqual(friday, subtract_from_next_monday_case)
        self.assertEqual(next_monday, subtract_from_next_tuesday_case)
        self.assertEqual(friday, subtract_from_saturday_case)
        self.assertEqual(friday, subtract_from_sunday_case)
        self.assertEqual(tuesday_after_christmas,
                         add_to_friday_before_christmas)
Пример #14
0
    def import_from_csv_prices(self) -> str:
        BaseService._truncate(SPD)

        files: List[str] = FileUtils.get_files(AppConsts.STOCK_PRICE_FOLDER,
                                               is_full=True)
        for file in files:
            symbol: str = FileUtils.get_wo_ext(FileUtils.get_base_name(file))
            org_symbol: SM = self.__stock_service.get_symbol(
                symbol, AppConsts.INSTRUMENT_STOCK)
            if not org_symbol:
                continue
            records: List[List[str]] = CsvUtils.parse_as_list(file)
            if not records:
                continue
            for record in records:
                record[AppConsts.KIBOT_IDX_DATE] = DateUtils.get_date(
                    record[AppConsts.KIBOT_IDX_DATE],
                    AppConsts.KIBOT_DATE_FORMAT)
                record.insert(0, org_symbol.id)
            BaseService._insert_bulk(SPD, records)
        return "1"
Пример #15
0
 def date_to_obj(self) -> date:
     return DateUtils.get_date(self._date_to, '%Y-%m-%d')
Пример #16
0
 def date_from_obj(self) -> date:
     return DateUtils.get_date(self._date_from, '%Y-%m-%d')
Пример #17
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
Пример #18
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
Пример #19
0
    def sync_orders(self) -> int:
        errors: List[Exception] = []
        try:
            req: GetTradeOrdersRequest = GetTradeOrdersRequest()
            req.status = [
                AppConsts.ORDER_STATUS_SUBMITTED_ENTRY,
                AppConsts.ORDER_STATUS_SUBMITTED_EXIT
            ]
            orders: List[TradeOrderCustom] = self.get_trade_orders(req)

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

            for order in orders:
                try:
                    LogUtils.debug('Sync order for = {0}'.format(
                        order.symbol_master.symbol))

                    resp: Order = None
                    if order.trade_order.status == AppConsts.ORDER_STATUS_SUBMITTED_ENTRY:
                        resp = self.__alpaca_client.get_order(
                            order.trade_order.alpaca_id)
                    elif order.trade_order.status == AppConsts.ORDER_STATUS_SUBMITTED_EXIT:
                        resp = self.__alpaca_client.get_order(
                            order.trade_order.exit_alpaca_id)

                    if resp:
                        org: TradeOrder = BaseService._get_by_id(
                            TradeOrder, order.trade_order.id)
                        if not org:
                            raise NotFoundException('TradeOrder', 'id',
                                                    order.trade_order.id)

                        if order.trade_order.status == AppConsts.ORDER_STATUS_SUBMITTED_ENTRY \
                                and resp.status == AppConsts.ALPACA_ORDER_STATUS_FILLED:
                            org.status = AppConsts.ORDER_STATUS_IN_POSITION
                            org.actual_qty = NumberUtils.to_int(
                                resp.filled_qty)
                            org.actual_entry_price = NumberUtils.to_float(
                                resp.filled_avg_price)
                            org.modified = datetime.now()
                            BaseService._update()
                        elif order.trade_order.status == AppConsts.ORDER_STATUS_SUBMITTED_ENTRY \
                                and resp.status == AppConsts.ALPACA_ORDER_STATUS_CANCELLED:
                            org.status = AppConsts.ORDER_STATUS_CANCELLED_ENTRY
                            org.modified = datetime.now()
                            BaseService._update()
                        elif order.trade_order.status == AppConsts.ORDER_STATUS_SUBMITTED_EXIT \
                                and resp.status == AppConsts.ALPACA_ORDER_STATUS_FILLED:
                            exit_price: StockPriceDaily = self.__stock_service.get_single_stock_price_daily(
                                order.symbol_master.id,
                                DateUtils.get_date(
                                    datetime.today().strftime('%Y-%m-%d'),
                                    '%Y-%m-%d'))
                            if exit_price:
                                org.exit_stock_price_daily_id = exit_price.id
                            org.status = AppConsts.ORDER_STATUS_COMPLETED
                            org.actual_exit_price = NumberUtils.to_float(
                                resp.filled_avg_price)
                            org.modified = datetime.now()
                            BaseService._update()
                        elif order.trade_order.status == AppConsts.ORDER_STATUS_SUBMITTED_EXIT \
                                and resp.status == AppConsts.ALPACA_ORDER_STATUS_CANCELLED:
                            org.status = AppConsts.ORDER_STATUS_CANCELLED_EXIT
                            org.modified = datetime.now()
                            BaseService._update()
                            raise Exception('Exit Error = {0}'.format(
                                resp.status))
                        else:
                            raise Exception('Sync Status = {0}'.format(
                                resp.status))
                    else:
                        raise NotFoundException('Alpaca Order', 'id',
                                                order.trade_order.alpaca_id)

                except Exception as ex:
                    LogUtils.error('Sync Orders Error', ex)
                    errors.append(ex)

        except Exception as ex:
            LogUtils.error('Sync Orders Error', ex)
            errors.append(ex)
        finally:
            self.__email_client.send_html(
                subject=AppConsts.EMAIL_SUBJECT_SYNC_ORDERS,
                template_path=AppConsts.TEMPLATE_PATH_SYNC_ORDERS,
                model={'errors': errors})
            return 1
Пример #20
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
Пример #21
0
    def test_get_quarter_should_return_appropriately(self) -> None:
        # ARRANGE
        less_than_1_case: int = 0
        more_than_12_case: int = 13
        invalid_type_case: str = 'foo'
        january_case: int = 1
        february_case: int = 2
        march_case: int = 3
        april_case: int = 4
        may_case: int = 5
        june_case: int = 6
        july_case: int = 7
        august_case: int = 8
        september_case: int = 9
        october_case: int = 10
        november_case: int = 11
        december_case: int = 12

        # ACT
        ret_less_than_1_case: int = DateUtils.get_quarter(less_than_1_case)
        ret_more_than_12_case: int = DateUtils.get_quarter(more_than_12_case)
        ret_invalid_type_case: int = DateUtils.get_quarter(invalid_type_case)
        ret_january_case: int = DateUtils.get_quarter(january_case)
        ret_february_case: int = DateUtils.get_quarter(february_case)
        ret_march_case: int = DateUtils.get_quarter(march_case)
        ret_april_case: int = DateUtils.get_quarter(april_case)
        ret_may_case: int = DateUtils.get_quarter(may_case)
        ret_june_case: int = DateUtils.get_quarter(june_case)
        ret_july_case: int = DateUtils.get_quarter(july_case)
        ret_august_case: int = DateUtils.get_quarter(august_case)
        ret_september_case: int = DateUtils.get_quarter(september_case)
        ret_october_case: int = DateUtils.get_quarter(october_case)
        ret_november_case: int = DateUtils.get_quarter(november_case)
        ret_december_case: int = DateUtils.get_quarter(december_case)

        # ASSERT
        self.assertIsNone(ret_less_than_1_case)
        self.assertIsNone(ret_more_than_12_case)
        self.assertIsNone(ret_invalid_type_case)
        self.assertEqual(ret_january_case, 1)
        self.assertEqual(ret_february_case, 1)
        self.assertEqual(ret_march_case, 1)
        self.assertEqual(ret_april_case, 2)
        self.assertEqual(ret_may_case, 2)
        self.assertEqual(ret_june_case, 2)
        self.assertEqual(ret_july_case, 3)
        self.assertEqual(ret_august_case, 3)
        self.assertEqual(ret_september_case, 3)
        self.assertEqual(ret_october_case, 4)
        self.assertEqual(ret_november_case, 4)
        self.assertEqual(ret_december_case, 4)