Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Multi-Instrument Rotation Trading Example with Controller for Binance #1939

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
# -------------------------------------------------------------------------------------------------
# Copyright (C) 2015-2024 Nautech Systems Pty Ltd. All rights reserved.
# https://nautechsystems.io
#
# Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -------------------------------------------------------------------------------------------------


from datetime import datetime

from nautilus_trader.adapters.binance.common.enums import BinanceAccountType
from nautilus_trader.adapters.binance.config import BinanceDataClientConfig
from nautilus_trader.adapters.binance.config import BinanceExecClientConfig
from nautilus_trader.adapters.binance.factories import BinanceLiveDataClientFactory
from nautilus_trader.adapters.binance.factories import BinanceLiveExecClientFactory
from nautilus_trader.cache.config import CacheConfig
from nautilus_trader.config import ImportableControllerConfig
from nautilus_trader.config import InstrumentProviderConfig
from nautilus_trader.config import LiveExecEngineConfig
from nautilus_trader.config import LoggingConfig
from nautilus_trader.config import TradingNodeConfig
from nautilus_trader.live.node import TradingNode
from nautilus_trader.model.identifiers import TraderId


# *** THIS IS A TEST STRATEGY WITH NO ALPHA ADVANTAGE WHATSOEVER. ***
# *** IT IS NOT INTENDED TO BE USED TO TRADE LIVE WITH REAL MONEY. ***

# Configure the trading node
config_node = TradingNodeConfig(
trader_id=TraderId("TESTER-001"),
logging=LoggingConfig(log_level="INFO"),
exec_engine=LiveExecEngineConfig(
# debug=True,
reconciliation=True,
reconciliation_lookback_mins=1440,
# snapshot_orders=True,
# snapshot_positions=True,
# snapshot_positions_interval_secs=5.0,
),
cache=CacheConfig(
# database=DatabaseConfig(timeout=2),
timestamps_as_iso8601=True,
flush_on_start=False,
),
# message_bus=MessageBusConfig(
# database=DatabaseConfig(timeout=2),
# timestamps_as_iso8601=True,
# use_instance_id=False,
# # types_filter=[QuoteTick],
# stream_per_topic=False,
# external_streams=["bybit"],
# autotrim_mins=30,
# ),
controller=ImportableControllerConfig(
controller_path="nautilus_trader.examples.strategies.simple_instrument_selector_controller:BinanceFutureInstrumentSelectorController",
config_path="nautilus_trader.examples.strategies.simple_instrument_selector_controller:BinanceFutureInstrumentSelectorControllerConfig",
config={
"interval_secs": 3600,
"min_notional_threshold": 6,
"quote_asset": "USDT",
"onboard_date_filter_type": "range",
"onboard_date_reference_date": datetime(2024, 1, 1),
"onboard_date_end_date": datetime(2024, 6, 1),
},
),
data_clients={
"BINANCE": BinanceDataClientConfig(
api_key=None, # 'BINANCE_API_KEY' env var
api_secret=None, # 'BINANCE_API_SECRET' env var
account_type=BinanceAccountType.USDT_FUTURE,
base_url_http=None, # Override with custom endpoint
base_url_ws=None, # Override with custom endpoint
us=False, # If client is for Binance US
testnet=True, # If client uses the testnet
instrument_provider=InstrumentProviderConfig(load_all=True),
),
},
exec_clients={
"BINANCE": BinanceExecClientConfig(
api_key=None, # 'BINANCE_API_KEY' env var
api_secret=None, # 'BINANCE_API_SECRET' env var
account_type=BinanceAccountType.USDT_FUTURE,
base_url_http=None, # Override with custom endpoint
base_url_ws=None, # Override with custom endpoint
us=False, # If client is for Binance US
testnet=True, # If client uses the testnet
instrument_provider=InstrumentProviderConfig(load_all=True),
use_position_ids=False,
max_retries=3,
retry_delay=1.0,
),
},
timeout_connection=30.0,
timeout_reconciliation=10.0,
timeout_portfolio=10.0,
timeout_disconnection=10.0,
timeout_post_stop=5.0,
)

# Instantiate the node with a configuration
node = TradingNode(config=config_node)

# Register your client factories with the node (can take user-defined factories)
node.add_data_client_factory("BINANCE", BinanceLiveDataClientFactory)
node.add_exec_client_factory("BINANCE", BinanceLiveExecClientFactory)
node.build()


# Stop and dispose of the node with SIGINT/CTRL+C
if __name__ == "__main__":
try:
node.run()
finally:
node.dispose()
143 changes: 143 additions & 0 deletions nautilus_trader/examples/strategies/simple_binance_symbols_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# -------------------------------------------------------------------------------------------------
# Copyright (C) 2015-2024 Nautech Systems Pty Ltd. All rights reserved.
# https://nautechsystems.io
#
# Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -------------------------------------------------------------------------------------------------

import json
from datetime import datetime

import pandas as pd
import requests


def extract_symbol_info(
api_url: str = "https://fapi.binance.com/fapi/v1/exchangeInfo",
) -> tuple[pd.DataFrame | None, str | None]:
"""
Fetch and extract symbol info from Binance Futures API and return it as a DataFrame.

Args:
api_url (str): The API URL to fetch data from (default: Binance Futures API).

Returns:
Tuple containing the DataFrame and an optional error message.

"""
try:
# Send a GET request to the Binance API
response = requests.get(api_url)
response.raise_for_status()

# Load the JSON response
json_data = response.json()

# Extract symbols data from JSON
symbols = json_data["symbols"]
df = pd.DataFrame(symbols)

# Filter DataFrame for 'PERPETUAL' contract type and 'TRADING' status
df = df[(df["contractType"] == "PERPETUAL") & (df["status"] == "TRADING")]

return df, None

except requests.exceptions.RequestException as e:
return None, f"Request error: {e}"
except json.JSONDecodeError:
return None, "Error decoding JSON"
except KeyError as e:
return None, f"Unexpected JSON structure, missing key: {e}"


def select_with_quoteAsset(df: pd.DataFrame, quoteAsset: str) -> pd.DataFrame:
"""
Filter DataFrame for products with a specific quote asset.

Args:
df (pd.DataFrame): The DataFrame to filter.
quoteAsset (str): The quote asset to filter by.

Returns:
pd.DataFrame: Filtered DataFrame.

"""
return df[df["quoteAsset"] == quoteAsset]


def select_with_min_notional(df: pd.DataFrame, min_notional_threshold: float) -> pd.DataFrame:
"""
Add a min_notional column to the DataFrame and filter it based on a threshold.

Args:
df (pd.DataFrame): The DataFrame to process.
min_notional_threshold (float): The threshold to filter min_notional values.

Returns:
pd.DataFrame: DataFrame with min_notional columns and filtered values.

"""
# Extract 'MIN_NOTIONAL' from filters and set it in the DataFrame
df = df.copy()
df["min_notional"] = df["filters"].apply(
lambda x: next(
(
float(filter_item.get("notional", 0))
for filter_item in x
if filter_item.get("filterType") == "MIN_NOTIONAL"
),
0,
),
)
# Filter the DataFrame where min_notional is less than the threshold
return df[df["min_notional"] < min_notional_threshold]


def filter_with_onboard_date(
df: pd.DataFrame,
filter_type: str,
reference_date: datetime,
end_date: datetime | None = None,
) -> pd.DataFrame:
"""
Filter the DataFrame based on onboardDate according to the specified criteria.

Args:
df (pd.DataFrame): The DataFrame containing the onboardDate column.
filter_type (str): The type of filter to apply. Can be 'before', 'range', or 'after'.
reference_date (datetime): The date to use for filtering.
end_date (datetime, optional): The end date for 'range' filter type. Required if filter_type is 'range'.

Returns:
pd.DataFrame: Filtered DataFrame based on the specified filter type and dates.

"""
# Convert onboardDate from milliseconds timestamp to datetime
df = df.copy()
df["onboardDate_convert"] = pd.to_datetime(df["onboardDate"], unit="ms")

if filter_type == "before":
# Filter the DataFrame for onboardDate earlier than the reference date
filtered_df = df[df["onboardDate_convert"] < reference_date]
elif filter_type == "range":
if end_date is None:
raise ValueError("end_date must be provided for 'range' filter type.")
# Filter the DataFrame for onboardDate within the specified date range
filtered_df = df[
(df["onboardDate_convert"] >= reference_date) & (df["onboardDate_convert"] <= end_date)
]
elif filter_type == "after":
# Filter the DataFrame for onboardDate later than the reference date
filtered_df = df[df["onboardDate_convert"] > reference_date]
else:
raise ValueError("filter_type must be one of 'before', 'range', or 'after'.")

return filtered_df
Loading
Loading