-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathfile_loading_strategy.py
More file actions
200 lines (157 loc) · 7.74 KB
/
file_loading_strategy.py
File metadata and controls
200 lines (157 loc) · 7.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
from datetime import datetime
import json
import time
from typing import Any, Dict, Optional
from freqtrade.persistence.trade_model import Order, Trade
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
import numpy as np
import pandas_ta as pta
from custom_order_form_handler import OrderStatus, StrategyDataHandler, ACTIVE_ORDER_STATUSES_VALUES
from entry_conditions import price_crosses_upward, price_reverses_up, price_under
from dateutil import parser
import threading
import logging
class FileLoadingStrategy(IStrategy):
"""
Strategy class that reads order details from a file and sets strategy variables accordingly.
"""
# Default parameters
stoploss = -1.0
def __init__(self, config) -> None:
"""
Initialize the strategy with the given configuration.
"""
super().__init__(config)
self.strategy_name = self.__class__.__name__
self.order_handler = StrategyDataHandler(
strategy_name=self.strategy_name)
self.monitoring_initialized = False
def input_strategy_data(self, pair: str):
"""
Placeholder method for handling argument input from file data handler.
Implementation will vary based on specific strategy.
"""
raise NotImplementedError
def set_entry_signal(self, pair: str, dataframe: DataFrame, data: Dict[str, Any]):
"""
Placeholder method for setting entry signal.
Implementation will vary based on specific strategy.
"""
raise NotImplementedError
def does_pair_have_data(self, pair) -> bool:
data = self.order_handler.read_strategy_data()
return pair in data
def get_pair_data(self, pair) -> Dict[str, Any]:
if not self.does_pair_have_data(pair):
raise LookupError(f"{pair} doesn't have data yet!")
return self.order_handler.read_strategy_data()[pair]
def does_pair_have_active_order(self, pair) -> bool:
if self.does_pair_have_data(pair):
data = self.get_pair_data(pair)
if data['status'] in ACTIVE_ORDER_STATUSES_VALUES:
return True
return False
def get_dfile_arg(self, pair, key):
data = self.get_pair_data(pair)
if key in data:
return data[key]
return None
def set_dfile_arg(self, pair, key, value):
data = self.order_handler.read_strategy_data()
if not self.does_pair_have_data(pair):
data[pair] = {}
data[pair][key] = value
self.order_handler.save_strategy_data(data)
def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
if not self.monitoring_initialized:
self.start_monitoring()
self.monitoring_initialized = True
def start_monitoring(self):
monitor_thread = threading.Thread(target=self.monitor_entry_conditions)
monitor_thread.start()
# Ensure last_prices is a list in monitor_entry_conditions
def monitor_entry_conditions(self):
while True:
strategy_data = self.order_handler.read_strategy_data()
for pair, data in strategy_data.items():
if data['status'] == OrderStatus.WAITING.value:
condition_type = data['entry_condition']
entry_condition_timeout = data['entry_condition_timeout']
entry_condition_price = data.get('entry_condition_price')
threshold_pct = data.get('threshold_pct', 0.15)
ma_type = data.get('ma_type', 'EMA')
period = data.get('period', 14)
# USING EMA/HMA NOT CLOSE!
last_prices = data.get('prices', [])
# Check if prices is a LIST filled with price data!
if not isinstance(last_prices, list) or len(last_prices) < 10 or not all(isinstance(price, (int, float)) for price in last_prices):
logging.error(f"Invalid data type or insufficient data in last_prices: {type(last_prices)} for {pair}")
continue
# Check the condition
if condition_type == 'PriceReversesUpCondition':
is_satisfied = price_reverses_up(
last_prices=last_prices,
period=period,
threshold_pct=threshold_pct,
)
elif condition_type == 'PriceCrossesUpwardCondition':
if not entry_condition_price:
logging.error(f"Missing entry_condition_price for pair {pair}: {data}")
continue
is_satisfied = price_crosses_upward(
entry_condition_price,
last_prices=last_prices
)
elif condition_type == 'PriceUnderCondition':
if not entry_condition_price:
logging.error(f"Missing entry_condition_price for pair {pair}: {data}")
continue
is_satisfied = price_under(
entry_condition_price,
last_prices=last_prices
)
else:
logging.error(f"Invalid condition type for pair {pair}: {data}")
continue
if is_satisfied:
data['status'] = OrderStatus.PENDING.value
if entry_condition_timeout and datetime.now() >= parser.parse(entry_condition_timeout):
data['status'] = OrderStatus.CANCELED.value
self.order_handler.update_strategy_data(pair, data)
# SLEEP 2x per min!
time.sleep(31)
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
pair = metadata['pair']
last_candle = dataframe.iloc[-1].squeeze()
price = last_candle['close']
strategy_data = self.order_handler.read_strategy_data()
# only enter if PENDING pair!
if pair in strategy_data and strategy_data[pair]['status'] == OrderStatus.PENDING.value:
# Enter trade freqtrade bot
self.set_entry_signal(pair, dataframe, strategy_data[pair])
# write variabels to saved log
strategy_data[pair]['status'] = OrderStatus.HOLDING.value
strategy_data[pair]['entry_price'] = price
# write to file
self.order_handler.update_strategy_data(pair, strategy_data[pair])
else:
self.set_no_entry(dataframe)
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return dataframe
def set_no_entry(self, dataframe):
dataframe.loc[dataframe.index[-1],
['enter_long', 'enter_tag']] = (0, "no_enter")
def set_no_exit(self, dataframe):
dataframe.loc[dataframe.index[-1],
['exit_long', 'exit_tag']] = (0, "no_exit")
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:
default_stake = 10 # $10 if no stake is found
try:
return self.get_dfile_arg(pair, 'stake_amount')
except ValueError as e:
print(f"Error: {e}")
return default_stake