示例#1
0
def analyze_results(output, data, kind, build_plots):
    log_info("Analyze results...")

    if len(output.time) == 0 or len(output.asset) == 0:
        log_err("ERROR! Output is empty!")
        return

    log_info("Check...")
    qnout.check(output, data, kind)
    log_info("---")
    log_info("Calc global stats...")
    stat_global = qnstat.calc_stat(data, output)
    stat_global = stat_global.loc[output.time[0]:]
    if not build_plots:
        log_info(stat_global.to_pandas().tail())
        return
    log_info("---")
    log_info("Calc stats per asset...")
    stat_per_asset = qnstat.calc_stat(data, output, per_asset=True)
    stat_per_asset = stat_per_asset.loc[output.time.values[0]:]

    if is_notebook():
        build_plots_jupyter(output, stat_global, stat_per_asset)
    else:
        build_plots_dash(output, stat_global, stat_per_asset)
示例#2
0
def check_output(output, data_type='stocks'):
    if data_type != 'stocks' and data_type != 'stocks_long' and data_type != 'futures' \
            and data_type != 'crypto' and data_type != 'crypto_futures' and data_type != 'cryptofutures':
        log_err("Unsupported data_type", data_type)
        return

    in_sample_points = qnt.stats.get_default_is_period_for_type(data_type)

    min_date = qnt.stats.get_default_is_start_date_for_type(data_type)
    output_tail = output.where(output.time > np.datetime64(min_date)).dropna(
        'time', 'all')
    if len(output_tail) < in_sample_points:
        log_err("ERROR! In sample period does not contain enough points. " +
                str(len(output_tail)) + " < " + str(in_sample_points))
    else:
        log_info("Ok. In sample period contains enough points." +
                 str(len(output_tail)) + " >= " + str(in_sample_points))

    log_info()

    log_info("Load data...")

    data = qnt.data.load_data_by_type(
        data_type,
        assets=output.asset.values.tolist(),
        min_date=(pd.Timestamp(min_date) -
                  pd.Timedelta(days=60)).to_pydatetime())

    log_info()

    qnt.output.check(output, data)
示例#3
0
def get_env(key, def_val, silent=False):
    if key in os.environ:
        return os.environ[key]
    else:
        if not silent:
            log_err("NOTICE: The environment variable " + key + " was not specified. The default value is '" + def_val + "'")
        return def_val
示例#4
0
def calc_correlation(relative_returns, suppress_exception=True):
    try:
        if "SUBMISSION_ID" in os.environ and os.environ["SUBMISSION_ID"] != "":
            log_info("correlation check disabled")
            return []

        ENGINE_CORRELATION_URL = get_env(
            "ENGINE_CORRELATION_URL",
            "https://quantiacs.io/referee/submission/forCorrelation")
        STATAN_CORRELATION_URL = get_env(
            "STATAN_CORRELATION_URL",
            "https://quantiacs.io/statan/correlation")
        PARTICIPANT_ID = get_env("PARTICIPANT_ID", "0")

        with request.urlopen(ENGINE_CORRELATION_URL + "?participantId=" +
                             PARTICIPANT_ID) as response:
            submissions = response.read()
            submissions = json.loads(submissions)
            submission_ids = [s['id'] for s in submissions]

        rr = relative_returns.to_netcdf(compute=True)
        rr = gzip.compress(rr)
        rr = base64.b64encode(rr)
        rr = rr.decode()

        cofactors = []

        chunks = [
            submission_ids[x:x + 50] for x in range(0, len(submission_ids), 50)
        ]

        for c in chunks:
            r = {"relative_returns": rr, "submission_ids": c}
            r = json.dumps(r)
            r = r.encode()
            with request.urlopen(STATAN_CORRELATION_URL, r) as response:
                cs = response.read()
                cs = json.loads(cs)
                cofactors = cofactors + cs

        result = []
        for c in cofactors:
            sub = next(
                (s for s in submissions if str(c['id']) == str(s['id'])))
            sub['cofactor'] = c['cofactor']
            sub['sharpe_ratio'] = c['sharpe_ratio']
            result.append(sub)
        return result
    except Exception as e:
        log_err("WARNING! Can't calculate correlation.")
        if suppress_exception:
            import logging
            logging.exception("network error")
            return []
        else:
            raise e
示例#5
0
def read(path=None):
    if path is None:
        path = get_env("IN_STATE_PATH", "state.in.pickle.gz")
    try:
        with gzip.open(path, 'rb') as gz:
            res = pickle.load(gz)
            log_info("State loaded.")
            return res
    except Exception as e:
        log_err("Can't load state.", e)
        return None
示例#6
0
def unpack_result(result):
    state = None
    if type(result) == tuple:
        if len(result) > 1:
            state = result[1]
        if len(result) == 0:
            log_err("ERROR! The result tuple is empty.")
        result = result[0]
    if result is None:
        log_err("ERROR! Strategy output is None!")
    return result, state
示例#7
0
def run_iterations(time_series, data, window, start_date, lookback_period,
                   strategy, step, collect_all_states):
    def copy_window(data, dt, tail):
        return copy.deepcopy(window(data, dt, tail))

    log_info("Run iterations...\n")

    ts = np.sort(time_series)
    outputs = []
    all_states = []

    output_time_coord = ts[ts >= start_date]
    output_time_coord = output_time_coord[::step]

    i = 0

    sys.stdout.flush()

    with progressbar.ProgressBar(max_value=len(output_time_coord),
                                 poll_interval=1) as p:
        state = None
        for t in output_time_coord:
            tail = copy_window(data, t, lookback_period)
            result = strategy(tail, copy.deepcopy(state))
            output, state = unpack_result(result)
            if type(output) != xr.DataArray:
                log_err("Output is not xarray!")
                return
            if set(output.dims) != {'asset'} and set(
                    output.dims) != {'asset', 'time'}:
                log_err("Wrong output dimensions. ", output.dims,
                        "Should contain only:", {'asset', 'time'})
                return
            if 'time' in output.dims:
                output = output.sel(time=t)
            output = output.drop(['field', 'time'], errors='ignore')
            outputs.append(output)
            if collect_all_states:
                all_states.append(state)
            i += 1
            p.update(i)

    sys.stderr.flush()

    log_info("Merge outputs...")
    output = xr.concat(outputs, pd.Index(output_time_coord,
                                         name=qndata.ds.TIME))

    return output, all_states if collect_all_states else state
示例#8
0
def cache_get(*args):
    crop_cache()
    p = pickle.dumps(args)
    key = hashlib.sha1(p).hexdigest()
    value_fn = os.path.join(CACHE_DIR, key + ".value.pickle.gz")
    args_fn = os.path.join(CACHE_DIR, key + ".args.pickle.gz")
    if os.path.exists(value_fn) and os.path.exists(args_fn):
        try:
            old_args = pickle.load(gzip.open(args_fn, 'rb'))
            if old_args == args:
                old_data = pickle.load(gzip.open(value_fn, 'rb'))
                return old_data
        except Exception as e:
            log_err("Cache read problem:", e)
    return None
示例#9
0
def wma(series: nda.NdType,
        weights: tp.Union[tp.List[float], np.ndarray] = None) -> nda.NdType:
    """
    :param weights: weights in decreasing order. lwma(series, 3) == wma(series, [3,2,1])
    """
    global last_alert
    if (weights is None or type(weights) is int):
        if time.time() - last_alert > 60:
            last_alert = time.time()
            log_err(
                "Warning! wma(series:ndarray, periods:int) deprecated. Use lwma instead of wma."
            )
        return lwma(series, weights)
    if type(weights) is list:
        weights = np.array(weights, np.float64)
    return nda.nd_universal_adapter(wma_np_1d, (series, ), (weights, ))
示例#10
0
def assemble_output(add_mode='all'):
    log_info("Merge outputs...")
    files = os.listdir(result_dir)
    files = [f for f in files if f.endswith(".fractions.nc.gz")]
    files.sort()
    output = None

    if len(files) == 0:
        log_err("ERROR! There are no outputs.")

    for f in files:
        date = f.split(".")[0]
        date = datetime.date.fromisoformat(date)
        fn = result_dir + "/" + f
        _output = load_output(fn, date)
        _output = _output.where(_output.time <= np.datetime64(date)).dropna(
            'time', 'all')
        if len(_output) == 0:
            continue
        if output is None:
            log_info("init output:", fn,
                     str(_output.time.min().values)[:10],
                     str(_output.time.max().values)[:10])
            output = _output
        else:
            if add_mode == 'all':
                _output = _output.where(
                    _output.time > output.time.max()).dropna('time', 'all')
            elif add_mode == 'one':
                _output = _output.where(
                    _output.time == np.datetime64(date)).dropna('time', 'all')
            else:
                raise Exception("wrong add_mode")
            if len(_output) == 0:
                continue
            log_info("add output:", fn,
                     str(_output.time.min().values)[:10],
                     str(_output.time.max().values)[:10])
            output = xr.concat([output, _output], dim="time")
    return output
示例#11
0
def backtest(*,
             competition_type: str,
             strategy:  tp.Union[
                tp.Callable[[DataSet], xr.DataArray],
                tp.Callable[[DataSet, tp.Any], tp.Tuple[xr.DataArray, tp.Any]],
             ],
             load_data: tp.Union[tp.Callable[[int], tp.Union[DataSet,tp.Tuple[DataSet,np.ndarray]]],None] = None,
             lookback_period: int = 365,
             test_period: int = 365*15,
             start_date: tp.Union[np.datetime64, str, datetime.datetime, datetime.date, None] = None,
             window: tp.Union[tp.Callable[[DataSet,np.datetime64,int], DataSet], None] = None,
             step: int = 1,
             analyze: bool = True,
             build_plots: bool = True,
             collect_all_states: bool = False,
             ):
    """

    :param competition_type: "futures" | "stocks" | "cryptofutures" | "stocks_long" | "crypto"
    :param load_data: data load function, accepts tail arg, returns time series and data
    :param lookback_period: calendar days period for one iteration
    :param strategy: accepts data, returns weights distribution for the last day
    :param test_period: test period (calendar days)
    :param start_date: start date for backtesting, overrides test period
    :param step: step size
    :param window: function which isolates data for one iterations
    :param analyze: analyze the output and calc stats
    :param build_plots: build plots (require analyze=True)
    :patam collect_all_states: collect all states instead of the last one
    :return:
    """
    qndc.track_event("BACKTEST")

    if window is None:
        window = standard_window

    if load_data is None:
        load_data = lambda tail: qndata.load_data_by_type(competition_type, tail=tail)

    args_count = len(inspect.getfullargspec(strategy).args)
    strategy_wrap = (lambda d, s: strategy(d)) if args_count < 2 else strategy

    log_info("Run last pass...")
    log_info("Load data...")
    data = load_data(lookback_period)
    try:
        if data.name == 'stocks' and competition_type != 'stocks' and competition_type != 'stocks_long'\
            or data.name == 'cryptofutures' and competition_type != 'cryptofutures' and competition_type != 'crypto_futures'\
            or data.name == 'crypto' and competition_type != 'crypto'\
            or data.name == 'futures' and competition_type != 'futures':
            log_err("WARNING! The data type and the competition type are mismatch.")
    except:
        pass
    data, time_series = extract_time_series(data)

    log_info("Run strategy...")
    state = None
    if is_submitted() and args_count > 1:
        state = qnstate.read()
    result = strategy_wrap(data, state)
    result, state = unpack_result(result)

    log_info("Load data for cleanup...")
    data = qndata.load_data_by_type(competition_type, assets=result.asset.values.tolist(), tail=60)
    result = qnout.clean(result, data)
    result.name = competition_type
    log_info("Write result...")
    qnout.write(result)
    qnstate.write(state)

    if is_submitted():
        if args_count > 1:
            return result, state
        else:
            return result

    log_info("---")

    if start_date is None:
        start_date = pd.Timestamp.today().to_datetime64() - np.timedelta64(test_period-1, 'D')
    else:
        start_date = pd.Timestamp(start_date).to_datetime64()
        test_period = (pd.Timestamp.today().to_datetime64() - start_date) / np.timedelta64(1, 'D')

    log_info("Run first pass...")
    try:
        qndc.MAX_DATETIME_LIMIT = pd.Timestamp(start_date).to_pydatetime()
        qndc.MAX_DATE_LIMIT = qndc.MAX_DATETIME_LIMIT.date()
        print("Load data...")
        data = load_data(lookback_period)
        data, time_series = extract_time_series(data)
        print("Run strategy...")
        result = strategy_wrap(data, None)
        result, state = unpack_result(result)
    finally:
        qndc.MAX_DATE_LIMIT = None
        qndc.MAX_DATETIME_LIMIT = None

    log_info("---")

    log_info("Load full data...")
    data = load_data(test_period + lookback_period)
    data, time_series = extract_time_series(data)
    if len(time_series) < 1:
        log_err("Time series is empty")
        return

    log_info("---")
    result, state = run_iterations(time_series, data, window, start_date, lookback_period, strategy_wrap, step, collect_all_states)
    if result is None:
        return

    log_info("Load data for cleanup and analysis...")
    min_date = time_series[0] - np.timedelta64(60, 'D')
    data = qndata.load_data_by_type(competition_type, assets=result.asset.values.tolist(), min_date=str(min_date)[:10])
    result = qnout.clean(result, data, competition_type)
    result.name = competition_type
    log_info("Write result...")
    qnout.write(result)
    qnstate.write(state)

    if analyze:
        log_info("---")
        analyze_results(result, data, competition_type, build_plots)

    if args_count > 1:
        return result, state
    else:
        return result
示例#12
0
def check_correlation(portfolio_history, data, print_stack_trace=True):
    """ Checks correlation for current output. """
    track_event("CHECK_CORRELATION")
    portfolio_history = output_normalize(portfolio_history)
    rr = calc_relative_return(data, portfolio_history)

    try:
        cr_list = calc_correlation(rr, False)
    except:
        import logging
        if print_stack_trace:
            logging.exception("Correlation check failed.")
        else:
            log_err("Correlation check failed.")
        return

    log_info()

    if len(cr_list) == 0:
        log_info("Ok. This strategy does not correlate with other strategies.")
        return

    log_err(
        "WARNING! This strategy correlates with other strategies and will be rejected."
    )
    log_err("Modify the strategy to produce the different output.")
    log_info(
        "The number of systems with a larger Sharpe ratio and correlation larger than 0.9:",
        len(cr_list))
    log_info(
        "The max correlation value (with systems with a larger Sharpe ratio):",
        max([i['cofactor'] for i in cr_list]))
    my_cr = [i for i in cr_list if i['my']]

    log_info(
        "Current sharpe ratio(3y):",
        calc_sharpe_ratio_annualized(rr,
                                     calc_avg_points_per_year(data) *
                                     3)[-1].values.item())

    log_info()

    if len(my_cr) > 0:
        log_info("My correlated submissions:\n")
        headers = ['Name', "Coefficient", "Sharpe ratio"]
        rows = []

        for i in my_cr:
            rows.append([i['name'], i['cofactor'], i['sharpe_ratio']])

        log_info(tabulate(rows, headers))

    ex_cr = [i for i in cr_list if i['template']]
    if len(ex_cr) > 0:
        log_info("Correlated examples:\n")
        headers = ['Name', "Coefficient", "Sharpe ratio"]
        rows = []

        for i in ex_cr:
            rows.append([i['name'], i['cofactor'], i['sharpe_ratio']])

        log_info(tabulate(rows, headers))
示例#13
0
def calc_stat(data,
              portfolio_history,
              slippage_factor=None,
              roll_slippage_factor=None,
              min_periods=1,
              max_periods=None,
              per_asset=False,
              points_per_year=None):
    """
    :param data: xarray with historical data, data must be split adjusted
    :param portfolio_history: portfolio weights set for every day
    :param slippage_factor: slippage
    :param roll_slippage_factor: slippage for contract roll
    :param min_periods: minimal number of days
    :param max_periods: max number of days for rolling
    :param per_asset: calculate stats per asset
    :return: xarray with all statistics
    """
    track_event("CALC_STAT")

    if points_per_year is None:
        points_per_year = calc_avg_points_per_year(data)

    if max_periods is None:
        max_periods = len(data.time)

    if slippage_factor is None:
        slippage_factor = get_default_slippage(data)

    if roll_slippage_factor is None:
        roll_slippage_factor = get_default_slippage(data)

    missed_dates = find_missed_dates(portfolio_history, data)
    if len(missed_dates) > 0:
        log_err("WARNING: some dates are missed in the portfolio_history")

    portfolio_history = output_normalize(portfolio_history, per_asset)

    non_liquid = calc_non_liquid(data, portfolio_history)
    if len(non_liquid.coords[ds.TIME]) > 0:
        log_err("WARNING: Strategy trades non-liquid assets.")

    RR = calc_relative_return(data, portfolio_history, slippage_factor,
                              roll_slippage_factor, per_asset, points_per_year)

    E = calc_equity(RR)
    V = calc_volatility_annualized(RR,
                                   max_periods=max_periods,
                                   min_periods=min_periods,
                                   points_per_year=points_per_year)
    U = calc_underwater(E)
    DD = calc_max_drawdown(U)
    SR = calc_sharpe_ratio_annualized(RR,
                                      max_periods=max_periods,
                                      min_periods=min_periods,
                                      points_per_year=points_per_year)
    MR = calc_mean_return_annualized(RR,
                                     max_periods=max_periods,
                                     min_periods=min_periods,
                                     points_per_year=points_per_year)
    adj_data, adj_ph = arrange_data(data, portfolio_history, per_asset)
    B = calc_bias(adj_ph, per_asset)
    I = calc_instruments(adj_ph, per_asset)
    T = calc_avg_turnover(adj_ph,
                          E,
                          adj_data,
                          min_periods=min_periods,
                          max_periods=max_periods,
                          per_asset=per_asset,
                          points_per_year=points_per_year)

    HT = calc_avg_holding_time(
        adj_ph,  # E, adj_data,
        min_periods=min_periods,
        max_periods=max_periods,
        per_asset=per_asset,
        points_per_year=points_per_year)

    stat = xr.concat(
        [E, RR, V, U, DD, SR, MR, B, I, T, HT],
        pd.Index([
            stf.EQUITY, stf.RELATIVE_RETURN, stf.VOLATILITY, stf.UNDERWATER,
            stf.MAX_DRAWDOWN, stf.SHARPE_RATIO, stf.MEAN_RETURN, stf.BIAS,
            stf.INSTRUMENTS, stf.AVG_TURNOVER, stf.AVG_HOLDINGTIME
        ],
                 name=ds.FIELD))

    dims = [ds.TIME, ds.FIELD]
    if per_asset:
        dims.append(ds.ASSET)
    return stat.transpose(*dims)
示例#14
0
def check_exposure(portfolio_history,
                   soft_limit=0.05,
                   hard_limit=0.1,
                   days_tolerance=0.02,
                   excess_tolerance=0.02,
                   avg_period=252,
                   check_period=252 * 5):
    """
    Checks exposure according to the submission filters.
    :param portfolio_history: output DataArray
    :param soft_limit: soft limit for exposure
    :param hard_limit: hard limit for exposure
    :param days_tolerance: the number of days when exposure may be in range 0.05..0.1
    :param excess_tolerance: max allowed average excess
    :param avg_period: period for the ratio calculation
    :param check_period: period for checking
    :return:
    """
    portfolio_history = portfolio_history.loc[{
        ds.TIME:
        np.sort(portfolio_history.coords[ds.TIME])
    }]

    exposure = calc_exposure(portfolio_history)
    max_exposure = exposure.max(ds.ASSET)

    max_exposure_over_limit = max_exposure.where(
        max_exposure > soft_limit).dropna(ds.TIME)
    if len(max_exposure_over_limit) > 0:
        max_exposure_asset = exposure.sel({
            ds.TIME:
            max_exposure_over_limit.coords[ds.TIME]
        }).idxmax(ds.ASSET)
        log_info("Positions with max exposure over the limit:")
        pos = xr.concat([max_exposure_over_limit, max_exposure_asset],
                        pd.Index(['exposure', 'asset'], name='field'))
        log_info(pos.to_pandas().T)

    periods = min(avg_period, len(portfolio_history.coords[ds.TIME]))

    bad_days = xr.where(max_exposure > soft_limit, 1.0, 0.0)
    bad_days_proportion = bad_days[-check_period:].rolling(dim={
        ds.TIME: periods
    }).mean()
    days_ok = xr.where(bad_days_proportion > days_tolerance, 1,
                       0).sum().values == 0

    excess = exposure - soft_limit
    excess = excess.where(excess > 0, 0).sum(ds.ASSET)
    excess = excess[-check_period:].rolling(dim={ds.TIME: periods}).mean()
    excess_ok = xr.where(excess > excess_tolerance, 1, 0).sum().values == 0

    hard_limit_ok = xr.where(max_exposure > hard_limit, 1, 0).sum().values == 0

    if hard_limit_ok and (days_ok or excess_ok):
        log_info("Ok. The exposure check succeed.")
        return True
    else:
        log_err("WARNING! The exposure check failed.")
        log_info("Hard limit check: ", 'Ok.' if hard_limit_ok else 'Failed.')
        log_info("Days check: ", 'Ok.' if days_ok else 'Failed.')
        log_info("Excess check:", 'Ok.' if excess_ok else 'Failed.')
        return False
示例#15
0
def calc_sharpe_ratio_for_check(data, output, kind=None, check_dates=True):
    """
    Calculates sharpe ratio for check according to the rules
    :param data:
    :param output:
    :param kind: competition type
    :param check_dates: do you need to check the sharpe ratio dates?
    :return:
    """
    import qnt.stats as qns

    if kind is None:
        kind = data.name

    start_date = qns.get_default_is_start_date_for_type(kind)
    sdd = pd.Timestamp(start_date)
    osd = pd.Timestamp(output.where(abs(output).sum('asset') > 0).dropna('time', 'all').time.min().values)
    dsd = pd.Timestamp(data.time.min().values)
    if check_dates:
        if (dsd - sdd).days > 10:
            log_err("WARNING! There are not enough points in the data")
            log_err("The first point(" + str(dsd.date()) + ") should be earlier than " + str(sdd.date()))
            log_err("Load data more historical data.")
        else:
            if len(data.sel(time=slice(None, sdd)).time) < 15:
                log_err("WARNING! There are not enough points in the data for the slippage calculation.")
                log_err("Add 15 extra data points to the data head (load data more historical data).")
        if (osd - sdd).days > 7:
            log_err("WARNING! There are not enough points in the output.")
            log_err("The output series should start from " + str(sdd.date()) + " or earlier instead of " + str(osd.date()))
    sd = max(sdd, dsd)
    sd = sd.to_pydatetime()
    fd = pd.Timestamp(data.time.max().values).to_pydatetime()
    log_info("Period: " + str(sd.date()) + " - " + str(fd.date()))
    output_slice = align(output, data.time, sd, fd)
    rr = qns.calc_relative_return(data, output_slice)
    sr = qns.calc_sharpe_ratio_annualized(rr)
    sr = sr.isel(time=-1).values
    return sr
示例#16
0
def check(output, data, kind=None):
    """
    This function checks your output and warn you if it contains errors.
    :return:
    """
    import qnt.stats as qns
    from qnt.data.common import ds, f, get_env, track_event

    if kind is None:
        kind = data.name

    single_day = ds.TIME not in output.dims
    if single_day:
        output = xr.concat([output], pd.Index([data.coords[ds.TIME].values.max()], name=ds.TIME))

    try:
        if kind == "stocks" or kind == "stocks_long":
            log_info("Check liquidity...")
            non_liquid = qns.calc_non_liquid(data, output)
            if len(non_liquid.coords[ds.TIME]) > 0:
                log_err("ERROR! Strategy trades non-liquid assets.")
                log_err("Multiply the output by data.sel(field='is_liquid') or use qnt.output.clean")
            else:
                log_info("Ok.")

        if not single_day:
            log_info("Check missed dates...")
            missed_dates = qns.find_missed_dates(output, data)
            if len(missed_dates) > 0:
                log_err("ERROR! Some dates were missed)")
                log_err("Your strategy dropped some days, your strategy should produce a continuous series.")
            else:
                log_info("Ok.")
            track_event("OUTPUT_CHECK")

        if kind == "stocks" or kind == "stocks_long":
            log_info("Check exposure...")
            if not qns.check_exposure(output):
                log_err("Use more assets or/and use qnt.output.clean")

        if kind == "crypto":
            log_info("Check BTC...")
            if output.where(output != 0).dropna("asset", "all").coords[ds.ASSET].values.tolist() != ['BTC']:
                log_err("ERROR! Output contains not only BTC.\n")
                log_err("Remove the other assets from the output or use qnt.output.clean")
            else:
                log_info("Ok.")

        if not single_day:
            if abs(output).sum() == 0:
                log_err("ERROR! Output is empty. All positions are zero.")
            else:
                # if kind == 'crypto' or kind == 'cryptofutures' or kind == 'crypto_futures':
                #     log_info("Check holding time...")
                #     ht = qns.calc_avg_holding_time(output)
                #     ht = ht.isel(time=-1).values
                #     if ht < 4:
                #         log_err("ERROR! The holding time is too low.", ht, "<", 4)
                #     else:
                #         log_info("Ok.")
                #
                # if kind == 'stocks_long':
                #     log_info("Check holding time...")
                #     ht = qns.calc_avg_holding_time(output)
                #     ht = ht.isel(time=-1).values
                #     if ht < 15:
                #         log_err("ERROR! The holding time is too low.", ht, "<", 15)
                #     else:
                #         log_info("Ok.")

                if kind == 'stocks_long':
                    log_info("Check positive positions...")
                    neg = output.where(output < 0).dropna(ds.TIME, 'all')
                    if len(neg.time) > 0:
                        log_err("ERROR! Output contains negative positions.")
                        log_err("Drop all negative positions.")
                    else:
                        log_info("Ok.")

                log_info("Check the sharpe ratio...")

                sr = calc_sharpe_ratio_for_check(data, output, kind, True)
                log_info("Sharpe Ratio =", sr)

                if sr < 1:
                    log_err("ERROR! The Sharpe Ratio is too low.", sr, '<', 1,)
                    log_err("Improve the strategy and make sure that the in-sample Sharpe Ratio more than 1.")
                else:
                    log_info("Ok.")

                log_info("Check correlation.")
                qns.check_correlation(output, data, False)
    except Exception as e:
        log_err(e)
示例#17
0
from qnt.data import ds
import xarray as xr
import numpy as np
from qnt.data import sort_and_crop_output
import datetime
import qnt.data.common as qdc
from qnt.log import log_info, log_err

FORWARD_LOOKING_TEST_OFFSET = 182
FORWARD_LOOKING_TEST_DELTA = 10**-7

log_err(
    "qnt.forward_looking is deprecated and will be removed. see qnt.backtester"
)


def load_data_calc_output_and_check_forward_looking(strategy):
    """
    :param strategy: function with data loading and output calculation
    :return: whole output
    """
    qdc.MAX_DATE_LIMIT = None
    qdc.MAX_DATETIME_LIMIT = None

    log_info("Computing of the whole output...")
    whole_output = strategy()

    last_date = datetime.datetime.now().date()
    last_date = last_date - datetime.timedelta(
        days=FORWARD_LOOKING_TEST_OFFSET)
    qdc.MAX_DATE_LIMIT = last_date
示例#18
0
def evaluate_passes(data_type='stocks', passes=3, dates=None):

    log_info("Output directory is:", result_dir)
    os.makedirs(result_dir, exist_ok=True)

    log_info("Rm previous results...")
    for i in os.listdir(result_dir):
        fn = result_dir + "/" + i
        if os.path.isfile(fn):
            log_info("rm:", fn)
            os.remove(fn)

    if dates is None:
        log_info("Prepare test dates...")
        min_date = (pd.Timestamp(
            qnt.stats.get_default_is_start_date_for_type(data_type))
                    ).to_pydatetime()
        data = qnt.data.load_data_by_type(data_type, min_date=min_date)
        if 'is_liquid' in data.field:
            data = data.where(data.sel(field='is_liquid') > 0).dropna(
                'time', 'all')
        data = data.time
        dates = [data.isel(time=-1).values, data.isel(time=1).values] \
                + [data.isel(time=round(len(data) * (i+1)/(passes-1))).values for i in range(passes-2)]
        dates = list(set(dates))
        dates.sort()
        dates = [pd.Timestamp(i).date() for i in dates]

        del data
    else:
        dates = [qnt.data.common.parse_date(d) for d in dates]

    log_info("Dates:", *(i.isoformat() for i in dates))

    i = 0
    for date in dates:
        try:
            os.remove(fractions_fn)
        except FileNotFoundError:
            pass
        try:
            os.remove(last_data_fn)
        except FileNotFoundError:
            pass
        try:
            os.remove(html_fn)
        except FileNotFoundError:
            pass

        log_info("---")
        i += 1
        log_info("pass:"******"/", len(dates), "max_date:", date.isoformat())

        if data_type == 'stocks' or data_type == 'stocks_long':
            timeout = 30 * 60
        if data_type == 'futures':
            timeout = 10 * 60
        if data_type == 'crypto' or data_type == 'crypto_futures' or data_type == 'cryptofutures':
            timeout = 5 * 60

        data_url = urllib.parse.urljoin(
            urllib.parse.urljoin(qnt.data.common.BASE_URL, 'last/'),
            date.isoformat()) + "/"
        cmd = "DATA_BASE_URL=" + data_url + " \\\n" + \
              "LAST_DATA_PATH=" + last_data_fn + " \\\n" + \
              "OUTPUT_PATH=" + fractions_fn + " \\\n" + \
              "SUBMISSION_ID=-1\\\n" + \
              " jupyter nbconvert --to html --ExecutePreprocessor.timeout=" + str(timeout)+ " --execute strategy.ipynb --output=" + html_fn  # + \
        # "\\\n 2>&1"
        log_info("cmd:", cmd)
        log_info("output:")
        proc = subprocess.Popen(cmd,
                                shell=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT,
                                executable='bash')
        for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):
            sys.stdout.write(line)
        proc.wait()
        code = proc.returncode
        log_info("return code:", code)

        if not os.path.exists(fractions_fn):
            log_err("ERROR! Output is not found.")
        if not os.path.exists(last_data_fn):
            log_err("ERROR! The strategy does not use all data.")
        if not os.path.exists(html_fn):
            log_err("ERROR! Conversion to html failed.")
        if code != 0:
            log_err("ERROR! Return code != 0.")

        if os.path.exists(fractions_fn):
            log_info("Check the output...")
            output = load_output(fractions_fn, date)

            if data_type == 'stocks' or data_type == 'stocks_long':
                qnt.stats.check_exposure(output)

            log_info("Load data...")
            data = qnt.data.load_data_by_type(
                data_type,
                assets=output.asset.values.tolist(),
                min_date=str(output.time.min().values)[:10],
                max_date=date)

            if data_type == 'stocks' or data_type == 'stocks_long':
                non_liquid = qnt.stats.calc_non_liquid(data, output)
                if len(non_liquid.time) > 0:
                    log_err("ERROR! The output contains illiquid positions.")

            missed = qnt.stats.find_missed_dates(output, data)
            if len(missed) > 0:
                log_err("ERROR: some dates are missed in the output.", missed)
            else:
                log_info("There are no missed dates.")

            del data

        try:
            shutil.move(
                fractions_fn,
                result_dir + "/" + date.isoformat() + ".fractions.nc.gz")
        except FileNotFoundError:
            pass
        try:
            shutil.move(last_data_fn,
                        result_dir + "/" + date.isoformat() + ".last_data.txt")
        except FileNotFoundError:
            pass
        try:
            shutil.move(html_fn,
                        result_dir + "/" + date.isoformat() + ".strategy.html")
        except FileNotFoundError:
            pass

    log_info("---")
    log_info("Evaluation complete.")
示例#19
0
from qnt.log import log_info, log_err
import time
import pandas as pd
import xarray as xr
import numpy as np

from .data import load_data, f, ds, write_output
from .stats import calc_non_liquid

log_err("qnt.stepper is deprecated and will be removed. see qnt.backtester")


class SimpleStrategy:
    init_data_length = 0  # optional - data length for init

    def init(self, data):
        """
        optional

        called before testing,
        use it for learning or indicators warming

        :param data: xarray
        :return:
        """
        pass

    def step(self, data):
        """
        process one step of strategy test
示例#20
0
 def wrap(*args, **kwargs):
     log_err('WARNING: ' + deprecated_name + ' deprecated, use ' + origin.__module__ + '.' + origin.__name__)
     return origin(*args, **kwargs)
示例#21
0
                cache_min_mod_time = m_time


cache_min_mod_time = None
os.makedirs(CACHE_DIR, exist_ok=True)


if MAX_DATE_LIMIT is None:
    MAX_DATETIME_LIMIT = parse_max_datetime_from_url(BASE_URL)
    MAX_DATE_LIMIT = None if MAX_DATETIME_LIMIT is None else MAX_DATETIME_LIMIT.date()

api_key = os.environ.get("API_KEY", '').strip()
tracking_host = os.environ.get("TRACKING_HOST", "https://quantiacs.io")
if api_key != 'default':
    if api_key == '':
        log_err("Please, specify the API_KEY.")
        log_err("See: https://quantiacs.io/documentation/en/user_guide/local_development.html")
        sys.exit(1)
    else:
        url = tracking_host + "/auth/system/account/accountByKey?apiKey=" + api_key
        try:
            resp = urllib.request.urlopen(url)
        except urllib.error.HTTPError as e:
            if e.code == 404:
                log_err("Wrong API_KEY.")
                log_err("See: https://quantiacs.io/documentation/en/user_guide/local_development.html")
                sys.exit(1)
sent_events = set()


def track_event(event):