Exemplo n.º 1
0
 def _init_dates(self):
     if self.asset_groups == 1 and self.asset_types[0] == keys.keys[
             'ASSETS']['CRYPTO']:
         self.start_date, self.end_date = errors.validate_dates(
             self.start_date, self.end_date, keys.keys['ASSETS']['CRYPTO'])
         self.weekends = 1
     else:
         self.start_date, self.end_date = errors.validate_dates(
             self.start_date, self.end_date, keys.keys['ASSETS']['EQUITY'])
         self.weekends = 0
Exemplo n.º 2
0
def market_premium(start_date: Union[date, None] = None,
                   end_date: Union[date, None] = None,
                   market_profile: Union[dict, None] = None,
                   method: str = settings.ESTIMATION_METHOD) -> float:
    """
    Returns the excess of the market return defined by the environment variable `MARKET_PROXY` over the risk free rate defined by the `RISK_FREE` environment variable.

    Parameters
    ----------
    1. **start_date**: ``datetime.date``
        *Optional*. Start date of the time period for which the market premium will be computed. Defaults to 100 days ago.
    2. **end_date**: ``datetime.date``
        *Optional*. End_date of the time period for which the market premium will be computed. Defaults to today.
    3. **market_profile**: ``dict``
        *Optional*. Manually inputted market risk profile. Will override call to `scrilla.analysis.models.geometric.statistics.calculate_risk_return`.
    4. **method** : ``str``
        *Optional*. Estimation method used to calculate financial statistics. Defaults to the value set by `scrilla.settings.ESTIMATION_METHOD`. Allowable value are accessible through the `scrilla.keys.keys` dictionary.

    """
    start_date, end_date = errors.validate_dates(
        start_date=start_date,
        end_date=end_date,
        asset_type=keys.keys['ASSETS']['EQUITY'])

    if market_profile is None:
        market_profile = statistics.calculate_risk_return(
            ticker=settings.MARKET_PROXY,
            start_date=start_date,
            end_date=end_date,
            method=method)

    market_prem = (market_profile['annual_return'] -
                   services.get_risk_free_rate())
    return market_prem
Exemplo n.º 3
0
def test_date_validation(ticker, asset_type, start_date_str, end_date_str,
                         expected_start_str, expected_end_str):
    start_date = dater.parse(start_date_str)
    end_date = dater.parse(end_date_str)
    validated_start, validated_end = errors.validate_dates(
        start_date, end_date, asset_type)
    assert(dater.to_string(validated_start) == expected_start_str and \
            dater.to_string(validated_end) == expected_end_str)
Exemplo n.º 4
0
def get_daily_interest_history(maturity: str,
                               start_date: Union[date, None] = None,
                               end_date: Union[date, None] = None) -> list:
    """
    Wrapper around external service request for US Treasury Yield Curve data. Relies on an instance of `StatManager` configured by `settings.STAT_MANAGER` value, which in turn is configured by the `STAT_MANAGER` environment variable, to hydrate with data.

    Before deferring to the `StatManager` and letting it call the external service, however, this function checks if response is in local cache. If the response is not in the cache, it will pass the request off to `StatManager` and then save the response in the cache so subsequent calls to the function can bypass the service request. Used to prevent excessive external HTTP requests and improve the performance of the application. Other parts of the program should interface with the external statistics data services through this function to utilize the cache functionality. 

    Parameters
    ----------
    1. **maturity** : ``str``
        Maturity of the US Treasury for which the interest rate will be retrieved. List of allowable values can in `scrilla.stats.keys['SERVICES']['STATISTICS']['QUANDL']['MAP']['YIELD_CURVE']`
    2. **start_date** : ``datetime.date``
        *Optional*. Start date of price history. Defaults to None. If `start_date is None`, the calculation is made as if the `start_date` were set to 100 trading days ago. This excludes weekends and holidays.
    3. **end_date** : ``datetime.date``
        *Optional*. End date of price history. Defaults to None. If `end_date is None`, the calculation is made as if the `end_date` were set to today. This excludes weekends and holidays so that `end_date` is set to the last previous business date.

    Returns
    ------
    ``dict`` : `{ 'date' :  value ,  'date':  value , ... }`
        Dictionary with date strings formatted `YYYY-MM-DD` as keys and the interest on that date as the corresponding value.

    .. notes::
        * Yield rates are not reported on weekends or holidays, so the `asset_type` for interest is functionally equivalent to equities, at least as far as date calculations are concerned. The dates inputted into this function are validated as if they were labelled as equity `asset_types` for this reason.
    """
    start_date, end_date = errors.validate_dates(
        start_date=start_date,
        end_date=end_date,
        asset_type=keys.keys['ASSETS']['EQUITY'])

    rates = None
    rates = interest_cache.filter_interest_cache(maturity,
                                                 start_date=start_date,
                                                 end_date=end_date)

    if rates is not None:
        logger.debug(
            f'Comparing {len(rates)} = {dater.business_days_between(start_date, end_date)}'
        )

    # TODO: this only works when stats are reported daily and that the latest date in the dataset is actually end_date.
    if rates is not None and \
            dater.to_string(end_date) in rates.keys() and \
            dater.business_days_between(start_date, end_date) == len(rates):
        return rates

    logger.debug(
        f'Cached {maturity} data is out of date, passing request to external service'
    )
    rates = stat_manager.get_interest_rates(start_date=start_date,
                                            end_date=end_date)

    for this_date in rates:
        interest_cache.save_row(date=this_date, value=rates[this_date])

    rates = stat_manager.format_for_maturity(maturity=maturity, results=rates)

    return rates
Exemplo n.º 5
0
def cost_of_equity(ticker: str,
                   start_date: Union[datetime.date, None] = None,
                   end_date: Union[datetime.date, None] = None,
                   market_profile: Union[Dict[str, float], None] = None,
                   market_correlation: Union[Dict[str, float], None] = None,
                   method=settings.ESTIMATION_METHOD) -> float:
    """
    Returns the cost of equity of an asset as estimated by the Capital Asset Pricing Model, i.e. the product of the market premium and asset beta increased by the risk free rate.

    Parameters
    ----------
    1. **ticker**: ``str``
        A string of the ticker symbol whose cost of equity ratio will be computed.
    2. **start_date**: ``Union[datetime.date, None]``
        *Optional*. Start date of the time period for which the cost of equity ratio will be computed
    3. **end_date**: ``Union[datetime.date, None]``
        *Optional.* End_date of the time period for which the cost of equity ratio will be computed.
    4. **market_profile**: ``Union[Dict[str, float], None]``
        *Optional*. Dictionary containing the assumed risk profile for the market proxy. Overrides calls to services and staistical methods, forcing the calculation fo the cost of equity with the inputted market profile. Format: ``{ 'annual_return': value, 'annual_volatility': value}``
    5. **market_correlation**: ``Union[Dict[str, float], None]``
        *Optional*. Dictionary containing the assumed correlation for the calculation. Overrides calls to services and statistical methods, forcing the calculation of the cost of equity with the inputted correlation. Format: ``{ 'correlation' : value }``
    6. **method** : ``str``
        *Optional*. Estimation method used to calculate financial statistics. Defaults to the value set by `scrilla.settings.ESTIMATION_METHOD`. Allowable value are accessible through the `scrilla.keys.keys` dictionary.
    """
    start_date, end_date = errors.validate_dates(
        start_date=start_date,
        end_date=end_date,
        asset_type=keys.keys['ASSETS']['EQUITY'])

    result = profile_cache.filter_profile_cache(ticker=ticker,
                                                start_date=start_date,
                                                end_date=end_date,
                                                method=method)

    if result is not None and result[keys.keys['STATISTICS']
                                     ['EQUITY']] is not None:
        return result[keys.keys['STATISTICS']['EQUITY']]

    beta = market_beta(ticker=ticker,
                       start_date=start_date,
                       end_date=end_date,
                       market_profile=market_profile,
                       market_correlation=market_correlation,
                       method=method)
    premium = market_premium(start_date=start_date,
                             end_date=end_date,
                             market_profile=market_profile,
                             method=method)

    equity_cost = (premium * beta + services.get_risk_free_rate())

    profile_cache.save_or_update_row(ticker=ticker,
                                     start_date=start_date,
                                     end_date=end_date,
                                     equity_cost=equity_cost,
                                     method=method)
    return equity_cost
Exemplo n.º 6
0
def test_service_date_validation(ticker, asset_type):
    with HTTMock(mock.mock_prices):
        response = services.get_daily_price_history(ticker=ticker,
                                                    start_date=settings.START,
                                                    end_date=settings.END)
    validated_start, validated_end = validate_dates(start_date=settings.START,
                                                    end_date=settings.END,
                                                    asset_type=asset_type)
    assert (dater.to_string(validated_start) in list(response.keys()))
    assert (dater.to_string(validated_end) in list(response.keys()))
Exemplo n.º 7
0
def get_daily_fred_history(symbol: str,
                           start_date: Union[date, None] = None,
                           end_date: Union[date, None] = None) -> list:
    """
    Wrapper around external service request for financial statistics data constructed by the Federal Reserve Economic Data. Relies on an instance of `StatManager` configured by `settings.STAT_MANAGER` value, which in turn is configured by the `STAT_MANAGER` environment variable, to hydrate with data.

    Parameters
    ----------
    1. **symbol**: ``str`` 
        Symbol representing the statistic whose history is to be retrieved. List of allowable values can be found [here](https://www.quandl.com/data/FRED-Federal-Reserve-Economic-Data/documentation)
    2. **start_date**: ``Union[date, None]`` 
        *Optional*. Start date of price history. Defaults to None. If `start_date is None`, the calculation is made as if the `start_date` were set to 100 trading days ago. This excludes weekends and holidays.
    3. **end_date**: ``Union[date, None]``
        *Optional*. End date of price history. Defaults to None. If `end_date is None`, the calculation is made as if the `end_date` were set to today. This excludes weekends and holidays so that `end_date` is set to the last previous business date.

    Returns
    ------
    ``list``: `{ 'date' (str) :  value (str),  'date' (str):  value (str), ... }`
        Dictionary with date strings formatted `YYYY-MM-DD` as keys and the statistic on that date as the corresponding value.

    Raises
    ------
    1. **scrilla.errors.PriceError**
        If no sample prices can be retrieved, this error is thrown. 

    .. notes::
        * Most financial statistics are not reported on weekends or holidays, so the `asset_type` for financial statistics is functionally equivalent to equities, at least as far as date calculations are concerned. The dates inputted into this function are validated as if they were labelled as equity `asset_types` for this reason.

    """

    start_date, end_date = errors.validate_dates(
        start_date=start_date,
        end_date=end_date,
        asset_type=keys.keys['ASSETS']['EQUITY'])

    stats = stat_manager.get_stats(symbol=symbol,
                                   start_date=start_date,
                                   end_date=end_date)

    if not stats:
        raise errors.PriceError(f'Prices could not be retrieved for {symbol}')

    return stats
Exemplo n.º 8
0
def get_daily_price_history(
        ticker: str,
        start_date: Union[None, date] = None,
        end_date: Union[None, date] = None,
        asset_type: Union[None, str] = None) -> Dict[str, Dict[str, float]]:
    """
    Wrapper around external service request for price data. Relies on an instance of `PriceManager` configured by `settings.PRICE_MANAGER` value, which in turn is configured by the `PRICE_MANAGER` environment variable, to hydrate with data. 

    Before deferring to the `PriceManager` and letting it call the external service, however, this function checks if response is in local cache. If the response is not in the cache, it will pass the request off to `PriceManager` and then save the response in the cache so subsequent calls to the function can bypass the service request. Used to prevent excessive external HTTP requests and improve the performance of the application. Other parts of the program should interface with the external price data services through this function to utilize the cache functionality.

    Parameters
    ----------
    1. **ticker** :  ``str``
        Ticker symbol corresponding to the price history to be retrieved.
    2. **start_date** : ``datetime.date`` 
        *Optional*. Start date of price history. Defaults to None. If `start_date is None`, the calculation is made as if the `start_date` were set to 100 trading days ago. If `scrilla.files.get_asset_type(ticker)==scrill.keys.keys['ASSETS']['CRYPTO']`, this includes weekends and holidays. If `scrilla.files.get_asset_type(ticker)==scrilla.keys.keys['ASSETS']['EQUITY']`, this excludes weekends and holidays.
    3. **end_date** : ``datetime.date``
        Optional End date of price history. Defaults to None. If `end_date is None`, the calculation is made as if the `end_date` were set to today. If `scrilla.files.get_asset_type(ticker)==scrill.keys.keys['ASSETS']['CRYPTO']`, this means today regardless. If `scrilla.files.get_asset_type(ticker)==scrilla.keys.keys['ASSETS']['EQUITY']`, this excludes weekends and holidays so that `end_date` is set to the previous business date. 
    4. **asset_type** : ``string``
        *Optional*. Asset type of the ticker whose history is to be retrieved. Used to prevent excessive calls to IO and list searching. `asset_type` is determined by comparing the ticker symbol `ticker` to a large static list of ticker symbols maintained in installation directory's /data/static/ subdirectory, which can slow the program down if the file is constantly accessed and lots of comparison are made against it. Once an `asset_type` is calculated, it is best to preserve it in the process environment somehow, so this function allows the value to be passed in. If no value is detected, it will make a call to the aforementioned directory and parse the file to determine to the `asset_type`. Asset types are statically accessible through the `scrilla.keys.keys['ASSETS']` dictionary.

    Returns
    ------
    ``Dict[str, Dict[str, float]]`` : Dictionary with date strings formatted `YYYY-MM-DD` as keys and a nested dictionary containing the 'open' and 'close' price as values. Ordered from latest to earliest, e.g., 
    ```
        { 
            'date' : 
                { 
                    'open': value, 
                    'close': value  
                }, 
            'date': 
                { 
                    'open' : value, 
                    'close' : value 
                }, 
            ... 
        }
    ```

    Raises
    ------
    1. **scrilla.errors.PriceError**
        If no sample prices can be retrieved, this error is thrown. 

    .. notes::
        * The default analysis period, if no `start_date` and `end_date` are specified, is determined by the *DEFAULT_ANALYSIS_PERIOD** variable in the `settings,py` file. The default value of this variable is 100.
    """
    asset_type = errors.validate_asset_type(ticker, asset_type)
    start_date, end_date = errors.validate_dates(start_date, end_date,
                                                 asset_type)

    cached_prices = price_cache.filter_price_cache(ticker=ticker,
                                                   start_date=start_date,
                                                   end_date=end_date)

    if cached_prices is not None:
        if asset_type == keys.keys['ASSETS']['EQUITY']:
            logger.debug(
                f'Comparing {len(cached_prices)} = {dater.business_days_between(start_date, end_date)}'
            )
        elif asset_type == keys.keys['ASSETS']['CRYPTO']:
            logger.debug(
                f'Comparing {len(cached_prices)} = {dater.days_between(start_date, end_date)}'
            )

    # make sure the length of cache is equal to the length of the requested sample
    if cached_prices is not None and dater.to_string(
            end_date) in cached_prices.keys() and (
                (asset_type == keys.keys['ASSETS']['EQUITY'] and
                 (dater.business_days_between(
                     start_date, end_date)) == len(cached_prices)) or
                (asset_type == keys.keys['ASSETS']['CRYPTO'] and
                 (dater.days_between(start_date,
                                     end_date)) == len(cached_prices))):
        # TODO: debug the crypto out of date check.
        return cached_prices

    if cached_prices is not None:
        logger.debug(
            f'Cached {ticker} prices are out of date, passing request off to external service'
        )

    prices = price_manager.get_prices(ticker=ticker,
                                      start_date=start_date,
                                      end_date=end_date,
                                      asset_type=asset_type)

    if cached_prices is not None:
        new_prices = helper.complement_dict_keys(prices, cached_prices)
    else:
        new_prices = prices

    for this_date in new_prices:
        open_price = new_prices[this_date][keys.keys['PRICES']['OPEN']]
        close_price = new_prices[this_date][keys.keys['PRICES']['CLOSE']]
        price_cache.save_row(ticker=ticker,
                             date=this_date,
                             open_price=open_price,
                             close_price=close_price)

    if not prices:
        raise errors.PriceError(f'Prices could not be retrieved for {ticker}')

    return prices
Exemplo n.º 9
0
def sharpe_ratio(ticker: str,
                 start_date: Union[date, None] = None,
                 end_date: Union[date, None] = None,
                 risk_free_rate: Union[float, None] = None,
                 ticker_profile: Union[dict, None] = None,
                 method: str = settings.ESTIMATION_METHOD) -> float:
    """
    Calculates the sharpe ratio for the supplied ticker over the specified time range. If no start and end date are supplied, calculation will default to the last 100 days of prices. The risk free rate and ticker risk profile can be passed in to force calculations without historical data.

    Parameters
    ----------
    1. **ticker**: ``str``
        A string of the ticker symbol whose sharpe ratio will be computed.
    2. **start_date**: ``datetime.date``
        Start date of the time period for which the sharpe ratio will be computed.
    3. **end_date**: ``datetime.date``
        End_date of the time period for which the sharpe ratio will be computed.
    4. **risk_free_rate**: ``float``
        Risk free rate used to evaluate excess return. Defaults to settings.RISK_FREE_RATE.
    5. **ticker_profile**: ``dict`` 
        Risk-return profile for the supplied ticker. Formatted as follows: `{ 'annual_return': float, 'annual_volatility': float } `
    6. **method** : ``str``
        Estimation method used to calculate financial statistics. Defaults to the value set by `scrilla.settings.ESTIMATION_METHOD`. Allowable value are accessible through the `scrilla.keys.keys` dictionary.

    .. notes:: 
        * if ``ticker_profile`` is provided, this function will skip both an external data service call and the calculation of the ticker's risk profile. The calculation will proceed as if the supplied profile were the true profile. If ``ticker_profile`` is not provided, all statistics will be estimated from historical data.
    """
    start_date, end_date = errors.validate_dates(
        start_date=start_date,
        end_date=end_date,
        asset_type=keys.keys['ASSETS']['EQUITY'])

    if ticker_profile is None:
        result = profile_cache.filter_profile_cache(ticker=ticker,
                                                    start_date=start_date,
                                                    end_date=end_date,
                                                    method=method)

        if result is not None and result[keys.keys['STATISTICS']
                                         ['SHARPE']] is not None:
            return result[keys.keys['STATISTICS']['SHARPE']]

        ticker_profile = statistics.calculate_risk_return(
            ticker=ticker,
            start_date=start_date,
            end_date=end_date,
            method=method)

    if risk_free_rate is None:
        risk_free_rate = services.get_risk_free_rate()

    sh_ratio = (ticker_profile['annual_return'] -
                risk_free_rate) / ticker_profile['annual_volatility']

    profile_cache.save_or_update_row(ticker=ticker,
                                     start_date=start_date,
                                     end_date=end_date,
                                     sharpe_ratio=sh_ratio,
                                     method=method)

    return sh_ratio
Exemplo n.º 10
0
def market_beta(ticker: str,
                start_date: Union[date, None] = None,
                end_date: Union[date, None] = None,
                market_profile: Union[dict, None] = None,
                market_correlation: Union[dict, None] = None,
                ticker_profile: Union[dict, None] = None,
                sample_prices: Union[dict, None] = None,
                method: str = settings.ESTIMATION_METHOD) -> float:
    """
    Returns the beta of an asset against the market return defined by the ticker symbol set `scrilla.settings.MARKET_PROXY`, which in turn is configured through the environment variable of the same name, `MARKET_PROXY`. 

    Parameters
    ----------
    1. **ticker**: ``str``
        A string of the ticker symbol whose asset beta will be computed.
    2. **start_date**: ``datetime.date``
        Start date of the time period for which the asset beta will be computed.
    3. **end_date**: ``datetime.date`` 
        End_date of the time period for which the asset beta will be computed.
    4. **method** : ``str``
        Estimation method used to calculate financial statistics. Defaults to the value set by `scrilla.settings.ESTIMATION_METHOD`. Allowable value are accessible through the `scrilla.keys.keys` dictionary.

    .. notes::
        * If not configured by an environment variable, `scrilla.settings.MARKET_PROXY` defaults to ``SPY``, the ETF tracking the *S&P500*.
    """
    start_date, end_date = errors.validate_dates(
        start_date=start_date,
        end_date=end_date,
        asset_type=keys.keys['ASSETS']['EQUITY'])

    result = profile_cache.filter_profile_cache(ticker=ticker,
                                                start_date=start_date,
                                                end_date=end_date,
                                                method=method)

    if result is not None and result[keys.keys['STATISTICS']
                                     ['BETA']] is not None:
        return result[keys.keys['STATISTICS']['BETA']]

    if market_profile is None:
        if sample_prices is None:
            market_profile = statistics.calculate_risk_return(
                ticker=settings.MARKET_PROXY,
                start_date=start_date,
                end_date=end_date,
                method=method)
        else:
            market_profile = statistics.calculate_risk_return(
                ticker=settings.MARKET_PROXY,
                method=method,
                sample_prices=sample_prices[settings.MARKET_PROXY])
    if ticker_profile is None:
        if sample_prices is None:
            ticker_profile = statistics.calculate_risk_return(
                ticker=ticker,
                start_date=start_date,
                end_date=end_date,
                method=method)
        else:
            ticker_profile = statistics.calculate_risk_return(
                ticker=ticker,
                method=method,
                sample_prices=sample_prices[ticker])

    market_covariance = statistics.calculate_return_covariance(
        ticker_1=ticker,
        ticker_2=settings.MARKET_PROXY,
        profile_1=ticker_profile,
        profile_2=market_profile,
        correlation=market_correlation,
        sample_prices=sample_prices,
        start_date=start_date,
        end_date=end_date)

    beta = market_covariance / (market_profile['annual_volatility']**2)

    profile_cache.save_or_update_row(ticker=ticker,
                                     start_date=start_date,
                                     end_date=end_date,
                                     asset_beta=beta,
                                     method=method)

    return beta