forked from nautechsystems/nautilus_trader
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Multi-Instrument Rotation Trading Example with Controller for Bin…
…ance
- Loading branch information
1 parent
24dd6f9
commit 8508cfe
Showing
4 changed files
with
651 additions
and
0 deletions.
There are no files selected for viewing
132 changes: 132 additions & 0 deletions
132
examples/live/binance/binance_futures_testnet_ema_cross_with_instrument_selector.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
#!/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 decimal import Decimal | ||
from datetime import datetime | ||
|
||
from nautilus_trader.adapters.binance.common.enums import BinanceAccountType | ||
from nautilus_trader.adapters.binance.config import ( | ||
BinanceDataClientConfig, | ||
BinanceExecClientConfig, | ||
) | ||
from nautilus_trader.adapters.binance.factories import ( | ||
BinanceLiveDataClientFactory, | ||
BinanceLiveExecClientFactory, | ||
) | ||
from nautilus_trader.cache.config import CacheConfig | ||
from nautilus_trader.config import ( | ||
ImportableControllerConfig, | ||
InstrumentProviderConfig, | ||
LiveExecEngineConfig, | ||
LoggingConfig, | ||
TradingNodeConfig, | ||
) | ||
from nautilus_trader.live.node import TradingNode | ||
from nautilus_trader.model.data import BarType | ||
from nautilus_trader.model.identifiers import InstrumentId, 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_insturment_selector_controller:BinanceFutureInstrumentSelectorController", | ||
config_path="nautilus_trader.examples.strategies.simple_insturment_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() |
121 changes: 121 additions & 0 deletions
121
nautilus_trader/examples/strategies/simple_binance_symbols_filter.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# ------------------------------------------------------------------------------------------------- | ||
# 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, timedelta | ||
from typing import Optional, Tuple | ||
|
||
import pandas as pd | ||
import requests | ||
|
||
|
||
def extract_symbol_info(api_url: str = "https://fapi.binance.com/fapi/v1/exchangeInfo") -> Tuple[Optional[pd.DataFrame], Optional[str]]: | ||
"""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) -> 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 |
Oops, something went wrong.