Updated
This commit is contained in:
parent
12750cb765
commit
1c7daa713b
@ -1,6 +1,9 @@
|
|||||||
|
|
||||||
# --- Do not remove these libs ---
|
# --- Do not remove these libs ---
|
||||||
from freqtrade.strategy.interface import IStrategy
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from typing import Dict, List
|
||||||
|
from hyperopt import hp
|
||||||
|
from functools import reduce
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
# --------------------------------
|
# --------------------------------
|
||||||
|
|
||||||
@ -10,12 +13,40 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|||||||
import numpy # noqa
|
import numpy # noqa
|
||||||
|
|
||||||
|
|
||||||
# Update this variable if you change the class name
|
# Make a backup of default_strategy then move this file over your default strategy in freqtrade/strategy and to run:
|
||||||
class_name = 'TestStrategy'
|
# 'python3.6 freqtrade/main.py -c config.json hyperopt -s all --realistic-simulation -i 5'
|
||||||
|
# Make sure to take note of the if statements at the bottom of the file, and the pattern of > < and >= <= and the triggers configuration.
|
||||||
|
# Then, take note of the results: There is one trigger in the results, map the trigger as an example:
|
||||||
|
# 'Trigger: 7'
|
||||||
|
# The 7th trigger in this file's if statement such as:
|
||||||
|
# 'sar_reversal': (qtpylib.crossed_above(
|
||||||
|
# dataframe['close'], dataframe['sar']
|
||||||
|
#
|
||||||
|
# Drop the trigger into 'def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:' into your default_strategy backup as:
|
||||||
|
# dataframe['close'], dataframe['sar'] &
|
||||||
|
# Then for your other values such as: 'Mfi-Value: 15.00'
|
||||||
|
# The if statement looks like: conditions.append(dataframe['mfi'] < params['mfi']['value']
|
||||||
|
# Drop this in as:
|
||||||
|
# (dataframe['mfi'] < 15.00) &
|
||||||
|
# The last condition in 'def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:' will have no &
|
||||||
|
|
||||||
|
|
||||||
# This class is a sample. Feel free to customize it.
|
### As an example the sell/buy trend looks like this:
|
||||||
class TestStrategy(IStrategy):
|
# dataframe.loc[
|
||||||
|
# (
|
||||||
|
# (dataframe['adx'] > 70) &
|
||||||
|
# (dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
|
# ),
|
||||||
|
# 'sell'] = 1
|
||||||
|
#
|
||||||
|
|
||||||
|
# Once done, move your backup strategy back over this one and backtest.
|
||||||
|
|
||||||
|
|
||||||
|
class_name = 'DefaultStrategy'
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultStrategy(IStrategy):
|
||||||
"""
|
"""
|
||||||
This is a test strategy to inspire you.
|
This is a test strategy to inspire you.
|
||||||
More information in https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md
|
More information in https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md
|
||||||
@ -62,7 +93,7 @@ class TestStrategy(IStrategy):
|
|||||||
# ADX
|
# ADX
|
||||||
dataframe['adx'] = ta.ADX(dataframe)
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
|
|
||||||
"""
|
|
||||||
# Awesome oscillator
|
# Awesome oscillator
|
||||||
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
||||||
|
|
||||||
@ -114,18 +145,28 @@ class TestStrategy(IStrategy):
|
|||||||
stoch_rsi = ta.STOCHRSI(dataframe)
|
stoch_rsi = ta.STOCHRSI(dataframe)
|
||||||
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
||||||
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
||||||
"""
|
|
||||||
|
|
||||||
# Overlap Studies
|
# Overlap Studies
|
||||||
# ------------------------------------
|
# ------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# Previous Bollinger bands
|
||||||
|
# Because ta.BBANDS implementation is broken with small numbers, it actually
|
||||||
|
# returns middle band for all the three bands. Switch to qtpylib.bollinger_bands
|
||||||
|
# and use middle band instead.
|
||||||
|
# Is broken
|
||||||
|
"""
|
||||||
|
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
||||||
|
"""
|
||||||
|
|
||||||
# Bollinger bands
|
# Bollinger bands
|
||||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
dataframe['bb_lowerband'] = bollinger['lower']
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
dataframe['bb_middleband'] = bollinger['mid']
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
dataframe['bb_upperband'] = bollinger['upper']
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
|
||||||
"""
|
|
||||||
# EMA - Exponential Moving Average
|
# EMA - Exponential Moving Average
|
||||||
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
||||||
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
||||||
@ -138,7 +179,6 @@ class TestStrategy(IStrategy):
|
|||||||
|
|
||||||
# SMA - Simple Moving Average
|
# SMA - Simple Moving Average
|
||||||
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
"""
|
|
||||||
|
|
||||||
# TEMA - Triple Exponential Moving Average
|
# TEMA - Triple Exponential Moving Average
|
||||||
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
@ -152,7 +192,7 @@ class TestStrategy(IStrategy):
|
|||||||
|
|
||||||
# Pattern Recognition - Bullish candlestick patterns
|
# Pattern Recognition - Bullish candlestick patterns
|
||||||
# ------------------------------------
|
# ------------------------------------
|
||||||
"""
|
|
||||||
# Hammer: values [0, 100]
|
# Hammer: values [0, 100]
|
||||||
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
||||||
# Inverted Hammer: values [0, 100]
|
# Inverted Hammer: values [0, 100]
|
||||||
@ -165,11 +205,11 @@ class TestStrategy(IStrategy):
|
|||||||
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
||||||
# Three White Soldiers: values [0, 100]
|
# Three White Soldiers: values [0, 100]
|
||||||
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
||||||
"""
|
|
||||||
|
|
||||||
# Pattern Recognition - Bearish candlestick patterns
|
# Pattern Recognition - Bearish candlestick patterns
|
||||||
# ------------------------------------
|
# ------------------------------------
|
||||||
"""
|
|
||||||
# Hanging Man: values [0, 100]
|
# Hanging Man: values [0, 100]
|
||||||
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
||||||
# Shooting Star: values [0, 100]
|
# Shooting Star: values [0, 100]
|
||||||
@ -182,11 +222,10 @@ class TestStrategy(IStrategy):
|
|||||||
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
||||||
# Evening Star: values [0, 100]
|
# Evening Star: values [0, 100]
|
||||||
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
||||||
"""
|
|
||||||
|
|
||||||
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
||||||
# ------------------------------------
|
# ------------------------------------
|
||||||
"""
|
|
||||||
# Three Line Strike: values [0, -100, 100]
|
# Three Line Strike: values [0, -100, 100]
|
||||||
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
||||||
# Spinning Top: values [0, -100, 100]
|
# Spinning Top: values [0, -100, 100]
|
||||||
@ -199,18 +238,18 @@ class TestStrategy(IStrategy):
|
|||||||
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
||||||
# Three Inside Up/Down: values [0, -100, 100]
|
# Three Inside Up/Down: values [0, -100, 100]
|
||||||
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
||||||
"""
|
|
||||||
|
|
||||||
# Chart type
|
# Chart type
|
||||||
# ------------------------------------
|
# ------------------------------------
|
||||||
"""
|
|
||||||
# Heikinashi stategy
|
# Heikinashi stategy
|
||||||
heikinashi = qtpylib.heikinashi(dataframe)
|
heikinashi = qtpylib.heikinashi(dataframe)
|
||||||
dataframe['ha_open'] = heikinashi['open']
|
dataframe['ha_open'] = heikinashi['open']
|
||||||
dataframe['ha_close'] = heikinashi['close']
|
dataframe['ha_close'] = heikinashi['close']
|
||||||
dataframe['ha_high'] = heikinashi['high']
|
dataframe['ha_high'] = heikinashi['high']
|
||||||
dataframe['ha_low'] = heikinashi['low']
|
dataframe['ha_low'] = heikinashi['low']
|
||||||
"""
|
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
@ -220,12 +259,65 @@ class TestStrategy(IStrategy):
|
|||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']:
|
||||||
(
|
conditions.append(dataframe['ema50'] > dataframe['ema100'])
|
||||||
(dataframe['adx'] > 30) &
|
if 'macd_below_zero' in params and params['macd_below_zero']['enabled']:
|
||||||
(dataframe['tema'] <= dataframe['bb_middleband']) &
|
conditions.append(dataframe['macd'] < 0)
|
||||||
(dataframe['tema'] > dataframe['tema'].shift(1))
|
if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema5'] > dataframe['ema10'])
|
||||||
|
if 'mfi' in params and params['mfi']['enabled']:
|
||||||
|
conditions.append(dataframe['mfi'] < params['mfi']['value'])
|
||||||
|
if 'fastd' in params and params['fastd']['enabled']:
|
||||||
|
conditions.append(dataframe['fastd'] < params['fastd']['value'])
|
||||||
|
if 'adx' in params and params['adx']['enabled']:
|
||||||
|
conditions.append(dataframe['adx'] > params['adx']['value'])
|
||||||
|
if 'rsi' in params and params['rsi']['enabled']:
|
||||||
|
conditions.append(dataframe['rsi'] < params['rsi']['value'])
|
||||||
|
if 'over_sar' in params and params['over_sar']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['sar'])
|
||||||
|
if 'green_candle' in params and params['green_candle']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['open'])
|
||||||
|
if 'uptrend_sma' in params and params['uptrend_sma']['enabled']:
|
||||||
|
prevsma = dataframe['sma'].shift(1)
|
||||||
|
conditions.append(dataframe['sma'] > prevsma)
|
||||||
|
# TRIGGERS
|
||||||
|
triggers = {
|
||||||
|
'lower_bb': (
|
||||||
|
dataframe['close'] < dataframe['bb_lowerband']
|
||||||
),
|
),
|
||||||
|
'lower_bb_tema': (
|
||||||
|
dataframe['tema'] < dataframe['bb_lowerband']
|
||||||
|
),
|
||||||
|
'faststoch10': (qtpylib.crossed_above(
|
||||||
|
dataframe['fastd'], 10.0
|
||||||
|
)),
|
||||||
|
'ao_cross_zero': (qtpylib.crossed_above(
|
||||||
|
dataframe['ao'], 0.0
|
||||||
|
)),
|
||||||
|
'ema3_cross_ema10': (qtpylib.crossed_above(
|
||||||
|
dataframe['ema3'], dataframe['ema10']
|
||||||
|
)),
|
||||||
|
'macd_cross_signal': (qtpylib.crossed_above(
|
||||||
|
dataframe['macd'], dataframe['macdsignal']
|
||||||
|
)),
|
||||||
|
'sar_reversal': (qtpylib.crossed_above(
|
||||||
|
dataframe['close'], dataframe['sar']
|
||||||
|
)),
|
||||||
|
'ht_sine': (qtpylib.crossed_above(
|
||||||
|
dataframe['htleadsine'], dataframe['htsine']
|
||||||
|
)),
|
||||||
|
'heiken_reversal_bull': (
|
||||||
|
(qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) &
|
||||||
|
(dataframe['ha_low'] == dataframe['ha_open'])
|
||||||
|
),
|
||||||
|
'di_cross': (qtpylib.crossed_above(
|
||||||
|
dataframe['plus_di'], dataframe['minus_di']
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
conditions.append(triggers.get(params['trigger']['type']))
|
||||||
|
|
||||||
|
dataframe.loc[
|
||||||
|
reduce(lambda x, y: x & y, conditions),
|
||||||
'buy'] = 1
|
'buy'] = 1
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
@ -239,8 +331,142 @@ class TestStrategy(IStrategy):
|
|||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe['adx'] > 70) &
|
(dataframe['adx'] > 70) &
|
||||||
(dataframe['tema'] > dataframe['bb_middleband']) &
|
|
||||||
(dataframe['tema'] < dataframe['tema'].shift(1))
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
),
|
),
|
||||||
'sell'] = 1
|
'sell'] = 1
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
def hyperopt_space(self) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Define your Hyperopt space for the strategy
|
||||||
|
:return: Dict
|
||||||
|
"""
|
||||||
|
space = {
|
||||||
|
'macd_below_zero': hp.choice('macd_below_zero', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'mfi': hp.choice('mfi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
|
||||||
|
]),
|
||||||
|
'fastd': hp.choice('fastd', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)}
|
||||||
|
]),
|
||||||
|
'adx': hp.choice('adx', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
|
||||||
|
]),
|
||||||
|
'rsi': hp.choice('rsi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
||||||
|
]),
|
||||||
|
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_short_ema': hp.choice('uptrend_short_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'over_sar': hp.choice('over_sar', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'green_candle': hp.choice('green_candle', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_sma': hp.choice('uptrend_sma', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'trigger': hp.choice('trigger', [
|
||||||
|
{'type': 'lower_bb'},
|
||||||
|
{'type': 'lower_bb_tema'},
|
||||||
|
{'type': 'faststoch10'},
|
||||||
|
{'type': 'ao_cross_zero'},
|
||||||
|
{'type': 'ema3_cross_ema10'},
|
||||||
|
{'type': 'macd_cross_signal'},
|
||||||
|
{'type': 'sar_reversal'},
|
||||||
|
{'type': 'ht_sine'},
|
||||||
|
{'type': 'heiken_reversal_bull'},
|
||||||
|
{'type': 'di_cross'},
|
||||||
|
]),
|
||||||
|
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
|
||||||
|
}
|
||||||
|
return space
|
||||||
|
|
||||||
|
def buy_strategy_generator(self, params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
||||||
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
|
conditions = []
|
||||||
|
# GUARDS AND TRENDS
|
||||||
|
if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema50'] > dataframe['ema100'])
|
||||||
|
if 'macd_below_zero' in params and params['macd_below_zero']['enabled']:
|
||||||
|
conditions.append(dataframe['macd'] < 0)
|
||||||
|
if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema5'] > dataframe['ema10'])
|
||||||
|
if 'mfi' in params and params['mfi']['enabled']:
|
||||||
|
conditions.append(dataframe['mfi'] < params['mfi']['value'])
|
||||||
|
if 'fastd' in params and params['fastd']['enabled']:
|
||||||
|
conditions.append(dataframe['fastd'] < params['fastd']['value'])
|
||||||
|
if 'adx' in params and params['adx']['enabled']:
|
||||||
|
conditions.append(dataframe['adx'] > params['adx']['value'])
|
||||||
|
if 'rsi' in params and params['rsi']['enabled']:
|
||||||
|
conditions.append(dataframe['rsi'] < params['rsi']['value'])
|
||||||
|
if 'over_sar' in params and params['over_sar']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['sar'])
|
||||||
|
if 'green_candle' in params and params['green_candle']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['open'])
|
||||||
|
if 'uptrend_sma' in params and params['uptrend_sma']['enabled']:
|
||||||
|
prevsma = dataframe['sma'].shift(1)
|
||||||
|
conditions.append(dataframe['sma'] > prevsma)
|
||||||
|
|
||||||
|
# TRIGGERS
|
||||||
|
triggers = {
|
||||||
|
'lower_bb': (
|
||||||
|
dataframe['close'] < dataframe['bb_lowerband']
|
||||||
|
),
|
||||||
|
'lower_bb_tema': (
|
||||||
|
dataframe['tema'] < dataframe['bb_lowerband']
|
||||||
|
),
|
||||||
|
'faststoch10': (qtpylib.crossed_above(
|
||||||
|
dataframe['fastd'], 10.0
|
||||||
|
)),
|
||||||
|
'ao_cross_zero': (qtpylib.crossed_above(
|
||||||
|
dataframe['ao'], 0.0
|
||||||
|
)),
|
||||||
|
'ema3_cross_ema10': (qtpylib.crossed_above(
|
||||||
|
dataframe['ema3'], dataframe['ema10']
|
||||||
|
)),
|
||||||
|
'macd_cross_signal': (qtpylib.crossed_above(
|
||||||
|
dataframe['macd'], dataframe['macdsignal']
|
||||||
|
)),
|
||||||
|
'sar_reversal': (qtpylib.crossed_above(
|
||||||
|
dataframe['close'], dataframe['sar']
|
||||||
|
)),
|
||||||
|
'ht_sine': (qtpylib.crossed_above(
|
||||||
|
dataframe['htleadsine'], dataframe['htsine']
|
||||||
|
)),
|
||||||
|
'heiken_reversal_bull': (
|
||||||
|
(qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) &
|
||||||
|
(dataframe['ha_low'] == dataframe['ha_open'])
|
||||||
|
),
|
||||||
|
'di_cross': (qtpylib.crossed_above(
|
||||||
|
dataframe['plus_di'], dataframe['minus_di']
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
conditions.append(triggers.get(params['trigger']['type']))
|
||||||
|
|
||||||
|
dataframe.loc[
|
||||||
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
return populate_buy_trend
|
||||||
|
Loading…
Reference in New Issue
Block a user