Timeframe
1d
Direction
Long Only
Stoploss
-99.0%
Trailing Stop
Yes
ROI
0m: 50.0%, 60m: 45.0%, 120m: 40.0%, 180m: 30.0%
Interface Version
N/A
Startup Candles
N/A
Indicators
3
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
import logging
from functools import reduce
import datetime
import ephem
import talib.abstract as ta
import pandas_ta as pta
import numpy as np
import pandas as pd
import freqtrade.vendor.qtpylib.indicators as qtpylib
from datetime import timedelta, datetime, timezone
from pandas import DataFrame, Series
from freqtrade.persistence import Trade
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
IStrategy, IntParameter, RealParameter)
from typing import Optional
logger = logging.getLogger(__name__)
class Astro(IStrategy):
exit_profit_only = True
use_custom_stoploss = True
trailing_stop = True
position_adjustment_enable = True
ignore_roi_if_entry_signal = True
max_entry_position_adjustment = 2
max_dca_multiplier = 2.5
process_only_new_candles = True
can_short = False
use_exit_signal = True
startup_candle_count: int = 50
stoploss = -0.99
timeframe = '1d'
minimal_roi = {
"48000": 0.01,
"24000": 0.025,
"12000": 0.05,
"4800": 0.10,
"300": 0.15,
"180": 0.30,
"120": 0.40,
"60": 0.45,
"0": 0.50
}
# Protections
cooldown_lookback = IntParameter(24, 48, default=12, space="protection", optimize=True)
stop_duration = IntParameter(12, 200, default=5, space="protection", optimize=True)
use_stop_protection = BooleanParameter(default=True, space="protection", optimize=True)
# Trailing stop loss optimization
tsl_enable = IntParameter(low=70, high=90, default=80, space='sell', optimize=True, load=True)
tsl_target5 = DecimalParameter(low=0.3, high=0.4, default=0.3, decimals=2, space='sell', optimize=True, load=True)
ts5 = DecimalParameter(low=0.04, high=0.06, default=0.05, decimals=2, space='sell', optimize=True, load=True)
tsl_target4 = DecimalParameter(low=0.18, high=0.3, default=0.2, decimals=2, space='sell', optimize=True, load=True)
ts4 = DecimalParameter(low=0.03, high=0.05, default=0.045, decimals=3, space='sell', optimize=True, load=True)
tsl_target3 = DecimalParameter(low=0.12, high=0.18, default=0.15, decimals=2, space='sell', optimize=True, load=True)
ts3 = DecimalParameter(low=0.025, high=0.04, default=0.035, decimals=3, space='sell', optimize=True, load=True)
tsl_target2 = DecimalParameter(low=0.07, high=0.12, default=0.1, decimals=2, space='sell', optimize=True, load=True)
ts2 = DecimalParameter(low=0.015, high=0.03, default=0.02, decimals=2, space='sell', optimize=True, load=True)
tsl_target1 = DecimalParameter(low=0.04, high=0.08, default=0.075, decimals=3, space='sell', optimize=True, load=True)
ts1 = DecimalParameter(low=0.01, high=0.016, default=0.013, decimals=3, space='sell', optimize=True, load=True)
tsl_target0 = DecimalParameter(low=0.02, high=0.05, default=0.05, decimals=3, space='sell', optimize=True, load=True)
ts0 = DecimalParameter(low=0.008, high=0.015, default=0.01, decimals=3, space='sell', optimize=True, load=True)
@property
def protections(self):
prot = []
prot.append({
"method": "CooldownPeriod",
"stop_duration_candles": self.cooldown_lookback.value
})
if self.use_stop_protection.value:
prot.append({
"method": "StoplossGuard",
"lookback_period_candles": 24 * 3,
"trade_limit": 2,
"stop_duration_candles": self.stop_duration.value,
"only_per_pair": False
})
return prot
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: Optional[float], max_stake: float,
leverage: float, entry_tag: Optional[str], side: str, **kwargs) -> float:
return proposed_stake / self.max_dca_multiplier
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float, **kwargs) -> Optional[float]:
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
filled_entries = trade.select_filled_orders(trade.entry_side)
count_of_entries = trade.nr_of_successful_entries
if current_profit > 0.10 and trade.nr_of_successful_exits == 0:
return -(trade.stake_amount / 2)
if current_profit > -0.015 and trade.nr_of_successful_entries == 1:
return None
if current_profit > -0.065 and trade.nr_of_successful_entries == 2:
return None
if current_profit > -0.105 and trade.nr_of_successful_entries == 3:
return None
if current_profit > -0.13 and trade.nr_of_successful_entries == 4:
return None
if current_profit > -0.18 and trade.nr_of_successful_entries == 5:
return None
try:
stake_amount = filled_entries[0].cost
if count_of_entries == 1:
stake_amount = stake_amount * 1
elif count_of_entries == 2:
stake_amount = stake_amount * 1
elif count_of_entries == 3:
stake_amount = stake_amount * 1
elif count_of_entries == 4:
stake_amount = stake_amount * 1.5
elif count_of_entries == 5:
stake_amount = stake_amount * 1.625
else:
stake_amount = stake_amount
return stake_amount
except Exception:
return None
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze()
# Tighten stoploss during challenging aspects
challenging_aspects = [col for col in dataframe.columns if any(
aspect in col for aspect in ['square', 'opposition', 'quincunx']
)]
if any(current_candle[col] == 1 for col in challenging_aspects) and current_profit > 0:
self.dp.send_msg(f'*** {pair} *** Challenging aspect detected - Tightening stoploss')
return 0.02 # Tighten to 2%
if current_candle['rsi'] < self.tsl_enable.value:
for stop5 in self.tsl_target5.range:
if current_profit > stop5:
for stop5a in self.ts5.range:
self.dp.send_msg(f'*** {pair} *** Profit: {current_profit} - lvl5 {stop5}/{stop5a} activated')
return stop5a
for stop4 in self.tsl_target4.range:
if current_profit > stop4:
for stop4a in self.ts4.range:
self.dp.send_msg(f'*** {pair} *** Profit {current_profit} - lvl4 {stop4}/{stop4a} activated')
return stop4a
for stop3 in self.tsl_target3.range:
if current_profit > stop3:
for stop3a in self.ts3.range:
self.dp.send_msg(f'*** {pair} *** Profit {current_profit} - lvl3 {stop3}/{stop3a} activated')
return stop3a
for stop2 in self.tsl_target2.range:
if current_profit > stop2:
for stop2a in self.ts2.range:
self.dp.send_msg(f'*** {pair} *** Profit {current_profit} - lvl2 {stop2}/{stop2a} activated')
return stop2a
for stop1 in self.tsl_target1.range:
if current_profit > stop1:
for stop1a in self.ts1.range:
self.dp.send_msg(f'*** {pair} *** Profit {current_profit} - lvl1 {stop1}/{stop1a} activated')
return stop1a
for stop0 in self.tsl_target0.range:
if current_profit > stop0:
for stop0a in self.ts0.range:
self.dp.send_msg(f'*** {pair} *** Profit {current_profit} - lvl0 {stop0}/{stop0a} activated')
return stop0a
else:
for stop0 in self.tsl_target0.range:
if current_profit > stop0:
self.dp.send_msg(f'*** {pair} *** Profit {current_profit} SWINGING FOR THE MOON!!!')
return 0.99
return self.stoploss
def compute_planet_position(self, planet_name, date):
observer = ephem.Observer()
observer.lat = '33.4484' # Phoenix, Arizona
observer.lon = '-112.0740'
planet = getattr(ephem, planet_name)(observer)
planet.compute(date)
return planet.ra, planet.dec
def calculate_angular_separation(self, ra1, ra2):
"""Calculate angular separation between two right ascensions."""
separation = abs(ra1 - ra2)
if separation > np.pi:
separation = 2 * np.pi - separation
return separation * 180 / np.pi # Convert to degrees
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Core Technical Indicators
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['ema_8'] = ta.EMA(dataframe, timeperiod=8)
dataframe['ema_34'] = ta.EMA(dataframe, timeperiod=34)
# VWAP
vwap_low, vwap, vwap_high = VWAPB(dataframe, 20, 1)
dataframe['%-vwap_middleband'] = vwap
dataframe['%-dist_to_vwap'] = get_distance(dataframe['close'], dataframe['%-vwap_middleband'])
# TTM Squeeze
ttm_Squeeze = pta.squeeze(high=dataframe['high'], low=dataframe['low'], close=dataframe["close"], lazybear=True)
dataframe['%-ttm_Squeeze'] = ttm_Squeeze['SQZ_20_2.0_20_1.5_LB']
dataframe['%-squeeze_ON'] = ttm_Squeeze['SQZ_ON']
# Astrological Features
dataframe['e_date'] = dataframe['date'].apply(lambda x: ephem.Date(x.strftime("%Y/%m/%d %H:%M:%S")))
moon_phase = dataframe['e_date'].apply(lambda x: ephem.Moon(x).phase)
dataframe['%-Moon_Phase'] = moon_phase
planets = [
'Sun', 'Moon', 'Mercury', 'Venus', 'Mars',
'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto'
]
# Compute planet positions
for planet_name in planets:
dataframe[f'{planet_name}_ra'], dataframe[f'{planet_name}_dec'] = zip(*dataframe['e_date'].apply(
lambda x: self.compute_planet_position(planet_name, x)
))
dataframe[planet_name] = dataframe['e_date'].apply(
lambda x: ephem.constellation(getattr(ephem, planet_name)(x))[1]
)
dataframe[f'%-{planet_name}_rax'] = dataframe[f'{planet_name}_ra'] * 180 / np.pi
dataframe[f'%-{planet_name}_dec'] = (dataframe[f'{planet_name}_dec'] + 0.5) * 100
# Define aspects with angles and tolerances
aspects = {
'conjunction': (0, 8),
'semi_sextile': (30, 4),
'semi_square': (45, 4),
'sextile': (60, 6),
'square': (90, 8),
'trine': (120, 8),
'sesquiquadrate': (135, 4),
'quincunx': (150, 5),
'opposition': (180, 8)
}
# Calculate aspects for all planet pairs
for i, planet1 in enumerate(planets):
for planet2 in planets[i+1:]:
angle_col = f'{planet1.lower()}_{planet2.lower()}_angle'
dataframe[angle_col] = dataframe.apply(
lambda row: self.calculate_angular_separation(
row[f'%-{planet1}_rax'], row[f'%-{planet2}_rax']
), axis=1
)
for aspect, (angle, orb) in aspects.items():
dataframe[f'{planet1.lower()}_{planet2.lower()}_{aspect}'] = np.where(
(dataframe[angle_col] >= angle - orb) &
(dataframe[angle_col] <= angle + orb), 1, 0
)
# Balsamic Moon
dataframe['balsamic_moon'] = np.where(
(dataframe['%-Moon_Phase'] >= 315) & (dataframe['%-Moon_Phase'] < 360), 1, 0
)
dataframe['aspects'] = [col for col in dataframe.columns if any(
aspect in col for aspect in ['trine', 'sextile', 'conjunction']
)]
return dataframe
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
# Harmonious aspects
harmonious_aspects = [col for col in df.columns if any(
aspect in col for aspect in ['trine', 'sextile', 'conjunction']
)]
# Entry: RSI oversold, close below VWAP, harmonious aspect
df.loc[
(
(df['rsi'] < 40) &
(df['close'] < df['%-vwap_middleband']) &
(df['ema_8'] > df['ema_34']) &
(df[harmonious_aspects].sum(axis=1) > 0) &
(df['balsamic_moon'] == 0) &
(df['volume'] > 0)
),
['enter_long', 'enter_tag']] = (1, 'Oversold with Harmonious Aspect')
return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
# Challenging aspects
challenging_aspects = [col for col in df.columns if any(
aspect in col for aspect in ['square', 'opposition', 'quincunx', 'semi_square', 'sesquiquadrate']
)]
# Exit: RSI overbought or challenging aspect
df.loc[
(
((df['rsi'] > 70) | (df['%-squeeze_ON'] == 1)) &
(df[challenging_aspects].sum(axis=1) > 0) &
(df['volume'] > 0)
),
['exit_long', 'exit_tag']] = (1, 'Overbought or Challenging Aspect')
return df
# Helper Functions
def VWAPB(dataframe, window_size=20, num_of_std=1):
df = dataframe.copy()
df['vwap'] = qtpylib.rolling_vwap(df, window=window_size)
rolling_std = df['vwap'].rolling(window=window_size).std()
df['vwap_low'] = df['vwap'] - (rolling_std * num_of_std)
df['vwap_high'] = df['vwap'] + (rolling_std * num_of_std)
return df['vwap_low'], df['vwap'], df['vwap_high']
def get_distance(p1, p2):
return abs((p1) - (p2))
def celestial_house(planet_name):
house = 0
if planet_name == "Aries":
house = 1
elif planet_name == "Taurus":
house = 2
elif planet_name == "Gemini":
house = 3
elif planet_name == "Cancer":
house = 4
elif planet_name == "Leo":
house = 5
elif planet_name == "Virgo":
house = 6
elif planet_name == "Libra":
house = 7
elif planet_name == "Scorpio":
house = 8
elif planet_name == "Sagittarius":
house = 9
elif planet_name == "Capricorn":
house = 10
elif planet_name == "Aquarius":
house = 11
elif planet_name == "Pisces":
house = 12
return house