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)
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)
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
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
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
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
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
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
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, ))
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
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
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))
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)
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
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
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)
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
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.")
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
def wrap(*args, **kwargs): log_err('WARNING: ' + deprecated_name + ' deprecated, use ' + origin.__module__ + '.' + origin.__name__) return origin(*args, **kwargs)
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):