768 lines
33 KiB
Python
768 lines
33 KiB
Python
import time
|
||
import threading
|
||
import json
|
||
import os
|
||
from datetime import datetime
|
||
from market import (
|
||
market_info_loop,
|
||
get_latest_price,
|
||
get_all_latest_prices,
|
||
get_latest_price_with_timestamp,
|
||
get_binance_price_for_market,
|
||
get_price_comparison,
|
||
get_market_arbitrage_opportunities
|
||
)
|
||
from order import create_market_order, create_trigger_order, update_trigger_order, cancel_trigger_order, list_trigger_orders
|
||
from position import list_open_positions
|
||
from flextrade.constants.markets import BASE_MARKET_ETH_USD, BASE_MARKET_SOL_USD, BASE_MARKET_BTC_USD
|
||
from flextrade.enum import Action
|
||
|
||
# Validate Action enum is properly imported
|
||
print(f"✅ Action enum imported: BUY={Action.BUY} (boolean), SELL={Action.SELL} (boolean)")
|
||
|
||
# Configuration
|
||
ENABLE_AUTO_HEDGE = True # Set to True to enable automatic hedging
|
||
ENABLE_TRIGGER_ORDERS = True # Set to True to enable trigger order functionality
|
||
ORDER_TRACKING_FILE = "opened_trigger_orders.json" # File to store all opened trigger orders
|
||
|
||
# Single coin configuration
|
||
MONITORED_COIN = "SOL" # Change this to monitor different coins: SOL, ETH, BTC, etc.
|
||
MONITORED_MARKET_NAME = "SOLUSD" # Market name for price lookup
|
||
MONITORED_MARKET_CONSTANT = BASE_MARKET_SOL_USD # Market constant for orders
|
||
SUB_ACCOUNT_ID = 1 # Example sub-account ID for testing
|
||
|
||
HEDGE_PRICE_THRESHOLD = 167.75 # Price threshold for auto-hedging
|
||
HEDGE_SIZE = 500.0 # Size of the hedge order
|
||
HEDGE_PRICE_SL = 1.0 # Stop-loss threshold for hedging
|
||
|
||
# Global storage for active orders
|
||
active_orders = {
|
||
"trigger_orders": [], # List of {order_id, market, account_id, action, size, trigger_price}
|
||
"market_orders": [] # List of market order transactions
|
||
}
|
||
|
||
# Track executed hedges to prevent duplicates
|
||
executed_hedges = {
|
||
"hedge_executed": False,
|
||
"last_hedge_price": 0,
|
||
"initial_position_check_done": False
|
||
}
|
||
|
||
def save_last_trigger_order_id(order_id, account_id, market, action, size, trigger_price):
|
||
"""
|
||
Save a new trigger order to the opened orders JSON file
|
||
|
||
Args:
|
||
order_id (int): Order ID
|
||
account_id (int): Account ID
|
||
market (str): Market constant
|
||
action (Action): Buy/Sell action
|
||
size (float): Order size
|
||
trigger_price (float): Trigger price
|
||
"""
|
||
try:
|
||
# Load existing orders
|
||
existing_orders = load_opened_trigger_orders_data()
|
||
if existing_orders is None:
|
||
existing_orders = {"opened_orders": []}
|
||
|
||
# Create new order data
|
||
new_order = {
|
||
"order_id": order_id,
|
||
"account_id": account_id,
|
||
"market": str(market),
|
||
"action": safe_action_to_string(action),
|
||
"size": size,
|
||
"trigger_price": trigger_price,
|
||
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||
"coin": MONITORED_COIN
|
||
}
|
||
|
||
# Check if order already exists (prevent duplicates)
|
||
existing_order_ids = [order.get("order_id") for order in existing_orders.get("opened_orders", [])]
|
||
if order_id not in existing_order_ids:
|
||
existing_orders["opened_orders"].append(new_order)
|
||
|
||
# Save updated orders list
|
||
with open(ORDER_TRACKING_FILE, 'w') as f:
|
||
json.dump(existing_orders, f, indent=2)
|
||
|
||
print(f"💾 Added trigger order ID {order_id} to {ORDER_TRACKING_FILE}")
|
||
print(f" Total opened orders: {len(existing_orders['opened_orders'])}")
|
||
else:
|
||
print(f"ℹ️ Order ID {order_id} already exists in {ORDER_TRACKING_FILE}")
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error saving trigger order ID: {e}")
|
||
|
||
def load_opened_trigger_orders_data():
|
||
"""
|
||
Load all opened trigger orders from JSON file
|
||
|
||
Returns:
|
||
dict: Orders data or None if file doesn't exist or error
|
||
"""
|
||
try:
|
||
if not os.path.exists(ORDER_TRACKING_FILE):
|
||
print(f"ℹ️ No existing order tracking file found: {ORDER_TRACKING_FILE}")
|
||
return None
|
||
|
||
with open(ORDER_TRACKING_FILE, 'r') as f:
|
||
orders_data = json.load(f)
|
||
|
||
return orders_data
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error loading trigger orders: {e}")
|
||
return None
|
||
|
||
def load_last_trigger_order_id():
|
||
"""
|
||
Load and display all opened trigger orders from JSON file
|
||
|
||
Returns:
|
||
dict: Orders data or None if file doesn't exist or error
|
||
"""
|
||
try:
|
||
orders_data = load_opened_trigger_orders_data()
|
||
if not orders_data:
|
||
return None
|
||
|
||
opened_orders = orders_data.get("opened_orders", [])
|
||
|
||
print(f"📂 Loaded opened trigger orders from {ORDER_TRACKING_FILE}:")
|
||
print(f" Total active orders: {len(opened_orders)}")
|
||
|
||
if opened_orders:
|
||
print(" Order details:")
|
||
for i, order in enumerate(opened_orders, 1):
|
||
print(f" {i}. ✅ Order ID: {order.get('order_id', 'N/A')}")
|
||
print(f" Account: {order.get('account_id', 'N/A')} | Market: {order.get('market', 'N/A')}")
|
||
print(f" Action: {order.get('action', 'N/A')} | Size: {order.get('size', 'N/A')}")
|
||
print(f" Trigger Price: ${order.get('trigger_price', 0):.2f}")
|
||
print(f" Created: {order.get('created_at', 'N/A')} | Coin: {order.get('coin', 'N/A')}")
|
||
if i < len(opened_orders): # Don't add extra line after last order
|
||
print()
|
||
|
||
return orders_data
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error loading trigger orders: {e}")
|
||
return None
|
||
|
||
def clear_last_trigger_order_file():
|
||
"""
|
||
Clear the opened trigger orders tracking file
|
||
"""
|
||
try:
|
||
if os.path.exists(ORDER_TRACKING_FILE):
|
||
os.remove(ORDER_TRACKING_FILE)
|
||
print(f"🗑️ Cleared order tracking file: {ORDER_TRACKING_FILE}")
|
||
else:
|
||
print(f"ℹ️ Order tracking file doesn't exist: {ORDER_TRACKING_FILE}")
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error clearing order tracking file: {e}")
|
||
|
||
def mark_order_as_cancelled(order_id):
|
||
"""
|
||
Remove a cancelled order from the tracking file
|
||
|
||
Args:
|
||
order_id (int): Order ID to remove
|
||
"""
|
||
try:
|
||
orders_data = load_opened_trigger_orders_data()
|
||
if not orders_data:
|
||
print(f"ℹ️ No orders data found to update")
|
||
return False
|
||
|
||
opened_orders = orders_data.get("opened_orders", [])
|
||
original_count = len(opened_orders)
|
||
|
||
# Remove the order with the matching order_id
|
||
updated_orders = [order for order in opened_orders if order.get("order_id") != order_id]
|
||
|
||
if len(updated_orders) < original_count:
|
||
# Order was found and removed
|
||
orders_data["opened_orders"] = updated_orders
|
||
|
||
# Save updated orders list
|
||
with open(ORDER_TRACKING_FILE, 'w') as f:
|
||
json.dump(orders_data, f, indent=2)
|
||
|
||
print(f"✅ Removed cancelled order ID {order_id} from {ORDER_TRACKING_FILE}")
|
||
print(f" Remaining orders: {len(updated_orders)}")
|
||
return True
|
||
else:
|
||
print(f"⚠️ Order ID {order_id} not found in tracking file")
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error removing cancelled order: {e}")
|
||
return False
|
||
|
||
def get_active_orders_from_file():
|
||
"""
|
||
Get list of active orders from the tracking file
|
||
|
||
Returns:
|
||
list: List of active orders
|
||
"""
|
||
try:
|
||
orders_data = load_opened_trigger_orders_data()
|
||
if not orders_data:
|
||
return []
|
||
|
||
# All orders in the file are active since cancelled ones are removed
|
||
opened_orders = orders_data.get("opened_orders", [])
|
||
|
||
return opened_orders
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error getting active orders: {e}")
|
||
return []
|
||
|
||
def check_existing_positions_for_hedges():
|
||
"""
|
||
Check existing open positions and mark appropriate hedges as executed
|
||
to prevent duplicate hedge orders for the monitored coin.
|
||
Also closes all existing trigger orders and creates new stop-loss orders for hedged positions.
|
||
"""
|
||
try:
|
||
print(f"🔍 Checking existing positions for {MONITORED_COIN} hedge requirements...")
|
||
|
||
# First, close all existing trigger orders (if method is available)
|
||
print("🧹 Attempting to close existing trigger orders...")
|
||
# Load active trigger orders from file
|
||
active_orders_in_file = get_active_orders_from_file()
|
||
if active_orders_in_file:
|
||
print(f"🗑️ Found {len(active_orders_in_file)} active trigger orders to cancel...")
|
||
else:
|
||
print("ℹ️ No active trigger orders found in file.")
|
||
|
||
for account_id in [SUB_ACCOUNT_ID]:
|
||
# Cancel each active trigger order found in the file
|
||
for order in active_orders_in_file:
|
||
order_id = order.get("order_id")
|
||
print(f"🗑️ Attempting to cancel trigger order ID: {order_id}")
|
||
try:
|
||
cancel_result = handle_cancel_trigger_order(account_id, MONITORED_MARKET_CONSTANT, order_id)
|
||
if cancel_result:
|
||
print(f"✅ Successfully cancelled order {order_id}")
|
||
# Remove from file after successful cancellation
|
||
mark_order_as_cancelled(order_id)
|
||
else:
|
||
print(f"❌ Failed to cancel order {order_id}")
|
||
except Exception as e:
|
||
print(f"⚠️ Error cancelling trigger order {order_id}: {e}")
|
||
time.sleep(8) # Delay between cancellations
|
||
|
||
print("⏳ Brief wait before position check...")
|
||
time.sleep(3)
|
||
# Now check positions for hedge requirements
|
||
hedge_positions_found = []
|
||
for account_id in [SUB_ACCOUNT_ID]:
|
||
positions = list_open_positions(account_id, delay=2)
|
||
|
||
if positions:
|
||
print(f"📊 Found {len(positions)} open position(s) in account {account_id}")
|
||
|
||
for position in positions:
|
||
market = position.get("market", "")
|
||
size = position.get("position_size", 0)
|
||
entry_price = position.get("avg_entry_price", 0)
|
||
orderIndex = position.get("orderIndex", 0)
|
||
direction = "LONG" if size > 0 else "SHORT"
|
||
|
||
print(f" - {market} ({direction}): Size={size:.4f}, Entry=${entry_price:.2f}, orderIndex={orderIndex}")
|
||
|
||
# Check hedge requirements for monitored coin
|
||
if MONITORED_MARKET_NAME in market and size > 0 and entry_price > HEDGE_PRICE_THRESHOLD:
|
||
print(f"✅ {MONITORED_COIN} hedge requirement met: Position size {size:.4f} at ${entry_price:.2f} (above ${HEDGE_PRICE_THRESHOLD})")
|
||
executed_hedges["hedge_executed"] = True
|
||
executed_hedges["last_hedge_price"] = entry_price
|
||
|
||
# Store hedge position info for stop-loss creation
|
||
hedge_positions_found.append({
|
||
'account_id': account_id,
|
||
'size': size,
|
||
'entry_price': entry_price
|
||
})
|
||
|
||
# Add delay between account checks to avoid rate limiting
|
||
time.sleep(3)
|
||
|
||
# Create stop-loss trigger orders for hedged positions
|
||
if hedge_positions_found:
|
||
print(f"\n🛡️ Creating stop-loss orders for {len(hedge_positions_found)} hedged position(s)...")
|
||
|
||
for hedge_pos in hedge_positions_found:
|
||
account_id = hedge_pos['account_id']
|
||
size = hedge_pos['size']
|
||
entry_price = hedge_pos['entry_price']
|
||
|
||
# Calculate stop-loss price: entry_price - HEDGE_PRICE_SL
|
||
stop_loss_price = entry_price - HEDGE_PRICE_SL
|
||
|
||
print(f"📉 Creating stop-loss order for position:")
|
||
print(f" Account: {account_id}, Size: {size:.4f}")
|
||
print(f" Entry Price: ${entry_price:.2f}")
|
||
print(f" Stop-Loss Price: ${stop_loss_price:.2f} (Entry - ${HEDGE_PRICE_SL})")
|
||
print(f" orderIndex: {hedge_pos.get('orderIndex', 'N/A')}")
|
||
|
||
# Retry logic for creating stop-loss trigger order
|
||
max_retries = 3
|
||
retry_delay = 5
|
||
trigger_result = None
|
||
|
||
for attempt in range(max_retries):
|
||
try:
|
||
print(f"🔄 Attempt {attempt + 1}/{max_retries} to create stop-loss order...")
|
||
|
||
# Create stop-loss trigger order (SELL when price goes below stop-loss price)
|
||
trigger_result = handle_trigger_order(
|
||
account_id=account_id,
|
||
market=MONITORED_MARKET_CONSTANT,
|
||
action=Action.SELL,
|
||
size=size,
|
||
trigger_price=stop_loss_price,
|
||
trigger_above=False # Trigger when price goes BELOW stop-loss price
|
||
)
|
||
|
||
if trigger_result:
|
||
print(f"✅ Stop-loss trigger order created successfully!")
|
||
break
|
||
else:
|
||
print(f"❌ Failed to create stop-loss trigger order (attempt {attempt + 1})")
|
||
|
||
except Exception as e:
|
||
print(f"❌ Exception during stop-loss creation (attempt {attempt + 1}): {e}")
|
||
|
||
# Wait before retry if not the last attempt
|
||
if attempt < max_retries - 1:
|
||
wait_time = retry_delay * (attempt + 1)
|
||
print(f"⏳ Waiting {wait_time} seconds before retry...")
|
||
time.sleep(wait_time)
|
||
|
||
if not trigger_result:
|
||
print(f"❌ Failed to create stop-loss trigger order after {max_retries} attempts")
|
||
|
||
# Add longer delay between different positions
|
||
time.sleep(5)
|
||
|
||
executed_hedges["initial_position_check_done"] = True
|
||
print("✅ Initial position check completed")
|
||
|
||
# Show current hedge status
|
||
print(f"\n📋 {MONITORED_COIN} Hedge Status Summary:")
|
||
print(f" {MONITORED_COIN} Hedge: {'✅ Executed' if executed_hedges['hedge_executed'] else '❌ Not executed'}")
|
||
if executed_hedges["hedge_executed"]:
|
||
print(f" Last Hedge Price: ${executed_hedges['last_hedge_price']:.2f}")
|
||
print(f" Stop-Loss Orders: {'✅ Created' if hedge_positions_found else '❌ None needed'}")
|
||
print()
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error checking existing positions: {e}")
|
||
executed_hedges["initial_position_check_done"] = True # Mark as done even on error to prevent infinite retries
|
||
|
||
def start_market_data_thread(debug=True, include_binance=True):
|
||
"""Start market data collection in a separate thread"""
|
||
market_thread = threading.Thread(target=lambda: market_info_loop(debug, include_binance), daemon=True)
|
||
market_thread.start()
|
||
return market_thread
|
||
|
||
def store_trigger_order(result, market, account_id, action, size, trigger_price):
|
||
"""Store trigger order information for later management"""
|
||
if result and 'order' in result and 'orderIndex' in result['order']:
|
||
order_id = result['order']['orderIndex']
|
||
order_info = {
|
||
'order_id': order_id,
|
||
'market': market,
|
||
'account_id': account_id,
|
||
'action': action,
|
||
'size': size,
|
||
'trigger_price': trigger_price,
|
||
'tx_hash': result['tx'].hex(),
|
||
'created_at': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
}
|
||
active_orders["trigger_orders"].append(order_info)
|
||
print(f"📝 Stored trigger order info: ID {order_id}")
|
||
|
||
# Save to file for persistence
|
||
save_last_trigger_order_id(order_id, account_id, market, action, size, trigger_price)
|
||
|
||
return order_info
|
||
return None
|
||
|
||
def get_active_trigger_orders():
|
||
"""Get list of all active trigger orders"""
|
||
return active_orders["trigger_orders"].copy()
|
||
|
||
def remove_trigger_order(order_id):
|
||
"""Remove a trigger order from active list and remove from file"""
|
||
active_orders["trigger_orders"] = [
|
||
order for order in active_orders["trigger_orders"]
|
||
if order['order_id'] != order_id
|
||
]
|
||
print(f"📝 Removed trigger order ID {order_id} from active list")
|
||
|
||
# Also remove from the persistent file
|
||
mark_order_as_cancelled(order_id)
|
||
|
||
def safe_action_to_string(action):
|
||
"""Convert action to string safely, handling both enum and boolean cases"""
|
||
if hasattr(action, 'name'):
|
||
return action.name
|
||
elif action is True or action == 1:
|
||
return "BUY"
|
||
elif action is False or action == 0:
|
||
return "SELL"
|
||
else:
|
||
return str(action)
|
||
|
||
def handle_market_order(account_id, market, action, size):
|
||
"""
|
||
Handle market order creation with error handling
|
||
|
||
Args:
|
||
account_id (int): Account ID to trade with
|
||
market (str): Market constant (e.g., BASE_MARKET_ETH_USD)
|
||
action (Action): Action.BUY or Action.SELL
|
||
size (float): Order size
|
||
"""
|
||
try:
|
||
print(f"\n--- Creating Market Order ---")
|
||
|
||
# Convert action to string safely
|
||
action_name = safe_action_to_string(action)
|
||
print(f"Account: {account_id}, Market: {market}, Action: {action_name}, Size: {size}")
|
||
|
||
result = create_market_order(account_id, market, action, size)
|
||
|
||
if result:
|
||
print(f"✅ Market order successful!")
|
||
print(f"Transaction: {result['tx'].hex()}")
|
||
else:
|
||
print("❌ Market order failed!")
|
||
|
||
print("--- Order Complete ---\n")
|
||
return result
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error handling market order: {e}")
|
||
return None
|
||
|
||
|
||
def handle_trigger_order(account_id, market, action, size, trigger_price, trigger_above=True):
|
||
"""
|
||
Handle trigger order creation with error handling
|
||
|
||
Args:
|
||
account_id (int): Account ID to trade with
|
||
market (str): Market constant (e.g., BASE_MARKET_ETH_USD)
|
||
action (Action): Action.BUY or Action.SELL
|
||
size (float): Order size
|
||
trigger_price (float): Price at which the order should trigger
|
||
trigger_above (bool): True to trigger when price goes above, False for below
|
||
"""
|
||
try:
|
||
print(f"\n--- Creating Trigger Order ---")
|
||
|
||
# Convert action to string safely
|
||
action_name = safe_action_to_string(action)
|
||
trigger_direction = "above" if trigger_above else "below"
|
||
print(f"Account: {account_id}, Market: {market}, Action: {action_name}, Size: {size}")
|
||
print(f"Trigger: {trigger_direction} ${trigger_price:.2f}")
|
||
|
||
result = create_trigger_order(account_id, market, action, size, trigger_price, trigger_above)
|
||
|
||
if result:
|
||
print(f"✅ Trigger order successful!")
|
||
print(f"Transaction: {result['tx'].hex()}")
|
||
print(f"Order ID: {result['order']['orderIndex']}")
|
||
# Store the order for later management
|
||
store_trigger_order(result, market, account_id, action, size, trigger_price)
|
||
else:
|
||
print("❌ Trigger order failed!")
|
||
|
||
print("--- Order Complete ---\n")
|
||
return result
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error handling trigger order: {e}")
|
||
return None
|
||
|
||
|
||
def handle_update_trigger_order(account_id, order_id, action, size, trigger_price, trigger_above=True):
|
||
"""
|
||
Handle trigger order update with error handling
|
||
|
||
Args:
|
||
account_id (int): Account ID
|
||
order_id (int): Order ID to update
|
||
action (Action): Action.BUY or Action.SELL
|
||
size (float): New order size
|
||
trigger_price (float): New trigger price
|
||
trigger_above (bool): True to trigger when price goes above, False for below
|
||
"""
|
||
try:
|
||
print(f"\n--- Updating Trigger Order ---")
|
||
trigger_direction = "above" if trigger_above else "below"
|
||
action_name = safe_action_to_string(action)
|
||
print(f"Order ID: {order_id}, Action: {action_name}, Size: {size}")
|
||
print(f"New trigger: {trigger_direction} ${trigger_price:.2f}")
|
||
|
||
result = update_trigger_order(account_id, order_id, action, size, trigger_price, trigger_above)
|
||
|
||
if result:
|
||
print(f"✅ Trigger order updated successfully!")
|
||
print(f"Transaction: {result['tx'].hex()}")
|
||
else:
|
||
print("❌ Trigger order update failed!")
|
||
|
||
print("--- Update Complete ---\n")
|
||
return result
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error updating trigger order: {e}")
|
||
return None
|
||
|
||
|
||
def handle_cancel_trigger_order(account_id, market, order_id):
|
||
"""
|
||
Handle trigger order cancellation with error handling
|
||
|
||
Args:
|
||
account_id (int): Account ID
|
||
market (str): Market constant
|
||
order_id (int): Order ID to cancel
|
||
"""
|
||
try:
|
||
print(f"\n--- Cancelling Trigger Order ---")
|
||
print(f"Order ID: {order_id}, Market: {market}")
|
||
|
||
result = cancel_trigger_order(account_id, market, order_id)
|
||
|
||
if result:
|
||
print(f"✅ Trigger order cancelled successfully!")
|
||
print(f"Transaction: {result['tx'].hex()}")
|
||
# Remove from active orders list
|
||
remove_trigger_order(order_id)
|
||
else:
|
||
print("❌ Trigger order cancellation failed!")
|
||
|
||
print("--- Cancellation Complete ---\n")
|
||
return result
|
||
|
||
except Exception as e:
|
||
print(f"❌ Error cancelling trigger order: {e}")
|
||
return None
|
||
|
||
|
||
def smart_market_order(market_name, market_constant, action, size, price_threshold=None):
|
||
"""
|
||
Create a market order with optional price threshold checking
|
||
|
||
Args:
|
||
market_name (str): Market name for price lookup (e.g., "ETHUSD")
|
||
market_constant (str): Market constant for order (e.g., BASE_MARKET_ETH_USD)
|
||
action (Action): Action.BUY or Action.SELL
|
||
size (float): Order size
|
||
price_threshold (float): Optional price threshold for conditional orders
|
||
|
||
Returns:
|
||
dict: Order result or None if conditions not met
|
||
"""
|
||
current_price_data = get_latest_price_with_timestamp(market_name)
|
||
|
||
if not current_price_data:
|
||
print(f"❌ No price data available for {market_name}")
|
||
return None
|
||
|
||
current_price = current_price_data['price']
|
||
print(f"📊 Current {market_name} price: ${current_price:.2f}")
|
||
|
||
# Check price threshold if provided
|
||
if price_threshold:
|
||
if action == Action.BUY and current_price > price_threshold:
|
||
print(f"⏸️ Price ${current_price:.2f} above buy threshold ${price_threshold:.2f} - order not placed")
|
||
return None
|
||
elif action == Action.SELL and current_price < price_threshold:
|
||
print(f"⏸️ Price ${current_price:.2f} below sell threshold ${price_threshold:.2f} - order not placed")
|
||
return None
|
||
|
||
print(f"✅ Price conditions met, placing order...")
|
||
return handle_market_order(SUB_ACCOUNT_ID, market_constant, action, size)
|
||
|
||
def smart_trigger_order(market_name, market_constant, action, size, trigger_price, trigger_above=True, offset_percentage=None):
|
||
"""
|
||
Create a trigger order with smart pricing based on current market conditions
|
||
|
||
Args:
|
||
market_name (str): Market name for price lookup (e.g., "ETHUSD")
|
||
market_constant (str): Market constant for order (e.g., BASE_MARKET_ETH_USD)
|
||
action (Action): Action.BUY or Action.SELL
|
||
size (float): Order size
|
||
trigger_price (float): Price at which the order should trigger
|
||
trigger_above (bool): True to trigger when price goes above, False for below
|
||
offset_percentage (float): Optional percentage offset from current price for automatic trigger calculation
|
||
|
||
Returns:
|
||
dict: Order result or None if conditions not met
|
||
"""
|
||
current_price_data = get_latest_price_with_timestamp(market_name)
|
||
|
||
if not current_price_data:
|
||
print(f"❌ No price data available for {market_name}")
|
||
return None
|
||
|
||
current_price = current_price_data['price']
|
||
print(f"📊 Current {market_name} price: ${current_price:.2f}")
|
||
|
||
# Calculate trigger price based on offset if provided
|
||
if offset_percentage:
|
||
if trigger_above:
|
||
calculated_trigger = current_price * (1 + offset_percentage / 100)
|
||
else:
|
||
calculated_trigger = current_price * (1 - offset_percentage / 100)
|
||
|
||
print(f"🧮 Calculated trigger price: ${calculated_trigger:.2f} ({offset_percentage:+.1f}% from current)")
|
||
trigger_price = calculated_trigger
|
||
|
||
# Validate trigger price makes sense
|
||
trigger_direction = "above" if trigger_above else "below"
|
||
if trigger_above and trigger_price <= current_price:
|
||
print(f"⚠️ Warning: Trigger price ${trigger_price:.2f} is not above current price ${current_price:.2f}")
|
||
elif not trigger_above and trigger_price >= current_price:
|
||
print(f"⚠️ Warning: Trigger price ${trigger_price:.2f} is not below current price ${current_price:.2f}")
|
||
|
||
print(f"✅ Creating trigger order: {trigger_direction} ${trigger_price:.2f}")
|
||
return handle_trigger_order(SUB_ACCOUNT_ID, market_constant, action, size, trigger_price, trigger_above)
|
||
|
||
|
||
|
||
def main():
|
||
print(f"Starting app - monitoring {MONITORED_COIN} with market data collection...")
|
||
print(f"Monitored Coin: {MONITORED_COIN}")
|
||
print(f"Market: {MONITORED_MARKET_NAME}")
|
||
print(f"Hedge Price Threshold: ${HEDGE_PRICE_THRESHOLD}")
|
||
print(f"Auto-hedge enabled: {ENABLE_AUTO_HEDGE}")
|
||
print("-" * 50)
|
||
|
||
# Load last trigger order information on startup
|
||
print("🔍 Checking for existing opened trigger orders...")
|
||
orders_data = load_last_trigger_order_id()
|
||
if orders_data:
|
||
total_count = len(orders_data.get("opened_orders", []))
|
||
print(f"✅ Found {total_count} active trigger orders for {MONITORED_COIN} coin")
|
||
else:
|
||
print("ℹ️ No existing opened trigger orders found")
|
||
print()
|
||
|
||
# Start market data collection in background thread with Binance comparison
|
||
start_market_data_thread(debug=False, include_binance=True)
|
||
|
||
|
||
# Give some time for initial market data to be collected
|
||
time.sleep(10)
|
||
|
||
try:
|
||
while True:
|
||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
print(f"[{current_time}] update!")
|
||
|
||
# Show some sample prices with timestamps (FlexTrade and Binance)
|
||
monitored_data = get_latest_price_with_timestamp(MONITORED_MARKET_NAME)
|
||
|
||
# Check if we have received first successful price update and haven't checked positions yet
|
||
if (not executed_hedges["initial_position_check_done"] and monitored_data):
|
||
print(f"\n🎯 First successful price update received for {MONITORED_COIN} - checking existing positions...")
|
||
check_existing_positions_for_hedges()
|
||
|
||
# Get Binance prices for comparison
|
||
monitored_binance = get_latest_price_with_timestamp(f"{MONITORED_MARKET_NAME}_BINANCE")
|
||
|
||
if monitored_data or monitored_binance:
|
||
ft_price = f"${monitored_data['price']:.2f}" if monitored_data else "N/A"
|
||
bn_price = f"${monitored_binance['price']:.2f}" if monitored_binance else "N/A"
|
||
ft_time = monitored_data['timestamp_str'] if monitored_data else "N/A"
|
||
bn_time = monitored_binance['timestamp_str'] if monitored_binance else "N/A"
|
||
print(f"{MONITORED_COIN}/USD: FlexTrade={ft_price} ({ft_time}) | Binance={bn_price} ({bn_time})")
|
||
|
||
# Show price difference if both available
|
||
if monitored_data and monitored_binance:
|
||
price_diff = monitored_data['price'] - monitored_binance['price']
|
||
diff_pct = (price_diff / monitored_binance['price']) * 100 if monitored_binance['price'] > 0 else 0
|
||
diff_emoji = "📈" if price_diff > 0 else "📉" if price_diff < 0 else "➡️"
|
||
print(f"{MONITORED_COIN} Difference: {diff_emoji} {price_diff:+.4f} ({diff_pct:+.2f}%)")
|
||
|
||
# Show total number of markets with prices
|
||
all_prices = get_all_latest_prices()
|
||
flextrade_markets = len([k for k in all_prices.keys() if not k.endswith('_BINANCE') and k != '_comparison'])
|
||
binance_markets = len([k for k in all_prices.keys() if k.endswith('_BINANCE')])
|
||
print(f"Total markets tracked: FlexTrade={flextrade_markets}, Binance={binance_markets}")
|
||
|
||
# Show arbitrage opportunities if available
|
||
try:
|
||
arbitrage_ops = get_market_arbitrage_opportunities(min_percentage_diff=0.5)
|
||
if arbitrage_ops:
|
||
print(f"\n🎯 Found {len(arbitrage_ops)} arbitrage opportunities (>0.5% difference):")
|
||
for i, op in enumerate(arbitrage_ops[:3]): # Show top 3
|
||
action_emoji = "📈" if "buy_flextrade" in op['action'] else "📉"
|
||
print(f" {i+1}. {action_emoji} {op['market']}: {op['potential_profit_pct']:.2f}% potential profit")
|
||
print(f" FlexTrade: ${op['flextrade_price']:.4f} | Binance: ${op['binance_price']:.4f}")
|
||
print(f" Strategy: {op['action'].replace('_', ' ').title()}")
|
||
else:
|
||
print("🎯 No significant arbitrage opportunities found (threshold: 0.5%)")
|
||
|
||
# Show smaller differences for informational purposes
|
||
small_ops = get_market_arbitrage_opportunities(min_percentage_diff=0.1)
|
||
if small_ops:
|
||
best_small = small_ops[0] # Show best small opportunity
|
||
diff_emoji = "📈" if best_small['difference_pct'] > 0 else "📉"
|
||
print(f" Best small difference: {diff_emoji} {best_small['market']} ({best_small['potential_profit_pct']:.2f}%)")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ Error checking arbitrage opportunities: {e}")
|
||
|
||
print() # Extra line for readability
|
||
|
||
# Auto-hedging logic
|
||
if ENABLE_AUTO_HEDGE:
|
||
# Hedging logic with duplicate prevention for monitored coin
|
||
if (monitored_data and monitored_data['price'] > HEDGE_PRICE_THRESHOLD and
|
||
not executed_hedges["hedge_executed"]): # Only execute once
|
||
|
||
print(f"🤖 Auto-hedge triggered: {MONITORED_COIN} price above ${HEDGE_PRICE_THRESHOLD}")
|
||
|
||
# Execute market order to buy the monitored coin
|
||
market_result = smart_market_order(MONITORED_MARKET_NAME, MONITORED_MARKET_CONSTANT, Action.BUY, 100.0)
|
||
|
||
if market_result:
|
||
print(f"🤖 Auto-hedge triggered: {MONITORED_COIN} stop-loss trigger")
|
||
time.sleep(10)
|
||
# Create stop-loss trigger order
|
||
trigger_result = smart_trigger_order(MONITORED_MARKET_NAME, MONITORED_MARKET_CONSTANT, Action.SELL, 100.0, 0, trigger_above=False, offset_percentage=5)
|
||
|
||
if trigger_result:
|
||
# Mark hedge as executed
|
||
executed_hedges["hedge_executed"] = True
|
||
executed_hedges["last_hedge_price"] = monitored_data['price']
|
||
print(f"✅ {MONITORED_COIN} hedge completed at price ${monitored_data['price']:.2f}")
|
||
|
||
# Reset hedge flag if price drops significantly below hedge price
|
||
if (monitored_data and monitored_data['price'] < (HEDGE_PRICE_THRESHOLD * 0.95) and
|
||
executed_hedges["hedge_executed"]):
|
||
print(f"🔄 Resetting {MONITORED_COIN} hedge flag - price dropped to ${monitored_data['price']:.2f}")
|
||
executed_hedges["hedge_executed"] = False
|
||
|
||
# Sleep for 60 seconds (1 minute)
|
||
time.sleep(60)
|
||
|
||
except Exception as e:
|
||
print(f"An error occurred: {e}")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|