Skip to content

Commit

Permalink
Add Multi-Instrument Rotation Trading Example with Controller for Bin…
Browse files Browse the repository at this point in the history
…ance
  • Loading branch information
graceyangfan committed Sep 14, 2024
1 parent 24dd6f9 commit 8508cfe
Show file tree
Hide file tree
Showing 4 changed files with 651 additions and 0 deletions.
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 nautilus_trader/examples/strategies/simple_binance_symbols_filter.py
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
Loading

0 comments on commit 8508cfe

Please sign in to comment.