Merge branch 'freqtrade:develop' into strategy_utils
This commit is contained in:
commit
209811d23a
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@ -16,7 +16,8 @@ on:
|
|||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
permissions:
|
||||||
|
repository-projects: read
|
||||||
jobs:
|
jobs:
|
||||||
build_linux:
|
build_linux:
|
||||||
|
|
||||||
@ -321,7 +322,6 @@ jobs:
|
|||||||
build_linux_online:
|
build_linux_online:
|
||||||
# Run pytest with "live" checks
|
# Run pytest with "live" checks
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
# permissions:
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
@ -425,7 +425,7 @@ jobs:
|
|||||||
python setup.py sdist bdist_wheel
|
python setup.py sdist bdist_wheel
|
||||||
|
|
||||||
- name: Publish to PyPI (Test)
|
- name: Publish to PyPI (Test)
|
||||||
uses: pypa/gh-action-pypi-publish@v1.6.4
|
uses: pypa/gh-action-pypi-publish@v1.7.1
|
||||||
if: (github.event_name == 'release')
|
if: (github.event_name == 'release')
|
||||||
with:
|
with:
|
||||||
user: __token__
|
user: __token__
|
||||||
@ -433,7 +433,7 @@ jobs:
|
|||||||
repository_url: https://test.pypi.org/legacy/
|
repository_url: https://test.pypi.org/legacy/
|
||||||
|
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@v1.6.4
|
uses: pypa/gh-action-pypi-publish@v1.7.1
|
||||||
if: (github.event_name == 'release')
|
if: (github.event_name == 'release')
|
||||||
with:
|
with:
|
||||||
user: __token__
|
user: __token__
|
||||||
@ -466,12 +466,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and test and push docker images
|
- name: Build and test and push docker images
|
||||||
env:
|
env:
|
||||||
IMAGE_NAME: freqtradeorg/freqtrade
|
|
||||||
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
|
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
|
||||||
run: |
|
run: |
|
||||||
build_helpers/publish_docker_multi.sh
|
build_helpers/publish_docker_multi.sh
|
||||||
|
|
||||||
deploy_arm:
|
deploy_arm:
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
needs: [ deploy ]
|
needs: [ deploy ]
|
||||||
# Only run on 64bit machines
|
# Only run on 64bit machines
|
||||||
runs-on: [self-hosted, linux, ARM64]
|
runs-on: [self-hosted, linux, ARM64]
|
||||||
@ -494,8 +495,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and test and push docker images
|
- name: Build and test and push docker images
|
||||||
env:
|
env:
|
||||||
IMAGE_NAME: freqtradeorg/freqtrade
|
|
||||||
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
|
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
|
||||||
|
GHCR_USERNAME: ${{ github.actor }}
|
||||||
|
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
build_helpers/publish_docker_arm64.sh
|
build_helpers/publish_docker_arm64.sh
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ repos:
|
|||||||
|
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: 'v0.0.251'
|
rev: 'v0.0.255'
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
# Use BuildKit, otherwise building on ARM fails
|
# Use BuildKit, otherwise building on ARM fails
|
||||||
export DOCKER_BUILDKIT=1
|
export DOCKER_BUILDKIT=1
|
||||||
|
|
||||||
|
IMAGE_NAME=freqtradeorg/freqtrade
|
||||||
|
CACHE_IMAGE=freqtradeorg/freqtrade_cache
|
||||||
|
GHCR_IMAGE_NAME=ghcr.io/freqtrade/freqtrade
|
||||||
|
|
||||||
# Replace / with _ to create a valid tag
|
# Replace / with _ to create a valid tag
|
||||||
TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g")
|
TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g")
|
||||||
TAG_PLOT=${TAG}_plot
|
TAG_PLOT=${TAG}_plot
|
||||||
@ -14,7 +18,6 @@ TAG_ARM=${TAG}_arm
|
|||||||
TAG_PLOT_ARM=${TAG_PLOT}_arm
|
TAG_PLOT_ARM=${TAG_PLOT}_arm
|
||||||
TAG_FREQAI_ARM=${TAG_FREQAI}_arm
|
TAG_FREQAI_ARM=${TAG_FREQAI}_arm
|
||||||
TAG_FREQAI_RL_ARM=${TAG_FREQAI_RL}_arm
|
TAG_FREQAI_RL_ARM=${TAG_FREQAI_RL}_arm
|
||||||
CACHE_IMAGE=freqtradeorg/freqtrade_cache
|
|
||||||
|
|
||||||
echo "Running for ${TAG}"
|
echo "Running for ${TAG}"
|
||||||
|
|
||||||
@ -38,13 +41,13 @@ if [ $? -ne 0 ]; then
|
|||||||
echo "failed building multiarch images"
|
echo "failed building multiarch images"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
# Tag image for upload and next build step
|
|
||||||
docker tag freqtrade:$TAG_ARM ${CACHE_IMAGE}:$TAG_ARM
|
|
||||||
|
|
||||||
docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_PLOT_ARM} -f docker/Dockerfile.plot .
|
docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_PLOT_ARM} -f docker/Dockerfile.plot .
|
||||||
docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_FREQAI_ARM} -f docker/Dockerfile.freqai .
|
docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_FREQAI_ARM} -f docker/Dockerfile.freqai .
|
||||||
docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_FREQAI_RL_ARM} -f docker/Dockerfile.freqai_rl .
|
docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_FREQAI_RL_ARM} -f docker/Dockerfile.freqai_rl .
|
||||||
|
|
||||||
|
# Tag image for upload and next build step
|
||||||
|
docker tag freqtrade:$TAG_ARM ${CACHE_IMAGE}:$TAG_ARM
|
||||||
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
||||||
docker tag freqtrade:$TAG_FREQAI_ARM ${CACHE_IMAGE}:$TAG_FREQAI_ARM
|
docker tag freqtrade:$TAG_FREQAI_ARM ${CACHE_IMAGE}:$TAG_FREQAI_ARM
|
||||||
docker tag freqtrade:$TAG_FREQAI_RL_ARM ${CACHE_IMAGE}:$TAG_FREQAI_RL_ARM
|
docker tag freqtrade:$TAG_FREQAI_RL_ARM ${CACHE_IMAGE}:$TAG_FREQAI_RL_ARM
|
||||||
@ -59,7 +62,6 @@ fi
|
|||||||
|
|
||||||
docker images
|
docker images
|
||||||
|
|
||||||
# docker push ${IMAGE_NAME}
|
|
||||||
docker push ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
docker push ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
||||||
docker push ${CACHE_IMAGE}:$TAG_FREQAI_ARM
|
docker push ${CACHE_IMAGE}:$TAG_FREQAI_ARM
|
||||||
docker push ${CACHE_IMAGE}:$TAG_FREQAI_RL_ARM
|
docker push ${CACHE_IMAGE}:$TAG_FREQAI_RL_ARM
|
||||||
@ -82,14 +84,30 @@ docker manifest push -p ${IMAGE_NAME}:${TAG_FREQAI}
|
|||||||
docker manifest create ${IMAGE_NAME}:${TAG_FREQAI_RL} ${CACHE_IMAGE}:${TAG_FREQAI_RL} ${CACHE_IMAGE}:${TAG_FREQAI_RL_ARM}
|
docker manifest create ${IMAGE_NAME}:${TAG_FREQAI_RL} ${CACHE_IMAGE}:${TAG_FREQAI_RL} ${CACHE_IMAGE}:${TAG_FREQAI_RL_ARM}
|
||||||
docker manifest push -p ${IMAGE_NAME}:${TAG_FREQAI_RL}
|
docker manifest push -p ${IMAGE_NAME}:${TAG_FREQAI_RL}
|
||||||
|
|
||||||
|
# copy images to ghcr.io
|
||||||
|
|
||||||
|
alias crane="docker run --rm -i -v $(pwd)/.crane:/home/nonroot/.docker/ gcr.io/go-containerregistry/crane"
|
||||||
|
mkdir .crane
|
||||||
|
chmod a+rwx .crane
|
||||||
|
|
||||||
|
echo "${GHCR_TOKEN}" | crane auth login ghcr.io -u "${GHCR_USERNAME}" --password-stdin
|
||||||
|
|
||||||
|
crane copy ${IMAGE_NAME}:${TAG} ${GHCR_IMAGE_NAME}:${TAG}
|
||||||
|
crane copy ${IMAGE_NAME}:${TAG_PLOT} ${GHCR_IMAGE_NAME}:${TAG_PLOT}
|
||||||
|
crane copy ${IMAGE_NAME}:${TAG_FREQAI} ${GHCR_IMAGE_NAME}:${TAG_FREQAI}
|
||||||
|
crane copy ${IMAGE_NAME}:${TAG_FREQAI_RL} ${GHCR_IMAGE_NAME}:${TAG_FREQAI_RL}
|
||||||
|
|
||||||
# Tag as latest for develop builds
|
# Tag as latest for develop builds
|
||||||
if [ "${TAG}" = "develop" ]; then
|
if [ "${TAG}" = "develop" ]; then
|
||||||
echo 'Tagging image as latest'
|
echo 'Tagging image as latest'
|
||||||
docker manifest create ${IMAGE_NAME}:latest ${CACHE_IMAGE}:${TAG_ARM} ${IMAGE_NAME}:${TAG_PI} ${CACHE_IMAGE}:${TAG}
|
docker manifest create ${IMAGE_NAME}:latest ${CACHE_IMAGE}:${TAG_ARM} ${IMAGE_NAME}:${TAG_PI} ${CACHE_IMAGE}:${TAG}
|
||||||
docker manifest push -p ${IMAGE_NAME}:latest
|
docker manifest push -p ${IMAGE_NAME}:latest
|
||||||
|
|
||||||
|
crane copy ${IMAGE_NAME}:latest ${GHCR_IMAGE_NAME}:latest
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker images
|
docker images
|
||||||
|
rm -rf .crane
|
||||||
|
|
||||||
# Cleanup old images from arm64 node.
|
# Cleanup old images from arm64 node.
|
||||||
docker image prune -a --force --filter "until=24h"
|
docker image prune -a --force --filter "until=24h"
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
# The below assumes a correctly setup docker buildx environment
|
# The below assumes a correctly setup docker buildx environment
|
||||||
|
|
||||||
|
IMAGE_NAME=freqtradeorg/freqtrade
|
||||||
|
CACHE_IMAGE=freqtradeorg/freqtrade_cache
|
||||||
# Replace / with _ to create a valid tag
|
# Replace / with _ to create a valid tag
|
||||||
TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g")
|
TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g")
|
||||||
TAG_PLOT=${TAG}_plot
|
TAG_PLOT=${TAG}_plot
|
||||||
@ -11,7 +13,6 @@ TAG_PI="${TAG}_pi"
|
|||||||
|
|
||||||
PI_PLATFORM="linux/arm/v7"
|
PI_PLATFORM="linux/arm/v7"
|
||||||
echo "Running for ${TAG}"
|
echo "Running for ${TAG}"
|
||||||
CACHE_IMAGE=freqtradeorg/freqtrade_cache
|
|
||||||
CACHE_TAG=${CACHE_IMAGE}:${TAG_PI}_cache
|
CACHE_TAG=${CACHE_IMAGE}:${TAG_PI}_cache
|
||||||
|
|
||||||
# Add commit and commit_message to docker container
|
# Add commit and commit_message to docker container
|
||||||
|
@ -248,13 +248,13 @@ FreqAI also provides a built in episodic summary logger called `self.tensorboard
|
|||||||
"""
|
"""
|
||||||
def calculate_reward(self, action: int) -> float:
|
def calculate_reward(self, action: int) -> float:
|
||||||
if not self._is_valid(action):
|
if not self._is_valid(action):
|
||||||
self.tensorboard_log("is_valid")
|
self.tensorboard_log("invalid")
|
||||||
return -2
|
return -2
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
The `self.tensorboard_log()` function is designed for tracking incremented objects only i.e. events, actions inside the training environment. If the event of interest is a float, the float can be passed as the second argument e.g. `self.tensorboard_log("float_metric1", 0.23)` would add 0.23 to `float_metric`. In this case you can also disable incrementing using `inc=False` parameter.
|
The `self.tensorboard_log()` function is designed for tracking incremented objects only i.e. events, actions inside the training environment. If the event of interest is a float, the float can be passed as the second argument e.g. `self.tensorboard_log("float_metric1", 0.23)`. In this case the metric values are not incremented.
|
||||||
|
|
||||||
### Choosing a base environment
|
### Choosing a base environment
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
markdown==3.3.7
|
markdown==3.3.7
|
||||||
mkdocs==1.4.2
|
mkdocs==1.4.2
|
||||||
mkdocs-material==9.1.1
|
mkdocs-material==9.1.2
|
||||||
mdx_truly_sane_lists==1.3
|
mdx_truly_sane_lists==1.3
|
||||||
pymdown-extensions==9.10
|
pymdown-extensions==9.10
|
||||||
jinja2==3.1.2
|
jinja2==3.1.2
|
||||||
|
@ -1041,10 +1041,9 @@ from datetime import timedelta, datetime, timezone
|
|||||||
# Within populate indicators (or populate_buy):
|
# Within populate indicators (or populate_buy):
|
||||||
if self.config['runmode'].value in ('live', 'dry_run'):
|
if self.config['runmode'].value in ('live', 'dry_run'):
|
||||||
# fetch closed trades for the last 2 days
|
# fetch closed trades for the last 2 days
|
||||||
trades = Trade.get_trades([Trade.pair == metadata['pair'],
|
trades = Trade.get_trades_proxy(
|
||||||
Trade.open_date > datetime.utcnow() - timedelta(days=2),
|
pair=metadata['pair'], is_open=False,
|
||||||
Trade.is_open.is_(False),
|
open_date=datetime.now(timezone.utc) - timedelta(days=2))
|
||||||
]).all()
|
|
||||||
# Analyze the conditions you'd like to lock the pair .... will probably be different for every strategy
|
# Analyze the conditions you'd like to lock the pair .... will probably be different for every strategy
|
||||||
sumprofit = sum(trade.close_profit for trade in trades)
|
sumprofit = sum(trade.close_profit for trade in trades)
|
||||||
if sumprofit < 0:
|
if sumprofit < 0:
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1087,7 +1087,7 @@ class Exchange:
|
|||||||
f'Tried to {side} amount {amount} at rate {rate}.'
|
f'Tried to {side} amount {amount} at rate {rate}.'
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise ExchangeError(
|
raise InvalidOrderException(
|
||||||
f'Could not create {ordertype} {side} order on market {pair}. '
|
f'Could not create {ordertype} {side} order on market {pair}. '
|
||||||
f'Tried to {side} amount {amount} at rate {rate}. '
|
f'Tried to {side} amount {amount} at rate {rate}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
@ -1138,7 +1138,10 @@ class Exchange:
|
|||||||
# Ensure rate is less than stop price
|
# Ensure rate is less than stop price
|
||||||
if bad_stop_price:
|
if bad_stop_price:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'In stoploss limit order, stop price should be more than limit price')
|
"In stoploss limit order, stop price should be more than limit price. "
|
||||||
|
f"Stop price: {stop_price}, Limit price: {limit_rate}, "
|
||||||
|
f"Limit Price pct: {limit_price_pct}"
|
||||||
|
)
|
||||||
return limit_rate
|
return limit_rate
|
||||||
|
|
||||||
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict:
|
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict:
|
||||||
@ -2755,10 +2758,10 @@ class Exchange:
|
|||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f"{self.name} does not support {self.margin_mode} {self.trading_mode}")
|
f"{self.name} does not support {self.margin_mode} {self.trading_mode}")
|
||||||
|
|
||||||
isolated_liq = None
|
liquidation_price = None
|
||||||
if self._config['dry_run'] or not self.exchange_has("fetchPositions"):
|
if self._config['dry_run'] or not self.exchange_has("fetchPositions"):
|
||||||
|
|
||||||
isolated_liq = self.dry_run_liquidation_price(
|
liquidation_price = self.dry_run_liquidation_price(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
open_rate=open_rate,
|
open_rate=open_rate,
|
||||||
is_short=is_short,
|
is_short=is_short,
|
||||||
@ -2773,16 +2776,16 @@ class Exchange:
|
|||||||
positions = self.fetch_positions(pair)
|
positions = self.fetch_positions(pair)
|
||||||
if len(positions) > 0:
|
if len(positions) > 0:
|
||||||
pos = positions[0]
|
pos = positions[0]
|
||||||
isolated_liq = pos['liquidationPrice']
|
liquidation_price = pos['liquidationPrice']
|
||||||
|
|
||||||
if isolated_liq is not None:
|
if liquidation_price is not None:
|
||||||
buffer_amount = abs(open_rate - isolated_liq) * self.liquidation_buffer
|
buffer_amount = abs(open_rate - liquidation_price) * self.liquidation_buffer
|
||||||
isolated_liq = (
|
liquidation_price_buffer = (
|
||||||
isolated_liq - buffer_amount
|
liquidation_price - buffer_amount
|
||||||
if is_short else
|
if is_short else
|
||||||
isolated_liq + buffer_amount
|
liquidation_price + buffer_amount
|
||||||
)
|
)
|
||||||
return isolated_liq
|
return max(liquidation_price_buffer, 0.0)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class Base3ActionRLEnv(BaseEnvironment):
|
|||||||
self._update_unrealized_total_profit()
|
self._update_unrealized_total_profit()
|
||||||
step_reward = self.calculate_reward(action)
|
step_reward = self.calculate_reward(action)
|
||||||
self.total_reward += step_reward
|
self.total_reward += step_reward
|
||||||
self.tensorboard_log(self.actions._member_names_[action])
|
self.tensorboard_log(self.actions._member_names_[action], category="actions")
|
||||||
|
|
||||||
trade_type = None
|
trade_type = None
|
||||||
if self.is_tradesignal(action):
|
if self.is_tradesignal(action):
|
||||||
|
@ -48,7 +48,7 @@ class Base4ActionRLEnv(BaseEnvironment):
|
|||||||
self._update_unrealized_total_profit()
|
self._update_unrealized_total_profit()
|
||||||
step_reward = self.calculate_reward(action)
|
step_reward = self.calculate_reward(action)
|
||||||
self.total_reward += step_reward
|
self.total_reward += step_reward
|
||||||
self.tensorboard_log(self.actions._member_names_[action])
|
self.tensorboard_log(self.actions._member_names_[action], category="actions")
|
||||||
|
|
||||||
trade_type = None
|
trade_type = None
|
||||||
if self.is_tradesignal(action):
|
if self.is_tradesignal(action):
|
||||||
|
@ -49,7 +49,7 @@ class Base5ActionRLEnv(BaseEnvironment):
|
|||||||
self._update_unrealized_total_profit()
|
self._update_unrealized_total_profit()
|
||||||
step_reward = self.calculate_reward(action)
|
step_reward = self.calculate_reward(action)
|
||||||
self.total_reward += step_reward
|
self.total_reward += step_reward
|
||||||
self.tensorboard_log(self.actions._member_names_[action])
|
self.tensorboard_log(self.actions._member_names_[action], category="actions")
|
||||||
|
|
||||||
trade_type = None
|
trade_type = None
|
||||||
if self.is_tradesignal(action):
|
if self.is_tradesignal(action):
|
||||||
|
@ -137,7 +137,8 @@ class BaseEnvironment(gym.Env):
|
|||||||
self.np_random, seed = seeding.np_random(seed)
|
self.np_random, seed = seeding.np_random(seed)
|
||||||
return [seed]
|
return [seed]
|
||||||
|
|
||||||
def tensorboard_log(self, metric: str, value: Union[int, float] = 1, inc: bool = True):
|
def tensorboard_log(self, metric: str, value: Optional[Union[int, float]] = None,
|
||||||
|
inc: Optional[bool] = None, category: str = "custom"):
|
||||||
"""
|
"""
|
||||||
Function builds the tensorboard_metrics dictionary
|
Function builds the tensorboard_metrics dictionary
|
||||||
to be parsed by the TensorboardCallback. This
|
to be parsed by the TensorboardCallback. This
|
||||||
@ -149,17 +150,24 @@ class BaseEnvironment(gym.Env):
|
|||||||
|
|
||||||
def calculate_reward(self, action: int) -> float:
|
def calculate_reward(self, action: int) -> float:
|
||||||
if not self._is_valid(action):
|
if not self._is_valid(action):
|
||||||
self.tensorboard_log("is_valid")
|
self.tensorboard_log("invalid")
|
||||||
return -2
|
return -2
|
||||||
|
|
||||||
:param metric: metric to be tracked and incremented
|
:param metric: metric to be tracked and incremented
|
||||||
:param value: value to increment `metric` by
|
:param value: `metric` value
|
||||||
:param inc: sets whether the `value` is incremented or not
|
:param inc: (deprecated) sets whether the `value` is incremented or not
|
||||||
|
:param category: `metric` category
|
||||||
"""
|
"""
|
||||||
if not inc or metric not in self.tensorboard_metrics:
|
increment = True if value is None else False
|
||||||
self.tensorboard_metrics[metric] = value
|
value = 1 if increment else value
|
||||||
|
|
||||||
|
if category not in self.tensorboard_metrics:
|
||||||
|
self.tensorboard_metrics[category] = {}
|
||||||
|
|
||||||
|
if not increment or metric not in self.tensorboard_metrics[category]:
|
||||||
|
self.tensorboard_metrics[category][metric] = value
|
||||||
else:
|
else:
|
||||||
self.tensorboard_metrics[metric] += value
|
self.tensorboard_metrics[category][metric] += value
|
||||||
|
|
||||||
def reset_tensorboard_log(self):
|
def reset_tensorboard_log(self):
|
||||||
self.tensorboard_metrics = {}
|
self.tensorboard_metrics = {}
|
||||||
|
@ -46,14 +46,12 @@ class TensorboardCallback(BaseCallback):
|
|||||||
local_info = self.locals["infos"][0]
|
local_info = self.locals["infos"][0]
|
||||||
tensorboard_metrics = self.training_env.get_attr("tensorboard_metrics")[0]
|
tensorboard_metrics = self.training_env.get_attr("tensorboard_metrics")[0]
|
||||||
|
|
||||||
for info in local_info:
|
for metric in local_info:
|
||||||
if info not in ["episode", "terminal_observation"]:
|
if metric not in ["episode", "terminal_observation"]:
|
||||||
self.logger.record(f"_info/{info}", local_info[info])
|
self.logger.record(f"info/{metric}", local_info[metric])
|
||||||
|
|
||||||
for info in tensorboard_metrics:
|
for category in tensorboard_metrics:
|
||||||
if info in [action.name for action in self.actions]:
|
for metric in tensorboard_metrics[category]:
|
||||||
self.logger.record(f"_actions/{info}", tensorboard_metrics[info])
|
self.logger.record(f"{category}/{metric}", tensorboard_metrics[category][metric])
|
||||||
else:
|
|
||||||
self.logger.record(f"_custom/{info}", tensorboard_metrics[info])
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -100,7 +100,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel):
|
|||||||
"""
|
"""
|
||||||
# first, penalize if the action is not valid
|
# first, penalize if the action is not valid
|
||||||
if not self._is_valid(action):
|
if not self._is_valid(action):
|
||||||
self.tensorboard_log("is_valid")
|
self.tensorboard_log("invalid", category="actions")
|
||||||
return -2
|
return -2
|
||||||
|
|
||||||
pnl = self.get_unrealized_profit()
|
pnl = self.get_unrealized_profit()
|
||||||
|
@ -586,7 +586,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
min_entry_stake = self.exchange.get_min_pair_stake_amount(trade.pair,
|
min_entry_stake = self.exchange.get_min_pair_stake_amount(trade.pair,
|
||||||
current_entry_rate,
|
current_entry_rate,
|
||||||
self.strategy.stoploss)
|
0.0)
|
||||||
min_exit_stake = self.exchange.get_min_pair_stake_amount(trade.pair,
|
min_exit_stake = self.exchange.get_min_pair_stake_amount(trade.pair,
|
||||||
current_exit_rate,
|
current_exit_rate,
|
||||||
self.strategy.stoploss)
|
self.strategy.stoploss)
|
||||||
@ -700,7 +700,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
pos_adjust = trade is not None
|
pos_adjust = trade is not None
|
||||||
|
|
||||||
enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake(
|
enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake(
|
||||||
pair, price, stake_amount, trade_side, enter_tag, trade, order_adjust, leverage_)
|
pair, price, stake_amount, trade_side, enter_tag, trade, order_adjust, leverage_,
|
||||||
|
pos_adjust)
|
||||||
|
|
||||||
if not stake_amount:
|
if not stake_amount:
|
||||||
return False
|
return False
|
||||||
@ -860,7 +861,12 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade: Optional[Trade],
|
trade: Optional[Trade],
|
||||||
order_adjust: bool,
|
order_adjust: bool,
|
||||||
leverage_: Optional[float],
|
leverage_: Optional[float],
|
||||||
|
pos_adjust: bool,
|
||||||
) -> Tuple[float, float, float]:
|
) -> Tuple[float, float, float]:
|
||||||
|
"""
|
||||||
|
Validate and eventually adjust (within limits) limit, amount and leverage
|
||||||
|
:return: Tuple with (price, amount, leverage)
|
||||||
|
"""
|
||||||
|
|
||||||
if price:
|
if price:
|
||||||
enter_limit_requested = price
|
enter_limit_requested = price
|
||||||
@ -906,7 +912,9 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# We do however also need min-stake to determine leverage, therefore this is ignored as
|
# We do however also need min-stake to determine leverage, therefore this is ignored as
|
||||||
# edge-case for now.
|
# edge-case for now.
|
||||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(
|
min_stake_amount = self.exchange.get_min_pair_stake_amount(
|
||||||
pair, enter_limit_requested, self.strategy.stoploss, leverage)
|
pair, enter_limit_requested,
|
||||||
|
self.strategy.stoploss if not pos_adjust else 0.0,
|
||||||
|
leverage)
|
||||||
max_stake_amount = self.exchange.get_max_pair_stake_amount(
|
max_stake_amount = self.exchange.get_max_pair_stake_amount(
|
||||||
pair, enter_limit_requested, leverage)
|
pair, enter_limit_requested, leverage)
|
||||||
|
|
||||||
@ -1122,8 +1130,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||||
logger.warning('Exiting the trade forcefully')
|
logger.warning('Exiting the trade forcefully')
|
||||||
self.execute_trade_exit(trade, stop_price, exit_check=ExitCheckTuple(
|
self.emergency_exit(trade, stop_price)
|
||||||
exit_type=ExitType.EMERGENCY_EXIT))
|
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
@ -1281,13 +1288,16 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:
|
if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:
|
||||||
logger.warning(f'Emergency exiting trade {trade}, as the exit order '
|
logger.warning(f'Emergency exiting trade {trade}, as the exit order '
|
||||||
f'timed out {max_timeouts} times.')
|
f'timed out {max_timeouts} times.')
|
||||||
|
self.emergency_exit(trade, order['price'])
|
||||||
|
|
||||||
|
def emergency_exit(self, trade: Trade, price: float) -> None:
|
||||||
try:
|
try:
|
||||||
self.execute_trade_exit(
|
self.execute_trade_exit(
|
||||||
trade, order['price'],
|
trade, price,
|
||||||
exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_EXIT))
|
exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_EXIT))
|
||||||
except DependencyException as exception:
|
except DependencyException as exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f'Unable to emergency sell trade {trade.pair}: {exception}')
|
f'Unable to emergency exit trade {trade.pair}: {exception}')
|
||||||
|
|
||||||
def replace_order(self, order: Dict, order_obj: Optional[Order], trade: Trade) -> None:
|
def replace_order(self, order: Dict, order_obj: Optional[Order], trade: Trade) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -749,7 +749,7 @@ class Backtesting:
|
|||||||
leverage = min(max(leverage, 1.0), max_leverage)
|
leverage = min(max(leverage, 1.0), max_leverage)
|
||||||
|
|
||||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(
|
min_stake_amount = self.exchange.get_min_pair_stake_amount(
|
||||||
pair, propose_rate, -0.05, leverage=leverage) or 0
|
pair, propose_rate, -0.05 if not pos_adjust else 0.0, leverage=leverage) or 0
|
||||||
max_stake_amount = self.exchange.get_max_pair_stake_amount(
|
max_stake_amount = self.exchange.get_max_pair_stake_amount(
|
||||||
pair, propose_rate, leverage=leverage)
|
pair, propose_rate, leverage=leverage)
|
||||||
stake_available = self.wallets.get_available_stake_amount()
|
stake_available = self.wallets.get_available_stake_amount()
|
||||||
|
@ -1088,6 +1088,11 @@ class LocalTrade():
|
|||||||
In live mode, converts the filter to a database query and returns all rows
|
In live mode, converts the filter to a database query and returns all rows
|
||||||
In Backtest mode, uses filters on Trade.trades to get the result.
|
In Backtest mode, uses filters on Trade.trades to get the result.
|
||||||
|
|
||||||
|
:param pair: Filter by pair
|
||||||
|
:param is_open: Filter by open/closed status
|
||||||
|
:param open_date: Filter by open_date (filters via trade.open_date > input)
|
||||||
|
:param close_date: Filter by close_date (filters via trade.close_date > input)
|
||||||
|
Will implicitly only return closed trades.
|
||||||
:return: unsorted List[Trade]
|
:return: unsorted List[Trade]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -311,7 +311,7 @@ class LockModel(BaseModel):
|
|||||||
lock_timestamp: int
|
lock_timestamp: int
|
||||||
pair: str
|
pair: str
|
||||||
side: str
|
side: str
|
||||||
reason: str
|
reason: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class Locks(BaseModel):
|
class Locks(BaseModel):
|
||||||
|
@ -388,12 +388,13 @@ class RPC:
|
|||||||
Trade.close_date.desc())
|
Trade.close_date.desc())
|
||||||
|
|
||||||
output = [trade.to_json() for trade in trades]
|
output = [trade.to_json() for trade in trades]
|
||||||
|
total_trades = Trade.get_trades([Trade.is_open.is_(False)]).count()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"trades": output,
|
"trades": output,
|
||||||
"trades_count": len(output),
|
"trades_count": len(output),
|
||||||
"offset": offset,
|
"offset": offset,
|
||||||
"total_trades": Trade.get_trades([Trade.is_open.is_(False)]).count(),
|
"total_trades": total_trades,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _rpc_stats(self) -> Dict[str, Any]:
|
def _rpc_stats(self) -> Dict[str, Any]:
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
-r docs/requirements-docs.txt
|
-r docs/requirements-docs.txt
|
||||||
|
|
||||||
coveralls==3.3.1
|
coveralls==3.3.1
|
||||||
ruff==0.0.254
|
ruff==0.0.255
|
||||||
mypy==1.0.1
|
mypy==1.1.1
|
||||||
pre-commit==3.1.1
|
pre-commit==3.1.1
|
||||||
pytest==7.2.1
|
pytest==7.2.2
|
||||||
pytest-asyncio==0.20.3
|
pytest-asyncio==0.20.3
|
||||||
pytest-cov==4.0.0
|
pytest-cov==4.0.0
|
||||||
pytest-mock==3.10.0
|
pytest-mock==3.10.0
|
||||||
|
@ -10,7 +10,7 @@ python-telegram-bot==13.15
|
|||||||
arrow==1.2.3
|
arrow==1.2.3
|
||||||
cachetools==4.2.2
|
cachetools==4.2.2
|
||||||
requests==2.28.2
|
requests==2.28.2
|
||||||
urllib3==1.26.14
|
urllib3==1.26.15
|
||||||
jsonschema==4.17.3
|
jsonschema==4.17.3
|
||||||
TA-Lib==0.4.25
|
TA-Lib==0.4.25
|
||||||
technical==1.4.0
|
technical==1.4.0
|
||||||
@ -34,9 +34,9 @@ orjson==3.8.7
|
|||||||
sdnotify==0.3.2
|
sdnotify==0.3.2
|
||||||
|
|
||||||
# API Server
|
# API Server
|
||||||
fastapi==0.92.0
|
fastapi==0.94.0
|
||||||
pydantic==1.10.5
|
pydantic==1.10.6
|
||||||
uvicorn==0.20.0
|
uvicorn==0.21.0
|
||||||
pyjwt==2.6.0
|
pyjwt==2.6.0
|
||||||
aiofiles==23.1.0
|
aiofiles==23.1.0
|
||||||
psutil==5.9.4
|
psutil==5.9.4
|
||||||
|
@ -12,8 +12,8 @@ from pandas import DataFrame
|
|||||||
|
|
||||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||||
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
||||||
InvalidOrderException, OperationalException, PricingError,
|
InsufficientFundsError, InvalidOrderException,
|
||||||
TemporaryError)
|
OperationalException, PricingError, TemporaryError)
|
||||||
from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision,
|
from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision,
|
||||||
date_minus_candles, market_is_active, price_to_precision,
|
date_minus_candles, market_is_active, price_to_precision,
|
||||||
timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
|
timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
|
||||||
@ -1599,13 +1599,13 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
|||||||
assert api_mock.create_order.call_args[0][4] == 200
|
assert api_mock.create_order.call_args[0][4] == 200
|
||||||
|
|
||||||
# test exception handling
|
# test exception handling
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(InsufficientFundsError):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200,
|
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200,
|
||||||
leverage=1.0)
|
leverage=1.0)
|
||||||
|
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200,
|
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200,
|
||||||
|
@ -302,8 +302,7 @@ def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None
|
|||||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
create_mock_trades(fee)
|
create_mock_trades(fee)
|
||||||
trades = Trade.get_trades([Trade.is_open.is_(False)])
|
trade = Trade.get_trades([Trade.is_open.is_(False)]).first()
|
||||||
trade = trades[0]
|
|
||||||
context = MagicMock()
|
context = MagicMock()
|
||||||
context.args = [str(trade.id)]
|
context.args = [str(trade.id)]
|
||||||
telegram._status(update=update, context=context)
|
telegram._status(update=update, context=context)
|
||||||
|
@ -2724,21 +2724,21 @@ def test_manage_open_orders_exit_usercustom(
|
|||||||
assert freqtrade.strategy.check_exit_timeout.call_count == 1
|
assert freqtrade.strategy.check_exit_timeout.call_count == 1
|
||||||
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||||
|
|
||||||
# 2nd canceled trade - Fail execute sell
|
# 2nd canceled trade - Fail execute exit
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
||||||
mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
|
mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit',
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit',
|
||||||
side_effect=DependencyException)
|
side_effect=DependencyException)
|
||||||
freqtrade.manage_open_orders()
|
freqtrade.manage_open_orders()
|
||||||
assert log_has_re('Unable to emergency sell .*', caplog)
|
assert log_has_re('Unable to emergency exit .*', caplog)
|
||||||
|
|
||||||
et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit')
|
et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit')
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
# 2nd canceled trade ...
|
# 2nd canceled trade ...
|
||||||
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
||||||
|
|
||||||
# If cancelling fails - no emergency sell!
|
# If cancelling fails - no emergency exit!
|
||||||
with patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit', return_value=False):
|
with patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit', return_value=False):
|
||||||
freqtrade.manage_open_orders()
|
freqtrade.manage_open_orders()
|
||||||
assert et_mock.call_count == 0
|
assert et_mock.call_count == 0
|
||||||
|
Loading…
Reference in New Issue
Block a user