Skip to content

Commit e3aab76

Browse files
committed
Allow to use profileID and service feeds
Signed-off-by: Herklos <herklos@drakkar.software>
1 parent 59c3a6a commit e3aab76

File tree

8 files changed

+605
-65
lines changed

8 files changed

+605
-65
lines changed

example.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,28 @@
33
import octobot_script as obs
44

55

6-
async def rsi_test():
6+
async def example():
7+
async def initialize(ctx):
8+
# Compute entries only once per backtest.
9+
closes = await obs.Close(ctx, max_history=True)
10+
times = await obs.Time(ctx, max_history=True, use_close_time=True)
11+
rsi_v = tulipy.rsi(closes, period=ctx.tentacle.trading_config["period"])
12+
delta = len(closes) - len(rsi_v)
13+
# Populate entries with timestamps of candles where RSI is
14+
# bellow the "rsi_value_buy_threshold" configuration.
15+
run_data["entries"] = {
16+
times[index + delta]
17+
for index, rsi_val in enumerate(rsi_v)
18+
if rsi_val < ctx.tentacle.trading_config["rsi_value_buy_threshold"]
19+
}
20+
await obs.plot_indicator(ctx, "RSI", times[delta:], rsi_v, run_data["entries"])
21+
722
async def strategy(ctx):
8-
# Will be called at each candle.
9-
if run_data["entries"] is None:
10-
# Compute entries only once per backtest.
11-
closes = await obs.Close(ctx, max_history=True)
12-
times = await obs.Time(ctx, max_history=True, use_close_time=True)
13-
rsi_v = tulipy.rsi(closes, period=ctx.tentacle.trading_config["period"])
14-
delta = len(closes) - len(rsi_v)
15-
# Populate entries with timestamps of candles where RSI is
16-
# bellow the "rsi_value_buy_threshold" configuration.
17-
run_data["entries"] = {
18-
times[index + delta]
19-
for index, rsi_val in enumerate(rsi_v)
20-
if rsi_val < ctx.tentacle.trading_config["rsi_value_buy_threshold"]
21-
}
22-
await obs.plot_indicator(ctx, "RSI", times[delta:], rsi_v, run_data["entries"])
23+
# Called at each candle.
24+
# Uses pre-computed entries times to enter positions when relevant.
25+
# Also, instantly set take profits and stop losses.
26+
# Position exits could also be set separately.
2327
if obs.current_live_time(ctx) in run_data["entries"]:
24-
# Uses pre-computed entries times to enter positions when relevant.
25-
# Also, instantly set take profits and stop losses.
26-
# Position exists could also be set separately.
2728
await obs.market(ctx, "buy", amount="10%", stop_loss_offset="-15%", take_profit_offset="25%")
2829

2930
# Configuration that will be passed to each run.
@@ -34,12 +35,12 @@ async def strategy(ctx):
3435
}
3536

3637
# Read and cache candle data to make subsequent backtesting runs faster.
37-
data = await obs.get_data("BTC/USDT", "1d", start_timestamp=1505606400)
38+
data = await obs.get_data("BTC/USDT", "1d", start_timestamp=1505606400, social_services=[])
3839
run_data = {
3940
"entries": None,
4041
}
4142
# Run a backtest using the above data, strategy and configuration.
42-
res = await obs.run(data, strategy, config)
43+
res = await obs.run(data, config, initialize_func=initialize, strategy_func=strategy)
4344
print(res.describe())
4445
# Generate and open report including indicators plots
4546
await res.plot(show=True)
@@ -49,4 +50,4 @@ async def strategy(ctx):
4950

5051
# Call the execution of the script inside "asyncio.run" as
5152
# OctoBot-Script runs using the python asyncio framework.
52-
asyncio.run(rsi_test())
53+
asyncio.run(example())

octobot_script/api/data_fetching.py

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,142 @@
1414
# You should have received a copy of the GNU General Public
1515
# License along with OctoBot-Script. If not, see <https://www.gnu.org/licenses/>.
1616

17+
import datetime
18+
1719
import octobot_backtesting.api as backtesting_api
1820
import octobot_commons.symbols as commons_symbols
1921
import octobot_commons.enums as commons_enums
2022
import octobot_trading.enums as trading_enums
2123
import octobot_script.internal.octobot_mocks as octobot_mocks
2224

2325

26+
def _validate_tentacles_source(tentacles_config, profile_id):
27+
if tentacles_config is not None and profile_id is not None:
28+
raise ValueError("Only one of tentacles_config or profile_id can be provided.")
29+
30+
2431
def _ensure_ms_timestamp(timestamp):
2532
if timestamp is None:
2633
return timestamp
2734
if timestamp < 16737955050: # Friday 28 May 2500 07:57:30
2835
return timestamp * 1000
2936

3037

38+
def _yesterday_midnight_ms() -> int:
39+
"""Return today at 00:00:00 UTC (= end of yesterday) in milliseconds.
40+
Used as a stable default end_timestamp so data files collected on the
41+
same calendar day share an identical end boundary and can be cached."""
42+
today_midnight = datetime.datetime.now(datetime.timezone.utc).replace(
43+
hour=0, minute=0, second=0, microsecond=0
44+
)
45+
return int(today_midnight.timestamp() * 1000)
46+
47+
48+
def _resolve_end_timestamp_ms(end_timestamp) -> int:
49+
if end_timestamp is None:
50+
return _yesterday_midnight_ms()
51+
return _ensure_ms_timestamp(end_timestamp)
52+
53+
3154
async def historical_data(symbol, timeframe, exchange="binance", exchange_type=trading_enums.ExchangeTypes.SPOT.value,
32-
start_timestamp=None, end_timestamp=None):
55+
start_timestamp=None, end_timestamp=None, tentacles_config=None, profile_id=None):
56+
_validate_tentacles_source(tentacles_config, profile_id)
3357
symbols = [symbol]
3458
time_frames = [commons_enums.TimeFrames(timeframe)]
59+
start_timestamp_ms = _ensure_ms_timestamp(start_timestamp)
60+
end_timestamp_ms = _resolve_end_timestamp_ms(end_timestamp)
61+
existing_file = await backtesting_api.find_matching_data_file(
62+
exchange_name=exchange,
63+
symbols=symbols,
64+
time_frames=time_frames,
65+
start_timestamp=start_timestamp_ms,
66+
end_timestamp=end_timestamp_ms,
67+
)
68+
if existing_file:
69+
return existing_file
3570
data_collector_instance = backtesting_api.exchange_historical_data_collector_factory(
3671
exchange,
3772
trading_enums.ExchangeTypes(exchange_type),
38-
octobot_mocks.get_tentacles_config(),
73+
octobot_mocks.get_tentacles_config(tentacles_config, profile_id, activate_strategy_tentacles=False),
3974
[commons_symbols.parse_symbol(symbol) for symbol in symbols],
4075
time_frames=time_frames,
41-
start_timestamp=_ensure_ms_timestamp(start_timestamp),
42-
end_timestamp=_ensure_ms_timestamp(end_timestamp)
76+
start_timestamp=start_timestamp_ms,
77+
end_timestamp=end_timestamp_ms,
78+
)
79+
return await backtesting_api.initialize_and_run_data_collector(data_collector_instance)
80+
81+
82+
async def social_historical_data(services: list[str], sources: list[str] | None = None,
83+
symbols: list[str] | None = None, start_timestamp=None, end_timestamp=None,
84+
tentacles_config=None, profile_id=None):
85+
_validate_tentacles_source(tentacles_config, profile_id)
86+
start_timestamp_ms = _ensure_ms_timestamp(start_timestamp)
87+
end_timestamp_ms = _resolve_end_timestamp_ms(end_timestamp)
88+
existing_file = await backtesting_api.find_matching_data_file(
89+
services=services,
90+
symbols=symbols or [],
91+
start_timestamp=start_timestamp_ms,
92+
end_timestamp=end_timestamp_ms,
93+
)
94+
if existing_file:
95+
return existing_file
96+
data_collector_instance = backtesting_api.social_historical_data_collector_factory(
97+
services=services,
98+
tentacles_setup_config=octobot_mocks.get_tentacles_config(
99+
tentacles_config, profile_id, activate_strategy_tentacles=False
100+
),
101+
sources=sources,
102+
symbols=[commons_symbols.parse_symbol(symbol) for symbol in symbols] if symbols else None,
103+
start_timestamp=start_timestamp_ms,
104+
end_timestamp=end_timestamp_ms,
105+
config=octobot_mocks.get_config(),
43106
)
44107
return await backtesting_api.initialize_and_run_data_collector(data_collector_instance)
45108

46109

47110
async def get_data(symbol, time_frame, exchange="binance", exchange_type=trading_enums.ExchangeTypes.SPOT.value,
48-
start_timestamp=None, end_timestamp=None, data_file=None):
49-
data = data_file or \
50-
await historical_data(symbol, timeframe=time_frame, exchange=exchange, exchange_type=exchange_type,
51-
start_timestamp=start_timestamp, end_timestamp=end_timestamp)
111+
start_timestamp=None, end_timestamp=None, data_file=None,
112+
social_data_files: list[str] | None = None, social_services: list[str] | None = None,
113+
social_sources: list[str] | None = None, social_symbols: list[str] | None = None,
114+
tentacles_config=None, profile_id=None):
115+
_validate_tentacles_source(tentacles_config, profile_id)
116+
data_files = [data_file] if data_file else [
117+
await historical_data(
118+
symbol,
119+
timeframe=time_frame,
120+
exchange=exchange,
121+
exchange_type=exchange_type,
122+
start_timestamp=start_timestamp,
123+
end_timestamp=end_timestamp,
124+
tentacles_config=tentacles_config,
125+
profile_id=profile_id,
126+
)
127+
]
128+
129+
if social_data_files is not None:
130+
data_files.extend(social_data_files)
131+
elif profile_id is not None:
132+
social_services = social_services if social_services is not None \
133+
else octobot_mocks.get_activated_social_services(
134+
tentacles_config, profile_id, requested_sources=social_sources
135+
)
136+
if social_services:
137+
for service in social_services:
138+
data_files.append(
139+
await social_historical_data(
140+
[service],
141+
sources=social_sources,
142+
symbols=social_symbols,
143+
start_timestamp=start_timestamp,
144+
end_timestamp=end_timestamp,
145+
tentacles_config=tentacles_config,
146+
profile_id=profile_id,
147+
)
148+
)
149+
52150
return await backtesting_api.create_and_init_backtest_data(
53-
[data],
151+
data_files,
54152
octobot_mocks.get_config(),
55-
octobot_mocks.get_tentacles_config(),
153+
octobot_mocks.get_tentacles_config(tentacles_config, profile_id, activate_strategy_tentacles=False),
56154
use_accurate_price_time_frame=True
57155
)

octobot_script/api/execution.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@
1818
import octobot_script.internal.runners as runners
1919

2020

21-
async def run(backtesting_data, update_func, strategy_config,
22-
enable_logs=False, enable_storage=True):
21+
async def run(backtesting_data, strategy_config,
22+
enable_logs=False, enable_storage=True,
23+
strategy_func=None, initialize_func=None,
24+
tentacles_config=None, profile_id=None):
25+
if tentacles_config is not None and profile_id is not None:
26+
raise ValueError("Only one of tentacles_config or profile_id can be provided.")
2327
if enable_logs:
2428
logging_util.load_logging_config()
2529
return await runners.run(
26-
backtesting_data, update_func, strategy_config,
27-
enable_logs=enable_logs, enable_storage=enable_storage
30+
backtesting_data, strategy_config,
31+
enable_logs=enable_logs, enable_storage=enable_storage,
32+
strategy_func=strategy_func, initialize_func=initialize_func,
33+
tentacles_config=tentacles_config, profile_id=profile_id,
2834
)

0 commit comments

Comments
 (0)