trade_executor, agent creator
This commit is contained in:
139
main_app.py
139
main_app.py
@ -25,6 +25,7 @@ DB_PATH = os.path.join("_data", "market_data.db")
|
||||
STATUS_FILE = os.path.join("_data", "fetcher_status.json")
|
||||
MARKET_CAP_SUMMARY_FILE = os.path.join("_data", "market_cap_data.json")
|
||||
LOGS_DIR = "_logs"
|
||||
TRADE_EXECUTOR_STATUS_FILE = os.path.join(LOGS_DIR, "trade_executor_status.json")
|
||||
|
||||
|
||||
def format_market_cap(mc_value):
|
||||
@ -81,11 +82,11 @@ def data_fetcher_scheduler():
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def run_resampler_job():
|
||||
def run_resampler_job(timeframes_to_generate: list):
|
||||
"""Defines the job for the resampler, redirecting output to a log file."""
|
||||
log_file = os.path.join(LOGS_DIR, "resampler.log")
|
||||
try:
|
||||
command = [sys.executable, RESAMPLER_SCRIPT, "--coins"] + WATCHED_COINS + ["--log-level", "off"]
|
||||
command = [sys.executable, RESAMPLER_SCRIPT, "--coins"] + WATCHED_COINS + ["--timeframes"] + timeframes_to_generate + ["--log-level", "off"]
|
||||
with open(log_file, 'a') as f:
|
||||
f.write(f"\n--- Starting resampler.py job at {datetime.now()} ---\n")
|
||||
subprocess.run(command, check=True, stdout=f, stderr=subprocess.STDOUT)
|
||||
@ -95,11 +96,11 @@ def run_resampler_job():
|
||||
f.write(f"Failed to run resampler.py job: {e}\n")
|
||||
|
||||
|
||||
def resampler_scheduler():
|
||||
def resampler_scheduler(timeframes_to_generate: list):
|
||||
"""Schedules the resampler.py script."""
|
||||
setup_logging('off', 'ResamplerScheduler')
|
||||
run_resampler_job()
|
||||
schedule.every(4).minutes.do(run_resampler_job)
|
||||
run_resampler_job(timeframes_to_generate)
|
||||
schedule.every(4).minutes.do(run_resampler_job, timeframes_to_generate)
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
@ -152,6 +153,7 @@ class MainApp:
|
||||
self.prices = {}
|
||||
self.market_caps = {}
|
||||
self.last_db_update_info = "Initializing..."
|
||||
self.open_positions = {}
|
||||
self.background_processes = processes
|
||||
self.process_status = {}
|
||||
self.strategy_configs = strategy_configs
|
||||
@ -182,16 +184,30 @@ class MainApp:
|
||||
|
||||
def read_strategy_statuses(self):
|
||||
"""Reads the status JSON file for each enabled strategy."""
|
||||
for name in self.strategy_configs.keys():
|
||||
status_file = os.path.join("_data", f"strategy_status_{name}.json")
|
||||
if os.path.exists(status_file):
|
||||
try:
|
||||
with open(status_file, 'r', encoding='utf-8') as f:
|
||||
self.strategy_statuses[name] = json.load(f)
|
||||
except (IOError, json.JSONDecodeError):
|
||||
self.strategy_statuses[name] = {"error": "Could not read status file."}
|
||||
else:
|
||||
self.strategy_statuses[name] = {"current_signal": "Initializing..."}
|
||||
enabled_statuses = {}
|
||||
for name, config in self.strategy_configs.items():
|
||||
if config.get("enabled", False):
|
||||
status_file = os.path.join("_data", f"strategy_status_{name}.json")
|
||||
if os.path.exists(status_file):
|
||||
try:
|
||||
with open(status_file, 'r', encoding='utf-8') as f:
|
||||
enabled_statuses[name] = json.load(f)
|
||||
except (IOError, json.JSONDecodeError):
|
||||
enabled_statuses[name] = {"error": "Could not read status file."}
|
||||
else:
|
||||
enabled_statuses[name] = {"current_signal": "Initializing..."}
|
||||
self.strategy_statuses = enabled_statuses
|
||||
|
||||
def read_executor_status(self):
|
||||
"""Reads the live status file from the trade executor."""
|
||||
if os.path.exists(TRADE_EXECUTOR_STATUS_FILE):
|
||||
try:
|
||||
with open(TRADE_EXECUTOR_STATUS_FILE, 'r', encoding='utf-8') as f:
|
||||
self.open_positions = json.load(f)
|
||||
except (IOError, json.JSONDecodeError):
|
||||
logging.debug("Could not read trade executor status file.")
|
||||
else:
|
||||
self.open_positions = {}
|
||||
|
||||
|
||||
def get_overall_db_status(self):
|
||||
@ -227,12 +243,11 @@ class MainApp:
|
||||
"""Displays a formatted dashboard with side-by-side tables."""
|
||||
print("\x1b[H\x1b[J", end="") # Clear screen
|
||||
|
||||
# --- Build Left Table (Market Dashboard) ---
|
||||
left_table_lines = []
|
||||
left_table_width = 44
|
||||
left_table_lines.append("--- Market Dashboard ---\t\t")
|
||||
left_table_lines.append("--- Market Dashboard ---")
|
||||
left_table_lines.append("-" * left_table_width)
|
||||
left_table_lines.append(f"{'#':^2} | {'Coin':^6} | {'Live Price':>10} | {'Market Cap':>15} |")
|
||||
left_table_lines.append(f"{'#':<2} | {'Coin':^6} | {'Live Price':>10} | {'Market Cap':>15} |")
|
||||
left_table_lines.append("-" * left_table_width)
|
||||
for i, coin in enumerate(self.watched_coins, 1):
|
||||
price = self.prices.get(coin, "Loading...")
|
||||
@ -241,12 +256,11 @@ class MainApp:
|
||||
left_table_lines.append(f"{i:<2} | {coin:^6} | {price:>10} | {formatted_mc:>15} |")
|
||||
left_table_lines.append("-" * left_table_width)
|
||||
|
||||
# --- Build Right Table (Strategy Status) ---
|
||||
right_table_lines = []
|
||||
right_table_width = 148
|
||||
right_table_width = 154
|
||||
right_table_lines.append("--- Strategy Status ---")
|
||||
right_table_lines.append("-" * right_table_width)
|
||||
right_table_lines.append(f"{'#':<2} | {'Strategy Name':<25} | {'Coin':^6} | {'Signal':<8} | {'Signal Price':>12} | {'Last Change (Local)':>22} | {'TF':^5} | {'Parameters':<45} |")
|
||||
right_table_lines.append(f"{'#':^2} | {'Strategy Name':<25} | {'Coin':^6} | {'Signal':^8} | {'Signal Price':>12} | {'Last Change':>17} | {'TF':^5} | {'Size':^8} | {'Parameters':<45} |")
|
||||
right_table_lines.append("-" * right_table_width)
|
||||
for i, (name, status) in enumerate(self.strategy_statuses.items(), 1):
|
||||
signal = status.get('current_signal', 'N/A')
|
||||
@ -255,7 +269,6 @@ class MainApp:
|
||||
last_change = status.get('last_signal_change_utc')
|
||||
last_change_display = 'Never'
|
||||
if last_change:
|
||||
# Convert UTC timestamp from file to local time for display
|
||||
dt_utc = datetime.fromisoformat(last_change.replace('Z', '+00:00')).replace(tzinfo=timezone.utc)
|
||||
dt_local = dt_utc.astimezone(None)
|
||||
last_change_display = dt_local.strftime('%Y-%m-%d %H:%M')
|
||||
@ -263,14 +276,14 @@ class MainApp:
|
||||
config_params = self.strategy_configs.get(name, {}).get('parameters', {})
|
||||
coin = config_params.get('coin', 'N/A')
|
||||
timeframe = config_params.get('timeframe', 'N/A')
|
||||
size = config_params.get('size', 'N/A')
|
||||
|
||||
other_params = {k: v for k, v in config_params.items() if k not in ['coin', 'timeframe']}
|
||||
other_params = {k: v for k, v in config_params.items() if k not in ['coin', 'timeframe', 'size']}
|
||||
params_str = ", ".join([f"{k}={v}" for k, v in other_params.items()])
|
||||
|
||||
right_table_lines.append(f"{i:^2} | {name:<25} | {coin:^6} | {signal:<8} | {price_display:>12} | {last_change_display:>22} | {timeframe:^5} | {params_str:<45} |")
|
||||
right_table_lines.append(f"{i:^2} | {name:<25} | {coin:^6} | {signal:^8} | {price_display:>12} | {last_change_display:>17} | {timeframe:^5} | {size:>8} | {params_str:<45} |")
|
||||
right_table_lines.append("-" * right_table_width)
|
||||
|
||||
# --- Combine Tables Side-by-Side ---
|
||||
output_lines = []
|
||||
max_rows = max(len(left_table_lines), len(right_table_lines))
|
||||
separator = " "
|
||||
@ -280,8 +293,43 @@ class MainApp:
|
||||
right_part = indent + right_table_lines[i] if i < len(right_table_lines) else ""
|
||||
output_lines.append(f"{left_part}{separator}{right_part}")
|
||||
|
||||
# --- Add Bottom Sections ---
|
||||
output_lines.append(f"\nDB Status: Last update -> {self.last_db_update_info}")
|
||||
|
||||
output_lines.append("\n--- Open Positions ---")
|
||||
pos_table_width = 100
|
||||
output_lines.append("-" * pos_table_width)
|
||||
output_lines.append(f"{'Account':<10} | {'Coin':<6} | {'Size':>15} | {'Entry Price':>12} | {'Mark Price':>12} | {'PNL':>15} | {'Leverage':>10} |")
|
||||
output_lines.append("-" * pos_table_width)
|
||||
|
||||
perps_positions = self.open_positions.get('perpetuals_account', {}).get('open_positions', [])
|
||||
spot_positions = self.open_positions.get('spot_account', {}).get('positions', [])
|
||||
|
||||
if not perps_positions and not spot_positions:
|
||||
output_lines.append("No open positions found.")
|
||||
else:
|
||||
for pos in perps_positions:
|
||||
# --- FIX: Safely handle potentially None values before formatting ---
|
||||
try:
|
||||
pnl = float(pos.get('pnl', 0.0))
|
||||
pnl_str = f"${pnl:,.2f}"
|
||||
except (ValueError, TypeError):
|
||||
pnl_str = "Error"
|
||||
|
||||
coin = pos.get('coin') or '-'
|
||||
size = pos.get('size') or '-'
|
||||
entry_price = pos.get('entry_price') or '-'
|
||||
mark_price = pos.get('mark_price') or '-'
|
||||
leverage = pos.get('leverage') or '-'
|
||||
|
||||
output_lines.append(f"{'Perps':<10} | {coin:<6} | {size:>15} | {entry_price:>12} | {mark_price:>12} | {pnl_str:>15} | {leverage:>10} |")
|
||||
|
||||
for pos in spot_positions:
|
||||
pnl = pos.get('pnl', 'N/A')
|
||||
coin = pos.get('coin') or '-'
|
||||
balance_size = pos.get('balance_size') or '-'
|
||||
output_lines.append(f"{'Spot':<10} | {coin:<6} | {balance_size:>15} | {'-':>12} | {'-':>12} | {pnl:>15} | {'-':>10} |")
|
||||
output_lines.append("-" * pos_table_width)
|
||||
|
||||
output_lines.append("\n--- Background Processes ---")
|
||||
for name, status in self.process_status.items():
|
||||
output_lines.append(f"{name:<25}: {status}")
|
||||
@ -297,6 +345,7 @@ class MainApp:
|
||||
self.read_market_caps()
|
||||
self.get_overall_db_status()
|
||||
self.read_strategy_statuses()
|
||||
self.read_executor_status()
|
||||
self.check_process_status()
|
||||
self.display_dashboard()
|
||||
time.sleep(2)
|
||||
@ -318,25 +367,37 @@ if __name__ == "__main__":
|
||||
processes = {}
|
||||
strategy_configs = {}
|
||||
|
||||
processes["Market Feeder"] = multiprocessing.Process(target=run_market_feeder, daemon=True)
|
||||
processes["Data Fetcher"] = multiprocessing.Process(target=data_fetcher_scheduler, daemon=True)
|
||||
processes["Resampler"] = multiprocessing.Process(target=resampler_scheduler, daemon=True)
|
||||
processes["Market Cap Fetcher"] = multiprocessing.Process(target=market_cap_fetcher_scheduler, daemon=True)
|
||||
|
||||
try:
|
||||
with open(STRATEGY_CONFIG_FILE, 'r') as f:
|
||||
strategy_configs = json.load(f)
|
||||
for name, config in strategy_configs.items():
|
||||
if config.get("enabled", False):
|
||||
if not os.path.exists(config['script']):
|
||||
logging.error(f"Strategy script '{config['script']}' for strategy '{name}' not found. Skipping.")
|
||||
continue
|
||||
proc = multiprocessing.Process(target=run_strategy, args=(name, config), daemon=True)
|
||||
processes[f"Strategy: {name}"] = proc
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
logging.error(f"Could not load strategies from '{STRATEGY_CONFIG_FILE}': {e}")
|
||||
sys.exit(1)
|
||||
|
||||
required_timeframes = set()
|
||||
for name, config in strategy_configs.items():
|
||||
if config.get("enabled", False):
|
||||
tf = config.get("parameters", {}).get("timeframe")
|
||||
if tf:
|
||||
required_timeframes.add(tf)
|
||||
|
||||
if not required_timeframes:
|
||||
logging.warning("No timeframes required by any enabled strategy. Resampler will not run effectively.")
|
||||
|
||||
|
||||
processes["Market Feeder"] = multiprocessing.Process(target=run_market_feeder, daemon=True)
|
||||
processes["Data Fetcher"] = multiprocessing.Process(target=data_fetcher_scheduler, daemon=True)
|
||||
processes["Resampler"] = multiprocessing.Process(target=resampler_scheduler, args=(list(required_timeframes),), daemon=True)
|
||||
processes["Market Cap Fetcher"] = multiprocessing.Process(target=market_cap_fetcher_scheduler, daemon=True)
|
||||
|
||||
for name, config in strategy_configs.items():
|
||||
if config.get("enabled", False):
|
||||
if not os.path.exists(config['script']):
|
||||
logging.error(f"Strategy script '{config['script']}' for strategy '{name}' not found. Skipping.")
|
||||
continue
|
||||
proc = multiprocessing.Process(target=run_strategy, args=(name, config), daemon=True)
|
||||
processes[f"Strategy: {name}"] = proc
|
||||
|
||||
# Launch all processes
|
||||
for name, proc in processes.items():
|
||||
logging.info(f"Starting process '{name}'...")
|
||||
proc.start()
|
||||
|
||||
Reference in New Issue
Block a user