From 717f4c963b334526eb797c7bb6e2532c00303772 Mon Sep 17 00:00:00 2001 From: "Don.Lin" <142398161+Lin-Dongzhao@users.noreply.github.com> Date: Fri, 12 Jan 2024 14:10:11 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E5=85=BC=E5=AE=B9=20python=3D=3D3.12=20(#8?= =?UTF-8?q?38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * modify .readthedocs.yml * 兼容py3.12改动 * rqalpha 兼容 py3.12 --------- Co-authored-by: 周嘉俊 <35399214+Zhou-JiaJun@users.noreply.github.com> Co-authored-by: Cuizi7 Co-authored-by: 周嘉俊 <654181984@qq.com> --- rqalpha/_version.py | 274 ++++++++--- setup.cfg | 2 +- versioneer.py | 1136 +++++++++++++++++++++++++------------------ 3 files changed, 845 insertions(+), 567 deletions(-) diff --git a/rqalpha/_version.py b/rqalpha/_version.py index 37e65fc17..d9c373d5d 100644 --- a/rqalpha/_version.py +++ b/rqalpha/_version.py @@ -5,8 +5,9 @@ # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. -# This file is released into the public domain. Generated by -# versioneer-0.18 (https://github.com/warner/python-versioneer) +# This file is released into the public domain. +# Generated by versioneer-0.28 +# https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" @@ -15,6 +16,8 @@ import re import subprocess import sys +from typing import Callable, Dict +import functools def get_keywords(): @@ -40,8 +43,8 @@ def get_config(): # _version.py cfg = VersioneerConfig() cfg.VCS = "git" - cfg.style = "pep440-ricequant" - cfg.tag_prefix = "release/" + cfg.style = "pep440" + cfg.tag_prefix = "" cfg.parentdir_prefix = "rqalpha" cfg.versionfile_source = "rqalpha/_version.py" cfg.verbose = False @@ -52,12 +55,12 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY = {} -HANDLERS = {} +LONG_VERSION_PY: Dict[str, str] = {} +HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" + """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: @@ -71,17 +74,25 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) - p = None - for c in commands: + process = None + + popen_kwargs = {} + if sys.platform == "win32": + # This hides the console window if pythonw.exe is used + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + popen_kwargs["startupinfo"] = startupinfo + + for command in commands: try: - dispcmd = str([c] + args) + dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None), **popen_kwargs) break - except EnvironmentError: + except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue @@ -93,15 +104,13 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, if verbose: print("unable to find command, tried %s" % (commands,)) return None, None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: + stdout = process.communicate()[0].strip().decode() + if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) - return None, p.returncode - return stdout, p.returncode + return None, process.returncode + return stdout, process.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): @@ -113,15 +122,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): """ rootdirs = [] - for i in range(3): + for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} - else: - rootdirs.append(root) - root = os.path.dirname(root) # up a level + rootdirs.append(root) + root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % @@ -138,22 +146,21 @@ def git_get_keywords(versionfile_abs): # _version.py. keywords = {} try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - f.close() - except EnvironmentError: + with open(versionfile_abs, "r") as fobj: + for line in fobj: + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + except OSError: pass return keywords @@ -161,10 +168,14 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because @@ -177,11 +188,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -190,7 +201,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -199,6 +210,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] + # Filter out refs that exactly match prefix or that don't start + # with a number once the prefix is stripped (mostly a concern + # when prefix is '') + if not re.match(r'\d', r): + continue if verbose: print("picking %s" % r) return {"version": r, @@ -214,7 +230,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): +def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -225,8 +241,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + # GIT_DIR can interfere with correct operation of Versioneer. + # It may be intended to be passed to the Versioneer-versioned project, + # but that should not change where we get our version from. + env = os.environ.copy() + env.pop("GIT_DIR", None) + runner = functools.partial(runner, env=env) + + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -234,15 +257,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out, rc = runner(GITS, [ + "describe", "--tags", "--dirty", "--always", "--long", + "--match", f"{tag_prefix}[[:digit:]]*" + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() @@ -252,6 +275,39 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) + # --abbrev-ref was added in git-1.6.3 + if rc != 0 or branch_name is None: + raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") + branch_name = branch_name.strip() + + if branch_name == "HEAD": + # If we aren't exactly on a branch, pick a branch which represents + # the current commit. If all else fails, we are on a branchless + # commit. + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + # --contains was added in git-1.5.4 + if rc != 0 or branches is None: + raise NotThisMethod("'git branch --contains' returned error") + branches = branches.split("\n") + + # Remove the first line if we're running detached + if "(" in branches[0]: + branches.pop(0) + + # Strip off the leading "* " from the list of branches. + branches = [branch[2:] for branch in branches] + if "master" in branches: + branch_name = "master" + elif not branches: + branch_name = None + else: + # Pick the first branch that is returned. Good or bad. + branch_name = branches[0] + + pieces["branch"] = branch_name + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out @@ -268,7 +324,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: - # unparseable. Maybe git-describe is misbehaving? + # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces @@ -293,13 +349,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) - pieces["distance"] = int(count_out) # total number of commits + out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) + pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], - cwd=root)[0].strip() + date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces @@ -337,19 +394,67 @@ def render_pep440(pieces): return rendered -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. +def render_pep440_branch(pieces): + """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . + + The ".dev0" means not master branch. Note that .dev0 sorts backwards + (a feature branch will appear "older" than the master branch). Exceptions: - 1: no tags. 0.post.devDISTANCE + 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def pep440_split_post(ver): + """Split pep440 version string at the post-release segment. + + Returns the release segments before the post-release and the + post-release version number (or -1 if no post-release segment is present). + """ + vc = str.split(ver, ".post") + return vc[0], int(vc[1] or 0) if len(vc) == 2 else None + + +def render_pep440_pre(pieces): + """TAG[.postN.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post0.devDISTANCE + """ + if pieces["closest-tag"]: if pieces["distance"]: - rendered += ".post.dev%d" % pieces["distance"] + # update the post release segment + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + rendered = tag_version + if post_version is not None: + rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) + else: + rendered += ".post0.dev%d" % (pieces["distance"]) + else: + # no commits, use the tag as the version + rendered = pieces["closest-tag"] else: # exception #1 - rendered = "0.post.dev%d" % pieces["distance"] + rendered = "0.post0.dev%d" % pieces["distance"] return rendered @@ -380,25 +485,32 @@ def render_pep440_post(pieces): return rendered -def render_pep440_ricequant(pieces): - """TAG[.devDISTANCE.gHEX[.dirty]] . +def render_pep440_post_branch(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . + + The ".dev0" means not master branch. Exceptions: - 1: no tags. 0.devDISTANCE.gHEX[.dirty] + 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: - rendered += ".dev%d" % pieces["distance"] - rendered += ".g%s" % pieces["short"] + rendered += ".post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" else: # exception #1 - rendered = "0.dev%d" % pieces["distance"] - rendered += ".g%s" % pieces["short"] - - if pieces["dirty"]: - rendered += ".dirty" - + rendered = "0.post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" return rendered @@ -407,7 +519,7 @@ def render_pep440_old(pieces): The ".dev0" means dirty. - Eexceptions: + Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: @@ -478,14 +590,16 @@ def render(pieces, style): if style == "pep440": rendered = render_pep440(pieces) + elif style == "pep440-branch": + rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) + elif style == "pep440-post-branch": + rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) - elif style == 'pep440-ricequant': - rendered = render_pep440_ricequant(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": @@ -519,7 +633,7 @@ def get_versions(): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): + for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, diff --git a/setup.cfg b/setup.cfg index 18281801d..54394e43c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ [metadata] name = rqalpha -version = 5.3.4 +version = 5.3.5 [versioneer] VCS = git diff --git a/versioneer.py b/versioneer.py index e389d28c4..645fc868a 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -# Version: 0.18 +# Version: 0.28 """The Versioneer - like a rocketeer, but for versions. @@ -7,18 +6,14 @@ ============== * like a rocketeer, but for versions! -* https://github.com/warner/python-versioneer +* https://github.com/python-versioneer/python-versioneer * Brian Warner -* License: Public Domain -* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy -* [![Latest Version] -(https://pypip.in/version/versioneer/badge.svg?style=flat) -](https://pypi.python.org/pypi/versioneer/) -* [![Build Status] -(https://travis-ci.org/warner/python-versioneer.png?branch=master) -](https://travis-ci.org/warner/python-versioneer) - -This is a tool for managing a recorded version number in distutils-based +* License: Public Domain (Unlicense) +* Compatible with: Python 3.7, 3.8, 3.9, 3.10 and pypy3 +* [![Latest Version][pypi-image]][pypi-url] +* [![Build Status][travis-image]][travis-url] + +This is a tool for managing a recorded version number in setuptools-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control @@ -27,9 +22,38 @@ ## Quick Install -* `pip install versioneer` to somewhere to your $PATH -* add a `[versioneer]` section to your setup.cfg (see below) -* run `versioneer install` in your source tree, commit the results +Versioneer provides two installation modes. The "classic" vendored mode installs +a copy of versioneer into your repository. The experimental build-time dependency mode +is intended to allow you to skip this step and simplify the process of upgrading. + +### Vendored mode + +* `pip install versioneer` to somewhere in your $PATH + * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is + available, so you can also use `conda install -c conda-forge versioneer` +* add a `[tool.versioneer]` section to your `pyproject.toml` or a + `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) + * Note that you will need to add `tomli; python_version < "3.11"` to your + build-time dependencies if you use `pyproject.toml` +* run `versioneer install --vendor` in your source tree, commit the results +* verify version information with `python setup.py version` + +### Build-time dependency mode + +* `pip install versioneer` to somewhere in your $PATH + * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is + available, so you can also use `conda install -c conda-forge versioneer` +* add a `[tool.versioneer]` section to your `pyproject.toml` or a + `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) +* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`) + to the `requires` key of the `build-system` table in `pyproject.toml`: + ```toml + [build-system] + requires = ["setuptools", "versioneer[toml]"] + build-backend = "setuptools.build_meta" + ``` +* run `versioneer install --no-vendor` in your source tree, commit the results +* verify version information with `python setup.py version` ## Version Identifiers @@ -61,7 +85,7 @@ for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has -uncommitted changes. +uncommitted changes). The version identifier is used for multiple purposes: @@ -166,7 +190,7 @@ Some situations are known to cause problems for Versioneer. This details the most significant ones. More can be found on Github -[issues page](https://github.com/warner/python-versioneer/issues). +[issues page](https://github.com/python-versioneer/python-versioneer/issues). ### Subprojects @@ -180,7 +204,7 @@ `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI distributions (and upload multiple independently-installable tarballs). * Source trees whose main purpose is to contain a C library, but which also - provide bindings to Python (and perhaps other langauges) in subdirectories. + provide bindings to Python (and perhaps other languages) in subdirectories. Versioneer will look for `.git` in parent directories, and most operations should get the right version string. However `pip` and `setuptools` have bugs @@ -194,9 +218,9 @@ Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in some later version. -[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking +[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking this issue. The discussion in -[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the +[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the issue from the Versioneer side in more detail. [pip PR#3176](https://github.com/pypa/pip/pull/3176) and [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve @@ -224,31 +248,20 @@ cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into a different virtualenv), so this can be surprising. -[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes +[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes this one, but upgrading to a newer version of setuptools should probably resolve it. -### Unicode version strings - -While Versioneer works (and is continually tested) with both Python 2 and -Python 3, it is not entirely consistent with bytes-vs-unicode distinctions. -Newer releases probably generate unicode version strings on py2. It's not -clear that this is wrong, but it may be surprising for applications when then -write these strings to a network connection or include them in bytes-oriented -APIs like cryptographic checksums. - -[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates -this question. - ## Updating Versioneer To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) -* edit `setup.cfg`, if necessary, to include any new configuration settings - indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. -* re-run `versioneer install` in your source tree, to replace +* edit `setup.cfg` and `pyproject.toml`, if necessary, + to include any new configuration settings indicated by the release notes. + See [UPGRADING](./UPGRADING.md) for details. +* re-run `versioneer install --[no-]vendor` in your source tree, to replace `SRC/_version.py` * commit any changed files @@ -265,33 +278,56 @@ direction and include code from all supported VCS systems, reducing the number of intermediate scripts. +## Similar projects + +* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time + dependency +* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of + versioneer +* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools + plugin ## License To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. -Specifically, both are released under the Creative Commons "Public Domain -Dedication" license (CC0-1.0), as described in -https://creativecommons.org/publicdomain/zero/1.0/ . +Specifically, both are released under the "Unlicense", as described in +https://unlicense.org/. -""" +[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg +[pypi-url]: https://pypi.python.org/pypi/versioneer/ +[travis-image]: +https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg +[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer -from __future__ import print_function +""" +# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring +# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements +# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error +# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with +# pylint:disable=attribute-defined-outside-init,too-many-arguments -try: - import configparser -except ImportError: - import ConfigParser as configparser +import configparser import errno import json import os import re import subprocess import sys +import functools from pkg_resources import parse_version +have_tomllib = True +if sys.version_info >= (3, 11): + import tomllib +else: + try: + import tomli as tomllib + except ImportError: + have_tomllib = False + class VersioneerConfig: """Container for Versioneer configuration parameters.""" @@ -325,12 +361,12 @@ def get_root(): # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. - me = os.path.realpath(os.path.abspath(__file__)) - me_dir = os.path.normcase(os.path.splitext(me)[0]) + my_path = os.path.realpath(os.path.abspath(__file__)) + me_dir = os.path.normcase(os.path.splitext(my_path)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) - if me_dir != vsr_dir: + if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals(): print("Warning: build in %s is using versioneer.py from %s" - % (os.path.dirname(me), versioneer_py)) + % (os.path.dirname(my_path), versioneer_py)) except NameError: pass return root @@ -338,31 +374,38 @@ def get_root(): def get_config_from_root(root): """Read the project setup.cfg file to determine Versioneer config.""" - # This might raise EnvironmentError (if setup.cfg is missing), or + # This might raise OSError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . + pyproject_toml = os.path.join(root, "pyproject.toml") setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.SafeConfigParser() - with open(setup_cfg, "r") as f: - parser.readfp(f) - VCS = parser.get("versioneer", "VCS") # mandatory + section = None + if os.path.exists(pyproject_toml) and have_tomllib: + try: + with open(pyproject_toml, 'rb') as fobj: + pp = tomllib.load(fobj) + section = pp['tool']['versioneer'] + except (tomllib.TOMLDecodeError, KeyError): + pass + if not section: + parser = configparser.ConfigParser() + with open(setup_cfg) as cfg_file: + parser.read_file(cfg_file) + parser.get("versioneer", "VCS") # raise error if missing - def get(parser, name): - if parser.has_option("versioneer", name): - return parser.get("versioneer", name) - return None + section = parser["versioneer"] cfg = VersioneerConfig() - cfg.VCS = VCS - cfg.style = get(parser, "style") or "" - cfg.versionfile_source = get(parser, "versionfile_source") - cfg.versionfile_build = get(parser, "versionfile_build") - cfg.tag_prefix = get(parser, "tag_prefix") - if cfg.tag_prefix in ("''", '""'): + cfg.VCS = section['VCS'] + cfg.style = section.get("style", "") + cfg.versionfile_source = section.get("versionfile_source") + cfg.versionfile_build = section.get("versionfile_build") + cfg.tag_prefix = section.get("tag_prefix") + if cfg.tag_prefix in ("''", '""', None): cfg.tag_prefix = "" - cfg.parentdir_prefix = get(parser, "parentdir_prefix") - cfg.verbose = get(parser, "verbose") + cfg.parentdir_prefix = section.get("parentdir_prefix") + cfg.verbose = section.get("verbose") return cfg @@ -376,15 +419,11 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" - + """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f + HANDLERS.setdefault(vcs, {})[method] = f return f - return decorate @@ -392,17 +431,25 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) - p = None - for c in commands: + process = None + + popen_kwargs = {} + if sys.platform == "win32": + # This hides the console window if pythonw.exe is used + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + popen_kwargs["startupinfo"] = startupinfo + + for command in commands: try: - dispcmd = str([c] + args) + dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None), **popen_kwargs) break - except EnvironmentError: + except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue @@ -414,26 +461,25 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, if verbose: print("unable to find command, tried %s" % (commands,)) return None, None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: + stdout = process.communicate()[0].strip().decode('utf-8') + if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) - return None, p.returncode - return stdout, p.returncode + return None, process.returncode + return stdout, process.returncode -LONG_VERSION_PY['git'] = ''' +LONG_VERSION_PY['git'] = r''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. -# This file is released into the public domain. Generated by -# versioneer-0.18 (https://github.com/warner/python-versioneer) +# This file is released into the public domain. +# Generated by versioneer-0.28 +# https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" @@ -442,6 +488,7 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, import re import subprocess import sys +import functools def get_keywords(): @@ -484,7 +531,7 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" + """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: @@ -498,17 +545,25 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) - p = None - for c in commands: + process = None + + popen_kwargs = {} + if sys.platform == "win32": + # This hides the console window if pythonw.exe is used + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + popen_kwargs["startupinfo"] = startupinfo + + for command in commands: try: - dispcmd = str([c] + args) + dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None), **popen_kwargs) break - except EnvironmentError: + except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue @@ -520,15 +575,13 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, if verbose: print("unable to find command, tried %%s" %% (commands,)) return None, None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: + stdout = process.communicate()[0].strip().decode() + if process.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) print("stdout was %%s" %% stdout) - return None, p.returncode - return stdout, p.returncode + return None, process.returncode + return stdout, process.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): @@ -540,15 +593,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): """ rootdirs = [] - for i in range(3): + for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} - else: - rootdirs.append(root) - root = os.path.dirname(root) # up a level + rootdirs.append(root) + root = os.path.dirname(root) # up a level if verbose: print("Tried directories %%s but none started with prefix %%s" %% @@ -565,22 +617,21 @@ def git_get_keywords(versionfile_abs): # _version.py. keywords = {} try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - f.close() - except EnvironmentError: + with open(versionfile_abs, "r") as fobj: + for line in fobj: + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + except OSError: pass return keywords @@ -588,10 +639,14 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] + # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because @@ -604,11 +659,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %%d @@ -617,7 +672,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: @@ -626,6 +681,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] + # Filter out refs that exactly match prefix or that don't start + # with a number once the prefix is stripped (mostly a concern + # when prefix is '') + if not re.match(r'\d', r): + continue if verbose: print("picking %%s" %% r) return {"version": r, @@ -641,7 +701,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): +def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -652,8 +712,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + # GIT_DIR can interfere with correct operation of Versioneer. + # It may be intended to be passed to the Versioneer-versioned project, + # but that should not change where we get our version from. + env = os.environ.copy() + env.pop("GIT_DIR", None) + runner = functools.partial(runner, env=env) + + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %%s not under git control" %% root) @@ -661,15 +728,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%%s*" %% tag_prefix], - cwd=root) + describe_out, rc = runner(GITS, [ + "describe", "--tags", "--dirty", "--always", "--long", + "--match", "{}[[:digit:]]*".format(tag_prefix) + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() @@ -679,6 +746,39 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) + # --abbrev-ref was added in git-1.6.3 + if rc != 0 or branch_name is None: + raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") + branch_name = branch_name.strip() + + if branch_name == "HEAD": + # If we aren't exactly on a branch, pick a branch which represents + # the current commit. If all else fails, we are on a branchless + # commit. + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + # --contains was added in git-1.5.4 + if rc != 0 or branches is None: + raise NotThisMethod("'git branch --contains' returned error") + branches = branches.split("\n") + + # Remove the first line if we're running detached + if "(" in branches[0]: + branches.pop(0) + + # Strip off the leading "* " from the list of branches. + branches = [branch[2:] for branch in branches] + if "master" in branches: + branch_name = "master" + elif not branches: + branch_name = None + else: + # Pick the first branch that is returned. Good or bad. + branch_name = branches[0] + + pieces["branch"] = branch_name + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out @@ -695,7 +795,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: - # unparseable. Maybe git-describe is misbehaving? + # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces @@ -720,13 +820,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) - pieces["distance"] = int(count_out) # total number of commits + out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) + pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], - cwd=root)[0].strip() + date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces @@ -764,19 +865,67 @@ def render_pep440(pieces): return rendered -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. +def render_pep440_branch(pieces): + """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . + + The ".dev0" means not master branch. Note that .dev0 sorts backwards + (a feature branch will appear "older" than the master branch). Exceptions: - 1: no tags. 0.post.devDISTANCE + 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%%d.g%%s" %% (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def pep440_split_post(ver): + """Split pep440 version string at the post-release segment. + + Returns the release segments before the post-release and the + post-release version number (or -1 if no post-release segment is present). + """ + vc = str.split(ver, ".post") + return vc[0], int(vc[1] or 0) if len(vc) == 2 else None + + +def render_pep440_pre(pieces): + """TAG[.postN.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post0.devDISTANCE + """ + if pieces["closest-tag"]: if pieces["distance"]: - rendered += ".post.dev%%d" %% pieces["distance"] + # update the post release segment + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + rendered = tag_version + if post_version is not None: + rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"]) + else: + rendered += ".post0.dev%%d" %% (pieces["distance"]) + else: + # no commits, use the tag as the version + rendered = pieces["closest-tag"] else: # exception #1 - rendered = "0.post.dev%%d" %% pieces["distance"] + rendered = "0.post0.dev%%d" %% pieces["distance"] return rendered @@ -807,49 +956,30 @@ def render_pep440_post(pieces): return rendered -def render_pep440_ricequant(pieces): - tag = pieces["closest-tag"] - parsed_tag = parse_version(tag) if tag else None - - working = working_version() - parsed_working = parse_version(working) +def render_pep440_post_branch(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . - rendered = working + The ".dev0" means not master branch. - if tag: - if parsed_working < parsed_tag: - if parsed_working.base_version == parsed_tag.base_version: - if not parsed_tag.is_postrelease: - raise Exception("Only post release tag allowed, tag %s" % tag) - if parsed_tag._version.post[0] != "post": - raise Exception("Only post release tag allowed, tag %s" % tag) - post_num = parsed_tag._version.post[1] - rendered += ".post%d" % (post_num + 1) - if pieces["distance"] or pieces["dirty"]: - rendered += ".dev%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dirty" - else: - raise Exception(f"Developing version can not go back in time: {working_version()} < {tag}") - elif parsed_working == parsed_tag: - if pieces["distance"] or pieces["dirty"]: - rendered += ".post1.dev%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dirty" - else: - if pieces["distance"] or pieces["dirty"]: - rendered += ".dev%d" % pieces["distance"] - else: + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%%d" %% pieces["distance"] + if pieces["branch"] != "master": rendered += ".dev0" - + rendered += plus_or_dot(pieces) + rendered += "g%%s" %% pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: - if pieces["distance"] or pieces["dirty"]: - rendered += ".dev%d" % pieces["distance"] - else: + # exception #1 + rendered = "0.post%%d" %% pieces["distance"] + if pieces["branch"] != "master": rendered += ".dev0" - + rendered += "+g%%s" %% pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered @@ -860,7 +990,7 @@ def render_pep440_old(pieces): The ".dev0" means dirty. - Eexceptions: + Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: @@ -931,14 +1061,16 @@ def render(pieces, style): if style == "pep440": rendered = render_pep440(pieces) + elif style == "pep440-branch": + rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) + elif style == "pep440-post-branch": + rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) - elif style == 'pep440-ricequant': - rendered = render_pep440_ricequant(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": @@ -972,7 +1104,7 @@ def get_versions(): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): + for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, @@ -1007,22 +1139,21 @@ def git_get_keywords(versionfile_abs): # _version.py. keywords = {} try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - f.close() - except EnvironmentError: + with open(versionfile_abs, "r") as fobj: + for line in fobj: + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + except OSError: pass return keywords @@ -1030,10 +1161,14 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because @@ -1046,11 +1181,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -1059,7 +1194,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -1068,6 +1203,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] + # Filter out refs that exactly match prefix or that don't start + # with a number once the prefix is stripped (mostly a concern + # when prefix is '') + if not re.match(r'\d', r): + continue if verbose: print("picking %s" % r) return {"version": r, @@ -1091,7 +1231,7 @@ def git_tracking_branch(): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): +def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -1102,8 +1242,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + # GIT_DIR can interfere with correct operation of Versioneer. + # It may be intended to be passed to the Versioneer-versioned project, + # but that should not change where we get our version from. + env = os.environ.copy() + env.pop("GIT_DIR", None) + runner = functools.partial(runner, env=env) + + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -1111,15 +1258,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out, rc = runner(GITS, [ + "describe", "--tags", "--dirty", "--always", "--long", + "--match", "{}[[:digit:]]*".format(tag_prefix) + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() @@ -1129,6 +1276,39 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) + # --abbrev-ref was added in git-1.6.3 + if rc != 0 or branch_name is None: + raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") + branch_name = branch_name.strip() + + if branch_name == "HEAD": + # If we aren't exactly on a branch, pick a branch which represents + # the current commit. If all else fails, we are on a branchless + # commit. + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + # --contains was added in git-1.5.4 + if rc != 0 or branches is None: + raise NotThisMethod("'git branch --contains' returned error") + branches = branches.split("\n") + + # Remove the first line if we're running detached + if "(" in branches[0]: + branches.pop(0) + + # Strip off the leading "* " from the list of branches. + branches = [branch[2:] for branch in branches] + if "master" in branches: + branch_name = "master" + elif not branches: + branch_name = None + else: + # Pick the first branch that is returned. Good or bad. + branch_name = branches[0] + + pieces["branch"] = branch_name + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out @@ -1145,7 +1325,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: - # unparseable. Maybe git-describe is misbehaving? + # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces @@ -1170,19 +1350,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) - pieces["distance"] = int(count_out) # total number of commits + out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) + pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], - cwd=root)[0].strip() + date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces -def do_vcs_install(manifest_in, versionfile_source, ipy): +def do_vcs_install(versionfile_source, ipy): """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py @@ -1191,31 +1372,31 @@ def do_vcs_install(manifest_in, versionfile_source, ipy): GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - files = [manifest_in, versionfile_source] + files = [versionfile_source] if ipy: files.append(ipy) - try: - me = __file__ - if me.endswith(".pyc") or me.endswith(".pyo"): - me = os.path.splitext(me)[0] + ".py" - versioneer_file = os.path.relpath(me) - except NameError: - versioneer_file = "versioneer.py" - files.append(versioneer_file) + if "VERSIONEER_PEP518" not in globals(): + try: + my_path = __file__ + if my_path.endswith((".pyc", ".pyo")): + my_path = os.path.splitext(my_path)[0] + ".py" + versioneer_file = os.path.relpath(my_path) + except NameError: + versioneer_file = "versioneer.py" + files.append(versioneer_file) present = False try: - f = open(".gitattributes", "r") - for line in f.readlines(): - if line.strip().startswith(versionfile_source): - if "export-subst" in line.strip().split()[1:]: - present = True - f.close() - except EnvironmentError: + with open(".gitattributes", "r") as fobj: + for line in fobj: + if line.strip().startswith(versionfile_source): + if "export-subst" in line.strip().split()[1:]: + present = True + break + except OSError: pass if not present: - f = open(".gitattributes", "a+") - f.write("%s export-subst\n" % versionfile_source) - f.close() + with open(".gitattributes", "a+") as fobj: + fobj.write("{} export-subst\n".format(versionfile_source)) files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) @@ -1229,15 +1410,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): """ rootdirs = [] - for i in range(3): + for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} - else: - rootdirs.append(root) - root = os.path.dirname(root) # up a level + rootdirs.append(root) + root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % @@ -1246,7 +1426,7 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.18) from +# This file was generated by 'versioneer.py' (0.28) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. @@ -1268,7 +1448,7 @@ def versions_from_file(filename): try: with open(filename) as f: contents = f.read() - except EnvironmentError: + except OSError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) @@ -1323,19 +1503,67 @@ def render_pep440(pieces): return rendered -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. +def render_pep440_branch(pieces): + """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . + + The ".dev0" means not master branch. Note that .dev0 sorts backwards + (a feature branch will appear "older" than the master branch). Exceptions: - 1: no tags. 0.post.devDISTANCE + 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def pep440_split_post(ver): + """Split pep440 version string at the post-release segment. + + Returns the release segments before the post-release and the + post-release version number (or -1 if no post-release segment is present). + """ + vc = str.split(ver, ".post") + return vc[0], int(vc[1] or 0) if len(vc) == 2 else None + + +def render_pep440_pre(pieces): + """TAG[.postN.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post0.devDISTANCE + """ + if pieces["closest-tag"]: if pieces["distance"]: - rendered += ".post.dev%d" % pieces["distance"] + # update the post release segment + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + rendered = tag_version + if post_version is not None: + rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) + else: + rendered += ".post0.dev%d" % (pieces["distance"]) + else: + # no commits, use the tag as the version + rendered = pieces["closest-tag"] else: # exception #1 - rendered = "0.post.dev%d" % pieces["distance"] + rendered = "0.post0.dev%d" % pieces["distance"] return rendered @@ -1366,12 +1594,33 @@ def render_pep440_post(pieces): return rendered -def debug_info(): - root = get_root() - cfg = get_config_from_root(root) - pieces = git_pieces_from_vcs(cfg.tag_prefix, root, cfg.verbose) - return "Developing: %s, CurrentTag: %s, Distance: %d, Dirty: %s" % ( - working_version(), pieces["closest-tag"], pieces["distance"], pieces["dirty"]) +def render_pep440_post_branch(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . + + The ".dev0" means not master branch. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + return rendered def render_pep440_ricequant(pieces): @@ -1397,39 +1646,33 @@ def render_pep440_ricequant(pieces): rendered += ".dev%d" % (pieces["distance"]) elif pieces["distance"] == 0: rendered += ".post%d" % parsed_tag._version.post[1] - if pieces["dirty"]: - rendered += ".dirty" else: raise Exception("Developing version can not go back in time: %s < %s" % (working, tag)) # 如果最近的tag是正式版tag,那么就是在开发该系列的.post1 elif parsed_working == parsed_tag: if pieces["distance"] > 0: rendered += ".post1.dev%d" % (pieces["distance"]) - if pieces["dirty"]: - rendered += ".dirty" # 如果正在开发到是一个新的系列,那么就从该系列的.dev0开始 else: if pieces["distance"] >= 0: rendered += ".dev%d" % (pieces["distance"]) - - if pieces["dirty"]: - rendered += ".dirty" # 没有最近的tag等价于正在开发到是一个新的系列,那么就从该系列的.dev0开始 else: if pieces["distance"] >= 0: rendered += ".dev%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dirty" - tracking_branch = git_tracking_branch() # # 如果是dev和master分支或者hotfix分支来的,或者是一个tag,那就用pep440的版本号,否则带上git commit id if tracking_branch in ["origin/develop", "origin/master"] or tracking_branch.startswith("origin/hotfix/") or pieces[ "distance"] == 0: + if pieces["dirty"]: + rendered += ".dirty" return rendered - else: - rendered += "+g{}".format(pieces["short"]) - return rendered + + rendered += "+%s" % format(pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered def render_pep440_old(pieces): @@ -1437,7 +1680,7 @@ def render_pep440_old(pieces): The ".dev0" means dirty. - Eexceptions: + Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: @@ -1508,14 +1751,18 @@ def render(pieces, style): if style == "pep440": rendered = render_pep440(pieces) + elif style == "pep440-ricequant": + rendered = render_pep440_ricequant(pieces) + elif style == "pep440-branch": + rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) + elif style == "pep440-post-branch": + rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) - elif style == 'pep440-ricequant': - rendered = render_pep440_ricequant(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": @@ -1613,8 +1860,12 @@ def get_version(): return get_versions()["version"] -def get_cmdclass(): - """Get the custom setuptools/distutils subclasses used by Versioneer.""" +def get_cmdclass(cmdclass=None): + """Get the custom setuptools subclasses used by Versioneer. + + If the package uses a different cmdclass (e.g. one from numpy), it + should be provide as an argument. + """ if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and @@ -1628,12 +1879,12 @@ def get_cmdclass(): # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. - # Also see https://github.com/warner/python-versioneer/issues/52 + # Also see https://github.com/python-versioneer/python-versioneer/issues/52 - cmds = {} + cmds = {} if cmdclass is None else cmdclass.copy() - # we add "version" to both distutils and setuptools - from distutils.core import Command + # we add "version" to setuptools + from setuptools import Command class cmd_version(Command): description = "report generated version string" @@ -1654,10 +1905,9 @@ def run(self): print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) - cmds["version"] = cmd_version - # we override "build_py" in both distutils and setuptools + # we override "build_py" in setuptools # # most invocation pathways end up running build_py: # distutils/build -> build_py @@ -1672,11 +1922,14 @@ def run(self): # then does setup.py bdist_wheel, or sometimes setup.py install # setup.py egg_info -> ? + # pip install -e . and setuptool/editable_wheel will invoke build_py + # but the build_py command is not expected to copy any files. + # we override different "build_py" commands for both environments - if "setuptools" in sys.modules: - from setuptools.command.build_py import build_py as _build_py + if 'build_py' in cmds: + _build_py = cmds['build_py'] else: - from distutils.command.build_py import build_py as _build_py + from setuptools.command.build_py import build_py as _build_py class cmd_build_py(_build_py): def run(self): @@ -1684,6 +1937,10 @@ def run(self): cfg = get_config_from_root(root) versions = get_versions() _build_py.run(self) + if getattr(self, "editable_mode", False): + # During editable installs `.py` and data files are + # not copied to build_lib + return # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: @@ -1691,9 +1948,40 @@ def run(self): cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) - cmds["build_py"] = cmd_build_py + if 'build_ext' in cmds: + _build_ext = cmds['build_ext'] + else: + from setuptools.command.build_ext import build_ext as _build_ext + + class cmd_build_ext(_build_ext): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + _build_ext.run(self) + if self.inplace: + # build_ext --inplace will only build extensions in + # build/lib<..> dir with no _version.py to write to. + # As in place builds will already have a _version.py + # in the module dir, we do not need to write one. + return + # now locate _version.py in the new build/ directory and replace + # it with an updated value + if not cfg.versionfile_build: + return + target_versionfile = os.path.join(self.build_lib, + cfg.versionfile_build) + if not os.path.exists(target_versionfile): + print("Warning: {} does not exist, skipping " + "version update. This can happen if you are running build_ext " + "without first running build_py.".format(target_versionfile)) + return + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + cmds["build_ext"] = cmd_build_ext + if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe # nczeczulin reports that py2exe won't like the pep440-style string @@ -1723,15 +2011,14 @@ def run(self): "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) - cmds["build_exe"] = cmd_build_exe del cmds["build_py"] if 'py2exe' in sys.modules: # py2exe enabled? try: - from py2exe.distutils_buildexe import py2exe as _py2exe # py3 + from py2exe.setuptools_buildexe import py2exe as _py2exe except ImportError: - from py2exe.build_exe import py2exe as _py2exe # py2 + from py2exe.distutils_buildexe import py2exe as _py2exe class cmd_py2exe(_py2exe): def run(self): @@ -1753,14 +2040,50 @@ def run(self): "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) - cmds["py2exe"] = cmd_py2exe + # sdist farms its file list building out to egg_info + if 'egg_info' in cmds: + _egg_info = cmds['egg_info'] + else: + from setuptools.command.egg_info import egg_info as _egg_info + + class cmd_egg_info(_egg_info, object): + def find_sources(self): + # egg_info.find_sources builds the manifest list and writes it + # in one shot + super(cmd_egg_info, self).find_sources() + + # Modify the filelist and normalize it + root = get_root() + cfg = get_config_from_root(root) + self.filelist.append('versioneer.py') + if cfg.versionfile_source: + # There are rare cases where versionfile_source might not be + # included by default, so we must be explicit + self.filelist.append(cfg.versionfile_source) + self.filelist.sort() + self.filelist.remove_duplicates() + + # The write method is hidden in the manifest_maker instance that + # generated the filelist and was thrown away + # We will instead replicate their final normalization (to unicode, + # and POSIX-style paths) + from setuptools import unicode_utils + normalized = [unicode_utils.filesys_decode(f).replace(os.sep, '/') + for f in self.filelist.files] + + manifest_filename = os.path.join(self.egg_info, 'SOURCES.txt') + with open(manifest_filename, 'w') as fobj: + fobj.write('\n'.join(normalized)) + + cmds['egg_info'] = cmd_egg_info + # we override different "sdist" commands for both environments - if "setuptools" in sys.modules: - from setuptools.command.sdist import sdist as _sdist + if 'sdist' in cmds: + _sdist = cmds['sdist'] else: - from distutils.command.sdist import sdist as _sdist + from setuptools.command.sdist import sdist as _sdist class cmd_sdist(_sdist): def run(self): @@ -1782,7 +2105,6 @@ def make_release_tree(self, base_dir, files): print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, self._versioneer_generated_versions) - cmds["sdist"] = cmd_sdist return cmds @@ -1825,26 +2147,30 @@ def make_release_tree(self, base_dir, files): """ -INIT_PY_SNIPPET = """ +OLD_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ +INIT_PY_SNIPPET = """ +from . import {0} +__version__ = {0}.get_versions()['version'] +""" + def do_setup(): - """Main VCS-independent setup function for installing Versioneer.""" + """Do main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) - except (EnvironmentError, configparser.NoSectionError, + except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e: - if isinstance(e, (EnvironmentError, configparser.NoSectionError)): - print("Adding sample versioneer config to setup.cfg", - file=sys.stderr) + if isinstance(e, (OSError, configparser.NoSectionError)): + print("Adding sample versioneer config to setup.cfg") with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) - print(CONFIG_ERROR, file=sys.stderr) + print(CONFIG_ERROR) return 1 print(" creating %s" % cfg.versionfile_source) @@ -1863,54 +2189,28 @@ def do_setup(): try: with open(ipy, "r") as f: old = f.read() - except EnvironmentError: + except OSError: old = "" - if INIT_PY_SNIPPET not in old: + module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] + snippet = INIT_PY_SNIPPET.format(module) + if OLD_SNIPPET in old: + print(" replacing boilerplate in %s" % ipy) + with open(ipy, "w") as f: + f.write(old.replace(OLD_SNIPPET, snippet)) + elif snippet not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: - f.write(INIT_PY_SNIPPET) + f.write(snippet) else: print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) ipy = None - # Make sure both the top-level "versioneer.py" and versionfile_source - # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so - # they'll be copied into source distributions. Pip won't be able to - # install the package without this. - manifest_in = os.path.join(root, "MANIFEST.in") - simple_includes = set() - try: - with open(manifest_in, "r") as f: - for line in f: - if line.startswith("include "): - for include in line.split()[1:]: - simple_includes.add(include) - except EnvironmentError: - pass - # That doesn't cover everything MANIFEST.in can do - # (http://docs.python.org/2/distutils/sourcedist.html#commands), so - # it might give some false negatives. Appending redundant 'include' - # lines is safe, though. - if "versioneer.py" not in simple_includes: - print(" appending 'versioneer.py' to MANIFEST.in") - with open(manifest_in, "a") as f: - f.write("include versioneer.py\n") - else: - print(" 'versioneer.py' already in MANIFEST.in") - if cfg.versionfile_source not in simple_includes: - print(" appending versionfile_source ('%s') to MANIFEST.in" % - cfg.versionfile_source) - with open(manifest_in, "a") as f: - f.write("include %s\n" % cfg.versionfile_source) - else: - print(" versionfile_source already in MANIFEST.in") - # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-subst keyword # substitution. - do_vcs_install(manifest_in, cfg.versionfile_source, ipy) + do_vcs_install(cfg.versionfile_source, ipy) return 0 @@ -1951,11 +2251,18 @@ def scan_setup_py(): return errors +def setup_command(): + """Set up Versioneer and exit with appropriate error code.""" + errors = do_setup() + errors += scan_setup_py() + sys.exit(1 if errors else 0) + + def get_metadata(): setup_cfg = os.path.join(get_root(), "setup.cfg") - parser = configparser.SafeConfigParser() + parser = configparser.RawConfigParser() with open(setup_cfg, "r") as f: - parser.readfp(f) + parser.read_file(f) def get(section, parser, name): if parser.has_option(section, name): @@ -1975,150 +2282,7 @@ def package_name(): def working_version(): return get_metadata()["version"] - -def next_tag_name(): - root = get_root() - cfg = get_config_from_root(root) - pieces = git_pieces_from_vcs(cfg.tag_prefix, root, cfg.verbose) - - tag = pieces["closest-tag"] - parsed_tag = parse_version(tag) if tag else None - - working = working_version() - parsed_working = parse_version(working) - - rendered = working - - curr_version = get_version() - parsed_curr_version = parse_version(curr_version) - - if tag: - # 如果当前开发系列正式版小于最近的tag,那么当前开发系列和该tag必须属于同一个开发版本系列 - # 当前开发都没有做完,大于当前开发系列的tag不可能存 - # 因为大于当前开发系列的正式版, 该tag必然是post release - # 如果发现当前commit和最近的tag的commit相同,规定next tag不变. 这也限制只有最近tag后的第一个commit存在, - # 才会获取到next tag name - if parsed_working < parsed_tag: - if parsed_working.base_version == parsed_tag.base_version: - if not parsed_tag.is_postrelease: - raise Exception("Only post release tag allowed, tag %s" % tag) - if parsed_tag._version.post[0] != "post": - raise Exception("Only post release tag allowed, tag %s" % tag) - # 正在开发的版本不可能小于等于最近的tag - if parsed_curr_version <= parsed_tag: - raise Exception("Current version %s should > latest tag %s" % (curr_version, tag)) - if pieces["distance"] == 0: - return tag - - rendered += ".post%d" % parsed_curr_version._version.post[1] - else: - raise Exception( - "Developing version can not go back in time: %s(working version series) < %s(latest tag)" % working, - tag) - # 如果当前开发系列的正式版等于最近的tag,那么下一个tag必定是.post1 - elif parsed_working == parsed_tag: - if parsed_curr_version._version.post[0] != "post": - raise Exception("Current version should be a post version %s" % curr_version) - rendered += ".post%d" % parsed_curr_version._version.post[1] - - # 如果当前开发版本系列正式版大于最近的tag,那么下一个的tag将是当前开发系列的正式版tag - return rendered - - -def is_same_package(package_file): - curr_version = get_versions() - v = _get_version_py_in_tar(package_file) - if not v: - v = _get_version_py_in_zip(package_file) - if not v: - raise Exception("Can not get _version.py from %s" % package_file) - - from types import ModuleType - m = ModuleType("get_versions") - exec(v, m.__dict__) - version_info = m.get_versions() - return curr_version["full-revisionid"] == version_info["full-revisionid"] - - -def _get_package_dir(): - package = None - for d in os.listdir(): - if os.path.isdir(d): - if "__init__.py" in os.listdir(d): - package = d - return package - - -def _get_version_py_in_zip(package_file): - import zipfile - package_dir = _get_package_dir() - - if zipfile.is_zipfile(package_file): - f = zipfile.ZipFile(package_file) - il = f.infolist() - if len(il) == 0: - raise Exception("No member found in %s" % package_file) - for i in il: - if i.filename == os.path.join(package_dir, "_version.py"): - return f.open(i).read() - return None - - -def _get_version_py_in_tar(package_file): - import tarfile - package_dir = _get_package_dir() - if tarfile.is_tarfile(package_file): - f = tarfile.open(package_file) - ms = f.getmembers() - if len(ms) == 0: - raise Exception("No members found in %s" % package_file) - for m in ms: - if os.path.join(ms[0].name, package_dir, "_version.py") == m.path: - version_file = f.extractfile(m) - return version_file.read() - return None - - -def get_package_name_in_pypi(version=None): - from pip._internal.commands.install import InstallCommand - from pip._internal.cli.cmdoptions import make_target_python - - class ListVersionCommand(InstallCommand): - def __init__(self, name, summary): - super().__init__(name, summary) - - def get_version_list(self): - with self.main_context(): - options, args = self.parse_args([package_name()]) - session = self.get_default_session(options=options) - target_python = make_target_python(options) - finder = self._build_package_finder( - options=options, - session=session, - target_python=target_python, - ignore_requires_python=options.ignore_requires_python, - ) - versions = {} - for c in finder.find_all_candidates(package_name()): - v = str(c.version) - f = c.link.filename - if (v in versions and f.endswith(".whl")) or v not in versions: - versions[v] = f - return versions - - if not version: - version = get_version() - c = ListVersionCommand("listversion", summary="list versions") - versions = c.get_version_list() - return versions.get(version, "") - - if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": - errors = do_setup() - errors += scan_setup_py() - if errors: - sys.exit(1) - elif cmd == "is_pypi_has": - get_package_name_in_pypi(sys.argv[2]) \ No newline at end of file + setup_command() From 04295aacf76d6962162ba7b2a97b38352e615914 Mon Sep 17 00:00:00 2001 From: "Don.Lin" <142398161+Lin-Dongzhao@users.noreply.github.com> Date: Fri, 12 Jan 2024 14:12:44 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E5=8D=B0=E8=8A=B1=E7=A8=8E=E6=94=B6?= =?UTF-8?q?=E5=8F=96=E6=96=B0=E5=A2=9EPIT=E6=A8=A1=E5=BC=8F=20(#837)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 印花税收取新增PIT模式 * test data update * pr update --- .../__init__.py | 10 ++++++++++ .../deciders.py | 15 ++++++++++++++- .../rqalpha_mod_sys_transaction_cost/mod.py | 2 +- tests/outs/test_f_mean_reverting.pkl | Bin 111814 -> 111502 bytes 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py index fd5c8925f..718047f00 100644 --- a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py +++ b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py @@ -26,6 +26,8 @@ "futures_commission_multiplier": 1, # 印花倍率,即在默认的印花税基础上按该倍数进行调整,股票默认印花税为千分之一,单边收取 "tax_multiplier": 1, + # 是否使用回测当时时间点对应的真实印花税率 + "pit_tax": False, } cli_prefix = "mod__sys_transaction_cost__" @@ -83,6 +85,14 @@ ) ) +cli.commands['run'].params.append( + click.Option( + ('--pit-tax', cli_prefix + "pit_tax"), + is_flag=True, default=False, + help="[sys_transaction_cost] use historical tax" + ) +) + def load_mod(): from .mod import TransactionCostMod diff --git a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py index f394b899b..d69f69e2e 100644 --- a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py +++ b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py @@ -13,10 +13,15 @@ # 详细的授权流程,请联系 public@ricequant.com 获取。 from collections import defaultdict +from datetime import datetime from rqalpha.interface import AbstractTransactionCostDecider from rqalpha.environment import Environment from rqalpha.const import SIDE, HEDGE_TYPE, COMMISSION_TYPE, POSITION_EFFECT +from rqalpha.core.events import EVENT + + +STOCK_PIT_TAX_CHANGE_DATE = datetime(2023, 8, 23) class StockTransactionCostDecider(AbstractTransactionCostDecider): @@ -75,9 +80,11 @@ def get_order_transaction_cost(self, order): class CNStockTransactionCostDecider(StockTransactionCostDecider): - def __init__(self, commission_multiplier, min_commission, tax_multiplier): + def __init__(self, commission_multiplier, min_commission, tax_multiplier, pit_tax, event_bus): super(CNStockTransactionCostDecider, self).__init__(0.0008, commission_multiplier, min_commission) self.tax_rate = 0.0005 + if pit_tax: + event_bus.add_listener(EVENT.PRE_BEFORE_TRADING, self.set_tax_rate) self.tax_multiplier = tax_multiplier def _get_tax(self, order_book_id, side, cost_money): @@ -85,6 +92,12 @@ def _get_tax(self, order_book_id, side, cost_money): if instrument.type != 'CS': return 0 return cost_money * self.tax_rate * self.tax_multiplier if side == SIDE.SELL else 0 + + def set_tax_rate(self, event): + if event.trading_dt < STOCK_PIT_TAX_CHANGE_DATE: + self.tax_rate = 0.001 + else: + self.tax_rate = 0.0005 class CNFutureTransactionCostDecider(AbstractTransactionCostDecider): diff --git a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/mod.py b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/mod.py index 5cc56e1c4..a4ffb9bf1 100644 --- a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/mod.py +++ b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/mod.py @@ -47,7 +47,7 @@ def start_up(self, env, mod_config): continue env.set_transaction_cost_decider(instrument_type, CNStockTransactionCostDecider( stock_commission_multiplier, mod_config.cn_stock_min_commission, - mod_config.tax_multiplier + mod_config.tax_multiplier, mod_config.pit_tax, env.event_bus )) env.set_transaction_cost_decider(INSTRUMENT_TYPE.FUTURE, CNFutureTransactionCostDecider( diff --git a/tests/outs/test_f_mean_reverting.pkl b/tests/outs/test_f_mean_reverting.pkl index 120ba74fe9285103ef16c51de544d52804ef42e0..8b4e561f859b16d3b094d941d97ee1a74491ca28 100644 GIT binary patch delta 15590 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 IKmF_f0ZB$x4*&oF delta 15928 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 Date: Mon, 15 Jan 2024 13:57:37 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E6=9C=9F=E8=B4=A7=E5=9B=9E=E6=B5=8B?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=97=B6=E5=BA=8F=E4=BA=A4=E6=98=93=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E5=8A=9F=E8=83=BD=20(#835)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * modify .readthedocs.yml * 期货回测新增时序交易参数功能 * 调整future_info更新方法 * pr update * pr update * pr update * pr update * 增加函数参数及返回值类型说明 * pr update * pr update * 修改提示语翻译 * pr update --------- Co-authored-by: 周嘉俊 <35399214+Zhou-JiaJun@users.noreply.github.com> Co-authored-by: Cuizi7 Co-authored-by: 周嘉俊 <654181984@qq.com> --- messages.pot | 497 ++++++++++------- requirements.txt | 2 +- rqalpha/data/base_data_source/data_source.py | 36 +- rqalpha/data/base_data_source/storages.py | 116 +++- rqalpha/data/bundle.py | 196 ++++++- rqalpha/data/data_proxy.py | 5 +- rqalpha/environment.py | 1 - rqalpha/interface.py | 4 +- rqalpha/main.py | 8 +- .../position_model.py | 21 +- rqalpha/mod/rqalpha_mod_sys_analyser/mod.py | 2 +- .../validators/cash_validator.py | 2 +- .../__init__.py | 2 + .../deciders.py | 26 +- rqalpha/model/instrument.py | 45 +- rqalpha/portfolio/account.py | 14 +- rqalpha/utils/repr.py | 2 +- rqalpha/utils/testing/fixtures.py | 2 +- .../zh_Hans_CN/LC_MESSAGES/messages.mo | Bin 19487 -> 19612 bytes .../zh_Hans_CN/LC_MESSAGES/messages.po | 527 ++++++++++-------- setup.py | 2 + .../test_simulation_event_source.py | 4 +- .../test_commission_multiplier.py | 4 +- tests/outs/test_f_mean_reverting.pkl | Bin 111502 -> 111839 bytes tests/test_f_buy_and_hold.py | 3 + tests/test_f_mean_reverting.py | 3 + 26 files changed, 1035 insertions(+), 489 deletions(-) diff --git a/messages.pot b/messages.pot index 450c1ec12..225508c54 100644 --- a/messages.pot +++ b/messages.pot @@ -1,21 +1,21 @@ # Translations template for PROJECT. -# Copyright (C) 2022 ORGANIZATION +# Copyright (C) 2024 ORGANIZATION # This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2022. +# FIRST AUTHOR , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2022-06-24 10:33+0800\n" +"POT-Creation-Date: 2024-01-12 13:38+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.10.1\n" +"Generated-By: Babel 2.13.1\n" #: rqalpha/environment.py:70 msgid "" @@ -27,139 +27,135 @@ msgstr "" msgid "No such transaction cost decider, order_book_id = {}" msgstr "" -#: rqalpha/main.py:61 +#: rqalpha/main.py:60 msgid "" "There is no data between {start_date} and {end_date}. Please check your " "data bundle or select other backtest period." msgstr "" -#: rqalpha/main.py:80 +#: rqalpha/main.py:79 msgid "" "Missing persist provider. You need to set persist_provider before use " "persist" msgstr "" -#: rqalpha/main.py:126 +#: rqalpha/main.py:127 msgid "rqdatac init failed, some apis will not function properly: {}" msgstr "" -#: rqalpha/main.py:213 +#: rqalpha/main.py:218 msgid "system restored" msgstr "" -#: rqalpha/main.py:243 +#: rqalpha/main.py:248 msgid "strategy run successfully, normal exit" msgstr "" -#: rqalpha/main.py:248 +#: rqalpha/main.py:253 msgid "strategy execute exception" msgstr "" -#: rqalpha/apis/api_base.py:67 rqalpha/apis/api_base.py:266 -#: rqalpha/apis/api_base.py:302 +#: rqalpha/apis/api_base.py:69 rqalpha/apis/api_base.py:286 +#: rqalpha/apis/api_base.py:322 msgid "unsupported order_book_id type" msgstr "" -#: rqalpha/apis/api_base.py:87 rqalpha/apis/api_base.py:91 -msgid "Limit order price should not be nan." -msgstr "" - -#: rqalpha/apis/api_base.py:148 +#: rqalpha/apis/api_base.py:164 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:59 msgid "Main Future contracts[88] are not supported in paper trading." msgstr "" -#: rqalpha/apis/api_base.py:152 +#: rqalpha/apis/api_base.py:168 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:61 msgid "Index Future contracts[99] are not supported in paper trading." msgstr "" -#: rqalpha/apis/api_base.py:158 +#: rqalpha/apis/api_base.py:174 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:66 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:89 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:128 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:308 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:104 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:142 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:346 msgid "Order Creation Failed: [{order_book_id}] No market data" msgstr "" -#: rqalpha/apis/api_rqdatac.py:46 +#: rqalpha/apis/api_rqdatac.py:50 msgid "rqdatac is not available, extension apis will not function properly" msgstr "" -#: rqalpha/apis/api_rqdatac.py:102 +#: rqalpha/apis/api_rqdatac.py:106 msgid "in get_split, start_date {} is no earlier than the previous test day {}" msgstr "" -#: rqalpha/apis/api_rqdatac.py:140 +#: rqalpha/apis/api_rqdatac.py:144 msgid "in index_components, date {} is no earlier than test date {}" msgstr "" -#: rqalpha/apis/api_rqdatac.py:187 +#: rqalpha/apis/api_rqdatac.py:191 msgid "in index_weights, date {} is no earlier than previous test date {}" msgstr "" -#: rqalpha/apis/api_rqdatac.py:330 +#: rqalpha/apis/api_rqdatac.py:398 msgid "in get_price, end_date {} is no earlier than the previous test day {}" msgstr "" -#: rqalpha/apis/api_rqdatac.py:338 +#: rqalpha/apis/api_rqdatac.py:406 msgid "in get_price, start_date {} is no earlier than the previous test day {}" msgstr "" -#: rqalpha/apis/api_rqdatac.py:343 +#: rqalpha/apis/api_rqdatac.py:411 msgid "in get_price, start_date {} > end_date {}" msgstr "" -#: rqalpha/apis/api_rqdatac.py:805 +#: rqalpha/apis/api_rqdatac.py:873 msgid "'{0}' future does not exist" msgstr "" -#: rqalpha/apis/api_rqdatac.py:962 +#: rqalpha/apis/api_rqdatac.py:1159 msgid "in get_fundamentals entry_date {} is no earlier than test date {}" msgstr "" -#: rqalpha/apis/api_rqdatac.py:996 rqalpha/apis/api_rqdatac.py:1061 -#: rqalpha/utils/arg_checker.py:311 +#: rqalpha/apis/api_rqdatac.py:1193 rqalpha/apis/api_rqdatac.py:1258 +#: rqalpha/utils/arg_checker.py:320 msgid "" "function {}: invalid {} argument, quarter should be in form of '2012q3', " "got {} (type: {})" msgstr "" -#: rqalpha/cmds/bundle.py:33 +#: rqalpha/cmds/bundle.py:34 msgid "create bundle using RQDatac" msgstr "" -#: rqalpha/cmds/bundle.py:43 +#: rqalpha/cmds/bundle.py:44 msgid "" "rqdatac is required to create bundle. you can visit " "https://www.ricequant.com/welcome/rqdata to get rqdatac, or use \"rqalpha" " download-bundle\" to download monthly updated bundle." msgstr "" -#: rqalpha/cmds/bundle.py:54 rqalpha/cmds/bundle.py:83 +#: rqalpha/cmds/bundle.py:55 rqalpha/cmds/bundle.py:84 msgid "rqdatac init failed with error: {}" msgstr "" -#: rqalpha/cmds/bundle.py:62 +#: rqalpha/cmds/bundle.py:63 msgid "Update bundle using RQDatac" msgstr "" -#: rqalpha/cmds/bundle.py:72 +#: rqalpha/cmds/bundle.py:73 msgid "" "rqdatac is required to update bundle. you can visit " "https://www.ricequant.com/welcome/rqdata to get rqdatac, or use \"rqalpha" " download-bundle\" to download monthly updated bundle." msgstr "" -#: rqalpha/cmds/bundle.py:87 +#: rqalpha/cmds/bundle.py:88 msgid "bundle not exist, use \"rqalpha create-bundle\" command instead" msgstr "" -#: rqalpha/cmds/bundle.py:94 +#: rqalpha/cmds/bundle.py:95 msgid "Download bundle (monthly updated)" msgstr "" -#: rqalpha/cmds/bundle.py:105 +#: rqalpha/cmds/bundle.py:106 msgid "" "\n" " [WARNING]\n" @@ -168,24 +164,56 @@ msgid "" " Are you sure to continue?" msgstr "" -#: rqalpha/cmds/bundle.py:123 +#: rqalpha/cmds/bundle.py:124 msgid "Data bundle download successfully in {bundle_path}" msgstr "" -#: rqalpha/cmds/bundle.py:134 +#: rqalpha/cmds/bundle.py:127 +msgid "Check bundle" +msgstr "" + +#: rqalpha/cmds/bundle.py:141 msgid "try {} ..." msgstr "" -#: rqalpha/cmds/bundle.py:146 +#: rqalpha/cmds/bundle.py:153 msgid "downloading ..." msgstr "" -#: rqalpha/cmds/bundle.py:160 +#: rqalpha/cmds/bundle.py:167 msgid "" "\n" "Download failed, retry in {} seconds." msgstr "" +#: rqalpha/cmds/bundle.py:188 +msgid "corrupted files" +msgstr "" + +#: rqalpha/cmds/bundle.py:189 +msgid "remove files" +msgstr "" + +#: rqalpha/cmds/bundle.py:192 +msgid "remove success" +msgstr "" + +#: rqalpha/cmds/bundle.py:194 +msgid "corrupted files not remove" +msgstr "" + +#: rqalpha/cmds/bundle.py:196 +msgid "input error" +msgstr "" + +#: rqalpha/cmds/bundle.py:198 +msgid "bundle's day bar is incomplete, please update bundle" +msgstr "" + +#: rqalpha/cmds/bundle.py:200 +msgid "good bundle's day bar" +msgstr "" + #: rqalpha/cmds/misc.py:26 msgid "Generate example strategies to target folder" msgstr "" @@ -219,12 +247,38 @@ msgstr "" msgid "deprecated parameter[bar_dict] in before_trading function." msgstr "" -#: rqalpha/data/base_data_source/storages.py:85 +#: rqalpha/data/bundle.py:483 +msgid "" +"File {} update failed, if it is using, please update later, or you can " +"delete then update again" +msgstr "" + +#: rqalpha/data/bundle.py:561 +msgid "" +"RQAlpha already supports backtesting using futures historical margins and" +" rates, please upgrade RQDatac to version 2.11.12 and above to use it" +msgstr "" + +#: rqalpha/data/bundle.py:567 +msgid "" +"Your RQData account does not have permission to use futures historical " +"margin and rates, and fixed data will be used for calculations\n" +"You can contact RiceQuant to activate permission: 0755-26569969" +msgstr "" + +#: rqalpha/data/base_data_source/storages.py:82 +msgid "" +"Your bundle data is too old, please use 'rqalpha update-bundle' or " +"'rqalpha download-bundle' to update it to lastest before using" +msgstr "" + +#: rqalpha/data/base_data_source/storages.py:98 +#: rqalpha/data/base_data_source/storages.py:124 msgid "unsupported future instrument {}" msgstr "" -#: rqalpha/data/base_data_source/storages.py:155 -#: rqalpha/data/base_data_source/storages.py:165 +#: rqalpha/data/base_data_source/storages.py:195 +#: rqalpha/data/base_data_source/storages.py:205 msgid "" "open data bundle failed, you can remove {} and try to regenerate bundle: " "{}" @@ -252,7 +306,7 @@ msgstr "" msgid "mod tear_down [END] {}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:296 +#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:318 msgid "{order_book_id} is expired, close all positions by system" msgstr "" @@ -270,15 +324,15 @@ msgid "" "{closable}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:49 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:96 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:144 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:50 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:112 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:158 msgid "Order Creation Failed: 0 order quantity, order_book_id={order_book_id}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:54 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:85 -msgid "Limit order price should be positive" +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:55 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:100 +msgid "Limit order price should not be nan." msgstr "" #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:78 @@ -299,266 +353,337 @@ msgid "" "[{new_orders_repr}]" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:65 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:66 msgid "" "order_book_id: {order_book_id} needs stock account, please set and try " "again!" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:106 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:120 msgid "insufficient cash, use all remaining cash({}) to create order" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:296 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:334 msgid "" "function order_target_portfolio: invalid keys of target_portfolio, " "expected order_book_ids or Instrument objects, got {} (type: {})" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:301 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:339 msgid "" "function order_target_portfolio: invalid instrument type, excepted " "CS/ETF/LOF/INDX, got {}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:318 -msgid "" -"function order_target_portfolio: invalid order price {target_price} of " -"{id_or_ins}" -msgstr "" - -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:324 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:354 msgid "" "function order_target_portfolio: invalid values of target_portfolio, " "excepted float between 0 and 1, got {} (key: {})" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:332 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:363 msgid "total percent should be lower than 1, current: {}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:655 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:383 +msgid "" +"Adjust position of {id_or_ins} Failed: Invalid close/open price " +"{close_price}/{open_price}" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:698 msgid "in get_dividend, start_date {} is later than the previous test day {}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:64 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:66 msgid "[sys_analyser] save report" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:69 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:71 msgid "[sys_analyser] output result pickle file" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:74 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:76 msgid "[sys_analyser] plot result" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:79 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:81 msgid "[sys_analyser] save plot to file" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:84 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:86 msgid "[sys_analyser] order_book_id of benchmark" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:89 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:91 msgid "[sys_analyser] show open close points on plot" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:94 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:96 msgid "[sys_analyser] show weekly indicators and return curve on plot" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:98 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:100 msgid "[sys_analyser] Plot from strategy output file" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:101 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:103 msgid "save plot result to file" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:102 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:104 msgid "show open close points on plot" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:103 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:105 msgid "show weekly indicators and return curve on plot" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:113 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:115 msgid "[sys_analyser] Generate report from strategy output file" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:108 +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:112 msgid "" "config 'base.benchmark' is deprecated, use 'mod.sys_analyser.benchmark' " "instead" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:190 +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:143 +msgid "benchmark {} not exists, please entry correct order_book_id" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:147 +msgid "" +"benchmark {} listed date {} > backtest start date {} or de_listed date {}" +" <= backtest end date {}" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:210 msgid "invalid init benchmark {}, should be in format 'order_book_id:weight'" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:195 +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:215 msgid "invalid weight for instrument {order_book_id}: {weight}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:59 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:60 msgid "Strategy" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:60 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:61 msgid "Benchmark" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:61 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:62 msgid "Excess" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:62 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:63 msgid "Weekly" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:63 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:64 msgid "BenchmarkWeekly" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:65 -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:82 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:66 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:123 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:170 msgid "MaxDrawDown" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:66 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:67 msgid "MaxDDD" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:67 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:68 msgid "Open" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:68 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:69 msgid "Close" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:71 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:103 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:155 msgid "TotalReturns" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:72 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:104 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:156 msgid "AnnualReturns" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:73 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:105 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:157 msgid "Alpha" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:74 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:106 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:158 msgid "Beta" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:75 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:107 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:161 msgid "Sharpe" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:76 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:108 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:165 msgid "Sortino" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:77 -msgid "InformationRatio" +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:109 +msgid "WeeklyUlcerIndex" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:79 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:111 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:175 msgid "BenchmarkReturns" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:80 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:112 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:176 msgid "BenchmarkAnnual" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:81 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:113 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:167 msgid "Volatility" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:83 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:114 msgid "TrackingError" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:84 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:115 msgid "DownsideRisk" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:85 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:116 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:164 +msgid "InformationRatio" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:117 +msgid "WeeklyUlcerPerformanceIndex" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:119 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:179 +msgid "ExcessCumReturns" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:120 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:159 +msgid "WinRate" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:121 +msgid "WeeklyWinRate" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:122 +msgid "ProfitLossRate" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:124 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:171 msgid "MaxDD/MaxDDD" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:89 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:125 +msgid "WeeklyExcessUlcerIndex" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:129 msgid "WeeklyAlpha" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:90 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:130 msgid "WeeklyBeta" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:91 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:131 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:162 msgid "WeeklySharpe" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:92 -msgid "WeeklySortino" -msgstr "" - -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:93 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:132 msgid "WeeklyInfoRatio" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:94 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:133 msgid "WeeklyTrackingError" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:95 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:134 msgid "WeeklyMaxDrawdown" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:99 -msgid "ExcessCumReturns" -msgstr "" - -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:100 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:138 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:177 msgid "ExcessReturns" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:101 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:139 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:178 msgid "ExcessAnnual" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:102 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:140 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:181 msgid "ExcessSharpe" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:103 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:141 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:182 msgid "ExcessVolatility" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:105 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:142 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:184 msgid "ExcessMaxDD" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:106 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:143 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:185 msgid "ExcessMaxDD/ExcessMaxDDD" msgstr "" +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:144 +msgid "WeeklyExcessUlcerPerformanceIndex" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:163 +msgid "MonthlySharpe" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:168 +msgid "WeeklyVolatility" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:169 +msgid "MonthlyVolatility" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:183 +msgid "ExcessWinRate" +msgstr "" + #: rqalpha/mod/rqalpha_mod_sys_risk/validators/cash_validator.py:33 msgid "" "Order Creation Failed: not enough money to buy {order_book_id}, needs " "{cost_money:.2f}, cash {cash:.2f}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_risk/validators/is_trading_validator.py:31 +#: rqalpha/mod/rqalpha_mod_sys_risk/validators/is_trading_validator.py:32 msgid "Order Creation Failed: {order_book_id} is not listing!" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_risk/validators/is_trading_validator.py:37 +#: rqalpha/mod/rqalpha_mod_sys_risk/validators/is_trading_validator.py:38 msgid "Order Creation Failed: security {order_book_id} is suspended on {date}" msgstr "" @@ -580,74 +705,72 @@ msgid "" "trade: [{}...]" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:101 -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:319 -#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:66 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:118 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:335 +#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:69 msgid "" "Order Cancelled: current security [{order_book_id}] can not be traded in " "listed date [{listed_date}]" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:108 -#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:72 -msgid "Order Cancelled: current bar [{order_book_id}] miss market data." -msgstr "" - -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:128 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:125 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:165 #: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:86 -msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_up price." +msgid "Order Cancelled: {order_book_id} bar no volume" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:134 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:150 #: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:95 -msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_down price." +msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_up price." msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:143 -msgid "Order Cancelled: {order_book_id} bar no volume" +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:156 +#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:104 +msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_down price." msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:159 -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:396 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:178 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:417 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:567 msgid "" "Order Cancelled: market order {order_book_id} volume {order_volume} due " "to volume limit" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:197 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:217 msgid "" "Order Cancelled: market order {order_book_id} volume {order_volume} is " "larger than {volume_percent_limit} percent of current bar volume, fill " "{filled_volume} actually" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:326 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:343 msgid "Order Cancelled: current tick [{order_book_id}] miss market data." msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:351 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:368 msgid "Order Cancelled: current tick [{order_book_id}] reach the limit_up price." msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:357 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:374 msgid "" "Order Cancelled: current tick [{order_book_id}] reach the limit_down " "price." msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:364 -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:370 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:381 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:387 msgid "Order Cancelled: [{order_book_id}] has no liquidity." msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:438 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:462 msgid "" "Order Cancelled: market order {order_book_id} volume {order_volume} is " "larger than {volume_percent_limit} percent of current tick volume, fill " "{filled_volume} actually" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:571 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:611 msgid "" "Order Cancelled: market order {order_book_id} fill {filled_volume} " "actually" @@ -668,14 +791,18 @@ msgid "" "matching_type is 'current_bar'." msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:107 +#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:119 msgid "NO account_type = ({}) in {}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:53 +#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:56 msgid "cancel_order function is not supported in signal mode" msgstr "" +#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:75 +msgid "Order Cancelled: current bar [{order_book_id}] miss market data." +msgstr "" + #: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:108 msgid "unsupported position_effect {}" msgstr "" @@ -688,7 +815,7 @@ msgstr "" msgid "Order Rejected: {order_book_id} can not match. Market close." msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:184 +#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:185 msgid "{order_book_id} should be subscribed when frequency is tick." msgstr "" @@ -716,13 +843,13 @@ msgstr "" msgid "invalid slippage rate value {} which cause price <= 0" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_transaction_cost/mod.py:30 +#: rqalpha/mod/rqalpha_mod_sys_transaction_cost/mod.py:42 msgid "" "invalid commission multiplier or tax multiplier value: value range is [0," " +∞)" msgstr "" -#: rqalpha/model/bar.py:371 +#: rqalpha/model/bar.py:372 msgid "id_or_symbols {} does not exist" msgstr "" @@ -733,117 +860,113 @@ msgid "" " {tax}, {frozen_price}" msgstr "" -#: rqalpha/portfolio/__init__.py:67 +#: rqalpha/portfolio/__init__.py:71 msgid "invalid init position {order_book_id}: no valid price at {date}" msgstr "" -#: rqalpha/portfolio/__init__.py:267 +#: rqalpha/portfolio/__init__.py:278 rqalpha/portfolio/__init__.py:291 msgid "invalid account type {}, choose in {}" msgstr "" -#: rqalpha/portfolio/__init__.py:271 +#: rqalpha/portfolio/__init__.py:282 msgid "Cash add {}. units {} become to {}" msgstr "" -#: rqalpha/portfolio/account.py:313 +#: rqalpha/portfolio/account.py:352 msgid "Trigger Forced Liquidation, current total_value is 0" msgstr "" -#: rqalpha/portfolio/account.py:457 +#: rqalpha/portfolio/account.py:502 rqalpha/portfolio/account.py:521 msgid "insufficient cash, current {}, target withdrawal {}" msgstr "" -#: rqalpha/portfolio/position.py:160 -msgid "invalid price of {order_book_id}: {price}" -msgstr "" - -#: rqalpha/utils/arg_checker.py:50 +#: rqalpha/utils/arg_checker.py:51 msgid "" "function {}: invalid {} argument, expect a value of type {}, got {} " "(type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:58 +#: rqalpha/utils/arg_checker.py:59 msgid "valid order_book_id/instrument" msgstr "" -#: rqalpha/utils/arg_checker.py:61 +#: rqalpha/utils/arg_checker.py:62 msgid "valid stock order_book_id/instrument" msgstr "" -#: rqalpha/utils/arg_checker.py:64 +#: rqalpha/utils/arg_checker.py:65 msgid "valid future order_book_id/instrument" msgstr "" -#: rqalpha/utils/arg_checker.py:67 +#: rqalpha/utils/arg_checker.py:68 msgid "listed order_book_id/instrument" msgstr "" -#: rqalpha/utils/arg_checker.py:70 +#: rqalpha/utils/arg_checker.py:71 msgid "function {}: invalid {} argument, expected a {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:96 +#: rqalpha/utils/arg_checker.py:97 msgid "" "function {}: invalid {} argument, expected instrument with types {}, got " "instrument with type {}" msgstr "" -#: rqalpha/utils/arg_checker.py:136 +#: rqalpha/utils/arg_checker.py:137 msgid "function {}: invalid {} argument, expect a number, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:151 +#: rqalpha/utils/arg_checker.py:160 msgid "function {}: invalid {} argument, valid: {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:165 +#: rqalpha/utils/arg_checker.py:174 msgid "function {}: invalid {} argument, valid fields are {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:177 +#: rqalpha/utils/arg_checker.py:186 msgid "function {}: invalid field {}, valid fields are {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:183 rqalpha/utils/arg_checker.py:198 +#: rqalpha/utils/arg_checker.py:192 rqalpha/utils/arg_checker.py:207 msgid "" "function {}: invalid {} argument, expect a string or a list of string, " "got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:225 rqalpha/utils/arg_checker.py:230 +#: rqalpha/utils/arg_checker.py:234 rqalpha/utils/arg_checker.py:239 msgid "function {}: invalid {} argument, expect a valid date, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:241 +#: rqalpha/utils/arg_checker.py:250 msgid "function {}: invalid {} argument, expect a value >= {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:251 +#: rqalpha/utils/arg_checker.py:260 msgid "function {}: invalid {} argument, expect a value > {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:261 +#: rqalpha/utils/arg_checker.py:270 msgid "function {}: invalid {} argument, expect a value <= {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:272 +#: rqalpha/utils/arg_checker.py:281 msgid "function {}: invalid {} argument, expect a value < {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:289 +#: rqalpha/utils/arg_checker.py:298 msgid "" "function {}: invalid {} argument, interval should be in form of '1d', " "'3m', '4q', '2y', got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:325 +#: rqalpha/utils/arg_checker.py:334 msgid "" "function {}: invalid {} argument, should be entity like " "Fundamentals.balance_sheet.total_equity, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:344 +#: rqalpha/utils/arg_checker.py:353 msgid "" "function {}: invalid {} argument, frequency should be in form of '1m', " "'5m', '1d', '1w' got {} (type: {})" diff --git a/requirements.txt b/requirements.txt index cb64365cb..987ed4688 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ simplejson >=3.10.0 dill ==0.2.5 PyYAML >=3.12 tabulate -rqrisk >=0.0.14 +rqrisk >=1.0.8 h5py matplotlib >=1.5.1 ; python_version >= '3.6' matplotlib >=1.5.1,<=3.0.3 ; python_version == '3.5' diff --git a/rqalpha/data/base_data_source/data_source.py b/rqalpha/data/base_data_source/data_source.py index fe42cc091..c5a3eaae3 100644 --- a/rqalpha/data/base_data_source/data_source.py +++ b/rqalpha/data/base_data_source/data_source.py @@ -16,7 +16,6 @@ # 详细的授权流程,请联系 public@ricequant.com 获取。 import os import pickle -from functools import lru_cache from datetime import date, datetime, timedelta from itertools import groupby from typing import Dict, Iterable, List, Optional, Sequence, Union @@ -32,6 +31,8 @@ from rqalpha.utils.functools import lru_cache from rqalpha.utils.typing import DateLike from rqalpha.environment import Environment +from rqalpha.data.bundle import update_futures_trading_parameters +from rqalpha.utils.logger import user_system_log from rqalpha.data.base_data_source.adjust import FIELDS_REQUIRE_ADJUSTMENT, adjust_bars from rqalpha.data.base_data_source.storage_interface import (AbstractCalendarStore, AbstractDateSet, @@ -39,7 +40,7 @@ AbstractInstrumentStore) from rqalpha.data.base_data_source.storages import (DateSet, DayBarStore, DividendStore, ExchangeTradingCalendarStore, FutureDayBarStore, - FutureInfoStore, InstrumentStore, + FutureInfoStore, FuturesTradingParametersStore,InstrumentStore, ShareTransformationStore, SimpleFactorStore, YieldCurveStore) @@ -71,7 +72,7 @@ class BaseDataSource(AbstractDataSource): INSTRUMENT_TYPE.PUBLIC_FUND, ) - def __init__(self, path, custom_future_info): + def __init__(self, path, custom_future_info, update_parameters_end_date=None): if not os.path.exists(path): raise RuntimeError('bundle path {} not exist'.format(os.path.abspath(path))) @@ -86,20 +87,29 @@ def _p(name): INSTRUMENT_TYPE.ETF: funds_day_bar_store, INSTRUMENT_TYPE.LOF: funds_day_bar_store } # type: Dict[INSTRUMENT_TYPE, AbstractDayBarStore] - + + self._futures_trading_parameters_store = None + if update_parameters_end_date: + if update_futures_trading_parameters(path, update_parameters_end_date): + self._futures_trading_parameters_store = FuturesTradingParametersStore(_p("futures_trading_parameters.h5")) self._future_info_store = FutureInfoStore(_p("future_info.json"), custom_future_info) - + self._instruments_stores = {} # type: Dict[INSTRUMENT_TYPE, AbstractInstrumentStore] self._ins_id_or_sym_type_map = {} # type: Dict[str, INSTRUMENT_TYPE] instruments = [] + with open(_p('instruments.pk'), 'rb') as f: for i in pickle.load(f): if i["type"] == "Future" and Instrument.is_future_continuous_contract(i["order_book_id"]): i["listed_date"] = datetime(1990, 1, 1) - instruments.append(Instrument(i, lambda i: self._future_info_store.get_future_info(i)["tick_size"])) + instruments.append(Instrument( + i, + lambda i: self._future_info_store.get_tick_size(i), + lambda i, dt: self.get_futures_trading_parameters(i, dt).long_margin_ratio, + lambda i, dt: self.get_futures_trading_parameters(i, dt).short_margin_ratio + )) for ins_type in self.DEFAULT_INS_TYPES: self.register_instruments_store(InstrumentStore(instruments, ins_type)) - dividend_store = DividendStore(_p('dividends.h5')) self._dividends = { INSTRUMENT_TYPE.CS: dividend_store, @@ -359,8 +369,16 @@ def available_data_range(self, frequency): def get_yield_curve(self, start_date, end_date, tenor=None): return self._yield_curve.get_yield_curve(start_date, end_date, tenor=tenor) - def get_commission_info(self, instrument): - return self._future_info_store.get_future_info(instrument) + @lru_cache(1024) + def get_futures_trading_parameters(self, instrument, dt): + # type: (Instrument, datetime.date) -> FuturesTradingParameters + if self._futures_trading_parameters_store: + trading_parameters = self._futures_trading_parameters_store.get_futures_trading_parameters(instrument, dt) + if trading_parameters is None: + return self._future_info_store.get_future_info(instrument.order_book_id, instrument.underlying_symbol) + return trading_parameters + else: + return self._future_info_store.get_future_info(instrument.order_book_id, instrument.underlying_symbol) def get_merge_ticks(self, order_book_id_list, trading_date, last_dt=None): raise NotImplementedError diff --git a/rqalpha/data/base_data_source/storages.py b/rqalpha/data/base_data_source/storages.py index 73c263172..a49d42db8 100644 --- a/rqalpha/data/base_data_source/storages.py +++ b/rqalpha/data/base_data_source/storages.py @@ -23,7 +23,7 @@ from copy import copy from itertools import chain from contextlib import contextmanager -from typing import Dict, Iterable, Optional +from typing import Dict, Iterable, Optional, NamedTuple import h5py import numpy as np @@ -34,6 +34,8 @@ from rqalpha.model.instrument import Instrument from rqalpha.utils.datetime_func import convert_date_to_date_int from rqalpha.utils.i18n import gettext as _ +from rqalpha.utils.logger import user_system_log +from rqalpha.environment import Environment from .storage_interface import (AbstractCalendarStore, AbstractDateSet, AbstractDayBarStore, AbstractDividendStore, @@ -41,6 +43,18 @@ AbstractSimpleFactorStore) +class FuturesTradingParameters(NamedTuple): + """ + 数据类,用以存储期货交易参数数据 + """ + close_commission_ratio: float + close_commission_today_ratio: float + commission_type: str + open_commission_ratio: float + long_margin_ratio: float + short_margin_ratio: float + + class ExchangeTradingCalendarStore(AbstractCalendarStore): def __init__(self, f): self._f = f @@ -64,27 +78,52 @@ def __init__(self, f, custom_future_info): ) for item in json.load(json_file) } self._custom_data = custom_future_info - self._future_info = {} + if "margin_rate" not in self._default_data[next(iter(self._default_data))]: + raise RuntimeError(_("Your bundle data is too old, please use 'rqalpha update-bundle' or 'rqalpha download-bundle' to update it to lastest before using")) @classmethod def _process_future_info_item(cls, item): item["commission_type"] = cls.COMMISSION_TYPE_MAP[item["commission_type"]] return item - def get_future_info(self, instrument): - # type: (Instrument) -> Dict[str, float] - order_book_id = instrument.order_book_id + @lru_cache(1024) + def get_future_info(self, order_book_id, underlying_symbol): + # type: (str, str) -> FuturesTradingParameters + custom_info = self._custom_data.get(order_book_id) or self._custom_data.get(underlying_symbol) + info = self._default_data.get(order_book_id) or self._default_data.get(underlying_symbol) + if custom_info: + info = copy(info) or {} + info.update(custom_info) + elif not info: + raise NotImplementedError(_("unsupported future instrument {}").format(order_book_id)) + info = self._to_namedtuple(info) + return info + + def _to_namedtuple(self, info): + # type: (dict) -> FuturesTradingParameters + info['long_margin_ratio'], info['short_margin_ratio'] = info['margin_rate'], info['margin_rate'] + del info['margin_rate'], info['tick_size'] try: - return self._future_info[order_book_id] + del info['order_book_id'] except KeyError: - custom_info = self._custom_data.get(order_book_id) or self._custom_data.get(instrument.underlying_symbol) - info = self._default_data.get(order_book_id) or self._default_data.get(instrument.underlying_symbol) - if custom_info: - info = copy(info) or {} - info.update(custom_info) - elif not info: - raise NotImplementedError(_("unsupported future instrument {}").format(order_book_id)) - return self._future_info.setdefault(order_book_id, info) + del info['underlying_symbol'] + info = FuturesTradingParameters(**info) + return info + + @lru_cache(8) + def get_tick_size(self, instrument): + # type: (str, str) -> float + order_book_id = instrument.order_book_id + underlying_symbol = instrument.underlying_symbol + custom_info = self._custom_data.get(order_book_id) or self._custom_data.get(underlying_symbol) + info = self._default_data.get(order_book_id) or self._custom_data.get(underlying_symbol) + if custom_info: + info = copy(info) or {} + info.update(custom_info) + elif not info: + raise NotImplementedError(_("unsupported future instrument {}".format(order_book_id))) + tick_size = info['tick_size'] + return tick_size class InstrumentStore(AbstractInstrumentStore): @@ -208,6 +247,55 @@ class FutureDayBarStore(DayBarStore): DEFAULT_DTYPE = np.dtype(DayBarStore.DEFAULT_DTYPE.descr + [("open_interest", ' FuturesTradingParameters or None + order_book_id = instrument.order_book_id + dt = convert_date_to_date_int(dt) + if dt < self.FUTURES_TRADING_PARAMETERS_START_DATE: + return None + data = self.get_futures_trading_parameters_all_time(order_book_id) + if data is None: + return None + else: + arr = data[data['datetime'] == dt][0] + if len(arr) == 0: + if dt >= convert_date_to_date_int(instrument.listed_date) and dt <= convert_date_to_date_int(instrument.de_listed_date): + user_system_log.info("Historical futures trading parameters are abnormal, the lastst parameters will be used for calculations.\nPlease contract RiceQuant to repair: 0755-26569969") + return None + futures_trading_parameters = self._to_namedtuple(arr) + return futures_trading_parameters + + @lru_cache(1024) + def get_futures_trading_parameters_all_time(self, order_book_id): + # type: (str) -> numpy.ndarray or None + with h5_file(self._path) as h5: + try: + data = h5[order_book_id][:] + except KeyError: + return None + return data + + def _to_namedtuple(self, arr): + # type: (numpy.void) -> FuturesTradingParameters + dic = dict(zip(arr.dtype.names, arr)) + del dic['datetime'] + dic["commission_type"] = self.COMMISSION_TYPE_MAP[dic['commission_type']] + futures_trading_parameters = FuturesTradingParameters(**dic) + return futures_trading_parameters + + class DividendStore(AbstractDividendStore): def __init__(self, path): self._path = path diff --git a/rqalpha/data/bundle.py b/rqalpha/data/bundle.py index 69159cfcc..0dc4eb117 100644 --- a/rqalpha/data/bundle.py +++ b/rqalpha/data/bundle.py @@ -26,6 +26,7 @@ ProgressedTask) from rqalpha.utils.datetime_func import (convert_date_to_date_int, convert_date_to_int) +from rqalpha.utils.i18n import gettext as _ START_DATE = 20050104 END_DATE = 29991231 @@ -136,42 +137,76 @@ def gen_share_transformation(d): def gen_future_info(d): future_info_file = os.path.join(d, 'future_info.json') + def _need_to_recreate(): + if not os.path.exists(future_info_file): + return + else: + with open(future_info_file, "r") as f: + all_futures_info = json.load(f) + if "margin_rate" not in all_futures_info[0]: + return True + + def update_margin_rate(file): + all_instruments_data = rqdatac.all_instruments("Future") + with open(file, "r") as f: + all_futures_info = json.load(f) + new_all_futures_info = [] + for future_info in all_futures_info: + if "order_book_id" in future_info: + future_info["margin_rate"] = all_instruments_data[all_instruments_data["order_book_id"] == future_info["order_book_id"]].iloc[0].margin_rate + elif "underlying_symbol" in future_info: + dominant = rqdatac.futures.get_dominant(future_info["underlying_symbol"])[-1] + future_info["margin_rate"] = all_instruments_data[all_instruments_data["order_book_id"] == dominant].iloc[0].margin_rate + new_all_futures_info.append(future_info) + os.remove(file) + with open(file, "w") as f: + json.dump(new_all_futures_info, f, separators=(',', ':'), indent=2) + + if (_need_to_recreate()): update_margin_rate(future_info_file) + + # 新增 hard_code 的种类时,需要同时修改rqalpha.data.base_data_source.storages.FutureInfoStore.data_compatible中的内容 hard_code = [ {'underlying_symbol': 'TC', 'close_commission_ratio': 4.0, 'close_commission_today_ratio': 0.0, 'commission_type': "by_volume", 'open_commission_ratio': 4.0, + 'margin_rate': 0.05, 'tick_size': 0.2}, {'underlying_symbol': 'ER', 'close_commission_ratio': 2.5, 'close_commission_today_ratio': 2.5, 'commission_type': "by_volume", 'open_commission_ratio': 2.5, + 'margin_rate': 0.05, 'tick_size': 1.0}, {'underlying_symbol': 'WS', 'close_commission_ratio': 2.5, 'close_commission_today_ratio': 0.0, 'commission_type': "by_volume", 'open_commission_ratio': 2.5, + 'margin_rate': 0.05, 'tick_size': 1.0}, {'underlying_symbol': 'RO', 'close_commission_ratio': 2.5, 'close_commission_today_ratio': 0.0, 'commission_type': "by_volume", 'open_commission_ratio': 2.5, + 'margin_rate': 0.05, 'tick_size': 2.0}, {'underlying_symbol': 'ME', 'close_commission_ratio': 1.4, 'close_commission_today_ratio': 0.0, 'commission_type': "by_volume", 'open_commission_ratio': 1.4, + 'margin_rate': 0.06, 'tick_size': 1.0}, {'underlying_symbol': 'WT', 'close_commission_ratio': 5.0, 'close_commission_today_ratio': 5.0, 'commission_type': "by_volume", 'open_commission_ratio': 5.0, + 'margin_rate': 0.05, 'tick_size': 1.0}, ] @@ -198,18 +233,21 @@ def gen_future_info(d): symbol_list.append(info["underlying_symbol"]) futures_order_book_id = rqdatac.all_instruments(type='Future')['order_book_id'].unique() + commission_df = rqdatac.futures.get_commission_margin() for future in futures_order_book_id: underlying_symbol = re.match(r'^[a-zA-Z]*', future).group() if future in future_list: continue future_dict = {} - commission = rqdatac.futures.get_commission_margin(future) + commission = commission_df[commission_df['order_book_id'] == future] if not commission.empty: future_dict['order_book_id'] = future commission = commission.iloc[0] for p in param: future_dict[p] = commission[p] - future_dict['tick_size'] = rqdatac.instruments(future).tick_size() + instruemnts_data = rqdatac.instruments(future) + future_dict['margin_rate'] = instruemnts_data.margin_rate + future_dict['tick_size'] = instruemnts_data.tick_size() elif underlying_symbol in symbol_list: continue else: @@ -220,11 +258,13 @@ def gen_future_info(d): except AttributeError: # FIXME: why get_dominant return None??? continue - commission = rqdatac.futures.get_commission_margin(dominant).iloc[0] + commission = commission_df[commission_df['order_book_id'] == dominant].iloc[0] for p in param: future_dict[p] = commission[p] - future_dict['tick_size'] = rqdatac.instruments(dominant).tick_size() + instruemnts_data = rqdatac.instruments(dominant) + future_dict['margin_rate'] = instruemnts_data.margin_rate + future_dict['tick_size'] = instruemnts_data.tick_size() all_futures_info.append(future_dict) with open(os.path.join(d, 'future_info.json'), 'w') as f: @@ -395,3 +435,151 @@ def update_bundle(path, create, enable_compression=False, concurrency=1): executor.submit(GenerateFileTask(func), path) for file, order_book_id, field in day_bar_args: executor.submit(_DayBarTask(order_book_id), os.path.join(path, file), field, **kwargs) + + +FUTURES_TRADING_PARAMETERS_FIELDS = ["long_margin_ratio", "short_margin_ratio", "commission_type", "open_commission", "close_commission", "close_commission_today"] +TRADING_PARAMETERS_START_DATE = 20100401 +FUTURES_TRADING_PARAMETERS_FILE = "futures_trading_parameters.h5" + + +class FuturesTradingParametersTask(object): + def __init__(self, order_book_ids): + self._order_book_ids = order_book_ids + + def __call__(self, path, fields, end_date): + if not os.path.exists(path): + self.generate_futures_trading_parameters(path, fields, end_date) + else: + self.update_futures_trading_parameters(path, fields, end_date) + + def generate_futures_trading_parameters(self, path, fields, end_date, recreate_futures_list=None): + # type: (str, list, datetime.date, list) -> None + order_book_ids = self._order_book_ids + if recreate_futures_list: + order_book_ids = recreate_futures_list + df = rqdatac.futures.get_trading_parameters(order_book_ids, TRADING_PARAMETERS_START_DATE, end_date, fields) + if not (df is None or df.empty): + df.dropna(axis=0, how="all") + df.reset_index(inplace=True) + df['datetime'] = df['trading_date'].map(convert_date_to_date_int) + del df["trading_date"] + df['commission_type'] = df['commission_type'].map(self.set_commission_type) + df.rename(columns={ + 'close_commission': "close_commission_ratio", + 'close_commission_today': "close_commission_today_ratio", + 'open_commission': 'open_commission_ratio' + }, inplace=True) + df.set_index(["order_book_id", "datetime"], inplace=True) + df.sort_index(inplace=True) + with h5py.File(path, "w") as h5: + for order_book_id in df.index.levels[0]: + h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records()) + + def update_futures_trading_parameters(self, path, fields, end_date): + # type: (str, list, datetime.date) -> None + try: + h5 = h5py.File(path, "a") + h5.close() + except OSError as e: + raise OSError(_("File {} update failed, if it is using, please update later, or you can delete then update again".format(path))) from e + last_date = self.get_h5_last_date(path) + recreate_futures_list = self.get_recreate_futures_list(path, last_date) + if recreate_futures_list: + self.generate_futures_trading_parameters(path, fields, last_date, recreate_futures_list=recreate_futures_list) + if end_date > last_date: + if rqdatac.get_previous_trading_date(end_date) == last_date: + return + else: + start_date = rqdatac.get_next_trading_date(last_date) + df = rqdatac.futures.get_trading_parameters(self._order_book_ids, start_date, end_date, fields) + if not(df is None or df.empty): + df = df.dropna(axis=0, how="all") + df.reset_index(inplace=True) + df['datetime'] = df['trading_date'].map(convert_date_to_date_int) + del [df['trading_date']] + df['commission_type'] = df['commission_type'].map(self.set_commission_type) + df.rename(columns={ + 'close_commission': "close_commission_ratio", + 'close_commission_today': "close_commission_today_ratio", + 'open_commission': 'open_commission_ratio' + }, inplace=True) + df.set_index(['order_book_id', 'datetime'], inplace=True) + with h5py.File(path, "a") as h5: + for order_book_id in df.index.levels[0]: + if order_book_id in h5: + data = np.array( + [tuple(i) for i in chain(h5[order_book_id][:], df.loc[order_book_id].to_records())], + dtype=h5[order_book_id].dtype + ) + del h5[order_book_id] + h5.create_dataset(order_book_id, data=data) + else: + h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records()) + + def set_commission_type(self, commission_type): + if commission_type == "by_money": + commission_type = 0 + elif commission_type == "by_volume": + commission_type = 1 + return commission_type + + def get_h5_last_date(self, path): + last_date = TRADING_PARAMETERS_START_DATE + with h5py.File(path, "r") as h5: + for key in h5.keys(): + if int(h5[key]['datetime'][-1]) > last_date: + last_date = h5[key]['datetime'][-1] + last_date = datetime.datetime.strptime(str(last_date), "%Y%m%d").date() + return last_date + + def get_recreate_futures_list(self, path, h5_last_date): + # type: (str, datetime.date) -> list + """ + 用户在运行策略的过程中可能中断进程,进而可能导致在创建 h5 文件时,部分合约没有成功 download + 通过该函数,获取在上一次更新中因为异常而没有更新的合约 + """ + recreate_futures_list = [] + df = rqdatac.all_instruments("Future") + last_update_futures_list = df[(df['de_listed_date'] >= str(TRADING_PARAMETERS_START_DATE)) & (df['listed_date'] <= h5_last_date.strftime("%Y%m%d"))].order_book_id.to_list() + with h5py.File(path, "r") as h5: + h5_order_book_ids = h5.keys() + for order_book_id in last_update_futures_list: + if order_book_id in h5_order_book_ids: + continue + else: + recreate_futures_list.append(order_book_id) + return recreate_futures_list + + +def check_rqdata_permission(): + """ + 检测以下内容,均符合才会更新期货交易参数: + 1. rqdatac 版本是否为具备 futures.get_trading_parameters API 的版本 + 2. 当前 rqdatac 是否具备上述 API 的使用权限 + """ + if rqdatac.__version__ < '2.11.12': + from rqalpha.utils.logger import system_log + system_log.warn(_("RQAlpha already supports backtesting using futures historical margins and rates, please upgrade RQDatac to version 2.11.12 and above to use it")) + return + try: + rqdatac.futures.get_trading_parameters("A1005") + except rqdatac.share.errors.PermissionDenied: + from rqalpha.utils.logger import system_log + system_log.warn(_("Your RQData account does not have permission to use futures historical margin and rates, and fixed data will be used for calculations\nYou can contact RiceQuant to activate permission: 0755-26569969")) + return + return True + + +def update_futures_trading_parameters(path, end_date): + # type: (str, datetime.date) -> Boolean + update_permission = check_rqdata_permission() + if not update_permission: + return False + df = rqdatac.all_instruments("Future") + order_book_ids = (df[df['de_listed_date'] >= str(TRADING_PARAMETERS_START_DATE)]).order_book_id.tolist() + FuturesTradingParametersTask(order_book_ids)( + os.path.join(path, FUTURES_TRADING_PARAMETERS_FILE), + FUTURES_TRADING_PARAMETERS_FIELDS, + end_date + ) + return True diff --git a/rqalpha/data/data_proxy.py b/rqalpha/data/data_proxy.py index e2337a02c..d43d55927 100644 --- a/rqalpha/data/data_proxy.py +++ b/rqalpha/data/data_proxy.py @@ -240,9 +240,10 @@ def tick_fields_for(ins): def available_data_range(self, frequency): return self._data_source.available_data_range(frequency) - def get_commission_info(self, order_book_id): + def get_futures_trading_parameters(self, order_book_id, dt): + # type: (str, datetime.date) -> FuturesTradingParameters instrument = self.instruments(order_book_id) - return self._data_source.get_commission_info(instrument) + return self._data_source.get_futures_trading_parameters(instrument, dt) def get_merge_ticks(self, order_book_id_list, trading_date, last_dt=None): return self._data_source.get_merge_ticks(order_book_id_list, trading_date, last_dt) diff --git a/rqalpha/environment.py b/rqalpha/environment.py index 02673a480..dc3a01d39 100644 --- a/rqalpha/environment.py +++ b/rqalpha/environment.py @@ -186,4 +186,3 @@ def can_submit_order(self, order): if not v.can_submit_order(order, account): return False return True - diff --git a/rqalpha/interface.py b/rqalpha/interface.py index 653cd8f1a..5b494f8c1 100644 --- a/rqalpha/interface.py +++ b/rqalpha/interface.py @@ -454,9 +454,9 @@ def available_data_range(self, frequency): """ raise NotImplementedError - def get_commission_info(self, instrument): + def get_futures_trading_parameters(self, instrument): """ - 获取合约的手续费信息 + 获取期货合约的时序手续费信息 :param instrument: :return: """ diff --git a/rqalpha/main.py b/rqalpha/main.py index 5f397cb29..7936a48d6 100644 --- a/rqalpha/main.py +++ b/rqalpha/main.py @@ -122,6 +122,7 @@ def init_rqdatac(rqdatac_uri): init_rqdatac_env(rqdatac_uri) try: rqdatac.init() + return True except Exception as e: system_log.warn(_('rqdatac init failed, some apis will not function properly: {}').format(str(e))) @@ -131,20 +132,23 @@ def run(config, source_code=None, user_funcs=None): persist_helper = None init_succeed = False mod_handler = ModHandler() + update_parameters_end_date = None try: # avoid register handlers everytime # when running in ipython set_loggers(config) - init_rqdatac(getattr(config.base, 'rqdatac_uri', None)) + rqdatac_enable = init_rqdatac(getattr(config.base, 'rqdatac_uri', None)) system_log.debug("\n" + pformat(config.convert_to_dict())) env.set_strategy_loader(init_strategy_loader(env, source_code, user_funcs, config)) mod_handler.set_env(env) mod_handler.start_up() + if config.mod.sys_transaction_cost.time_series_trading_parameters and "FUTURE" in config.base.accounts and rqdatac_enable: + update_parameters_end_date = config.base.end_date if not env.data_source: - env.set_data_source(BaseDataSource(config.base.data_bundle_path, getattr(config.base, "future_info", {}))) + env.set_data_source(BaseDataSource(config.base.data_bundle_path, getattr(config.base, "future_info", {}), update_parameters_end_date)) if env.price_board is None: from rqalpha.data.bar_dict_price_board import BarDictPriceBoard env.price_board = BarDictPriceBoard() diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py b/rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py index 81a3cfdfc..f851bc9e6 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py @@ -28,6 +28,7 @@ from rqalpha.utils.logger import user_system_log from rqalpha.utils.class_helper import deprecated_property, cached_property from rqalpha.utils.i18n import gettext as _ +from rqalpha.core.events import EVENT, Event def _int_to_date(d): @@ -222,10 +223,15 @@ class FuturePosition(Position): @cached_property def contract_multiplier(self): return self._instrument.contract_multiplier - - @cached_property + + @property def margin_rate(self): - return self._instrument.margin_rate * self._env.config.base.margin_multiplier + # type: () -> float + if self.direction == POSITION_DIRECTION.LONG: + margin_ratio = self._instrument.get_long_margin_ratio(self._env.trading_dt.date()) + elif self.direction == POSITION_DIRECTION.SHORT: + margin_ratio = self._instrument.get_short_margin_ratio(self._env.trading_dt.date()) + return margin_ratio * self._env.config.base.margin_multiplier @property def equity(self): @@ -234,7 +240,8 @@ def equity(self): return self._quantity * (self.last_price - self._avg_price) * self.contract_multiplier * self._direction_factor @property - def margin(self) -> float: + def margin(self): + # rtpe: () -> float """ 保证金 = 持仓量 * 最新价 * 合约乘数 * 保证金率 """ @@ -309,6 +316,12 @@ def settlement(self, trading_date): )) self._quantity = self._old_quantity = 0 return delta_cash + + def post_settlement(self): + try: + del self.__dict__["margin_ratio"] + except KeyError: + pass class StockPositionProxy(PositionProxy): diff --git a/rqalpha/mod/rqalpha_mod_sys_analyser/mod.py b/rqalpha/mod/rqalpha_mod_sys_analyser/mod.py index 9a064e47f..587773139 100644 --- a/rqalpha/mod/rqalpha_mod_sys_analyser/mod.py +++ b/rqalpha/mod/rqalpha_mod_sys_analyser/mod.py @@ -150,7 +150,7 @@ def _subscribe_events(self, event): self._env.event_bus.add_listener(EVENT.TRADE, self._collect_trade) self._env.event_bus.add_listener(EVENT.ORDER_CREATION_PASS, self._collect_order) - self._env.event_bus.add_listener(EVENT.POST_SETTLEMENT, self._collect_daily) + self._env.event_bus.prepend_listener(EVENT.POST_SETTLEMENT, self._collect_daily) def _collect_trade(self, event): self._trades.append(self._to_trade_record(event.trade)) diff --git a/rqalpha/mod/rqalpha_mod_sys_risk/validators/cash_validator.py b/rqalpha/mod/rqalpha_mod_sys_risk/validators/cash_validator.py index 7830b0686..43c2d1503 100644 --- a/rqalpha/mod/rqalpha_mod_sys_risk/validators/cash_validator.py +++ b/rqalpha/mod/rqalpha_mod_sys_risk/validators/cash_validator.py @@ -24,7 +24,7 @@ def is_cash_enough(env, order, cash, warn=False): instrument = env.data_proxy.instrument(order.order_book_id) - cost_money = instrument.calc_cash_occupation(order.frozen_price, order.quantity, order.position_direction) + cost_money = instrument.calc_cash_occupation(order.frozen_price, order.quantity, order.position_direction, order.trading_datetime.date()) cost_money += env.get_order_transaction_cost(order) if cost_money <= cash: return True diff --git a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py index 718047f00..82cc38994 100644 --- a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py +++ b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py @@ -26,6 +26,8 @@ "futures_commission_multiplier": 1, # 印花倍率,即在默认的印花税基础上按该倍数进行调整,股票默认印花税为千分之一,单边收取 "tax_multiplier": 1, + # 是否开启期货历史交易参数进行回测,默认为True + "time_series_trading_parameters": True, # 是否使用回测当时时间点对应的真实印花税率 "pit_tax": False, } diff --git a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py index d69f69e2e..cedf75a9f 100644 --- a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py +++ b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py @@ -21,7 +21,7 @@ from rqalpha.core.events import EVENT -STOCK_PIT_TAX_CHANGE_DATE = datetime(2023, 8, 23) +STOCK_PIT_TAX_CHANGE_DATE = datetime(2023, 8, 28) class StockTransactionCostDecider(AbstractTransactionCostDecider): @@ -107,30 +107,30 @@ def __init__(self, commission_multiplier): self.env = Environment.get_instance() - def _get_commission(self, order_book_id, position_effect, price, quantity, close_today_quantity): - info = self.env.data_proxy.get_commission_info(order_book_id) + def _get_commission(self, order_book_id, position_effect, price, quantity, close_today_quantity, dt): + # type: (str, POSITION_EFFECT, float, int, int, datetime.date) -> float + info = self.env.data_proxy.get_futures_trading_parameters(order_book_id, dt) commission = 0 - if info['commission_type'] == COMMISSION_TYPE.BY_MONEY: + if info.commission_type == COMMISSION_TYPE.BY_MONEY: contract_multiplier = self.env.get_instrument(order_book_id).contract_multiplier if position_effect == POSITION_EFFECT.OPEN: - commission += price * quantity * contract_multiplier * info[ - 'open_commission_ratio'] + commission += price * quantity * contract_multiplier * info.open_commission_ratio else: commission += price * ( quantity - close_today_quantity - ) * contract_multiplier * info['close_commission_ratio'] - commission += price * close_today_quantity * contract_multiplier * info['close_commission_today_ratio'] + ) * contract_multiplier * info.close_commission_ratio + commission += price * close_today_quantity * contract_multiplier * info.close_commission_today_ratio else: if position_effect == POSITION_EFFECT.OPEN: - commission += quantity * info['open_commission_ratio'] + commission += quantity * info.open_commission_ratio else: - commission += (quantity - close_today_quantity) * info['close_commission_ratio'] - commission += close_today_quantity * info['close_commission_today_ratio'] + commission += (quantity - close_today_quantity) * info.close_commission_ratio + commission += close_today_quantity * info.close_commission_today_ratio return commission * self.commission_multiplier def get_trade_commission(self, trade): return self._get_commission( - trade.order_book_id, trade.position_effect, trade.last_price, trade.last_quantity, trade.close_today_amount + trade.order_book_id, trade.position_effect, trade.last_price, trade.last_quantity, trade.close_today_amount, trade.trading_datetime.date() ) def get_trade_tax(self, trade): @@ -140,5 +140,5 @@ def get_order_transaction_cost(self, order): close_today_quantity = order.quantity if order.position_effect == POSITION_EFFECT.CLOSE_TODAY else 0 return self._get_commission( - order.order_book_id, order.position_effect, order.frozen_price, order.quantity, close_today_quantity + order.order_book_id, order.position_effect, order.frozen_price, order.quantity, close_today_quantity, order.trading_datetime.date() ) diff --git a/rqalpha/model/instrument.py b/rqalpha/model/instrument.py index eb08e43e2..fde4a65c0 100644 --- a/rqalpha/model/instrument.py +++ b/rqalpha/model/instrument.py @@ -18,7 +18,9 @@ import re import copy import datetime +import inspect from typing import Dict, Callable, Optional +from methodtools import lru_cache import numpy as np from dateutil.parser import parse @@ -46,10 +48,12 @@ def _fix_date(ds, dflt=None) -> datetime: __repr__ = property_repr - def __init__(self, dic, future_tick_size_getter=None): - # type: (Dict, Optional[Callable[[Instrument], float]]) -> None + def __init__(self, dic, futures_tick_size_getter=None, futures_long_margin_ratio_getter=None, futures_short_margin_ratio_getter=None): + # type: (Dict, Optional[Callable[[Instrument], float]], Optional[Callable[[Instrument], float]], Optional[Callable[[Instrument], float]]) -> None self.__dict__ = copy.copy(dic) - self._future_tick_size_getter = future_tick_size_getter + self._futures_tick_size_getter = futures_tick_size_getter + self._futures_long_margin_ratio_getter = futures_long_margin_ratio_getter + self._futures_short_margin_ratio_getter = futures_short_margin_ratio_getter if "listed_date" in dic: self.__dict__["listed_date"] = self._fix_date(dic["listed_date"]) @@ -235,13 +239,6 @@ def contract_multiplier(self): """ return self.__dict__.get('contract_multiplier', 1) - @property - def margin_rate(self): - """ - [float] 合约最低保证金率(期货专用) - """ - return self.__dict__.get("margin_rate", 1) - @property def underlying_order_book_id(self): """ @@ -444,17 +441,37 @@ def tick_size(self): elif self.type in ("ETF", "LOF"): return 0.001 elif self.type == INSTRUMENT_TYPE.FUTURE: - return self._future_tick_size_getter(self) + return self._futures_tick_size_getter(self) else: raise NotImplementedError - def calc_cash_occupation(self, price, quantity, direction): - # type: (float, float, POSITION_DIRECTION) -> float + @lru_cache(8) + def get_long_margin_ratio(self, dt): + # type: (datetime.date) -> float + """ + 获取多头保证金率(期货专用) + """ + return self._futures_long_margin_ratio_getter(self, dt) + + @lru_cache(8) + def get_short_margin_ratio(self, dt): + # type: (datetime.date) -> float + """ + 获取空头保证金率(期货专用) + """ + return self._futures_long_margin_ratio_getter(self, dt) + + def calc_cash_occupation(self, price, quantity, direction, dt): + # type: (float, int, POSITION_DIRECTION, datetime.date) -> float if self.type in INST_TYPE_IN_STOCK_ACCOUNT: return price * quantity elif self.type == INSTRUMENT_TYPE.FUTURE: margin_multiplier = Environment.get_instance().config.base.margin_multiplier - return price * quantity * self.contract_multiplier * self.margin_rate * margin_multiplier + if direction == POSITION_DIRECTION.LONG: + margin_rate = self.get_long_margin_ratio(dt) + elif direction == POSITION_DIRECTION.SHORT: + margin_rate = self.get_short_margin_ratio(dt) + return price * quantity * self.contract_multiplier * margin_rate * margin_multiplier else: raise NotImplementedError diff --git a/rqalpha/portfolio/account.py b/rqalpha/portfolio/account.py index d240d0317..4df32743c 100644 --- a/rqalpha/portfolio/account.py +++ b/rqalpha/portfolio/account.py @@ -30,6 +30,7 @@ from rqalpha.utils.i18n import gettext as _ from rqalpha.utils.logger import user_system_log from rqalpha.portfolio.position import Position, PositionProxyDict +from rqalpha.mod.rqalpha_mod_sys_accounts.position_model import FuturePosition OrderApiType = Callable[[str, Union[int, float], OrderStyle, bool], List[Order]] @@ -105,6 +106,7 @@ def register_event(self): event_bus.add_listener(EVENT.PRE_BEFORE_TRADING, self._on_before_trading) event_bus.add_listener(EVENT.SETTLEMENT, self._on_settlement) + event_bus.add_listener(EVENT.POST_SETTLEMENT, self._post_settlement) event_bus.prepend_listener(EVENT.BAR, self._on_bar) event_bus.prepend_listener(EVENT.TICK, self._on_tick) @@ -350,6 +352,16 @@ def _on_settlement(self, event): user_system_log.warn(_("Trigger Forced Liquidation, current total_value is 0")) self._positions.clear() self._total_cash = 0 + + def _post_settlement(self, event): + # type: (EVENT) -> None + """ + 该事件必须在 post_settlement 中最后执行,若有其他事件要加入到 post_settlement 中,请使用 event_bus.prepend_listener 添加 + """ + for order_book_id, positions in list(self._positions.items()): + for position in six.itervalues(positions): + if isinstance(position, FuturePosition): + position.post_settlement() def _on_order_pending_new(self, event): if event.account != self: @@ -443,7 +455,7 @@ def _on_bar(self, _): def _frozen_cash_of_order(self, order): if order.position_effect == POSITION_EFFECT.OPEN: instrument = self._env.data_proxy.instrument(order.order_book_id) - order_cost = instrument.calc_cash_occupation(order.frozen_price, order.quantity, order.position_direction) + order_cost = instrument.calc_cash_occupation(order.frozen_price, order.quantity, order.position_direction, order.trading_datetime.date()) else: order_cost = 0 return order_cost + self._env.get_order_transaction_cost(order) diff --git a/rqalpha/utils/repr.py b/rqalpha/utils/repr.py index e26cee153..6ebfd236e 100644 --- a/rqalpha/utils/repr.py +++ b/rqalpha/utils/repr.py @@ -36,7 +36,7 @@ def __new__(mcs, *args, **kwargs): else: repr_properties = [] for c in cls.mro(): - repr_properties.extend(v for v in vars(c) if isinstance(getattr(c, v), property)) + repr_properties.extend(v for v in list(vars(c).keys()) if isinstance(getattr(c, v), property)) cls.__repr__ = _repr(cls.__name__, repr_properties) return cls diff --git a/rqalpha/utils/testing/fixtures.py b/rqalpha/utils/testing/fixtures.py index a818bc733..0c4124ca6 100644 --- a/rqalpha/utils/testing/fixtures.py +++ b/rqalpha/utils/testing/fixtures.py @@ -83,7 +83,7 @@ def init_fixture(self): super(BaseDataSourceFixture, self).init_fixture() default_bundle_path = os.path.abspath(os.path.expanduser('~/.rqalpha/bundle')) - self.base_data_source = BaseDataSource(default_bundle_path, {}) + self.base_data_source = BaseDataSource(default_bundle_path, {}, {}) class BarDictPriceBoardFixture(EnvironmentFixture): diff --git a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo index 0426d7f0e2727ed7a48b210ead16738b972dc72f..f4493b300277f98d52a561424f911cef46b74507 100644 GIT binary patch delta 3910 zcma*p3s6+o8Nl&_Z>dD1!52QR#;~G*fPiAHZ>*@PkCC!=*d$G=iDstd-d)nBc9M=JZ70+J$Gt>5oy?S3_jk^@ z_uTWH?|gU1Bm3j(55$G<^zXJu@ppoM!}vF+yYwFXdE%97pt}l_vH!P}T84R;gf%Er zeu@d$g1zxI?2Fg2AO6wqw_`W@U*c$`!s;s;eHa+UmpmvP3o#q>aT{jgJ2(ovvkFb+ zz?GPdJ#eQzz8_`56Ud|01ssgOLO$w4zGQwo_G5i@&%V&}5v6{>g+!E0glu>YyqNHvJgV{I&XW&yfA8TJZl8>A~b* z2EIMSO3i%iNq-6U!e=mu#V9HK03|bRn2n#I6rp3NQq-rKjgsQ;p``d}dt67!tOuor zUO`Spoft~~$(DMR0a@Tplt|x0vaMn$3w&w!6X{3*$tZzlqn!0r{59&h9vu|WWITmw z_$Sn{-*7gIew5lc8>S&U`728CjN+gdVIfL{jmX;SBJynY4o<Dz$=s9?IKN zi_hULdpuvGpy?k)hSUX=``*Wy7#=pt8YsdP1{(0Y_?GQJUQ#*BBAkVFC_BD`lH#t9 zD>V`OqU>lkK7s3TIyR!zQWWzroyn4ca%96{)j&hu?^`H4OJKIt#5^p;LL85;p;YlF zC`Xl>Y~42-iA60%2_S^CF@mjl-}aQl%H*?Stsk=0m?`i7AsSLVZzF}NZsBA2IcnI8 zw2*C;Vml9ICuJy^Ifkit2lvO3m2tcg^c#p;0!!iITwI8hf!d9|u>})ZUtOlr2XDv# z-bOk5&yiPFb)|xO;6NlvYB)-Ov+VIgETCV6lG-NZ^i-Q|k96z#W6{lcj@>_kVY%@% z4Jod-P!{+E`KZ1Vtnq0m0j)p@@Hv!#D{&oGBX6SWonhVoILdRT<1}=l+Mo!xcoXFTcTtYw-}n?JQ8@k3hkdcqb_Ys8M^Q3&8Raee zCCc;fAu+3*iR51zE=pKZw+H2h-=mE8W0p7rXJG~E7{Qk?krJkaND8@g^qX1fD9zl8=?R2qhCY?D5;!mwwb9 zzmFT}cjHx(@hvF#AHWnmiBg0&P@Wt9ltzCV_faOqlLnbE6eZOTlp2|fa$mXaI+Xis z?EYaKOurSA@V6+}O_b-|Lph4ByfP9%JT^-*95f{250K#1N?u}`YA-&I@8S%c&bi1D zR3fLNUPnp&eS8vAr&|I35J%9j#$k9Gm*a0R0Y}fUjv@m~yweGcbORRSOGH0^Ws^i8oM+{#~5J`l?&L z^@}wX7tyaoKI#TvWK9jEq88&)lwY>vI02(5ujwFGmz~YULR^M&)~8Ww>V1@>8$Hi5 z2PHG}FkDB&MI(f91xlU6HMZk;StS#vpq0A=L7(4UqIQJyX+5N_-)2P;>fN+UpBye$=K*!_kx!)Mw!@)_MrS7#Z zr$${*@v^`;TPhOTboAZ=yq>+Eu z6fG+wD?2SCGmX4wPMk7v^7xF&85xl`#*c}Mn;7{^>Ze`9RGYcCs_n`Jv+lgPx5a2^ zjh#Pn_v$v|3+Ei`qs4+HG$F9}Ij%~B% za>?AhH&(gPe7VWk-rRQOgwe8zCm5TLwjXG)?r*QEW)JX%v_t#8yFHedaT z6&h=PU4BM&RrGRWtSa(#^%>i@5?9-$trSKYvT^nxk2LqRnAK+=4p??^_E_}nzIZER zvGujlaO6s~H4;6uBigvr+*)Z|t!%G67_GZxY>Zgz-@SV9zw6Boc$QUhovT!t`x>o; zSYiB6xph_>NA}>F)3e}0eu044Nw A3IG5A delta 3762 zcmZA3dr(wm7{~EvQ4!Sek|Z}}DMV3AMbtzPsL)Ksu*gJHP(fET1W75SRW!ROY9-}0 zG^6ZdP3|;OF*Va4c5%8`Y0hL;BOqBxrDf?%_5JNR_DApV?B{*oJ$uf3p7%M+?%M9D z-{$e3>eaE%X#0pbBDIs*w>Y^AFSHM?GJ6g`#3Z~o!t740!&p3ve8fV#n{kMB!*Gm2 z{w$G~9+>KmbFd@he7x4oZv}L^F;R^=(PKCrw_pyoVG7>BSrV`ab^KbKg)SNoF0Jxn9By#_?A>7rY0x&_$?)m17jww`w|5@F~0on^7yhj9k;gd$W3sMWrqcQ!x{> zuo7>>H}F=pNVEAc5wr1C)PjG(iI~nsG+vK>rTTL^&*Pvzq#2LmcvRj-!vwq={iqY4 zzy+8QZMGE~a5JXGIG_6n(;4@qKxSetaxeB0QZDw6>&LO=Unx1sL}xsWVfZ6f;sw-R z`Pdg_WI3kdI^->T6IG|jQ0Ms$mD)4z{CQMH|3=kLWM8K!N24-3p)dK@1!gdz6_%j( z_yN=fHn`&#Pzz{8E%Y#IuRp;X@jPCSL)fPxEWokYgc|{qYE9;;*Qh8P4PwtU_JrASx3laU@11 zIgfKLa)W+bLq|o?ge7Y$mR&60_fwk`ZvpA9Q zi>TC|N4DLD4t2~!U0@mdaJ4%=>%M;xRov0TSiCMUk`Cvv0@NFiVs~snt@sVp36CI; zx}88B_Z#Z-5#)6ojzAq(fw!R_Nt%6x9BRL#GMJR&6zRMa@~;oBWkRWY4|Ty7)QK*m zwjzwmy8{Pc4_t{_;6~S7sD-_P%G_bpguY0GvkM`dnrD*4v|-?(qYQKC9< zGU_Qn)nj6*AaY8PQZN3!`)bg?WiqY$d|+lY(srt6!PIoxZO`D zpH2%#;#i&u6-yo}12f(6Lew2sxbrorqTB4w_vUtVd?F^}a8&WlM4hh+dtnXgJawq! z{S9=K;)AFnI*K~*ylW^MrVm7;=94iB(=itF-S^8;CtQQtf=w8Qb*M}oME>k2UP>{U zXE#g9siUJ*A4j&!qS+62!=~UAtU+D)Gwg?_F%CmAoqtO4copL+tittJg8$&dSdwM7 z1ka%sJ}cX)wfk_kHfI~1yLjUwPR3kTqXq5cMSFS*bq7g&%Vc38YR~tg7Tkuq^SE1_ zDo;kX-3pMm>96u+fV0qJcjx}5@#QZi%^f{4%8ifh|}>a)E*CI zbtac~eXxgMBE{*{tK9@YfBAGPwGs0)69eervY!GBz1 zxT=mHiSalcc|dFhDuV|w5ig+!dlDUq?Sz`P_ep}f()~MyQ6B7ZH?DkF1o!_&oJq_j z?j|Uc|C;Vj3mrl%CpHmUz%D|q$ia#+n^;2(BG^%Tf_T`yzVow*3Dtp`3g-W<4-;L9 zr-TBDNAM2^DlS(Sax-)E*~Ph>JCFn)CawmcfQAQ?5^A zA#pvyzjS+uP|G9k3HF>{KyEk47G75q4+KZfH{^Qyqlf`S7b1t)M^MW&@*fLJg9GQ~ zS-0OC`x87R|FuWxdEHS2UQ6sCD&0AeMqEd%A~q74L?lr`)DWe_l{T)3-WXyk@hCBj zP+LK$Hq^q2X~7;pI53HbCmwU>L_X1tNFc(9s|mHS7Fg8fl@5XB5vx31=N5aHmM!yD z`j!_5PINyO7D$io9};*m?)}aQODlZyioIne-j!t)^NTCy%q=S`o#UHdo#tH`e5*RJ zAn{zMz=MMlJf2~JJ%fMl5C|WZ>q(#NTjHzq20!C%e)~}Co~D+!wzj?2(A@Y|>w$gk zO&i-@JJh;wb!&Y?Yt8;MJJvNfZfL2kbB+w$HT\n" "Language: zh_Hans_CN\n" @@ -16,7 +16,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.10.1\n" +"Generated-By: Babel 2.13.1\n" #: rqalpha/environment.py:70 msgid "" @@ -28,113 +28,109 @@ msgstr "Environment 并没有被创建,请再 RQAlpha 初始化之后使用 `E msgid "No such transaction cost decider, order_book_id = {}" msgstr "" -#: rqalpha/main.py:61 +#: rqalpha/main.py:60 msgid "" "There is no data between {start_date} and {end_date}. Please check your " "data bundle or select other backtest period." msgstr "未在 {start_date} 和 {end_date} 区间内查询到数据,请检查并升级您的 data bundle 或者选择其他回测区间。" -#: rqalpha/main.py:80 +#: rqalpha/main.py:79 msgid "" "Missing persist provider. You need to set persist_provider before use " "persist" msgstr "Persist provider 未加载,加载 Persist provider 方可使用 persist 功能。" -#: rqalpha/main.py:126 +#: rqalpha/main.py:127 msgid "rqdatac init failed, some apis will not function properly: {}" msgstr "" "rqdatac 初始化失败,部分扩展 API 可能无法使用,错误信息:{}。您可以访问 " "https://www.ricequant.com/welcome/rqdata 获取 rqdatac" -#: rqalpha/main.py:213 +#: rqalpha/main.py:218 msgid "system restored" msgstr "" -#: rqalpha/main.py:243 +#: rqalpha/main.py:248 msgid "strategy run successfully, normal exit" msgstr "策略运行成功,正常退出" -#: rqalpha/main.py:248 +#: rqalpha/main.py:253 msgid "strategy execute exception" msgstr "策略运行产生异常" -#: rqalpha/apis/api_base.py:67 rqalpha/apis/api_base.py:266 -#: rqalpha/apis/api_base.py:302 +#: rqalpha/apis/api_base.py:69 rqalpha/apis/api_base.py:286 +#: rqalpha/apis/api_base.py:322 msgid "unsupported order_book_id type" msgstr "不支持该 order_book_id 类型" -#: rqalpha/apis/api_base.py:87 rqalpha/apis/api_base.py:91 -msgid "Limit order price should not be nan." -msgstr "限价单价格不能为 nan。" - -#: rqalpha/apis/api_base.py:148 +#: rqalpha/apis/api_base.py:164 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:59 msgid "Main Future contracts[88] are not supported in paper trading." msgstr "期货主力连续合约[88] 在实盘模拟中暂不支持。" -#: rqalpha/apis/api_base.py:152 +#: rqalpha/apis/api_base.py:168 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:61 msgid "Index Future contracts[99] are not supported in paper trading." msgstr "期货指数连续合约[99] 在实盘模拟中暂不支持。" -#: rqalpha/apis/api_base.py:158 +#: rqalpha/apis/api_base.py:174 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:66 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:89 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:128 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:308 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:104 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:142 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:346 msgid "Order Creation Failed: [{order_book_id}] No market data" msgstr "订单创建失败: 当前合约[{order_book_id}]没有市场数据。" -#: rqalpha/apis/api_rqdatac.py:46 +#: rqalpha/apis/api_rqdatac.py:50 msgid "rqdatac is not available, extension apis will not function properly" msgstr "" "rqdatac 不存在,扩展 API 无法使用,您可以访问 https://www.ricequant.com/welcome/rqdata 获取" " rqdatac" -#: rqalpha/apis/api_rqdatac.py:102 +#: rqalpha/apis/api_rqdatac.py:106 msgid "in get_split, start_date {} is no earlier than the previous test day {}" msgstr "get_spit 中 start_date {} 晚于策略运行前一日 {}" -#: rqalpha/apis/api_rqdatac.py:140 +#: rqalpha/apis/api_rqdatac.py:144 msgid "in index_components, date {} is no earlier than test date {}" msgstr "index_compoments 中,date {} 晚于策略当前运行日期 {}" -#: rqalpha/apis/api_rqdatac.py:187 +#: rqalpha/apis/api_rqdatac.py:191 msgid "in index_weights, date {} is no earlier than previous test date {}" msgstr "在API index_weights 中, 参数date={} 不应晚于上一交易日 {}" -#: rqalpha/apis/api_rqdatac.py:330 +#: rqalpha/apis/api_rqdatac.py:398 msgid "in get_price, end_date {} is no earlier than the previous test day {}" msgstr "在API get_price 中, 当前参数end_date={} 不应晚于上一交易日 {}" -#: rqalpha/apis/api_rqdatac.py:338 +#: rqalpha/apis/api_rqdatac.py:406 msgid "in get_price, start_date {} is no earlier than the previous test day {}" msgstr "在API get_price中, 参数start_date={} 不应晚于上一交易日 {}" -#: rqalpha/apis/api_rqdatac.py:343 +#: rqalpha/apis/api_rqdatac.py:411 msgid "in get_price, start_date {} > end_date {}" msgstr "在API get_price 中,当前参数 start_date={} 大于 end_date={}" -#: rqalpha/apis/api_rqdatac.py:805 +#: rqalpha/apis/api_rqdatac.py:873 msgid "'{0}' future does not exist" msgstr "期货品种 '{0}' 不存在" -#: rqalpha/apis/api_rqdatac.py:962 +#: rqalpha/apis/api_rqdatac.py:1159 msgid "in get_fundamentals entry_date {} is no earlier than test date {}" msgstr "在API get_fundamentals 中,财务数据开始日期 {} 不应晚于上一交易日 {}" -#: rqalpha/apis/api_rqdatac.py:996 rqalpha/apis/api_rqdatac.py:1061 -#: rqalpha/utils/arg_checker.py:311 +#: rqalpha/apis/api_rqdatac.py:1193 rqalpha/apis/api_rqdatac.py:1258 +#: rqalpha/utils/arg_checker.py:320 msgid "" "function {}: invalid {} argument, quarter should be in form of '2012q3', " "got {} (type: {})" msgstr "函数 {}:参数 {} 不合法,季度应为形如 '2012q3' 的字符串,传入的是 {}(类型:{})" -#: rqalpha/cmds/bundle.py:33 +#: rqalpha/cmds/bundle.py:34 msgid "create bundle using RQDatac" msgstr "使用 RQDatac 创建 Bundle" -#: rqalpha/cmds/bundle.py:43 +#: rqalpha/cmds/bundle.py:44 msgid "" "rqdatac is required to create bundle. you can visit " "https://www.ricequant.com/welcome/rqdata to get rqdatac, or use \"rqalpha" @@ -143,15 +139,15 @@ msgstr "" "创建 bundle 依赖 rqdatac。您可以访问 https://www.ricequant.com/welcome/rqdata 以获取 " "rqdatac,或执行 \"rqalpha download-bundle\" 以下载月度更新的 bundle。" -#: rqalpha/cmds/bundle.py:54 rqalpha/cmds/bundle.py:83 +#: rqalpha/cmds/bundle.py:55 rqalpha/cmds/bundle.py:84 msgid "rqdatac init failed with error: {}" msgstr "rqdatac 初始化失败:{}" -#: rqalpha/cmds/bundle.py:62 +#: rqalpha/cmds/bundle.py:63 msgid "Update bundle using RQDatac" msgstr "使用 RQDatac 更新 Bundle" -#: rqalpha/cmds/bundle.py:72 +#: rqalpha/cmds/bundle.py:73 msgid "" "rqdatac is required to update bundle. you can visit " "https://www.ricequant.com/welcome/rqdata to get rqdatac, or use \"rqalpha" @@ -160,45 +156,15 @@ msgstr "" "更新 bundle 依赖 rqdatac。您可以访问 https://www.ricequant.com/welcome/rqdata 以获取 " "rqdatac,或执行 \"rqalpha download-bundle\" 以下载月度更新的 bundle。" -#: rqalpha/cmds/bundle.py:87 +#: rqalpha/cmds/bundle.py:88 msgid "bundle not exist, use \"rqalpha create-bundle\" command instead" msgstr "bundle 不存在,执行 \"rqalpha create-bundle\" 以创建 bundle" -#: rqalpha/cmds/bundle.py:94 +#: rqalpha/cmds/bundle.py:95 msgid "Download bundle (monthly updated)" msgstr "下载(月度更新的)Bundle" -msgid "Check bundle" -msgstr "检测 Bundle 数据" - -msgid "corrupted files" -msgstr "已损坏的文件" - -msgid "remove files" -msgstr "是否删除文件" - -msgid "remove success" -msgstr "文件删除完毕" - -msgid "corrupted files not remove" -msgstr "已损坏的文件尚未删除" - -msgid "input error" -msgstr "输入异常" - -msgid "bundle's day bar is incomplete, please update bundle" -msgstr "Bundle日线数据不完整,请及时更新bundle" - -msgid "good bundle's day bar" -msgstr "Bundle日线数据良好" - -msgid "benchmark {} not exists, please entry correct order_book_id" -msgstr "基准标的({})信息不存在,请输入正确的标的代码" - -msgid "benchmark {} listed date {} > backtest start date {} or de_listed date {} <= backtest end date {}" -msgstr "基准标的({})上市日期({})晚于回测起始日期({})或退市日期({})早于回测结束日期({})" - -#: rqalpha/cmds/bundle.py:105 +#: rqalpha/cmds/bundle.py:106 msgid "" "\n" " [WARNING]\n" @@ -212,19 +178,23 @@ msgstr "" " 该路径下的内容将会被移除.\n" " 确定继续吗?" -#: rqalpha/cmds/bundle.py:123 +#: rqalpha/cmds/bundle.py:124 msgid "Data bundle download successfully in {bundle_path}" msgstr "数据下载成功: {bundle_path}" -#: rqalpha/cmds/bundle.py:134 +#: rqalpha/cmds/bundle.py:127 +msgid "Check bundle" +msgstr "检测 Bundle 数据" + +#: rqalpha/cmds/bundle.py:141 msgid "try {} ..." msgstr "尝试 {} ..." -#: rqalpha/cmds/bundle.py:146 +#: rqalpha/cmds/bundle.py:153 msgid "downloading ..." msgstr "下载中 ..." -#: rqalpha/cmds/bundle.py:160 +#: rqalpha/cmds/bundle.py:167 msgid "" "\n" "Download failed, retry in {} seconds." @@ -232,6 +202,34 @@ msgstr "" "\n" "下载失败,{} 秒后重试。" +#: rqalpha/cmds/bundle.py:188 +msgid "corrupted files" +msgstr "已损坏的文件" + +#: rqalpha/cmds/bundle.py:189 +msgid "remove files" +msgstr "是否删除文件" + +#: rqalpha/cmds/bundle.py:192 +msgid "remove success" +msgstr "文件删除完毕" + +#: rqalpha/cmds/bundle.py:194 +msgid "corrupted files not remove" +msgstr "已损坏的文件尚未删除" + +#: rqalpha/cmds/bundle.py:196 +msgid "input error" +msgstr "输入异常" + +#: rqalpha/cmds/bundle.py:198 +msgid "bundle's day bar is incomplete, please update bundle" +msgstr "Bundle日线数据不完整,请及时更新bundle" + +#: rqalpha/cmds/bundle.py:200 +msgid "good bundle's day bar" +msgstr "Bundle日线数据良好" + #: rqalpha/cmds/misc.py:26 msgid "Generate example strategies to target folder" msgstr "在目标文件夹生成样例策略" @@ -265,12 +263,40 @@ msgstr "策略暂停中,取消执行 {}({}, {})" msgid "deprecated parameter[bar_dict] in before_trading function." msgstr "[Deprecated]在before_trading函数中,第二个参数bar_dict已经不再使用了。" -#: rqalpha/data/base_data_source/storages.py:85 +#: rqalpha/data/bundle.py:483 +msgid "" +"File {} update failed, if it is using, please update later, or you can " +"delete then update again" +msgstr "{} 文件更新失败,如果其正在使用中,请稍后再进行更新,或者您可以将其删除后再重新更新" + +#: rqalpha/data/bundle.py:561 +msgid "" +"RQAlpha already supports backtesting using futures historical margins and" +" rates, please upgrade RQDatac to version 2.11.12 and above to use it" +msgstr "RQAlpha 已支持使用期货历史保证金和费率进行回测,请将 RQDatac 升级至 2.11.12 及以上版本进行使用" + +#: rqalpha/data/bundle.py:567 +msgid "" +"Your RQData account does not have permission to use futures historical " +"margin and rates, and fixed data will be used for calculations\n" +"You can contact RiceQuant to activate permission: 0755-26569969" +msgstr "" +"您的 RQData 账号没有权限使用期货历史保证金和费率,将使用固定的数据进行回测和计算\n" +"您可联系米筐科技开通相关权限:0755-26569969" + +#: rqalpha/data/base_data_source/storages.py:82 +msgid "" +"Your bundle data is too old, please use 'rqalpha update-bundle' or " +"'rqalpha download-bundle' to update it to lastest before using" +msgstr "您的 Bundle 数据过旧,请使用 'rqalpha update-bundle' 或 'rqalpha download-bundle' 将其更新至最新后再进行使用" + +#: rqalpha/data/base_data_source/storages.py:98 +#: rqalpha/data/base_data_source/storages.py:124 msgid "unsupported future instrument {}" msgstr "不支持的期货标的{}" -#: rqalpha/data/base_data_source/storages.py:155 -#: rqalpha/data/base_data_source/storages.py:165 +#: rqalpha/data/base_data_source/storages.py:195 +#: rqalpha/data/base_data_source/storages.py:205 msgid "" "open data bundle failed, you can remove {} and try to regenerate bundle: " "{}" @@ -298,7 +324,7 @@ msgstr "" msgid "mod tear_down [END] {}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:296 +#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:318 msgid "{order_book_id} is expired, close all positions by system" msgstr "{order_book_id} 已退市/交割,系统自动平仓" @@ -316,18 +342,16 @@ msgid "" "{closable}" msgstr "订单创建失败:{order_book_id} 的仓位不足,目标平仓/行权量为 {quantity},可平仓/行权量为 {closable}" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:49 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:96 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:144 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:50 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:112 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:158 msgid "Order Creation Failed: 0 order quantity, order_book_id={order_book_id}" msgstr "{order_book_id} 的订单创建失败:下单数量为 0" -# msgid "Order Creation Failed: Order amount is 0." -# msgstr "订单创建失败: 下单量为 0" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:54 -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:85 -msgid "Limit order price should be positive" -msgstr "Limit order 价格应该为正,请检查您的下单价格" +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:55 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:100 +msgid "Limit order price should not be nan." +msgstr "限价单价格不能为 nan。" #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:78 msgid "" @@ -347,17 +371,17 @@ msgid "" "[{new_orders_repr}]" msgstr "订单被拆分,原订单:{original_order_repr},新订单:[{new_orders_repr}]" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:65 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:66 msgid "" "order_book_id: {order_book_id} needs stock account, please set and try " "again!" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:106 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:120 msgid "insufficient cash, use all remaining cash({}) to create order" msgstr "现金不足,使用当前剩余资金({})创建订单" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:296 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:334 #, fuzzy msgid "" "function order_target_portfolio: invalid keys of target_portfolio, " @@ -366,285 +390,323 @@ msgstr "" "函数 order_target_portfolio:不合法的参数。target_portfolio 的键应为合约代码或 Instrument " "对象,实际传入了 {}(类型:{})。" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:301 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:339 msgid "" "function order_target_portfolio: invalid instrument type, excepted " "CS/ETF/LOF/INDX, got {}" msgstr "函数 order_target_portfolio:不合法的资产类型。应传入股票、ETF、LOF 或指数,实际传入了 {}。" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:318 -#, fuzzy -msgid "" -"function order_target_portfolio: invalid order price {target_price} of " -"{id_or_ins}" -msgstr "函数 order_target_portfolio:{id_or_ins} 的下单价格 {target_price} 不合法。" - -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:324 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:354 #, fuzzy msgid "" "function order_target_portfolio: invalid values of target_portfolio, " "excepted float between 0 and 1, got {} (key: {})" msgstr "函数 order_target_portfolio:不合法的目标权重,应传入 0 和 1 之间的浮点数,实际传入了 {}(类型:{})。" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:332 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:363 msgid "total percent should be lower than 1, current: {}" msgstr "目标持仓占比总和应小于 1,当前值:{}。" -#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:655 +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:383 +msgid "" +"Adjust position of {id_or_ins} Failed: Invalid close/open price " +"{close_price}/{open_price}" +msgstr "" + +#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:698 msgid "in get_dividend, start_date {} is later than the previous test day {}" msgstr "在 get_dividend 函数中,start_date {} 晚于当前回测时间的上一个交易日 {}。" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:64 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:66 msgid "[sys_analyser] save report" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:69 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:71 msgid "[sys_analyser] output result pickle file" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:74 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:76 msgid "[sys_analyser] plot result" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:79 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:81 msgid "[sys_analyser] save plot to file" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:84 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:86 msgid "[sys_analyser] order_book_id of benchmark" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:89 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:91 msgid "[sys_analyser] show open close points on plot" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:94 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:96 msgid "[sys_analyser] show weekly indicators and return curve on plot" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:98 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:100 msgid "[sys_analyser] Plot from strategy output file" msgstr "【sys_analyser】使用策略输出的文件绘制收益图" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:101 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:103 msgid "save plot result to file" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:102 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:104 msgid "show open close points on plot" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:103 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:105 msgid "show weekly indicators and return curve on plot" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:113 +#: rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py:115 msgid "[sys_analyser] Generate report from strategy output file" msgstr "【sys_analyser】使用策略输出的文件生成报告" -#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:108 +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:112 msgid "" "config 'base.benchmark' is deprecated, use 'mod.sys_analyser.benchmark' " "instead" msgstr "配置'base.benchmark'已被弃用,使用'mod.sys_analyser.benchmark'代替" -#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:190 +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:143 +msgid "benchmark {} not exists, please entry correct order_book_id" +msgstr "基准标的({})信息不存在,请输入正确的标的代码" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:147 +msgid "" +"benchmark {} listed date {} > backtest start date {} or de_listed date {}" +" <= backtest end date {}" +msgstr "基准标的({})上市日期({})晚于回测起始日期({})或退市日期({})早于回测结束日期({})" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:210 msgid "invalid init benchmark {}, should be in format 'order_book_id:weight'" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:195 +#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:215 msgid "invalid weight for instrument {order_book_id}: {weight}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:61 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:60 msgid "Strategy" msgstr "策略收益" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:62 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:61 msgid "Benchmark" msgstr "基准收益" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:63 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:62 msgid "Excess" msgstr "超额收益" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:64 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:63 msgid "Weekly" msgstr "策略周度收益" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:65 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:64 msgid "BenchmarkWeekly" msgstr "基准周度收益" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:67 -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:91 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:66 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:123 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:170 msgid "MaxDrawDown" msgstr "最大回撤" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:68 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:67 msgid "MaxDDD" msgstr "最长回撤持续期" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:69 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:68 msgid "Open" msgstr "开仓" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:70 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:69 msgid "Close" msgstr "平仓" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:73 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:103 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:155 msgid "TotalReturns" msgstr "策略收益率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:74 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:104 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:156 msgid "AnnualReturns" msgstr "策略年化收益率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:75 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:105 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:157 msgid "Alpha" msgstr "阿尔法" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:76 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:106 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:158 msgid "Beta" msgstr "贝塔" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:77 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:107 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:161 msgid "Sharpe" msgstr "夏普率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:78 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:108 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:165 msgid "Sortino" msgstr "索提诺比率" +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:109 msgid "WeeklyUlcerIndex" msgstr "周度累计回撤深度" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:80 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:111 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:175 msgid "BenchmarkReturns" msgstr "基准收益率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:81 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:112 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:176 msgid "BenchmarkAnnual" msgstr "基准年化收益率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:82 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:113 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:167 msgid "Volatility" msgstr "波动率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:83 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:114 msgid "TrackingError" msgstr "跟踪误差" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:84 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:115 msgid "DownsideRisk" msgstr "下行风险" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:85 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:116 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:164 msgid "InformationRatio" msgstr "信息比率" +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:117 msgid "WeeklyUlcerPerformanceIndex" msgstr "周度累计回撤夏普率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:87 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:119 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:179 msgid "ExcessCumReturns" msgstr "超额收益率(算术)" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:88 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:120 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:159 msgid "WinRate" msgstr "胜率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:89 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:121 msgid "WeeklyWinRate" msgstr "周度胜率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:90 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:122 msgid "ProfitLossRate" msgstr "盈亏比" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:92 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:124 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:171 msgid "MaxDD/MaxDDD" msgstr "最大回撤/最长回撤持续期" +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:125 msgid "WeeklyExcessUlcerIndex" msgstr "周度超额累计回撤深度" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:96 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:129 msgid "WeeklyAlpha" msgstr "周度阿尔法" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:97 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:130 msgid "WeeklyBeta" msgstr "周度贝塔" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:98 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:131 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:162 msgid "WeeklySharpe" msgstr "周度夏普率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:99 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:132 msgid "WeeklyInfoRatio" msgstr "周度信息比率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:100 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:133 msgid "WeeklyTrackingError" msgstr "周度跟踪误差" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:101 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:134 msgid "WeeklyMaxDrawdown" msgstr "周度最大回撤" -msgid "WeeklyVolatility" -msgstr "周度波动率" - -msgid "MonthlySharpe" -msgstr "月度夏普率" - -msgid "MonthlyVolatility" -msgstr "月度波动率" - -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:105 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:138 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:177 msgid "ExcessReturns" msgstr "超额收益率(几何)" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:106 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:139 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:178 msgid "ExcessAnnual" msgstr "年化超额收益率(几何)" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:107 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:140 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:181 msgid "ExcessSharpe" msgstr "超额夏普率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:108 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:141 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:182 msgid "ExcessVolatility" msgstr "超额收益波动率" -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:109 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:142 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:184 msgid "ExcessMaxDD" msgstr "超额收益最大回撤(几何)" -msgid "ExcessWinRate" -msgstr "超额胜率" - -#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:110 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:143 +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:185 msgid "ExcessMaxDD/ExcessMaxDDD" msgstr "超额收益最大回撤/最长回撤持续期" +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:144 msgid "WeeklyExcessUlcerPerformanceIndex" msgstr "周度超额累计回撤夏普率" +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:163 +msgid "MonthlySharpe" +msgstr "月度夏普率" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:168 +msgid "WeeklyVolatility" +msgstr "周度波动率" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:169 +msgid "MonthlyVolatility" +msgstr "月度波动率" + +#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:183 +msgid "ExcessWinRate" +msgstr "超额胜率" + #: rqalpha/mod/rqalpha_mod_sys_risk/validators/cash_validator.py:33 msgid "" "Order Creation Failed: not enough money to buy {order_book_id}, needs " "{cost_money:.2f}, cash {cash:.2f}" msgstr "订单创建失败: 可用资金不足。当前资金: {cash:.2f},{order_book_id} 下单所需资金: {cost_money:.2f}" -#: rqalpha/mod/rqalpha_mod_sys_risk/validators/is_trading_validator.py:31 +#: rqalpha/mod/rqalpha_mod_sys_risk/validators/is_trading_validator.py:32 #, fuzzy msgid "Order Creation Failed: {order_book_id} is not listing!" msgstr "订单创建失败: {order_book_id} 未上市或已退市" -#: rqalpha/mod/rqalpha_mod_sys_risk/validators/is_trading_validator.py:37 +#: rqalpha/mod/rqalpha_mod_sys_risk/validators/is_trading_validator.py:38 msgid "Order Creation Failed: security {order_book_id} is suspended on {date}" msgstr "订单创建失败: {order_book_id} 在 {date} 时停牌" @@ -666,41 +728,39 @@ msgid "" "trade: [{}...]" msgstr "订单创建失败,当前存在可能导致自成交的挂单:[{}...]" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:101 -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:319 -#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:66 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:118 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:335 +#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:69 msgid "" "Order Cancelled: current security [{order_book_id}] can not be traded in " "listed date [{listed_date}]" msgstr "订单被撤销: [{order_book_id}] 上市首日无法交易" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:108 -#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:72 -msgid "Order Cancelled: current bar [{order_book_id}] miss market data." -msgstr "订单被拒单: [{order_book_id}] 当前缺失市场数据。" - -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:128 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:125 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:165 #: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:86 +msgid "Order Cancelled: {order_book_id} bar no volume" +msgstr "订单被撤销:{order_book_id} 当前无成交量" + +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:150 +#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:95 msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_up price." msgstr "订单被拒单: [{order_book_id}] 已涨停。" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:134 -#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:95 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:156 +#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:104 msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_down price." msgstr "订单被拒单: [{order_book_id}] 已跌停。" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:143 -msgid "Order Cancelled: {order_book_id} bar no volume" -msgstr "订单被撤销:{order_book_id} 当前无成交量" - -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:159 -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:396 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:178 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:417 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:567 msgid "" "Order Cancelled: market order {order_book_id} volume {order_volume} due " "to volume limit" msgstr "订单被撤销: 订单 [{order_book_id}] 的下单量 {order_volume} 超过了成交量限制。" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:197 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:217 msgid "" "Order Cancelled: market order {order_book_id} volume {order_volume} is " "larger than {volume_percent_limit} percent of current bar volume, fill " @@ -709,26 +769,26 @@ msgstr "" "{order_book_id} 下单量 {order_volume} 超过当前 Bar 成交量的 " "{volume_percent_limit}%,实际成交 {filled_volume}" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:326 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:343 msgid "Order Cancelled: current tick [{order_book_id}] miss market data." msgstr "订单被拒单: [{order_book_id}] 当前缺失市场数据。" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:351 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:368 msgid "Order Cancelled: current tick [{order_book_id}] reach the limit_up price." msgstr "订单被拒单: [{order_book_id}] 已涨停。" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:357 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:374 msgid "" "Order Cancelled: current tick [{order_book_id}] reach the limit_down " "price." msgstr "订单被拒单: [{order_book_id}] 已跌停。" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:364 -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:370 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:381 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:387 msgid "Order Cancelled: [{order_book_id}] has no liquidity." msgstr "合约 [{order_book_id}] 流动性不足,拒单。" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:438 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:462 #, fuzzy msgid "" "Order Cancelled: market order {order_book_id} volume {order_volume} is " @@ -738,7 +798,7 @@ msgstr "" "{order_book_id} 下单量 {order_volume} 超过当前 Tick 成交量的 " "{volume_percent_limit}%,实际成交 {filled_volume}" -#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:571 +#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:611 msgid "" "Order Cancelled: market order {order_book_id} fill {filled_volume} " "actually" @@ -759,14 +819,18 @@ msgid "" "matching_type is 'current_bar'." msgstr "日回测 'next_bar' 撮合方式已废弃,当前按 'current_bar' 撮合方式成交" -#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:107 +#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:119 msgid "NO account_type = ({}) in {}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:53 +#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:56 msgid "cancel_order function is not supported in signal mode" msgstr "在 Signal 模式下,不支持 cancel_order 函数" +#: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:75 +msgid "Order Cancelled: current bar [{order_book_id}] miss market data." +msgstr "订单被拒单: [{order_book_id}] 当前缺失市场数据。" + #: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:108 msgid "unsupported position_effect {}" msgstr "不支持的 position_effect {}" @@ -779,7 +843,7 @@ msgstr "订单 {order_id} 被手动取消。" msgid "Order Rejected: {order_book_id} can not match. Market close." msgstr "订单被拒单: {order_book_id} 当天交易结束,订单无法成交。" -#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:184 +#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:185 msgid "{order_book_id} should be subscribed when frequency is tick." msgstr "tick回测下单失败,请使用 subscribe 订阅合约 {order_book_id}。" @@ -807,13 +871,13 @@ msgstr "无效的 滑点 设置: 其值需为正数。" msgid "invalid slippage rate value {} which cause price <= 0" msgstr "无效的 滑点 设置: 该值导致了调整后的价格为负。" -#: rqalpha/mod/rqalpha_mod_sys_transaction_cost/mod.py:30 +#: rqalpha/mod/rqalpha_mod_sys_transaction_cost/mod.py:42 msgid "" "invalid commission multiplier or tax multiplier value: value range is [0," " +∞)" msgstr "" -#: rqalpha/model/bar.py:371 +#: rqalpha/model/bar.py:372 msgid "id_or_symbols {} does not exist" msgstr "您选择的证券[{}]不存在。" @@ -824,117 +888,113 @@ msgid "" " {tax}, {frozen_price}" msgstr "" -#: rqalpha/portfolio/__init__.py:67 +#: rqalpha/portfolio/__init__.py:71 msgid "invalid init position {order_book_id}: no valid price at {date}" msgstr "初始持仓 {order_book_id} 不合法:无法获取到该标 {date} 的价格" -#: rqalpha/portfolio/__init__.py:267 +#: rqalpha/portfolio/__init__.py:278 rqalpha/portfolio/__init__.py:291 msgid "invalid account type {}, choose in {}" msgstr "不合法的账户类型 {}, 请在以下账户类型中选择 {}" -#: rqalpha/portfolio/__init__.py:271 +#: rqalpha/portfolio/__init__.py:282 msgid "Cash add {}. units {} become to {}" msgstr "入金{}元,份额由{}变动为{}" -#: rqalpha/portfolio/account.py:313 +#: rqalpha/portfolio/account.py:352 msgid "Trigger Forced Liquidation, current total_value is 0" msgstr "触发强制清算,当前总权益为 0" -#: rqalpha/portfolio/account.py:457 +#: rqalpha/portfolio/account.py:502 rqalpha/portfolio/account.py:521 msgid "insufficient cash, current {}, target withdrawal {}" msgstr "现金不足,当前现金 {},目标出金 {}" -#: rqalpha/portfolio/position.py:160 -msgid "invalid price of {order_book_id}: {price}" -msgstr "{order_book_id} 的价格不合法:{price}" - -#: rqalpha/utils/arg_checker.py:50 +#: rqalpha/utils/arg_checker.py:51 msgid "" "function {}: invalid {} argument, expect a value of type {}, got {} " "(type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:58 +#: rqalpha/utils/arg_checker.py:59 msgid "valid order_book_id/instrument" msgstr "" -#: rqalpha/utils/arg_checker.py:61 +#: rqalpha/utils/arg_checker.py:62 msgid "valid stock order_book_id/instrument" msgstr "" -#: rqalpha/utils/arg_checker.py:64 +#: rqalpha/utils/arg_checker.py:65 msgid "valid future order_book_id/instrument" msgstr "" -#: rqalpha/utils/arg_checker.py:67 +#: rqalpha/utils/arg_checker.py:68 msgid "listed order_book_id/instrument" msgstr "" -#: rqalpha/utils/arg_checker.py:70 +#: rqalpha/utils/arg_checker.py:71 msgid "function {}: invalid {} argument, expected a {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:96 +#: rqalpha/utils/arg_checker.py:97 msgid "" "function {}: invalid {} argument, expected instrument with types {}, got " "instrument with type {}" msgstr "" -#: rqalpha/utils/arg_checker.py:136 +#: rqalpha/utils/arg_checker.py:137 msgid "function {}: invalid {} argument, expect a number, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:151 +#: rqalpha/utils/arg_checker.py:160 msgid "function {}: invalid {} argument, valid: {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:165 +#: rqalpha/utils/arg_checker.py:174 msgid "function {}: invalid {} argument, valid fields are {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:177 +#: rqalpha/utils/arg_checker.py:186 msgid "function {}: invalid field {}, valid fields are {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:183 rqalpha/utils/arg_checker.py:198 +#: rqalpha/utils/arg_checker.py:192 rqalpha/utils/arg_checker.py:207 msgid "" "function {}: invalid {} argument, expect a string or a list of string, " "got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:225 rqalpha/utils/arg_checker.py:230 +#: rqalpha/utils/arg_checker.py:234 rqalpha/utils/arg_checker.py:239 msgid "function {}: invalid {} argument, expect a valid date, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:241 +#: rqalpha/utils/arg_checker.py:250 msgid "function {}: invalid {} argument, expect a value >= {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:251 +#: rqalpha/utils/arg_checker.py:260 msgid "function {}: invalid {} argument, expect a value > {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:261 +#: rqalpha/utils/arg_checker.py:270 msgid "function {}: invalid {} argument, expect a value <= {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:272 +#: rqalpha/utils/arg_checker.py:281 msgid "function {}: invalid {} argument, expect a value < {}, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:289 +#: rqalpha/utils/arg_checker.py:298 msgid "" "function {}: invalid {} argument, interval should be in form of '1d', " "'3m', '4q', '2y', got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:325 +#: rqalpha/utils/arg_checker.py:334 msgid "" "function {}: invalid {} argument, should be entity like " "Fundamentals.balance_sheet.total_equity, got {} (type: {})" msgstr "" -#: rqalpha/utils/arg_checker.py:344 +#: rqalpha/utils/arg_checker.py:353 #, fuzzy msgid "" "function {}: invalid {} argument, frequency should be in form of '1m', " @@ -1473,3 +1533,16 @@ msgstr "不支持 API {}。请确保您设置了该 API 所需的账户并开启 #~ msgid "Order Creation Failed: {order_book_id} has been delisted!" #~ msgstr "订单创建失败: {order_book_id} 已退市" +# msgid "Order Creation Failed: Order amount is 0." +# msgstr "订单创建失败: 下单量为 0" +#~ msgid "Limit order price should be positive" +#~ msgstr "Limit order 价格应该为正,请检查您的下单价格" + +#~ msgid "" +#~ "function order_target_portfolio: invalid order " +#~ "price {target_price} of {id_or_ins}" +#~ msgstr "函数 order_target_portfolio:{id_or_ins} 的下单价格 {target_price} 不合法。" + +#~ msgid "invalid price of {order_book_id}: {price}" +#~ msgstr "{order_book_id} 的价格不合法:{price}" + diff --git a/setup.py b/setup.py index 7d1623c36..4d338f8e3 100644 --- a/setup.py +++ b/setup.py @@ -81,6 +81,8 @@ 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', ], python_requires=">=3.6" ) diff --git a/tests/api_tests/mod/sys_simulation/test_simulation_event_source.py b/tests/api_tests/mod/sys_simulation/test_simulation_event_source.py index a9b05101d..091d081a7 100644 --- a/tests/api_tests/mod/sys_simulation/test_simulation_event_source.py +++ b/tests/api_tests/mod/sys_simulation/test_simulation_event_source.py @@ -18,8 +18,8 @@ __config__ = { "base": { - "start_date": "2015-04-10", - "end_date": "2015-04-20", + "start_date": "2015-04-14", + "end_date": "2015-04-24", "frequency": "1d", "accounts": { "stock": 1000000, diff --git a/tests/api_tests/mod/sys_transaction_cost/test_commission_multiplier.py b/tests/api_tests/mod/sys_transaction_cost/test_commission_multiplier.py index 4855a90aa..71aeac6a9 100644 --- a/tests/api_tests/mod/sys_transaction_cost/test_commission_multiplier.py +++ b/tests/api_tests/mod/sys_transaction_cost/test_commission_multiplier.py @@ -46,10 +46,10 @@ def handle_bar(context, bar_dict): stock_order = order_percent(context.s1, 1) future_order = buy_open(context.s2, 1) env = Environment.get_instance() - future_commission_info = env.data_proxy.get_commission_info(context.s2) + future_commission_info = env.data_proxy.get_futures_trading_parameters(context.s2, bar_dict.dt.date()) context.fixed = False assert stock_order.transaction_cost == 16.66 * 59900 * 8 / 10000 * 2 - assert future_order.transaction_cost == 7308 * 200 * future_commission_info["open_commission_ratio"] * 3 + assert future_order.transaction_cost == 7308 * 200 * future_commission_info.open_commission_ratio * 3 return locals() diff --git a/tests/outs/test_f_mean_reverting.pkl b/tests/outs/test_f_mean_reverting.pkl index 8b4e561f859b16d3b094d941d97ee1a74491ca28..b1c1c0923e2265062d676e06150b25565872d0e6 100644 GIT binary patch delta 21709 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 Date: Wed, 17 Jan 2024 15:18:43 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E8=B0=83=E6=95=B4=20trading=5Fparameters?= =?UTF-8?q?=5Fstore=20=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B5=8B=E8=AF=95=E9=97=AE=E9=A2=98=20(#840)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * modify .readthedocs.yml * bug fix * bug fix * refactor * pr update * 翻译更新 --------- Co-authored-by: 周嘉俊 <35399214+Zhou-JiaJun@users.noreply.github.com> Co-authored-by: Cuizi7 Co-authored-by: 周嘉俊 <654181984@qq.com> Co-authored-by: cuizi7 --- messages.pot | 38 +++++++------ rqalpha/config.yml | 2 + rqalpha/data/base_data_source/data_source.py | 26 ++++++--- rqalpha/data/base_data_source/storages.py | 4 +- rqalpha/data/bundle.py | 31 ++--------- rqalpha/main.py | 13 +++-- .../__init__.py | 2 - rqalpha/utils/exception.py | 4 ++ rqalpha/utils/testing/fixtures.py | 2 +- .../zh_Hans_CN/LC_MESSAGES/messages.mo | Bin 19612 -> 19222 bytes .../zh_Hans_CN/LC_MESSAGES/messages.po | 52 ++++++++++++------ tests/test_f_buy_and_hold.py | 5 +- tests/test_f_mean_reverting.py | 5 +- 13 files changed, 99 insertions(+), 85 deletions(-) diff --git a/messages.pot b/messages.pot index 225508c54..45e919284 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-01-12 13:38+0800\n" +"POT-Creation-Date: 2024-01-17 14:36+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -39,19 +39,19 @@ msgid "" "persist" msgstr "" -#: rqalpha/main.py:127 +#: rqalpha/main.py:126 msgid "rqdatac init failed, some apis will not function properly: {}" msgstr "" -#: rqalpha/main.py:218 +#: rqalpha/main.py:219 msgid "system restored" msgstr "" -#: rqalpha/main.py:248 +#: rqalpha/main.py:249 msgid "strategy run successfully, normal exit" msgstr "" -#: rqalpha/main.py:253 +#: rqalpha/main.py:254 msgid "strategy execute exception" msgstr "" @@ -247,24 +247,30 @@ msgstr "" msgid "deprecated parameter[bar_dict] in before_trading function." msgstr "" -#: rqalpha/data/bundle.py:483 +#: rqalpha/data/bundle.py:452 +msgid "" +"RQAlpha already supports backtesting using futures historical margins and" +" rates, please upgrade RQDatac to version 2.11.12 and above to use it" +msgstr "" + +#: rqalpha/data/bundle.py:488 msgid "" "File {} update failed, if it is using, please update later, or you can " "delete then update again" msgstr "" -#: rqalpha/data/bundle.py:561 +#: rqalpha/data/base_data_source/data_source.py:140 msgid "" -"RQAlpha already supports backtesting using futures historical margins and" -" rates, please upgrade RQDatac to version 2.11.12 and above to use it" -msgstr "" +"RQDatac is not installed, " +"\"config.base.futures_time_series_trading_parameters\" will be disabled." +msgstr "未安装 RQDatac,将关闭配置 \"config.base.futures_time_series_trading_parameters\"" -#: rqalpha/data/bundle.py:567 +#: rqalpha/data/base_data_source/data_source.py:145 msgid "" -"Your RQData account does not have permission to use futures historical " -"margin and rates, and fixed data will be used for calculations\n" -"You can contact RiceQuant to activate permission: 0755-26569969" -msgstr "" +"RQDatac does not have permission to obtain futures histrical trading " +"parameters, \"config.base.futures_time_series_trading_parameters\" will " +"be disabled." +msgstr "RQDatac 缺少获取期货历史交易参数的权限,将关闭配置 \"config.base.futures_time_series_trading_parameters\"" #: rqalpha/data/base_data_source/storages.py:82 msgid "" @@ -306,7 +312,7 @@ msgstr "" msgid "mod tear_down [END] {}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:318 +#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:314 msgid "{order_book_id} is expired, close all positions by system" msgstr "" diff --git a/rqalpha/config.yml b/rqalpha/config.yml index 11f2239e1..2157ac47b 100644 --- a/rqalpha/config.yml +++ b/rqalpha/config.yml @@ -39,6 +39,8 @@ base: future_info: {} # 强平 forced_liquidation: true + # 是否开启期货历史交易参数进行回测,默认为 False + futures_time_series_trading_parameters: false extra: diff --git a/rqalpha/data/base_data_source/data_source.py b/rqalpha/data/base_data_source/data_source.py index c5a3eaae3..60a79051f 100644 --- a/rqalpha/data/base_data_source/data_source.py +++ b/rqalpha/data/base_data_source/data_source.py @@ -23,17 +23,17 @@ import numpy as np import pandas as pd import six -from rqalpha.const import INSTRUMENT_TYPE, TRADING_CALENDAR_TYPE +from rqalpha.utils.i18n import gettext as _ +from rqalpha.const import INSTRUMENT_TYPE, TRADING_CALENDAR_TYPE, DEFAULT_ACCOUNT_TYPE from rqalpha.interface import AbstractDataSource from rqalpha.model.instrument import Instrument from rqalpha.utils.datetime_func import (convert_date_to_int, convert_int_to_date, convert_int_to_datetime) -from rqalpha.utils.exception import RQInvalidArgument +from rqalpha.utils.exception import RQInvalidArgument, RQDatacVersionTooLow from rqalpha.utils.functools import lru_cache from rqalpha.utils.typing import DateLike from rqalpha.environment import Environment from rqalpha.data.bundle import update_futures_trading_parameters from rqalpha.utils.logger import user_system_log - from rqalpha.data.base_data_source.adjust import FIELDS_REQUIRE_ADJUSTMENT, adjust_bars from rqalpha.data.base_data_source.storage_interface import (AbstractCalendarStore, AbstractDateSet, AbstractDayBarStore, AbstractDividendStore, @@ -72,7 +72,8 @@ class BaseDataSource(AbstractDataSource): INSTRUMENT_TYPE.PUBLIC_FUND, ) - def __init__(self, path, custom_future_info, update_parameters_end_date=None): + def __init__(self, path, custom_future_info, futures_time_series_trading_parameters=False, end_date=None): + # type: (str, dict, bool, date) -> None if not os.path.exists(path): raise RuntimeError('bundle path {} not exist'.format(os.path.abspath(path))) @@ -89,9 +90,6 @@ def _p(name): } # type: Dict[INSTRUMENT_TYPE, AbstractDayBarStore] self._futures_trading_parameters_store = None - if update_parameters_end_date: - if update_futures_trading_parameters(path, update_parameters_end_date): - self._futures_trading_parameters_store = FuturesTradingParametersStore(_p("futures_trading_parameters.h5")) self._future_info_store = FutureInfoStore(_p("future_info.json"), custom_future_info) self._instruments_stores = {} # type: Dict[INSTRUMENT_TYPE, AbstractInstrumentStore] @@ -135,6 +133,20 @@ def _p(name): self._suspend_days = [DateSet(_p('suspended_days.h5'))] # type: List[AbstractDateSet] self._st_stock_days = DateSet(_p('st_stock_days.h5')) + if futures_time_series_trading_parameters: + try: + import rqdatac + except ImportError: + user_system_log.warn(_("RQDatac is not installed, \"config.base.futures_time_series_trading_parameters\" will be disabled.")) + else: + try: + update_futures_trading_parameters(path, end_date) + except (rqdatac.share.errors.PermissionDenied, RQDatacVersionTooLow): + user_system_log.warn(_("RQDatac does not have permission to obtain futures histrical trading parameters, \"config.base.futures_time_series_trading_parameters\" will be disabled.")) + else: + file = os.path.join(path, "futures_trading_parameters.h5") + self._futures_trading_parameters_store = FuturesTradingParametersStore(file) + def register_day_bar_store(self, instrument_type, store): # type: (INSTRUMENT_TYPE, AbstractDayBarStore) -> None self._day_bars[instrument_type] = store diff --git a/rqalpha/data/base_data_source/storages.py b/rqalpha/data/base_data_source/storages.py index a49d42db8..c28a4ddb9 100644 --- a/rqalpha/data/base_data_source/storages.py +++ b/rqalpha/data/base_data_source/storages.py @@ -269,12 +269,12 @@ def get_futures_trading_parameters(self, instrument, dt): if data is None: return None else: - arr = data[data['datetime'] == dt][0] + arr = data[data['datetime'] == dt] if len(arr) == 0: if dt >= convert_date_to_date_int(instrument.listed_date) and dt <= convert_date_to_date_int(instrument.de_listed_date): user_system_log.info("Historical futures trading parameters are abnormal, the lastst parameters will be used for calculations.\nPlease contract RiceQuant to repair: 0755-26569969") return None - futures_trading_parameters = self._to_namedtuple(arr) + futures_trading_parameters = self._to_namedtuple(arr[0]) return futures_trading_parameters @lru_cache(1024) diff --git a/rqalpha/data/bundle.py b/rqalpha/data/bundle.py index 0dc4eb117..a8d1cc245 100644 --- a/rqalpha/data/bundle.py +++ b/rqalpha/data/bundle.py @@ -26,6 +26,7 @@ ProgressedTask) from rqalpha.utils.datetime_func import (convert_date_to_date_int, convert_date_to_int) +from rqalpha.utils.exception import RQDatacVersionTooLow from rqalpha.utils.i18n import gettext as _ START_DATE = 20050104 @@ -447,6 +448,9 @@ def __init__(self, order_book_ids): self._order_book_ids = order_book_ids def __call__(self, path, fields, end_date): + if rqdatac.__version__ < '2.11.12': + raise RQDatacVersionTooLow(_("RQAlpha already supports backtesting using futures historical margins and rates, please upgrade RQDatac to version 2.11.12 and above to use it")) + if not os.path.exists(path): self.generate_futures_trading_parameters(path, fields, end_date) else: @@ -551,35 +555,12 @@ def get_recreate_futures_list(self, path, h5_last_date): return recreate_futures_list -def check_rqdata_permission(): - """ - 检测以下内容,均符合才会更新期货交易参数: - 1. rqdatac 版本是否为具备 futures.get_trading_parameters API 的版本 - 2. 当前 rqdatac 是否具备上述 API 的使用权限 - """ - if rqdatac.__version__ < '2.11.12': - from rqalpha.utils.logger import system_log - system_log.warn(_("RQAlpha already supports backtesting using futures historical margins and rates, please upgrade RQDatac to version 2.11.12 and above to use it")) - return - try: - rqdatac.futures.get_trading_parameters("A1005") - except rqdatac.share.errors.PermissionDenied: - from rqalpha.utils.logger import system_log - system_log.warn(_("Your RQData account does not have permission to use futures historical margin and rates, and fixed data will be used for calculations\nYou can contact RiceQuant to activate permission: 0755-26569969")) - return - return True - - def update_futures_trading_parameters(path, end_date): - # type: (str, datetime.date) -> Boolean - update_permission = check_rqdata_permission() - if not update_permission: - return False + # type: (str, datetime.date) -> None df = rqdatac.all_instruments("Future") order_book_ids = (df[df['de_listed_date'] >= str(TRADING_PARAMETERS_START_DATE)]).order_book_id.tolist() FuturesTradingParametersTask(order_book_ids)( os.path.join(path, FUTURES_TRADING_PARAMETERS_FILE), FUTURES_TRADING_PARAMETERS_FIELDS, end_date - ) - return True + ) diff --git a/rqalpha/main.py b/rqalpha/main.py index 7936a48d6..bf55616ff 100644 --- a/rqalpha/main.py +++ b/rqalpha/main.py @@ -122,7 +122,6 @@ def init_rqdatac(rqdatac_uri): init_rqdatac_env(rqdatac_uri) try: rqdatac.init() - return True except Exception as e: system_log.warn(_('rqdatac init failed, some apis will not function properly: {}').format(str(e))) @@ -132,23 +131,25 @@ def run(config, source_code=None, user_funcs=None): persist_helper = None init_succeed = False mod_handler = ModHandler() - update_parameters_end_date = None try: # avoid register handlers everytime # when running in ipython set_loggers(config) - rqdatac_enable = init_rqdatac(getattr(config.base, 'rqdatac_uri', None)) + init_rqdatac(getattr(config.base, 'rqdatac_uri', None)) system_log.debug("\n" + pformat(config.convert_to_dict())) env.set_strategy_loader(init_strategy_loader(env, source_code, user_funcs, config)) mod_handler.set_env(env) mod_handler.start_up() - if config.mod.sys_transaction_cost.time_series_trading_parameters and "FUTURE" in config.base.accounts and rqdatac_enable: - update_parameters_end_date = config.base.end_date if not env.data_source: - env.set_data_source(BaseDataSource(config.base.data_bundle_path, getattr(config.base, "future_info", {}), update_parameters_end_date)) + env.set_data_source(BaseDataSource( + config.base.data_bundle_path, + getattr(config.base, "future_info", {}), + const.DEFAULT_ACCOUNT_TYPE.FUTURE in config.base.accounts and config.base.futures_time_series_trading_parameters, + config.base.end_date + )) if env.price_board is None: from rqalpha.data.bar_dict_price_board import BarDictPriceBoard env.price_board = BarDictPriceBoard() diff --git a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py index 82cc38994..718047f00 100644 --- a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py +++ b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py @@ -26,8 +26,6 @@ "futures_commission_multiplier": 1, # 印花倍率,即在默认的印花税基础上按该倍数进行调整,股票默认印花税为千分之一,单边收取 "tax_multiplier": 1, - # 是否开启期货历史交易参数进行回测,默认为True - "time_series_trading_parameters": True, # 是否使用回测当时时间点对应的真实印花税率 "pit_tax": False, } diff --git a/rqalpha/utils/exception.py b/rqalpha/utils/exception.py index d4b6dfb15..a80c6d3c0 100644 --- a/rqalpha/utils/exception.py +++ b/rqalpha/utils/exception.py @@ -131,3 +131,7 @@ class RQTypeError(RQUserError): class RQApiNotSupportedError(RQUserError): pass + +class RQDatacVersionTooLow(RuntimeError): + pass + diff --git a/rqalpha/utils/testing/fixtures.py b/rqalpha/utils/testing/fixtures.py index 0c4124ca6..a818bc733 100644 --- a/rqalpha/utils/testing/fixtures.py +++ b/rqalpha/utils/testing/fixtures.py @@ -83,7 +83,7 @@ def init_fixture(self): super(BaseDataSourceFixture, self).init_fixture() default_bundle_path = os.path.abspath(os.path.expanduser('~/.rqalpha/bundle')) - self.base_data_source = BaseDataSource(default_bundle_path, {}, {}) + self.base_data_source = BaseDataSource(default_bundle_path, {}) class BarDictPriceBoardFixture(EnvironmentFixture): diff --git a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo index f4493b300277f98d52a561424f911cef46b74507..1423c4d328032000e4b5b404edf433231e22fd51 100644 GIT binary patch delta 2722 zcmYM#e@vBC9LMpmiXwRV8NwAoL6Jf)hJq3VauE^1K#>ef%vfn^k)>uj)a!LrtSwCv z7se)TNSkf7WDEPDG|Q=InAsfKG-|G8Zf-U!rT*ys+4F4OUGD3g^E~&Q@AEz1b7^lI zwtxSy!1dUWR^!#lUlM->Bh~x=RTu7DTY>2e4R$L8S8`!wq?3tNuDj63_(7b5=TQrc;qew?5>CZ6I1As! zG(3xicpJ5#k1r|EJXBzQ^s>G^Mxzo}VLWytu~;9*-~dj*-%+UxV=xCJ(T^oqflaso zFW__NjWsLA?WjO|u^h)d;PjVbK&d`NqYLljbnJ*Tn~xVT7vsm8EyF6*#2?`E=povD zxC!@QSc3DtHY}h&h|4jV{4=#}MUrQ4<8EyCl79_M8|S1ZA4BOE<7h0$O}GS=!qceC z{D3+53#tec63wVj%SNSm4l2c^?szpSvvsH%+KB9mwI-5(vSo)E&;mWENWVd{Z3CzU zZo2)EbQC~5D$q34Ui&bJ)%XZr!c6pVr0OvrAH{Z5O$?wOAU=@n6wN|h#6S=g*+pbc z`x%+b{>6OsQV6%#z$y447U4xy4aH369$bdXKnu>oe&kpOEQDG0 zWVxtX*nq1sh|};lRQ1L&ySC^V)O`)e8nz7;z(Fj*YxoJKraN9jW%Bh0ogc6coI(FI zhU@&_r$Iql$U`iG$(VvOk*r#c>ju=5>_uhf9QtrH>3eS&PlZkN`}r|YU^PUq3~WZW z+m2%xUPW!q4fL?S4Y>oM%%;6fMvkgw;wbbZNw6|hfG@h^K`f`=j7shI$SzqVl_#^% zgG+ENu5|ln-RqYzpyIkmLkq-Bcg}wvYJ3eUpe9tr`%n`e!A9&sPM~E!?A%|8nrAJ} z!QH6)zeW8(T}6^(Q?dvS7iW=w3e$r0^o2vH)D5B*9Kl9uf^n#=NXHs1#b|89C_Li& zIVzw&ROW7=j@d2L{3%o%G1yAiJu}I_QrF9XZurN&kWXpr#&tLs4`4lB!|hnateW^Z zAF7EzP^EW7f5TOo&BDSrhku?rR0T~xrK>|YY5<1#G42yDkKcm!ADhs*|WPhPdygXeZAmB)PKg~EMEWs delta 3054 zcmZA3dr(wW9Ki8I;)_(!1bO(lpn(uRd5My|guq7vYWQGiR#;>eSqFB-$(mi$9vb+> zCu%a{t04q8A7iFdPGiTfm??J`oXRE}$!wgyzwVu>iCOn^&e?m<`Q6|7ojY#Z z=CgaJkK^tD->r(j1N@!ugVkMq?qyV;0upc)Wt)*qv2qswgbNc z!4;T*`*8%`#Fz0I%7Q7}B!T9l1eS-vtgnh_%s@K^;dvw`bqo7r2M)zQQBoJeU_1`R zG@OM~u^w~q9+u+B0ZOG~9ZH~=F&D#L(EDa|NUGat9K_xONk6t?Hr~S|j0{w2E*7Iq zd=eMoV4|(ZHMjvo2I==5!4&$x;sP8+{#in;MUtoX;CgHhCjT-pDMU}rRP0GVAA8|E zwBbUO6y89|%zcc<-%yG$DpV=zQ)Qy0_*Ik?&oRbLD4DH5siD=#uBZc{L4av6ZKw034(GR2}0Yst%8jrHqDflIta1};TKojr?j=~?%gaJc%Sgb;+jTQ$D zd6J({iYJ_no{5Dh5gtO;Rwt3U)fJqGzZkzq4^wJB{bZEWQjd%AmNA~CQPA}FAw%i} z%5~Q<9UX(i^?_oHW}pdQ!Apig98%fKV$8q>lqbH6lHzW|m5M`ulqbr>7jYG);vtk; z@?tWM=4Q!2Ir6{`)kH(i_brqs>&x9z6FFFhh4>PlMXBNkC|i{nsb80g#G(pN0Gth_QGV zcleN%7)}KJCZd+WqWLi!rz2&cwqS2;#X#0qr)l)Tb{W7sD0}}1IkKu771RTRkR+)g zC;?^|QXW#F2k#m?>dN-By$f zzC{@i;4U!@GjJK2(1mYdASFywZ9%Dtizs{jT%vx8axtI&CR~9(qHK9l5)X;D9W>-c zF`F-`?mC=$Fsl?x7CLbzN+#Nk@jK{G-)oF_;TrnB93>fFi*o%=jK+f~Mc9rq zuj6ML188)i+~7wV%WxVw!3jF2SpqhNu@kO z<6Qh2`(i|zz7=D!Og7^PjrSCLozDMqa0kkh^kB6J9EQc1fv;gBPRBoxkILe0BX7+2 zQ68Wj)6j2$uYN#BWlR6Ac}O$AX=d02q*X4{Wr z(2H_R2eG<5SvD5pY?QrjMyaXmC|ehiqnm(|nH+R9&@j`mqff3<$FRmQhQlhExPwvy zkCA#+!OSWP=HU=5#!!6Ia68KN$8Z>4Lk@=e9VK%ye2u`G>B_ah?`eReAb*Oq})<%vsPJ3OcmNv zQ`I6%rDm!u)v8Q3b9L-L%`(dhbE#G;bJtiZDzt@WxxZ8^vsP)`RbsC&*_b!I+HaoK z&Pb(JVy(29N^DwzrNo?XHxUeLGGJNGtp5pRqBb@uAz{?`#Dv7;K|e}Q^zL<-5XANjx~CAul4L%-MMq)mURkqpd zPPSuwxgxd+p^uy^Y&r)hNB&;>bi?D}79lP>uG4NEM|yZ`_I diff --git a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po index 95de05ac6..d6907510e 100644 --- a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po +++ b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-01-12 13:38+0800\n" +"POT-Creation-Date: 2024-01-17 14:36+0800\n" "PO-Revision-Date: 2016-10-24 21:20+0800\n" "Last-Translator: FULL NAME \n" "Language: zh_Hans_CN\n" @@ -40,21 +40,21 @@ msgid "" "persist" msgstr "Persist provider 未加载,加载 Persist provider 方可使用 persist 功能。" -#: rqalpha/main.py:127 +#: rqalpha/main.py:126 msgid "rqdatac init failed, some apis will not function properly: {}" msgstr "" "rqdatac 初始化失败,部分扩展 API 可能无法使用,错误信息:{}。您可以访问 " "https://www.ricequant.com/welcome/rqdata 获取 rqdatac" -#: rqalpha/main.py:218 +#: rqalpha/main.py:219 msgid "system restored" msgstr "" -#: rqalpha/main.py:248 +#: rqalpha/main.py:249 msgid "strategy run successfully, normal exit" msgstr "策略运行成功,正常退出" -#: rqalpha/main.py:253 +#: rqalpha/main.py:254 msgid "strategy execute exception" msgstr "策略运行产生异常" @@ -263,32 +263,38 @@ msgstr "策略暂停中,取消执行 {}({}, {})" msgid "deprecated parameter[bar_dict] in before_trading function." msgstr "[Deprecated]在before_trading函数中,第二个参数bar_dict已经不再使用了。" -#: rqalpha/data/bundle.py:483 +#: rqalpha/data/bundle.py:452 +msgid "" +"RQAlpha already supports backtesting using futures historical margins and" +" rates, please upgrade RQDatac to version 2.11.12 and above to use it" +msgstr "RQAlpha 已支持使用期货历史保证金和费率进行回测,请将 RQDatac 升级至 2.11.12 及以上版本进行使用" + +#: rqalpha/data/bundle.py:488 msgid "" "File {} update failed, if it is using, please update later, or you can " "delete then update again" msgstr "{} 文件更新失败,如果其正在使用中,请稍后再进行更新,或者您可以将其删除后再重新更新" -#: rqalpha/data/bundle.py:561 +#: rqalpha/data/base_data_source/data_source.py:140 msgid "" -"RQAlpha already supports backtesting using futures historical margins and" -" rates, please upgrade RQDatac to version 2.11.12 and above to use it" -msgstr "RQAlpha 已支持使用期货历史保证金和费率进行回测,请将 RQDatac 升级至 2.11.12 及以上版本进行使用" +"RQDatac is not installed, " +"\"config.base.futures_time_series_trading_parameters\" will be disabled." +msgstr "" -#: rqalpha/data/bundle.py:567 +#: rqalpha/data/base_data_source/data_source.py:145 msgid "" -"Your RQData account does not have permission to use futures historical " -"margin and rates, and fixed data will be used for calculations\n" -"You can contact RiceQuant to activate permission: 0755-26569969" +"RQDatac does not have permission to obtain futures histrical trading " +"parameters, \"config.base.futures_time_series_trading_parameters\" will " +"be disabled." msgstr "" -"您的 RQData 账号没有权限使用期货历史保证金和费率,将使用固定的数据进行回测和计算\n" -"您可联系米筐科技开通相关权限:0755-26569969" #: rqalpha/data/base_data_source/storages.py:82 msgid "" "Your bundle data is too old, please use 'rqalpha update-bundle' or " "'rqalpha download-bundle' to update it to lastest before using" -msgstr "您的 Bundle 数据过旧,请使用 'rqalpha update-bundle' 或 'rqalpha download-bundle' 将其更新至最新后再进行使用" +msgstr "" +"您的 Bundle 数据过旧,请使用 'rqalpha update-bundle' 或 'rqalpha download-bundle' " +"将其更新至最新后再进行使用" #: rqalpha/data/base_data_source/storages.py:98 #: rqalpha/data/base_data_source/storages.py:124 @@ -324,7 +330,7 @@ msgstr "" msgid "mod tear_down [END] {}" msgstr "" -#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:318 +#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:314 msgid "{order_book_id} is expired, close all positions by system" msgstr "{order_book_id} 已退市/交割,系统自动平仓" @@ -1546,3 +1552,13 @@ msgstr "不支持 API {}。请确保您设置了该 API 所需的账户并开启 #~ msgid "invalid price of {order_book_id}: {price}" #~ msgstr "{order_book_id} 的价格不合法:{price}" +#~ msgid "" +#~ "Your RQData account does not have " +#~ "permission to use futures historical " +#~ "margin and rates, and fixed data " +#~ "will be used for calculations\n" +#~ "You can contact RiceQuant to activate permission: 0755-26569969" +#~ msgstr "" +#~ "您的 RQData 账号没有权限使用期货历史保证金和费率,将使用固定的数据进行回测和计算\n" +#~ "您可联系米筐科技开通相关权限:0755-26569969" + diff --git a/tests/test_f_buy_and_hold.py b/tests/test_f_buy_and_hold.py index ab23e5909..53de96472 100644 --- a/tests/test_f_buy_and_hold.py +++ b/tests/test_f_buy_and_hold.py @@ -26,9 +26,6 @@ def handle_bar(context, bar_dict): "sys_progress": { "enabled": True, "show": True, - }, - "sys_transaction_cost": { - 'time_series_trading_parameters': False - }, + } }, } diff --git a/tests/test_f_mean_reverting.py b/tests/test_f_mean_reverting.py index 43654f5eb..e6bb1403f 100644 --- a/tests/test_f_mean_reverting.py +++ b/tests/test_f_mean_reverting.py @@ -138,9 +138,6 @@ def handle_bar(context, bar_dict): "sys_progress": { "enabled": True, "show": True, - }, - 'sys_transaction_cost': { - 'time_series_trading_parameters': False - }, + } }, } From 2605075fb7abee50a0cd24c25b904e206072b62c Mon Sep 17 00:00:00 2001 From: "Don.Lin" <142398161+Lin-Dongzhao@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:31:03 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20(#841)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * modify .readthedocs.yml * bug fix * bug fix * refactor * pr update * 翻译更新 * 新增翻译 --------- Co-authored-by: 周嘉俊 <35399214+Zhou-JiaJun@users.noreply.github.com> Co-authored-by: Cuizi7 Co-authored-by: 周嘉俊 <654181984@qq.com> Co-authored-by: cuizi7 --- .../zh_Hans_CN/LC_MESSAGES/messages.mo | Bin 19222 -> 19717 bytes .../zh_Hans_CN/LC_MESSAGES/messages.po | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo index 1423c4d328032000e4b5b404edf433231e22fd51..b36936474b4b8dee23113ca292d35901dc1bc9c4 100644 GIT binary patch delta 3196 zcmb`{YfzL`7{Kv|#wZX0QBnl-6>p&kUQvvj1|*7kD{qy!$fAqOV!KP?q_cT}Y%sAD zFXUx3OGigj(J@KW#>%M=j-!qFC1b<|R&5x^876WZ`#-*K)2BW-6SM5^ob#T`bDneF z-L@^Lsy@hfE2>MK@b@MEar_rNueE>wR)vZjpt>3dV?tMvH!vS#aS!T{M(l3j>6m6AEQ}?qKw10F$aTjpVfW{ zb-~lfTJj_I#%st&?(wDLJ28^=B{WAG8`r5wQ*_f-l|Q$80p9;F4->H!U&HRar0RT3*XKA1Z=vqA{{Sgp;zUT3b(w)sfy$dy1g7r8Ig9e+ocR1=1twiltjl?8%r~!P9 zuVM$DzzIW4@1tgN&&&FQ<`X$fVI&PrsFx;+?Q=mT9(!Uc8aNfnzBnywPx6g zWXU4b0IRI_dd#Q35A|~0LrzhKB%9`=&c6&DScyJuxNUW4N4;F#Q_Ka1BTX_3wS5gn z;BM3akD`vhgzw{3(+ux2y{{Rp(C|P5ud5Drry;p{DMp z)uAW7wSBhbI-Eqk0d3fUt8v~~bK)DQccN>mdDgk8Z_h@YiN9ba4(4Z7kAAO@!U_ug z_?6%z+xgPp>Z|xB#xa}j+>If)0YAV`aTWI9RhfsIQ8N=j?bm^t3EhphN1|TFc+~d& zR=e*cg+v-Ipx(*=>IA`D=_rgu9WVfOqS2_S&qlpmg{b}3SZ+n_f6%I*!QRw=##n5& zovE4QjtI zR!hMdsITh>*bTqJxp*G+h@&T)cPk4uGd1Yb!X65mniKdLUcm}1&F7WECd)0nw3>-L zZs^mqM-6xl>Vju69xr1b^jm6rw0|u2!wlp_k#`EXDw?`2d=12=f`$d5*TQ`>b8KGQ zVxzd+?lD~DUSp~4J-e~o?k;n9JdSdg;Vn1Hi@Y|6%P6VvR=DZ4)Zy{E9gA&F!|S#c zJ6ubQRJaVA(J(URr+&VF7Hd7%=s*5R z+om)A+HEbn_O_im=-*WBuRVFE@j%OtE&kfIEp^9R>({sJUKiN8x&7Qm|M6;nRYPF= n(LmM5t>=$D>*k+^CM~<>9QGgksO|GA!;0g7nz^BKe0fpgq!5?BnT8vjGe~J9%j#cmf*UAXz+A2(Ccyy%MLzsqBupaf5HXM%~7>@7Z1U!b( zc-rk>#t{0$INdC0*J*edh~YyMO~o2a!6Mv&8F&Jd@lWJ!@vOEIQ*azMpvId}3$`Fr zTNirqDDt+meCWQ*7|r_ji+ka3T+M~CQBEe-xb8+jG^NTVFrU>x=!u~z^FTaFc|iQmDe&_lEb za0~9k@ObBYt(Z%H2v?ww{4=#}Ly~8&<6dm{l79`%n(U+|2Se!>U>KI*7F>!-;VD#R zzQZj18C8Vw31-x%Wuj7?k4kZoJ6?s#>`GJ(ZANy*S`x@V*|H-HXn|f-q+cP~wn5Ya z!)|{p9R(1F3N!_^*M4lkD!d;rVmf*_Qni?a4`4g0CI(Rt5Et}0MN@`L7-&F6b^%$_ zenRH5e=!HW6oRf-Wl>Xdwj8!%*=GrkGgCF@7tHpGYSvm~b`7NPpVCK_op2Cx`! zxdzBnF8wB4fG1H;8pfejYSS?jvoQ}h;!J!O=i>!b4aLmh9$b#fKr_z80pwT*EreP1 zWb;t9P={-=0cYW_sOp`_?AoHoQTJ^`*03F@01jaxUd0bEIo0tZDwBKebbi1(@Lu|- zFhb}5HVq2WLhfP_^kEWaAX&9)*E-ab>_=thEc!8w^u0BTr@}`11N;~$uxg@L23|zA z+m2&6UO{cmHT1B)jkp7$%%;8dAxG8HaU2GaBv>&jz-Qd?1}vf9gi7tV$SzqFl_%$* z2bbb{TJMJiTf5k_MxPQY&0 zk5K{jqcV35b_!5qr=lo982WgZs(2L7(48Ie1w7b5K%FGFT z8-K(Xa63;CJWAsR4cWQa$wW Date: Fri, 19 Jan 2024 18:06:45 +0800 Subject: [PATCH 6/8] Rqsdk 710 (#844) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * modify .readthedocs.yml * bug fix * bug fix * refactor * pr update * 翻译更新 * 新增翻译 * 修复自定义info未生效问题,未使用连续合约问题 * 期货历史交易参数bundle数据增加期货连续合约数据 --------- Co-authored-by: 周嘉俊 <35399214+Zhou-JiaJun@users.noreply.github.com> Co-authored-by: Cuizi7 Co-authored-by: 周嘉俊 <654181984@qq.com> Co-authored-by: cuizi7 --- messages.pot | 26 ++++--- rqalpha/data/base_data_source/data_source.py | 2 +- rqalpha/data/base_data_source/storages.py | 22 +++++- rqalpha/data/bundle.py | 65 +++++++++++++++++- .../zh_Hans_CN/LC_MESSAGES/messages.mo | Bin 19717 -> 19867 bytes .../zh_Hans_CN/LC_MESSAGES/messages.po | 26 ++++--- tests/api_tests/test_config.py | 2 +- 7 files changed, 117 insertions(+), 26 deletions(-) diff --git a/messages.pot b/messages.pot index 45e919284..cf814f535 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-01-17 14:36+0800\n" +"POT-Creation-Date: 2024-01-19 17:56+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -247,13 +247,19 @@ msgstr "" msgid "deprecated parameter[bar_dict] in before_trading function." msgstr "" -#: rqalpha/data/bundle.py:452 +#: rqalpha/data/bundle.py:454 msgid "" "RQAlpha already supports backtesting using futures historical margins and" " rates, please upgrade RQDatac to version 2.11.12 and above to use it" msgstr "" -#: rqalpha/data/bundle.py:488 +#: rqalpha/data/bundle.py:464 rqalpha/data/bundle.py:522 +msgid "" +"Futures historical trading parameters data is being updated, please " +"wait......" +msgstr "" + +#: rqalpha/data/bundle.py:516 msgid "" "File {} update failed, if it is using, please update later, or you can " "delete then update again" @@ -263,28 +269,28 @@ msgstr "" msgid "" "RQDatac is not installed, " "\"config.base.futures_time_series_trading_parameters\" will be disabled." -msgstr "未安装 RQDatac,将关闭配置 \"config.base.futures_time_series_trading_parameters\"" +msgstr "" #: rqalpha/data/base_data_source/data_source.py:145 msgid "" "RQDatac does not have permission to obtain futures histrical trading " "parameters, \"config.base.futures_time_series_trading_parameters\" will " "be disabled." -msgstr "RQDatac 缺少获取期货历史交易参数的权限,将关闭配置 \"config.base.futures_time_series_trading_parameters\"" +msgstr "" -#: rqalpha/data/base_data_source/storages.py:82 +#: rqalpha/data/base_data_source/storages.py:81 msgid "" "Your bundle data is too old, please use 'rqalpha update-bundle' or " "'rqalpha download-bundle' to update it to lastest before using" msgstr "" -#: rqalpha/data/base_data_source/storages.py:98 -#: rqalpha/data/base_data_source/storages.py:124 +#: rqalpha/data/base_data_source/storages.py:97 +#: rqalpha/data/base_data_source/storages.py:123 msgid "unsupported future instrument {}" msgstr "" -#: rqalpha/data/base_data_source/storages.py:195 -#: rqalpha/data/base_data_source/storages.py:205 +#: rqalpha/data/base_data_source/storages.py:194 +#: rqalpha/data/base_data_source/storages.py:204 msgid "" "open data bundle failed, you can remove {} and try to regenerate bundle: " "{}" diff --git a/rqalpha/data/base_data_source/data_source.py b/rqalpha/data/base_data_source/data_source.py index 60a79051f..b5118ee51 100644 --- a/rqalpha/data/base_data_source/data_source.py +++ b/rqalpha/data/base_data_source/data_source.py @@ -145,7 +145,7 @@ def _p(name): user_system_log.warn(_("RQDatac does not have permission to obtain futures histrical trading parameters, \"config.base.futures_time_series_trading_parameters\" will be disabled.")) else: file = os.path.join(path, "futures_trading_parameters.h5") - self._futures_trading_parameters_store = FuturesTradingParametersStore(file) + self._futures_trading_parameters_store = FuturesTradingParametersStore(file, custom_future_info) def register_day_bar_store(self, instrument_type, store): # type: (INSTRUMENT_TYPE, AbstractDayBarStore) -> None diff --git a/rqalpha/data/base_data_source/storages.py b/rqalpha/data/base_data_source/storages.py index c28a4ddb9..2998c04e4 100644 --- a/rqalpha/data/base_data_source/storages.py +++ b/rqalpha/data/base_data_source/storages.py @@ -35,7 +35,6 @@ from rqalpha.utils.datetime_func import convert_date_to_date_int from rqalpha.utils.i18n import gettext as _ from rqalpha.utils.logger import user_system_log -from rqalpha.environment import Environment from .storage_interface import (AbstractCalendarStore, AbstractDateSet, AbstractDayBarStore, AbstractDividendStore, @@ -256,15 +255,17 @@ class FuturesTradingParametersStore(object): # 历史期货交易参数的数据在2010年4月之后才有 FUTURES_TRADING_PARAMETERS_START_DATE = 20100401 - def __init__(self, path): + def __init__(self, path, custom_future_info): self._path = path + self._custom_data = custom_future_info def get_futures_trading_parameters(self, instrument, dt): # type: (Instrument, datetime.date) -> FuturesTradingParameters or None - order_book_id = instrument.order_book_id dt = convert_date_to_date_int(dt) if dt < self.FUTURES_TRADING_PARAMETERS_START_DATE: return None + order_book_id = instrument.order_book_id + underlying_symbol = instrument.underlying_symbol data = self.get_futures_trading_parameters_all_time(order_book_id) if data is None: return None @@ -274,6 +275,9 @@ def get_futures_trading_parameters(self, instrument, dt): if dt >= convert_date_to_date_int(instrument.listed_date) and dt <= convert_date_to_date_int(instrument.de_listed_date): user_system_log.info("Historical futures trading parameters are abnormal, the lastst parameters will be used for calculations.\nPlease contract RiceQuant to repair: 0755-26569969") return None + custom_info = self._custom_data.get(order_book_id) or self._custom_data.get(underlying_symbol) + if custom_info: + arr[0] = self.set_custom_info(arr[0], custom_info) futures_trading_parameters = self._to_namedtuple(arr[0]) return futures_trading_parameters @@ -286,6 +290,18 @@ def get_futures_trading_parameters_all_time(self, order_book_id): except KeyError: return None return data + + def set_custom_info(self, arr, custom_info): + for field in custom_info: + if field == "commission_type": + if custom_info[field] == COMMISSION_TYPE.BY_MONEY: + value = 0 + elif custom_info[field] == COMMISSION_TYPE.BY_VOLUME: + value = 1 + else: + value = custom_info[field] + arr[field] = value + return arr def _to_namedtuple(self, arr): # type: (numpy.void) -> FuturesTradingParameters diff --git a/rqalpha/data/bundle.py b/rqalpha/data/bundle.py index a8d1cc245..cacc09176 100644 --- a/rqalpha/data/bundle.py +++ b/rqalpha/data/bundle.py @@ -28,6 +28,7 @@ convert_date_to_int) from rqalpha.utils.exception import RQDatacVersionTooLow from rqalpha.utils.i18n import gettext as _ +from rqalpha.utils.logger import system_log, user_system_log START_DATE = 20050104 END_DATE = 29991231 @@ -444,8 +445,9 @@ def update_bundle(path, create, enable_compression=False, concurrency=1): class FuturesTradingParametersTask(object): - def __init__(self, order_book_ids): + def __init__(self, order_book_ids, underlying_symbols): self._order_book_ids = order_book_ids + self._underlying_symbols = underlying_symbols def __call__(self, path, fields, end_date): if rqdatac.__version__ < '2.11.12': @@ -458,6 +460,8 @@ def __call__(self, path, fields, end_date): def generate_futures_trading_parameters(self, path, fields, end_date, recreate_futures_list=None): # type: (str, list, datetime.date, list) -> None + if not recreate_futures_list: + system_log.info(_("Futures historical trading parameters data is being updated, please wait......")) order_book_ids = self._order_book_ids if recreate_futures_list: order_book_ids = recreate_futures_list @@ -478,6 +482,30 @@ def generate_futures_trading_parameters(self, path, fields, end_date, recreate_f with h5py.File(path, "w") as h5: for order_book_id in df.index.levels[0]: h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records()) + # 更新期货连续合约的历史交易参数数据(当函数执行目的为补充上次未正常更新的数据时,不需要执行此段逻辑) + if recreate_futures_list is None: + with h5py.File(path, "a") as h5: + df = rqdatac.all_instruments("Future") + for underlying_symbol in self._underlying_symbols: + futures_continuous_contract = df[(df['underlying_symbol'] == underlying_symbol) & (df["listed_date"] == '0000-00-00')].order_book_id.tolist() + s = rqdatac.futures.get_dominant(underlying_symbol, TRADING_PARAMETERS_START_DATE, end_date) + if (s is None or s.empty): + continue + s = s.to_frame().reset_index() + s['date'] = s['date'].map(convert_date_to_date_int) + s.set_index(['date'], inplace=True) + trading_parameters_list = [] + for date in s.index: + try: + data = h5[s['dominant'][date]][:] + except KeyError: + continue + trading_parameters = data[data['datetime'] == date] + if len(trading_parameters) != 0: + trading_parameters_list.append(trading_parameters[0]) + data = np.array(trading_parameters_list) + for order_book_id in futures_continuous_contract: + h5.create_dataset(order_book_id, data=data) def update_futures_trading_parameters(self, path, fields, end_date): # type: (str, list, datetime.date) -> None @@ -491,6 +519,7 @@ def update_futures_trading_parameters(self, path, fields, end_date): if recreate_futures_list: self.generate_futures_trading_parameters(path, fields, last_date, recreate_futures_list=recreate_futures_list) if end_date > last_date: + system_log.info(_("Futures historical trading parameters data is being updated, please wait......")) if rqdatac.get_previous_trading_date(end_date) == last_date: return else: @@ -519,6 +548,37 @@ def update_futures_trading_parameters(self, path, fields, end_date): h5.create_dataset(order_book_id, data=data) else: h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records()) + # 更新期货连续合约历史交易参数 + with h5py.File(path, "a") as h5: + df = rqdatac.all_instruments("Future") + for underlying_symbol in self._underlying_symbols: + futures_continuous_contract = df[(df['underlying_symbol'] == underlying_symbol) & (df["listed_date"] == '0000-00-00')].order_book_id.tolist() + s = rqdatac.futures.get_dominant(underlying_symbol, start_date, end_date) + if (s is None or s.empty): + continue + s = s.to_frame().reset_index() + s['date'] = s['date'].map(convert_date_to_date_int) + s.set_index(['date'], inplace=True) + trading_parameters_list = [] + for date in s.index: + try: + data = h5[s['dominant'][date]][:] + except KeyError: + continue + trading_parameters = data[data['datetime'] == date] + if len(trading_parameters) != 0: + trading_parameters_list.append(trading_parameters[0]) + for order_book_id in futures_continuous_contract: + print(order_book_id) + if order_book_id in h5: + data = np.array( + [tuple(i) for i in chain(h5[order_book_id][:], trading_parameters_list)], + dtype=h5[order_book_id].dtype + ) + del h5[order_book_id] + h5.create_dataset(order_book_id, data=data) + else: + h5.create_dataset(order_book_id, data=np.array(trading_parameters)) def set_commission_type(self, commission_type): if commission_type == "by_money": @@ -559,7 +619,8 @@ def update_futures_trading_parameters(path, end_date): # type: (str, datetime.date) -> None df = rqdatac.all_instruments("Future") order_book_ids = (df[df['de_listed_date'] >= str(TRADING_PARAMETERS_START_DATE)]).order_book_id.tolist() - FuturesTradingParametersTask(order_book_ids)( + underlying_symbols = list(set((df[df['de_listed_date'] >= str(TRADING_PARAMETERS_START_DATE)]).underlying_symbol.tolist())) + FuturesTradingParametersTask(order_book_ids, underlying_symbols)( os.path.join(path, FUTURES_TRADING_PARAMETERS_FILE), FUTURES_TRADING_PARAMETERS_FIELDS, end_date diff --git a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo index b36936474b4b8dee23113ca292d35901dc1bc9c4..54398e717f8d8ec119e84c69c708a421e940cb6a 100644 GIT binary patch delta 2886 zcmYM$3rv+|9LMoL^O7iDA{7wCBPI%7P!#Xnyr6&}rslQu6h}LzMsV7jL#ANV)&cgPPP9(s0*cq>2DBi-Zco)0j zBc~o3X4aN^6vmnPEtUe8W%8vL<>OqOjZ^RwOvc}_FZO2?iZ&G&;cRS=bxwODYQXcz zn05mr@n__+zxmShpT6{>oC z3QuDP{2G0D2^G?GGErMN9Y^8<$XlyLz2`GjPJHRKUyRoN({POjCD*UW_E~2V zfM{B8)Bs7SnNL7=#|n_H=632EP!p&?O|%-d_XqJ!yoh5kx3}34+=Ond_fr^8p*tI^ zBq>0xpaN6y8=QfUQ4`DVW5%LwK57C!9EUqm_nUA5-b9_AQ7*IPScYo9gY2G7WVLiL z|2zunxE3?T`BU98S&{|UQNe}bBD*s$OW z<8VFoB;+JpEsnzTsP{cVy)Tk5r(x=F;;)&M(U61NkZ9R8q_6#p%Fptw?fFgAX^P|LbS5tMQ&>yk47TP1zY=Sxe~Mf- zgs1fry%gtQJ@&v391Z16EN;YsScV_tJd9`7ii{uCuN)PL-A?;q+@$*r&W$k~YCSLw z6L1d3r~~Q+)fkS4Q7<};>VFXx`WvVu`vcW4g0m+RQTbfAzf|qMJewl(=5; zmiS&P_P*?1;qn!`3%#qBxr*Gy?v);&r?|vb==Ql?-V)bK9^EJ{qV6di>?&H}ahG^p zYusMnP<C_8MztI}&|F>9Qgy0j_sN#(5AL2i(p<5*xw8KD m7YAGR?rN@lt0i#qw=?B;Pker-W=HeRiq_W*ZSV}+AM+n*=yo*# delta 2737 zcmYM!dra0<9LMo54JnYj5-1M>;*FbtAPR9cAkoJ(sk~Hx% zkT{&B%V|?`rCqdHx%G#0+01`3CY7Ny#@twB&fcGXzb*Fgyv{kl^E;RC`JSh@H=v^@ zz_}V7*lqlM$iI(&|0LD^|7!~|J4kga&cT_J%^t;SjK@CIE2l9G&tfoMzzDp8k@$yK zpTI!s!J&+0=2$odK9<9m1}ecdSb>%JJ}$s(n2a&ZLeUoClURX4*zdI;MooAUnajSz zIQ#+m*iF9l{scxczXgT64`Q*72eVO;=re};$2jJDEV@UMuE7SxRQ7iu$ zIRzU-K6VcYqWTQf0PvCAMQDF2)he#MudE8?Xi+z>^LICCM0S2U#4nl4v8Y z#2(bbZXlbpzflW_Ni-|O9Mtn>T#s$2Tk{h(VnUMJ-ie%=4Iz(g0@cr%#`eo8)S(*s zF%_@lLm0&+mFuub&v6M}MeQ^>#SLv0=2Bmc+R%2Kjo)A?-azG48ZQ;#3rGYUJ3>J_ zzK*)*3B0A9twQBO7dBuIrenYyH+wTtN3;diZ#%Lk>qaf$Gkg%o@i-RFbsa%PvTvS# z&>XW<6mn=7L?ul$+b2OS0jFX#SM} zYvy4U?nKhTE@LqB+bs$@n>!eaQ^*w6lTc@yk6c}=!XR9S1k37C3+(XPd$5{%KPtIy zBBy9`=et&;#&5z+*ouxCu6Z3sQOOmRxliAHWSbj$3i< zLU-WHsGOLb@1Auf>h|owNAO2%#X0<}>ge}76kef_$gcz++ryWBt1seX=wmeP{6!4L zPTYp?VjE83s;tF#P>~r%^&3Y;Lc39WBq|vbQ0)i2cIO0zR2t5pvT_(TKoBV%jghDq zQcwfsqC#JWO0GIozjn{vsQ!n%`llF2{d%+s0ELs#*Zm;Benp;bO>D(T6wS+8!)=u z>`mN)n=yb{w1ekyCU#;i9>K>kgkKpyZa|vs80yT&P#Y+ya2Na%R#E>1wcy(r%KVm9 z>1KZsk`A^RdDlL`l{k!itbn&U9dmFMevaxF%4}J<3Uyt#VHkdlYw%0d5yvcbbE^~; znJ#ox=%b*}9LHX~fGyZq&6UDI&s|(vMWTuf{qO8i3vNeEcnTBnJWj{oJQW`GkH\n" "Language: zh_Hans_CN\n" @@ -263,13 +263,19 @@ msgstr "策略暂停中,取消执行 {}({}, {})" msgid "deprecated parameter[bar_dict] in before_trading function." msgstr "[Deprecated]在before_trading函数中,第二个参数bar_dict已经不再使用了。" -#: rqalpha/data/bundle.py:452 +#: rqalpha/data/bundle.py:454 msgid "" "RQAlpha already supports backtesting using futures historical margins and" " rates, please upgrade RQDatac to version 2.11.12 and above to use it" msgstr "RQAlpha 已支持使用期货历史保证金和费率进行回测,请将 RQDatac 升级至 2.11.12 及以上版本进行使用" -#: rqalpha/data/bundle.py:488 +#: rqalpha/data/bundle.py:464 rqalpha/data/bundle.py:522 +msgid "" +"Futures historical trading parameters data is being updated, please " +"wait......" +msgstr "正在更新期货历史交易参数,请稍后......" + +#: rqalpha/data/bundle.py:516 msgid "" "File {} update failed, if it is using, please update later, or you can " "delete then update again" @@ -286,9 +292,11 @@ msgid "" "RQDatac does not have permission to obtain futures histrical trading " "parameters, \"config.base.futures_time_series_trading_parameters\" will " "be disabled." -msgstr "RQDatac 缺少获取期货历史交易参数的权限,将关闭配置 \"config.base.futures_time_series_trading_parameters\"" +msgstr "" +"RQDatac 缺少获取期货历史交易参数的权限,将关闭配置 " +"\"config.base.futures_time_series_trading_parameters\"" -#: rqalpha/data/base_data_source/storages.py:82 +#: rqalpha/data/base_data_source/storages.py:81 msgid "" "Your bundle data is too old, please use 'rqalpha update-bundle' or " "'rqalpha download-bundle' to update it to lastest before using" @@ -296,13 +304,13 @@ msgstr "" "您的 Bundle 数据过旧,请使用 'rqalpha update-bundle' 或 'rqalpha download-bundle' " "将其更新至最新后再进行使用" -#: rqalpha/data/base_data_source/storages.py:98 -#: rqalpha/data/base_data_source/storages.py:124 +#: rqalpha/data/base_data_source/storages.py:97 +#: rqalpha/data/base_data_source/storages.py:123 msgid "unsupported future instrument {}" msgstr "不支持的期货标的{}" -#: rqalpha/data/base_data_source/storages.py:195 -#: rqalpha/data/base_data_source/storages.py:205 +#: rqalpha/data/base_data_source/storages.py:194 +#: rqalpha/data/base_data_source/storages.py:204 msgid "" "open data bundle failed, you can remove {} and try to regenerate bundle: " "{}" diff --git a/tests/api_tests/test_config.py b/tests/api_tests/test_config.py index e3e256a7a..2974f7ca6 100644 --- a/tests/api_tests/test_config.py +++ b/tests/api_tests/test_config.py @@ -50,7 +50,7 @@ def assert_almost_equal(first, second): assert round(abs(first - second), 10) == 0 -def test_future_info(): +def test_futures_info(): __config__ = { "base": { "future_info": { From bf88041cd89168c3e66a0f405d119cebd0fb5bc1 Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Mon, 22 Jan 2024 15:15:19 +0800 Subject: [PATCH 7/8] pr update --- rqalpha/data/bundle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rqalpha/data/bundle.py b/rqalpha/data/bundle.py index cacc09176..a9be5d559 100644 --- a/rqalpha/data/bundle.py +++ b/rqalpha/data/bundle.py @@ -569,7 +569,6 @@ def update_futures_trading_parameters(self, path, fields, end_date): if len(trading_parameters) != 0: trading_parameters_list.append(trading_parameters[0]) for order_book_id in futures_continuous_contract: - print(order_book_id) if order_book_id in h5: data = np.array( [tuple(i) for i in chain(h5[order_book_id][:], trading_parameters_list)], From 5d24b8432c11769c317a8e654b39e9854c670aff Mon Sep 17 00:00:00 2001 From: "Don.Lin" <142398161+Lin-Dongzhao@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:58:48 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E5=8D=B0=E8=8A=B1=E7=A8=8E=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6?= =?UTF-8?q?=20(#846)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/outs/test_s_pit_tax.pkl | Bin 0 -> 19508 bytes tests/test_s_pit_tax.py | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/outs/test_s_pit_tax.pkl create mode 100644 tests/test_s_pit_tax.py diff --git a/tests/outs/test_s_pit_tax.pkl b/tests/outs/test_s_pit_tax.pkl new file mode 100644 index 0000000000000000000000000000000000000000..8247c088d244ad0b50857c07554eeb2c0a59ab6e GIT binary patch literal 19508 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 1: + order_shares(context.s1, -1000) + + +def after_trading(context): + pass + + +__config__ = { + "base": { + "start_date": "2023-08-15", + "end_date": "2023-09-10", + "frequency": "1d", + "accounts": { + "stock": 1000000 + }, + }, + "extra": { + "log_level": "error", + "show": True, + }, + "mod": { + "sys_progress": { + "enabled": True, + "show": True, + }, + "sys_transaction_cost": { + "pit_tax": True + } + } +} \ No newline at end of file