Scheduler support #24sched
This commit is contained in:
51
_run_scheduler.sh
Executable file
51
_run_scheduler.sh
Executable file
@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Approach: (https://chat.openai.com/c/43be8685-b27b-4e3b-bd18-0856f8d23d7e)
|
||||
# cron runs this script every minute New York in range of 9:20 - 16:20 US time
|
||||
# Also this scripts writes the "heartbeat" message to log file, so the user knows
|
||||
#that cron is running
|
||||
|
||||
# Installation steps required:
|
||||
#chmod +x run_scheduler.sh
|
||||
#install tzdata package: sudo apt-get install tzdata
|
||||
#crontab -e
|
||||
#CRON_TZ=America/New_York
|
||||
# * 9-16 * * 1-5 /home/david/v2trading/run_scheduler.sh
|
||||
#
|
||||
# (Runs every minute of every hour on every day-of-week from Monday to Friday) US East time
|
||||
|
||||
# Path to the Python script
|
||||
PYTHON_SCRIPT="v2realbot/scheduler/scheduler.py"
|
||||
|
||||
# Log file path
|
||||
LOG_FILE="job.log"
|
||||
|
||||
# Timezone for New York
|
||||
TZ='America/New_York'
|
||||
NY_DATE_TIME=$(TZ=$TZ date +'%Y-%m-%d %H:%M:%S')
|
||||
echo "NY_DATE_TIME: $NY_DATE_TIME"
|
||||
|
||||
# Check if log file exists, create it if it doesn't
|
||||
if [ ! -f "$LOG_FILE" ]; then
|
||||
touch "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# Check the last line of the log file
|
||||
LAST_LINE=$(tail -n 1 "$LOG_FILE")
|
||||
|
||||
# Cron trigger message
|
||||
CRON_TRIGGER="Cron trigger: $NY_DATE_TIME"
|
||||
|
||||
# Update the log
|
||||
if [[ "$LAST_LINE" =~ "Cron trigger:".* ]]; then
|
||||
# Replace the last line with the new trigger message
|
||||
sed -i '' '$ d' "$LOG_FILE"
|
||||
echo "$CRON_TRIGGER" >> "$LOG_FILE"
|
||||
else
|
||||
# Append a new cron trigger message
|
||||
echo "$CRON_TRIGGER" >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
|
||||
# FOR DEBUG - Run the Python script and append output to log file
|
||||
python3 "$PYTHON_SCRIPT" >> "$LOG_FILE" 2>&1
|
||||
@ -35,6 +35,7 @@ entrypoints==0.4
|
||||
exceptiongroup==1.1.3
|
||||
executing==1.2.0
|
||||
fastapi==0.95.0
|
||||
filelock==3.13.1
|
||||
Flask==2.2.3
|
||||
flatbuffers==23.5.26
|
||||
fonttools==4.39.0
|
||||
@ -168,7 +169,7 @@ tzdata==2023.2
|
||||
tzlocal==4.3
|
||||
urllib3==1.26.14
|
||||
uvicorn==0.21.1
|
||||
-e git+https://github.com/drew2323/v2trading.git@eff78e8157c44b064c169e80ffa3d0b18cdb3d23#egg=v2realbot
|
||||
-e git+https://github.com/drew2323/v2trading.git@b58639454be921f9f0c9dd1880491cfcfdfdf3b7#egg=v2realbot
|
||||
validators==0.20.0
|
||||
wcwidth==0.2.9
|
||||
webencodings==0.5.1
|
||||
|
||||
@ -3,9 +3,12 @@ import sqlite3
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
from v2realbot.common.model import RunArchive, RunArchiveView
|
||||
from v2realbot.common.model import RunArchive, RunArchiveView, RunManagerRecord
|
||||
from datetime import datetime
|
||||
import orjson
|
||||
from v2realbot.utils.utils import json_serial, send_to_telegram, zoneNY
|
||||
import v2realbot.controller.services as cs
|
||||
from uuid import UUID
|
||||
|
||||
sqlite_db_file = DATA_DIR + "/v2trading.db"
|
||||
# Define the connection pool
|
||||
@ -31,7 +34,7 @@ class ConnectionPool:
|
||||
return connection
|
||||
|
||||
|
||||
def execute_with_retry(cursor: sqlite3.Cursor, statement: str, params = None, retry_interval: int = 1) -> sqlite3.Cursor:
|
||||
def execute_with_retry(cursor: sqlite3.Cursor, statement: str, params = None, retry_interval: int = 2) -> sqlite3.Cursor:
|
||||
"""get connection from pool and execute SQL statement with retry logic if required.
|
||||
|
||||
Args:
|
||||
@ -62,6 +65,37 @@ pool = ConnectionPool(10)
|
||||
insert_conn = sqlite3.connect(sqlite_db_file, check_same_thread=False)
|
||||
insert_queue = queue.Queue()
|
||||
|
||||
#prevede dict radku zpatky na objekt vcetme retypizace
|
||||
def row_to_runmanager(row: dict) -> RunManagerRecord:
|
||||
|
||||
is_running = cs.is_runner_running(row['runner_id']) if row['runner_id'] else False
|
||||
|
||||
res = RunManagerRecord(
|
||||
moddus=row['moddus'],
|
||||
id=row['id'],
|
||||
strat_id=row['strat_id'],
|
||||
symbol=row['symbol'],
|
||||
mode=row['mode'],
|
||||
account=row['account'],
|
||||
note=row['note'],
|
||||
ilog_save=bool(row['ilog_save']),
|
||||
bt_from=datetime.fromisoformat(row['bt_from']) if row['bt_from'] else None,
|
||||
bt_to=datetime.fromisoformat(row['bt_to']) if row['bt_to'] else None,
|
||||
weekdays_filter=[int(x) for x in row['weekdays_filter'].split(',')] if row['weekdays_filter'] else [],
|
||||
batch_id=row['batch_id'],
|
||||
testlist_id=row['testlist_id'],
|
||||
start_time=row['start_time'],
|
||||
stop_time=row['stop_time'],
|
||||
status=row['status'],
|
||||
#last_started=zoneNY.localize(datetime.fromisoformat(row['last_started'])) if row['last_started'] else None,
|
||||
last_processed=datetime.fromisoformat(row['last_processed']) if row['last_processed'] else None,
|
||||
history=row['history'],
|
||||
valid_from=datetime.fromisoformat(row['valid_from']) if row['valid_from'] else None,
|
||||
valid_to=datetime.fromisoformat(row['valid_to']) if row['valid_to'] else None,
|
||||
runner_id = row['runner_id'] if row['runner_id'] and is_running else None, #runner_id is only present if it is running
|
||||
strat_running = is_running) #cant believe this when called from separate process as not current
|
||||
return res
|
||||
|
||||
#prevede dict radku zpatky na objekt vcetme retypizace
|
||||
def row_to_runarchiveview(row: dict) -> RunArchiveView:
|
||||
return RunArchive(
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
from uuid import UUID
|
||||
from uuid import UUID, uuid4
|
||||
from alpaca.trading.enums import OrderSide, OrderStatus, TradeEvent,OrderType
|
||||
#from utils import AttributeDict
|
||||
from rich import print
|
||||
from typing import Any, Optional, List, Union
|
||||
from datetime import datetime, date
|
||||
from pydantic import BaseModel
|
||||
from v2realbot.enums.enums import Mode, Account
|
||||
from pydantic import BaseModel, Field
|
||||
from v2realbot.enums.enums import Mode, Account, SchedulerStatus, Moddus
|
||||
from alpaca.data.enums import Exchange
|
||||
|
||||
|
||||
|
||||
|
||||
#models for server side datatables
|
||||
# Model for individual column data
|
||||
class ColumnData(BaseModel):
|
||||
@ -134,7 +137,33 @@ class RunRequest(BaseModel):
|
||||
cash: int = 100000
|
||||
skip_cache: Optional[bool] = False
|
||||
|
||||
|
||||
#Trida, která je nadstavbou runrequestu a pouzivame ji v scheduleru, je zde navic jen par polí
|
||||
class RunManagerRecord(BaseModel):
|
||||
moddus: Moddus
|
||||
id: UUID = Field(default_factory=uuid4)
|
||||
strat_id: UUID
|
||||
symbol: Optional[str] = None
|
||||
account: Account
|
||||
mode: Mode
|
||||
note: Optional[str] = None
|
||||
ilog_save: bool = False
|
||||
bt_from: datetime = None
|
||||
bt_to: datetime = None
|
||||
#weekdays filter
|
||||
#pokud je uvedeny filtrujeme tyto dny
|
||||
weekdays_filter: Optional[list] = None #list of strings 0-6 representing days to run
|
||||
#GENERATED ID v ramci runu, vaze vsechny runnery v batchovem behu
|
||||
batch_id: Optional[str] = None
|
||||
testlist_id: Optional[str] = None
|
||||
start_time: str #time (HH:MM) that start function is called
|
||||
stop_time: Optional[str] #time (HH:MM) that stop function is called
|
||||
status: SchedulerStatus
|
||||
last_processed: Optional[datetime]
|
||||
history: Optional[str] = None
|
||||
valid_from: Optional[datetime] = None # US East time zone daetime
|
||||
valid_to: Optional[datetime] = None # US East time zone daetime
|
||||
runner_id: Optional[UUID] = None #last runner_id from scheduler after stratefy is started
|
||||
strat_running: Optional[bool] = None #automatically updated field based on status of runner_id above, it is added by row_to_RunManagerRecord
|
||||
class RunnerView(BaseModel):
|
||||
id: UUID
|
||||
strat_id: UUID
|
||||
|
||||
@ -4,12 +4,18 @@ from appdirs import user_data_dir
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
# Global flag to track if the ml module has been imported (solution for long import times of tensorflow)
|
||||
#the first occurence of using it will load it globally
|
||||
_ml_module_loaded = False
|
||||
|
||||
#directory for generated images and basic reports
|
||||
MEDIA_DIRECTORY = Path(__file__).parent.parent.parent / "media"
|
||||
RUNNER_DETAIL_DIRECTORY = Path(__file__).parent.parent.parent / "runner_detail"
|
||||
|
||||
#location of strat.log - it is used to fetch by gui
|
||||
LOG_PATH = Path(__file__).parent.parent
|
||||
LOG_FILE = Path(__file__).parent.parent / "strat.log"
|
||||
JOB_LOG_FILE = Path(__file__).parent.parent / "job.log"
|
||||
|
||||
#'0.0.0.0',
|
||||
#currently only prod server has acces to LIVE
|
||||
|
||||
466
v2realbot/controller/run_manager.py
Normal file
466
v2realbot/controller/run_manager.py
Normal file
@ -0,0 +1,466 @@
|
||||
from typing import Any, List, Tuple
|
||||
from uuid import UUID, uuid4
|
||||
import pickle
|
||||
from alpaca.data.historical import StockHistoricalDataClient
|
||||
from alpaca.data.requests import StockTradesRequest, StockBarsRequest
|
||||
from alpaca.data.enums import DataFeed
|
||||
from alpaca.data.timeframe import TimeFrame
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide
|
||||
from v2realbot.common.model import RunManagerRecord, StrategyInstance, RunDay, StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem, InstantIndicator, DataTablesRequest
|
||||
from v2realbot.utils.utils import validate_and_format_time, AttributeDict, zoneNY, zonePRG, safe_get, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram, concatenate_weekdays, transform_data
|
||||
from v2realbot.utils.ilog import delete_logs
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType
|
||||
from datetime import datetime
|
||||
from v2realbot.loader.trade_offline_streamer import Trade_Offline_Streamer
|
||||
from threading import Thread, current_thread, Event, enumerate
|
||||
from v2realbot.config import STRATVARS_UNCHANGEABLES, ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR,BT_FILL_CONS_TRADES_REQUIRED,BT_FILL_LOG_SURROUNDING_TRADES,BT_FILL_CONDITION_BUY_LIMIT,BT_FILL_CONDITION_SELL_LIMIT, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN, MEDIA_DIRECTORY, RUNNER_DETAIL_DIRECTORY, OFFLINE_MODE
|
||||
import importlib
|
||||
from alpaca.trading.requests import GetCalendarRequest
|
||||
from alpaca.trading.client import TradingClient
|
||||
#from alpaca.trading.models import Calendar
|
||||
from queue import Queue
|
||||
from tinydb import TinyDB, Query, where
|
||||
from tinydb.operations import set
|
||||
import orjson
|
||||
import numpy as np
|
||||
from rich import print
|
||||
import pandas as pd
|
||||
from traceback import format_exc
|
||||
from datetime import timedelta, time
|
||||
from threading import Lock
|
||||
import v2realbot.common.db as db
|
||||
from sqlite3 import OperationalError, Row
|
||||
import v2realbot.strategyblocks.indicators.custom as ci
|
||||
from v2realbot.strategyblocks.inits.init_indicators import initialize_dynamic_indicators
|
||||
from v2realbot.strategyblocks.indicators.indicators_hub import populate_dynamic_indicators
|
||||
from v2realbot.interfaces.backtest_interface import BacktestInterface
|
||||
import os
|
||||
import v2realbot.reporting.metricstoolsimage as mt
|
||||
import gzip
|
||||
import os
|
||||
import msgpack
|
||||
import v2realbot.controller.services as cs
|
||||
import v2realbot.scheduler.ap_scheduler as aps
|
||||
|
||||
# Functions for your 'run_manager' table
|
||||
|
||||
# CREATE TABLE "run_manager" (
|
||||
# "moddus" TEXT NOT NULL,
|
||||
# "id" varchar(32),
|
||||
# "strat_id" varchar(32) NOT NULL,
|
||||
# "symbol" TEXT,
|
||||
# "account" TEXT NOT NULL,
|
||||
# "mode" TEXT NOT NULL,
|
||||
# "note" TEXT,
|
||||
# "ilog_save" BOOLEAN,
|
||||
# "bt_from" TEXT,
|
||||
# "bt_to" TEXT,
|
||||
# "weekdays_filter" TEXT,
|
||||
# "batch_id" TEXT,
|
||||
# "start_time" TEXT NOT NULL,
|
||||
# "stop_time" TEXT NOT NULL,
|
||||
# "status" TEXT NOT NULL,
|
||||
# "last_processed" TEXT,
|
||||
# "history" TEXT,
|
||||
# "valid_from" TEXT,
|
||||
# "valid_to" TEXT,
|
||||
# "testlist_id" TEXT,
|
||||
# "runner_id" varchar2(32),
|
||||
# PRIMARY KEY("id")
|
||||
# )
|
||||
|
||||
# CREATE INDEX idx_moddus ON run_manager (moddus);
|
||||
# CREATE INDEX idx_status ON run_manager (status);
|
||||
# CREATE INDEX idx_status_moddus ON run_manager (status, moddus);
|
||||
# CREATE INDEX idx_valid_from_to ON run_manager (valid_from, valid_to);
|
||||
# CREATE INDEX idx_stopped_batch_id ON runner_header (stopped, batch_id);
|
||||
# CREATE INDEX idx_search_value ON runner_header (strat_id, batch_id);
|
||||
|
||||
|
||||
##weekdays are stored as comma separated values
|
||||
# Fetching (assume 'weekdays' field is a comma-separated string)
|
||||
# weekday_str = record['weekdays']
|
||||
# weekdays = [int(x) for x in weekday_str.split(',')]
|
||||
|
||||
# # ... logic to check whether today's weekday is in 'weekdays'
|
||||
|
||||
# # Storing
|
||||
# weekdays = [1, 2, 5] # Example
|
||||
# weekday_str = ",".join(str(x) for x in weekdays)
|
||||
# update_data = {'weekdays': weekday_str}
|
||||
# # ... use in an SQL UPDATE statement
|
||||
|
||||
# for row in records:
|
||||
# row['weekdays_filter'] = [int(x) for x in row['weekdays_filter'].split(',')] if row['weekdays_filter'] else []
|
||||
|
||||
|
||||
#get stratin info return
|
||||
# strat : StrategyInstance = None
|
||||
# result, strat = cs.get_stratin("625760ac-6376-47fa-8989-1e6a3f6ab66a")
|
||||
# if result == 0:
|
||||
# print(strat)
|
||||
# else:
|
||||
# print("Error:", strat)
|
||||
|
||||
|
||||
# Fetch all
|
||||
#result, records = fetch_all_run_manager_records()
|
||||
|
||||
#TODO zvazit rozsireni vystupu o strat_status (running/stopped)
|
||||
|
||||
|
||||
def fetch_all_run_manager_records() -> list[RunManagerRecord]:
|
||||
conn = db.pool.get_connection()
|
||||
try:
|
||||
conn.row_factory = Row
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT * FROM run_manager')
|
||||
rows = cursor.fetchall()
|
||||
results = []
|
||||
#Transform row to object
|
||||
for row in rows:
|
||||
#add transformed object into result list
|
||||
results.append(db.row_to_runmanager(row))
|
||||
|
||||
return 0, results
|
||||
finally:
|
||||
conn.row_factory = None
|
||||
db.pool.release_connection(conn)
|
||||
|
||||
# Fetch by strategy_id
|
||||
# result, record = fetch_run_manager_record_by_id('625760ac-6376-47fa-8989-1e6a3f6ab66a')
|
||||
def fetch_run_manager_record_by_id(strategy_id) -> RunManagerRecord:
|
||||
conn = db.pool.get_connection()
|
||||
try:
|
||||
conn.row_factory = Row
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT * FROM run_manager WHERE id = ?', (str(strategy_id),))
|
||||
row = cursor.fetchone()
|
||||
if row is None:
|
||||
return -2, "not found"
|
||||
else:
|
||||
return 0, db.row_to_runmanager(row)
|
||||
|
||||
except Exception as e:
|
||||
print("ERROR while fetching all records:", str(e) + format_exc())
|
||||
return -2, str(e) + format_exc()
|
||||
finally:
|
||||
conn.row_factory = None
|
||||
db.pool.release_connection(conn)
|
||||
|
||||
def add_run_manager_record(new_record: RunManagerRecord):
|
||||
#validation/standardization of time
|
||||
new_record.start_time = validate_and_format_time(new_record.start_time)
|
||||
if new_record.start_time is None:
|
||||
return -2, f"Invalid start_time format {new_record.start_time}"
|
||||
|
||||
if new_record.stop_time is not None:
|
||||
new_record.stop_time = validate_and_format_time(new_record.stop_time)
|
||||
if new_record.stop_time is None:
|
||||
return -2, f"Invalid stop_time format {new_record.stop_time}"
|
||||
|
||||
conn = db.pool.get_connection()
|
||||
try:
|
||||
|
||||
strat : StrategyInstance = None
|
||||
result, strat = cs.get_stratin(id=str(new_record.strat_id))
|
||||
if result == 0:
|
||||
new_record.symbol = strat.symbol
|
||||
else:
|
||||
return -1, f"Strategy {new_record.strat_id} not found"
|
||||
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Construct a suitable INSERT query based on your RunManagerRecord fields
|
||||
insert_query = """
|
||||
INSERT INTO run_manager (moddus, id, strat_id, symbol,account, mode, note,ilog_save,
|
||||
bt_from, bt_to, weekdays_filter, batch_id,
|
||||
start_time, stop_time, status, last_processed,
|
||||
history, valid_from, valid_to, testlist_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
values = [
|
||||
new_record.moddus, str(new_record.id), str(new_record.strat_id), new_record.symbol, new_record.account, new_record.mode, new_record.note,
|
||||
int(new_record.ilog_save),
|
||||
new_record.bt_from.isoformat() if new_record.bt_from is not None else None,
|
||||
new_record.bt_to.isoformat() if new_record.bt_to is not None else None,
|
||||
",".join(str(x) for x in new_record.weekdays_filter) if new_record.weekdays_filter else None,
|
||||
new_record.batch_id, new_record.start_time,
|
||||
new_record.stop_time, new_record.status,
|
||||
new_record.last_processed.isoformat() if new_record.last_processed is not None else None,
|
||||
new_record.history,
|
||||
new_record.valid_from.isoformat() if new_record.valid_from is not None else None,
|
||||
new_record.valid_to.isoformat() if new_record.valid_to is not None else None,
|
||||
new_record.testlist_id
|
||||
]
|
||||
db.execute_with_retry(cursor, insert_query, values)
|
||||
conn.commit()
|
||||
|
||||
#Add APS scheduler job refresh
|
||||
res, result = aps.initialize_jobs()
|
||||
if res < 0:
|
||||
return -2, f"Error initializing jobs: {res} {result}"
|
||||
|
||||
return 0, new_record.id # Assuming success, you might return something more descriptive
|
||||
except Exception as e:
|
||||
print("ERROR while adding record:", str(e) + format_exc())
|
||||
return -2, str(e) + format_exc()
|
||||
finally:
|
||||
db.pool.release_connection(conn)
|
||||
|
||||
# Update (example)
|
||||
# update_data = {'last_started': '2024-02-13 10:35:00'}
|
||||
# result, message = update_run_manager_record('625760ac-6376-47fa-8989-1e6a3f6ab66a', update_data)
|
||||
def update_run_manager_record(record_id, updated_record: RunManagerRecord):
|
||||
#validation/standardization of time
|
||||
updated_record.start_time = validate_and_format_time(updated_record.start_time)
|
||||
if updated_record.start_time is None:
|
||||
return -2, f"Invalid start_time format {updated_record.start_time}"
|
||||
|
||||
if updated_record.stop_time is not None:
|
||||
updated_record.stop_time = validate_and_format_time(updated_record.stop_time)
|
||||
if updated_record.stop_time is None:
|
||||
return -2, f"Invalid stop_time format {updated_record.stop_time}"
|
||||
|
||||
conn = db.pool.get_connection()
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
|
||||
#strategy lookup check, if strategy still exists
|
||||
strat : StrategyInstance = None
|
||||
result, strat = cs.get_stratin(id=str(updated_record.strat_id))
|
||||
if result == 0:
|
||||
updated_record.symbol = strat.symbol
|
||||
else:
|
||||
return -1, f"Strategy {updated_record.strat_id} not found"
|
||||
|
||||
#remove values with None, so they are not updated
|
||||
#updated_record_dict = updated_record.dict(exclude_none=True)
|
||||
|
||||
# Construct update query and handle weekdays conversion
|
||||
update_query = 'UPDATE run_manager SET '
|
||||
update_params = []
|
||||
for key, value in updated_record.dict().items(): # Iterate over model attributes
|
||||
if key in ['id', 'strat_running']: # Skip updating the primary key
|
||||
continue
|
||||
update_query += f"{key} = ?, "
|
||||
if key == "ilog_save":
|
||||
value = int(value)
|
||||
elif key in ["strat_id", "runner_id"]:
|
||||
value = str(value) if value else None
|
||||
elif key == "weekdays_filter":
|
||||
value = ",".join(str(x) for x in value) if value else None
|
||||
elif key in ['valid_from', 'valid_to', 'bt_from', 'bt_to', 'last_processed']:
|
||||
value = value.isoformat() if value else None
|
||||
update_params.append(value)
|
||||
# if 'weekdays_filter' in updated_record.dict():
|
||||
# updated_record.weekdays_filter = ",".join(str(x) for x in updated_record.weekdays_filter)
|
||||
update_query = update_query[:-2] # Remove trailing comma and space
|
||||
update_query += ' WHERE id = ?'
|
||||
update_params.append(str(record_id))
|
||||
|
||||
db.execute_with_retry(cursor, update_query, update_params)
|
||||
#cursor.execute(update_query, update_params)
|
||||
conn.commit()
|
||||
|
||||
#Add APS scheduler job refresh
|
||||
res, result = aps.initialize_jobs()
|
||||
if res < 0:
|
||||
return -2, f"Error initializing jobs: {res} {result}"
|
||||
|
||||
except Exception as e:
|
||||
print("ERROR while updating record:", str(e) + format_exc())
|
||||
return -2, str(e) + format_exc()
|
||||
finally:
|
||||
db.pool.release_connection(conn)
|
||||
return 0, record_id
|
||||
|
||||
# result, message = delete_run_manager_record('625760ac-6376-47fa-8989-1e6a3f6ab66a')
|
||||
def delete_run_manager_record(record_id):
|
||||
conn = db.pool.get_connection()
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
db.execute_with_retry(cursor, 'DELETE FROM run_manager WHERE id = ?', (str(record_id),))
|
||||
#cursor.execute('DELETE FROM run_manager WHERE id = ?', (str(strategy_id),))
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
print("ERROR while deleting record:", str(e) + format_exc())
|
||||
return -2, str(e) + format_exc()
|
||||
finally:
|
||||
db.pool.release_connection(conn)
|
||||
return 0, record_id
|
||||
|
||||
def fetch_scheduled_candidates_for_start_and_stop(market_datetime_now, market) -> tuple[int, dict]:
|
||||
"""
|
||||
Fetches all active records from the 'run_manager' table where the mode is 'schedule'. It checks if the current
|
||||
time in the America/New_York timezone is within the operational intervals specified by 'start_time' and 'stop_time'
|
||||
for each record. This function is designed to correctly handle scenarios where the operational interval crosses
|
||||
midnight, as well as intervals contained within a single day.
|
||||
|
||||
The function localizes 'valid_from', 'valid_to', 'start_time', and 'stop_time' using the 'zoneNY' timezone object
|
||||
for accurate comparison with the current time.
|
||||
|
||||
Parameters:
|
||||
market_datetime_now (datetime): The current date and time in the America/New_York timezone.
|
||||
market (str): The market identifier.
|
||||
|
||||
Returns:
|
||||
Tuple[int, dict]: A tuple where the first element is a status code (0 for success, -2 for error), and the
|
||||
second element is a dictionary. This dictionary has keys 'start' and 'stop', each containing a list of
|
||||
RunManagerRecord objects meeting the respective criteria. If an error occurs, the second element is a
|
||||
descriptive error message.
|
||||
|
||||
Note:
|
||||
- This function assumes that the 'zoneNY' pytz timezone object is properly defined and configured to represent
|
||||
the America/New York timezone.
|
||||
- It also assumes that the 'run_manager' table exists in the database with the required columns.
|
||||
- 'start_time' and 'stop_time' are expected to be strings representing times in 24-hour format.
|
||||
- If 'valid_from', 'valid_to', 'start_time', or 'stop_time' are NULL in the database, they are considered as
|
||||
having unlimited boundaries.
|
||||
|
||||
Pozor: je jeste jeden okrajovy pripad, kdy by to nemuselo zafungovat: kdyby casy byly nastaveny pro
|
||||
beh strategie pres pulnoc, ale zapla by se pozdeji az po pulnoci
|
||||
(https://chat.openai.com/c/3c77674a-8a2c-45aa-afbd-ab140f473e07)
|
||||
|
||||
"""
|
||||
conn = db.pool.get_connection()
|
||||
try:
|
||||
conn.row_factory = Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get current datetime in America/New York timezone
|
||||
market_datetime_now_str = market_datetime_now.strftime('%Y-%m-%d %H:%M:%S')
|
||||
current_time_str = market_datetime_now.strftime('%H:%M')
|
||||
print("current_market_datetime_str:", market_datetime_now_str)
|
||||
print("current_time_str:", current_time_str)
|
||||
|
||||
# Select also supports scenarios where strategy runs overnight
|
||||
# SQL query to fetch records with active status and date constraints for both start and stop times
|
||||
query = """
|
||||
SELECT *,
|
||||
CASE
|
||||
WHEN start_time <= stop_time AND (? >= start_time AND ? < stop_time) OR
|
||||
start_time > stop_time AND (? >= start_time OR ? < stop_time) THEN 1
|
||||
ELSE 0
|
||||
END as is_start_time,
|
||||
CASE
|
||||
WHEN start_time <= stop_time AND (? >= stop_time OR ? < start_time) OR
|
||||
start_time > stop_time AND (? >= stop_time AND ? < start_time) THEN 1
|
||||
ELSE 0
|
||||
END as is_stop_time
|
||||
FROM run_manager
|
||||
WHERE status = 'active' AND moddus = 'schedule' AND
|
||||
((valid_from IS NULL OR strftime('%Y-%m-%d %H:%M:%S', valid_from) <= ?) AND
|
||||
(valid_to IS NULL OR strftime('%Y-%m-%d %H:%M:%S', valid_to) >= ?))
|
||||
"""
|
||||
cursor.execute(query, (current_time_str, current_time_str, current_time_str, current_time_str,
|
||||
current_time_str, current_time_str, current_time_str, current_time_str,
|
||||
market_datetime_now_str, market_datetime_now_str))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
start_candidates = []
|
||||
stop_candidates = []
|
||||
for row in rows:
|
||||
run_manager_record = db.row_to_runmanager(row)
|
||||
if row['is_start_time']:
|
||||
start_candidates.append(run_manager_record)
|
||||
if row['is_stop_time']:
|
||||
stop_candidates.append(run_manager_record)
|
||||
|
||||
results = {'start': start_candidates, 'stop': stop_candidates}
|
||||
|
||||
return 0, results
|
||||
except Exception as e:
|
||||
msg_err = f"ERROR while fetching records for start and stop times with datetime {market_datetime_now_str}: {str(e)} {format_exc()}"
|
||||
print(msg_err)
|
||||
return -2, msg_err
|
||||
finally:
|
||||
conn.row_factory = None
|
||||
db.pool.release_connection(conn)
|
||||
|
||||
|
||||
def fetch_startstop_scheduled_candidates(market_datetime_now, time_check, market = "US") -> tuple[int, list[RunManagerRecord]]:
|
||||
"""
|
||||
Fetches all active records from the 'run_manager' table where moddus is schedule, the current date and time
|
||||
in the America/New_York timezone falls between the 'valid_from' and 'valid_to' datetime
|
||||
fields, and either 'start_time' or 'stop_time' matches the specified condition with the current time.
|
||||
If 'valid_from', 'valid_to', or the time column ('start_time'/'stop_time') are NULL, they are considered
|
||||
as having unlimited boundaries.
|
||||
|
||||
The function localizes the 'valid_from', 'valid_to', and the time column times using the 'zoneNY'
|
||||
timezone object for accurate comparison with the current time.
|
||||
|
||||
Parameters:
|
||||
market_datetime_now (datetime): Current datetime in the market timezone.
|
||||
market (str): The market for which to fetch candidates.
|
||||
time_check (str): Either 'start' or 'stop', indicating which time condition to check.
|
||||
|
||||
Returns:
|
||||
Tuple[int, list[RunManagerRecord]]: A tuple where the first element is a status code
|
||||
(0 for success, -2 for error), and the second element is a list of RunManagerRecord
|
||||
objects meeting the criteria. If an error occurs, the second element is a descriptive
|
||||
error message.
|
||||
|
||||
Note:
|
||||
This function assumes that the 'zoneNY' pytz timezone object is properly defined and
|
||||
configured to represent the America/New York timezone. It also assumes that the
|
||||
'run_manager' table exists in the database with the columns as described in the
|
||||
provided schema.
|
||||
"""
|
||||
if time_check not in ['start', 'stop']:
|
||||
return -2, "Invalid time_check parameter. Must be 'start' or 'stop'."
|
||||
|
||||
conn = db.pool.get_connection()
|
||||
try:
|
||||
conn.row_factory = Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get current datetime in America/New York timezone
|
||||
market_datetime_now_str = market_datetime_now.strftime('%Y-%m-%d %H:%M:%S')
|
||||
current_time_str = market_datetime_now.strftime('%H:%M')
|
||||
print("current_market_datetime_str:", market_datetime_now_str)
|
||||
print("current_time_str:", current_time_str)
|
||||
|
||||
# SQL query to fetch records with active status, date constraints, and time condition
|
||||
time_column = 'start_time' if time_check == 'start' else 'stop_time'
|
||||
query = f"""
|
||||
SELECT * FROM run_manager
|
||||
WHERE status = 'active' AND moddus = 'schedule' AND
|
||||
((valid_from IS NULL OR strftime('%Y-%m-%d %H:%M:%S', valid_from) <= ?) AND
|
||||
(valid_to IS NULL OR strftime('%Y-%m-%d %H:%M:%S', valid_to) >= ?)) AND
|
||||
({time_column} IS NULL OR {time_column} <= ?)
|
||||
"""
|
||||
cursor.execute(query, (market_datetime_now_str, market_datetime_now_str, current_time_str))
|
||||
rows = cursor.fetchall()
|
||||
results = [db.row_to_runmanager(row) for row in rows]
|
||||
|
||||
return 0, results
|
||||
except Exception as e:
|
||||
msg_err = f"ERROR while fetching records based on {time_check} time with datetime {market_datetime_now_str}: {str(e)} {format_exc()}"
|
||||
print(msg_err)
|
||||
return -2, msg_err
|
||||
finally:
|
||||
conn.row_factory = None
|
||||
db.pool.release_connection(conn)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
res, sada = fetch_startstop_scheduled_candidates(datetime.now().astimezone(zoneNY), "start")
|
||||
if res == 0:
|
||||
print(sada)
|
||||
else:
|
||||
print("Error:", sada)
|
||||
|
||||
# from apscheduler.schedulers.background import BackgroundScheduler
|
||||
# import time
|
||||
|
||||
# def print_hello():
|
||||
# print("Hello")
|
||||
|
||||
# def schedule_job():
|
||||
# scheduler = BackgroundScheduler()
|
||||
# scheduler.add_job(print_hello, 'interval', seconds=10)
|
||||
# scheduler.start()
|
||||
|
||||
# schedule_job()
|
||||
@ -80,7 +80,7 @@ def get_all_stratins():
|
||||
else:
|
||||
return (0, [])
|
||||
|
||||
def get_stratin(id: UUID):
|
||||
def get_stratin(id: UUID) -> List[StrategyInstance]:
|
||||
for i in db.stratins:
|
||||
if str(i.id) == str(id):
|
||||
return (0, i)
|
||||
@ -105,7 +105,7 @@ def create_stratin(si: StrategyInstance):
|
||||
if res < 0:
|
||||
return (-1, "None")
|
||||
si.id = uuid4()
|
||||
print(si)
|
||||
#print(si)
|
||||
db.stratins.append(si)
|
||||
db.save()
|
||||
#print(db.stratins)
|
||||
@ -242,13 +242,14 @@ def pause_runner(id: UUID):
|
||||
return (0, "paused runner " + str(i.id))
|
||||
print("no ID found")
|
||||
return (-1, "not running instance found")
|
||||
|
||||
def stop_runner(id: UUID = None):
|
||||
#allows to delete runner based on runner_id, strat_id or all (both none)
|
||||
#podpruje i hodnotu strat_id v id
|
||||
def stop_runner(id: UUID = None, strat_id: UUID = None):
|
||||
chng = []
|
||||
try:
|
||||
for i in db.runners:
|
||||
#print(i['id'])
|
||||
if id is None or str(i.id) == id:
|
||||
if (id is None and strat_id is None) or str(i.id) == str(id) or str(i.strat_id) == str(strat_id) or str(i.strat_id) == str(id):
|
||||
chng.append(i.id)
|
||||
print("Sending STOP signal to Runner", i.id)
|
||||
#just sending the signal, update is done in stop after plugin
|
||||
@ -356,7 +357,20 @@ def capsule(target: object, db: object, inter_batch_params: dict = None):
|
||||
except Exception as e:
|
||||
err_msg = "Nepodarilo se vytvorit daily report image" + str(e)+format_exc()
|
||||
send_to_telegram(err_msg)
|
||||
print(err_msg)
|
||||
print(err_msg)
|
||||
#PRO LIVE a PAPER pri vyplnenem batchi vytvarime batchovy soubor zde (pro BT ridi batch_manager)
|
||||
if i.run_mode in [Mode.LIVE, Mode.PAPER] and i.batch_id is not None:
|
||||
try:
|
||||
res, val = mt.generate_trading_report_image(batch_id=i.batch_id)
|
||||
if res == 0:
|
||||
print("BATCH REPORT CREATED")
|
||||
else:
|
||||
print(f"BATCH REPORT ERROR - {val}")
|
||||
except Exception as e:
|
||||
err_msg = f"Nepodarilo se vytvorit batchj report image pro {i.strat_id} a batch{i.batch_id}" + str(e)+format_exc()
|
||||
send_to_telegram(err_msg)
|
||||
print(err_msg)
|
||||
|
||||
target.release()
|
||||
print("Runner STOPPED")
|
||||
|
||||
@ -477,6 +491,9 @@ def run_batch_stratin(id: UUID, runReq: RunRequest):
|
||||
# bud ceka na dokonceni v runners nebo to bude ridit jinak a bude mit jednoho runnera?
|
||||
# nejak vymyslet.
|
||||
# logovani zatim jen do print
|
||||
|
||||
##OFFLINE BATCH RUN MANAGER (generuje batch_id, ridi datove provazani runnerů(inter_batch_data) a generuje batch report
|
||||
## a samozrejme spousti jednotlivé dny
|
||||
def batch_run_manager(id: UUID, runReq: RunRequest, rundays: list[RunDay]):
|
||||
#zde muzu iterovat nad intervaly
|
||||
#cekat az dobehne jeden interval a pak spustit druhy
|
||||
@ -1035,7 +1052,7 @@ def get_all_archived_runners() -> list[RunArchiveView]:
|
||||
|
||||
#new version to support search and ordering
|
||||
#TODO index nad strat_id a batch_id mam?
|
||||
def get_all_archived_runners_p(request: DataTablesRequest) -> Tuple[int, RunArchiveViewPagination]:
|
||||
def get_all_archived_runners_p_original(request: DataTablesRequest) -> Tuple[int, RunArchiveViewPagination]:
|
||||
conn = pool.get_connection()
|
||||
search_value = request.search.value # Extract the search value from the request
|
||||
try:
|
||||
@ -1084,6 +1101,76 @@ def get_all_archived_runners_p(request: DataTablesRequest) -> Tuple[int, RunArch
|
||||
return -2, str(e) + format_exc()
|
||||
|
||||
|
||||
#new version with batch_id asc sortin https://chat.openai.com/c/64511445-5181-411b-b9d0-51d16930bf71
|
||||
#Tato verze správně groupuje záznamy se stejnym batch_id (podle maximalniho batche) a non batch zaznamy prolne mezi ne podle jeho stopped date - vlozi zaznam po nebo pred jednotlivou skupinu (dle jejiho max.date)
|
||||
#diky tomu se mi radi batche a nonbatche spravne a pokud do batche pridame zaznam zobrazi se nam batch nahore
|
||||
def get_all_archived_runners_p(request: DataTablesRequest) -> Tuple[int, RunArchiveViewPagination]:
|
||||
conn = pool.get_connection()
|
||||
search_value = request.search.value # Extract the search value from the request
|
||||
try:
|
||||
conn.row_factory = Row
|
||||
c = conn.cursor()
|
||||
|
||||
# Total count query
|
||||
total_count_query = """
|
||||
SELECT COUNT(*) FROM runner_header
|
||||
WHERE (:search_value = '' OR strat_id LIKE :search_value OR batch_id LIKE :search_value)
|
||||
"""
|
||||
c.execute(total_count_query, {'search_value': f'%{search_value}%'})
|
||||
total_count = c.fetchone()[0]
|
||||
|
||||
# Paginated query with advanced sorting logic
|
||||
paginated_query = f"""
|
||||
WITH GroupedData AS (
|
||||
SELECT runner_id, strat_id, batch_id, symbol, name, note, started,
|
||||
stopped, mode, account, bt_from, bt_to, ilog_save, profit,
|
||||
trade_count, end_positions, end_positions_avgp, metrics,
|
||||
MAX(stopped) OVER (PARTITION BY batch_id) AS max_stopped
|
||||
FROM runner_header
|
||||
WHERE (:search_value = '' OR strat_id LIKE :search_value OR batch_id LIKE :search_value)
|
||||
),
|
||||
InterleavedGroups AS (
|
||||
SELECT *,
|
||||
CASE
|
||||
WHEN batch_id IS NOT NULL THEN max_stopped
|
||||
ELSE stopped
|
||||
END AS sort_key
|
||||
FROM GroupedData
|
||||
)
|
||||
SELECT runner_id, strat_id, batch_id, symbol, name, note, started,
|
||||
stopped, mode, account, bt_from, bt_to, ilog_save, profit,
|
||||
trade_count, end_positions, end_positions_avgp, metrics
|
||||
FROM InterleavedGroups
|
||||
ORDER BY
|
||||
sort_key DESC,
|
||||
CASE WHEN batch_id IS NOT NULL THEN 0 ELSE 1 END,
|
||||
stopped DESC
|
||||
LIMIT {request.length} OFFSET {request.start}
|
||||
"""
|
||||
c.execute(paginated_query, {'search_value': f'%{search_value}%'})
|
||||
rows = c.fetchall()
|
||||
|
||||
# Filtered count query
|
||||
filtered_count_query = """
|
||||
SELECT COUNT(*) FROM runner_header
|
||||
WHERE (:search_value = '' OR strat_id LIKE :search_value OR batch_id LIKE :search_value)
|
||||
"""
|
||||
c.execute(filtered_count_query, {'search_value': f'%{search_value}%'})
|
||||
filtered_count = c.fetchone()[0]
|
||||
|
||||
results = [row_to_runarchiveview(row) for row in rows]
|
||||
|
||||
finally:
|
||||
conn.row_factory = None
|
||||
pool.release_connection(conn)
|
||||
|
||||
try:
|
||||
obj = RunArchiveViewPagination(draw=request.draw, recordsTotal=total_count, recordsFiltered=filtered_count, data=results)
|
||||
return 0, obj
|
||||
except Exception as e:
|
||||
return -2, str(e) + format_exc()
|
||||
|
||||
|
||||
#DECOMMS
|
||||
# def get_all_archived_runners():
|
||||
# conn = pool.get_connection()
|
||||
@ -1574,6 +1661,9 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool =
|
||||
state.ind_mapping = {**local_dict_inds, **local_dict_bars, **local_dict_cbar_inds}
|
||||
#print("IND MAPPING DONE:", state.ind_mapping)
|
||||
|
||||
##intialize required vars from strat init
|
||||
state.vars["loaded_models"] = {}
|
||||
|
||||
##intialize dynamic indicators
|
||||
initialize_dynamic_indicators(state)
|
||||
|
||||
|
||||
@ -52,6 +52,16 @@ class Account(str, Enum):
|
||||
"""
|
||||
ACCOUNT1 = "ACCOUNT1"
|
||||
ACCOUNT2 = "ACCOUNT2"
|
||||
|
||||
class Moddus(str, Enum):
|
||||
"""
|
||||
Moddus for RunManager record
|
||||
|
||||
schedule - scheduled record
|
||||
queue - queued record
|
||||
"""
|
||||
SCHEDULE = "schedule"
|
||||
QUEUE = "queue"
|
||||
class RecordType(str, Enum):
|
||||
"""
|
||||
Represents output of aggregator
|
||||
@ -64,6 +74,15 @@ class RecordType(str, Enum):
|
||||
CBARRENKO = "cbarrenko"
|
||||
TRADE = "trade"
|
||||
|
||||
class SchedulerStatus(str, Enum):
|
||||
"""
|
||||
ACTIVE - active scheduling
|
||||
SUSPENDED - suspended for scheduling
|
||||
"""
|
||||
|
||||
ACTIVE = "active"
|
||||
SUSPENDED = "suspended"
|
||||
|
||||
class Mode(str, Enum):
|
||||
"""
|
||||
LIVE - live on production
|
||||
@ -77,7 +96,6 @@ class Mode(str, Enum):
|
||||
BT = "backtest"
|
||||
PREP = "prep"
|
||||
|
||||
|
||||
class StartBarAlign(str, Enum):
|
||||
"""
|
||||
Represents first bar start time alignement according to timeframe
|
||||
|
||||
@ -289,7 +289,7 @@ class Trade_Offline_Streamer(Thread):
|
||||
cnt = 1
|
||||
|
||||
|
||||
for t in tqdm(tradesResponse[symbol]):
|
||||
for t in tqdm(tradesResponse[symbol], desc="Loading Trades"):
|
||||
|
||||
#protoze je zde cely den, poustime dal, jen ty relevantni
|
||||
#pokud je start_time < trade < end_time
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import os,sys
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
os.environ["KERAS_BACKEND"] = "jax"
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY, LOG_FILE, MODEL_DIR
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY, LOG_PATH, MODEL_DIR
|
||||
from alpaca.data.timeframe import TimeFrame, TimeFrameUnit
|
||||
from datetime import datetime
|
||||
from rich import print
|
||||
@ -11,7 +11,7 @@ import uvicorn
|
||||
from uuid import UUID
|
||||
import v2realbot.controller.services as cs
|
||||
from v2realbot.utils.ilog import get_log_window
|
||||
from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem, InstantIndicator, DataTablesRequest, AnalyzerInputs
|
||||
from v2realbot.common.model import RunManagerRecord, StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem, InstantIndicator, DataTablesRequest, AnalyzerInputs
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query
|
||||
from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@ -39,6 +39,9 @@ import shutil
|
||||
from starlette.responses import JSONResponse
|
||||
import mlroom
|
||||
import mlroom.utils.mlutils as ml
|
||||
from typing import List
|
||||
import v2realbot.controller.run_manager as rm
|
||||
import v2realbot.scheduler.ap_scheduler as aps
|
||||
#from async io import Queue, QueueEmpty
|
||||
#
|
||||
# install()
|
||||
@ -249,11 +252,13 @@ def _run_stratin(stratin_id: UUID, runReq: RunRequest):
|
||||
runReq.bt_to = zoneNY.localize(runReq.bt_to)
|
||||
#pokud jedeme nad test intervaly anebo je požadováno více dní - pouštíme jako batch day by day
|
||||
#do budoucna dát na FE jako flag
|
||||
if runReq.mode != Mode.LIVE and runReq.test_batch_id is not None or (runReq.bt_from.date() != runReq.bt_to.date()):
|
||||
print(runReq)
|
||||
if runReq.mode not in [Mode.LIVE, Mode.PAPER] and (runReq.test_batch_id is not None or (runReq.bt_from is not None and runReq.bt_to is not None and runReq.bt_from.date() != runReq.bt_to.date())):
|
||||
res, id = cs.run_batch_stratin(id=stratin_id, runReq=runReq)
|
||||
else:
|
||||
if runReq.weekdays_filter is not None:
|
||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Weekday only for backtest mode with batch (not single day)")
|
||||
#not necessary for live/paper the weekdays are simply ignored, in the future maybe add validation if weekdays are presented
|
||||
#if runReq.weekdays_filter is not None:
|
||||
# raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Weekday only for backtest mode with batch (not single day)")
|
||||
res, id = cs.run_stratin(id=stratin_id, runReq=runReq)
|
||||
if res == 0: return id
|
||||
elif res < 0:
|
||||
@ -555,27 +560,30 @@ def _get_archived_runner_log_byID(runner_id: UUID, timestamp_from: float, timest
|
||||
# endregion
|
||||
# A simple function to read the last lines of a file
|
||||
def tail(file_path, n=10, buffer_size=1024):
|
||||
with open(file_path, 'rb') as f:
|
||||
f.seek(0, 2) # Move to the end of the file
|
||||
file_size = f.tell()
|
||||
lines = []
|
||||
buffer = bytearray()
|
||||
try:
|
||||
with open(file_path, 'rb') as f:
|
||||
f.seek(0, 2) # Move to the end of the file
|
||||
file_size = f.tell()
|
||||
lines = []
|
||||
buffer = bytearray()
|
||||
|
||||
for i in range(file_size // buffer_size + 1):
|
||||
read_start = max(-buffer_size * (i + 1), -file_size)
|
||||
f.seek(read_start, 2)
|
||||
read_size = min(buffer_size, file_size - buffer_size * i)
|
||||
buffer[0:0] = f.read(read_size) # Prepend to buffer
|
||||
for i in range(file_size // buffer_size + 1):
|
||||
read_start = max(-buffer_size * (i + 1), -file_size)
|
||||
f.seek(read_start, 2)
|
||||
read_size = min(buffer_size, file_size - buffer_size * i)
|
||||
buffer[0:0] = f.read(read_size) # Prepend to buffer
|
||||
|
||||
if buffer.count(b'\n') >= n + 1:
|
||||
break
|
||||
if buffer.count(b'\n') >= n + 1:
|
||||
break
|
||||
|
||||
lines = buffer.decode(errors='ignore').splitlines()[-n:]
|
||||
return lines
|
||||
lines = buffer.decode(errors='ignore').splitlines()[-n:]
|
||||
return lines
|
||||
except Exception as e:
|
||||
return [str(e) + format_exc()]
|
||||
|
||||
@app.get("/log", dependencies=[Depends(api_key_auth)])
|
||||
def read_log(lines: int = 10):
|
||||
log_path = LOG_FILE
|
||||
def read_log(lines: int = 700, logfile: str = "strat.log"):
|
||||
log_path = LOG_PATH / logfile
|
||||
return {"lines": tail(log_path, lines)}
|
||||
|
||||
#get alpaca history bars
|
||||
@ -674,7 +682,7 @@ def get_testlists():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
|
||||
|
||||
# API endpoint to retrieve a single record by ID
|
||||
@app.get('/testlists/{record_id}')
|
||||
@app.get('/testlists/{record_id}', dependencies=[Depends(api_key_auth)])
|
||||
def get_testlist(record_id: str):
|
||||
res, testlist = cs.get_testlist_byID(record_id=record_id)
|
||||
|
||||
@ -684,7 +692,7 @@ def get_testlist(record_id: str):
|
||||
raise HTTPException(status_code=404, detail='Record not found')
|
||||
|
||||
# API endpoint to update a record
|
||||
@app.put('/testlists/{record_id}')
|
||||
@app.put('/testlists/{record_id}', dependencies=[Depends(api_key_auth)])
|
||||
def update_testlist(record_id: str, testlist: TestList):
|
||||
# Check if the record exists
|
||||
conn = pool.get_connection()
|
||||
@ -704,7 +712,7 @@ def update_testlist(record_id: str, testlist: TestList):
|
||||
return testlist
|
||||
|
||||
# API endpoint to delete a record
|
||||
@app.delete('/testlists/{record_id}')
|
||||
@app.delete('/testlists/{record_id}', dependencies=[Depends(api_key_auth)])
|
||||
def delete_testlist(record_id: str):
|
||||
# Check if the record exists
|
||||
conn = pool.get_connection()
|
||||
@ -788,6 +796,66 @@ def delete_item(item_id: int) -> dict:
|
||||
|
||||
# endregion
|
||||
|
||||
# region scheduler
|
||||
# 1. Fetch All RunManagerRecords
|
||||
@app.get("/run_manager_records/", dependencies=[Depends(api_key_auth)], response_model=List[RunManagerRecord])
|
||||
#TODO zvazit rozsireni vystupu o strat_status (running/stopped)
|
||||
def get_all_run_manager_records():
|
||||
result, records = rm.fetch_all_run_manager_records()
|
||||
if result != 0:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error fetching records")
|
||||
return records
|
||||
|
||||
# 2. Fetch RunManagerRecord by ID
|
||||
@app.get("/run_manager_records/{record_id}", dependencies=[Depends(api_key_auth)], response_model=RunManagerRecord)
|
||||
#TODO zvazit rozsireni vystupu o strat_status (running/stopped)
|
||||
def get_run_manager_record(record_id: UUID):
|
||||
result, record = rm.fetch_run_manager_record_by_id(record_id)
|
||||
if result == -2: # Record not found
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Record not found")
|
||||
elif result != 0:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error fetching record")
|
||||
return record
|
||||
|
||||
# 3. Update RunManagerRecord
|
||||
@app.patch("/run_manager_records/{record_id}", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK)
|
||||
def update_run_manager_record(record_id: UUID, update_data: RunManagerRecord):
|
||||
#make dates zone aware zoneNY
|
||||
# if update_data.valid_from is not None:
|
||||
# update_data.valid_from = zoneNY.localize(update_data.valid_from)
|
||||
# if update_data.valid_to is not None:
|
||||
# update_data.valid_to = zoneNY.localize(update_data.valid_to)
|
||||
result, message = rm.update_run_manager_record(record_id, update_data)
|
||||
if result == -2: # Update failed
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||
elif result != 0:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error during update {result} {message}")
|
||||
return {"message": "Record updated successfully"}
|
||||
|
||||
# 4. Delete RunManagerRecord
|
||||
@app.delete("/run_manager_records/{record_id}", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK)
|
||||
def delete_run_manager_record(record_id: UUID):
|
||||
result, message = rm.delete_run_manager_record(record_id)
|
||||
if result == -2: # Delete failed
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||
elif result != 0:
|
||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error during deletion {result} {message}")
|
||||
return {"message": "Record deleted successfully"}
|
||||
|
||||
@app.post("/run_manager_records/", status_code=status.HTTP_201_CREATED)
|
||||
def create_run_manager_record(new_record: RunManagerRecord, api_key_auth: Depends = Depends(api_key_auth)):
|
||||
#make date zone aware - convert to zoneNY
|
||||
# if new_record.valid_from is not None:
|
||||
# new_record.valid_from = zoneNY.localize(new_record.valid_from)
|
||||
# if new_record.valid_to is not None:
|
||||
# new_record.valid_to = zoneNY.localize(new_record.valid_to)
|
||||
|
||||
result, record_id = rm.add_run_manager_record(new_record)
|
||||
if result != 0:
|
||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error during record creation: {result} {record_id}")
|
||||
return {"id": record_id}
|
||||
# endregion
|
||||
|
||||
#model section
|
||||
#UPLOAD MODEL
|
||||
@app.post("/model/upload_model", dependencies=[Depends(api_key_auth)])
|
||||
@ -924,7 +992,22 @@ if __name__ == "__main__":
|
||||
insert_thread = Thread(target=insert_queue2db)
|
||||
insert_thread.start()
|
||||
|
||||
#attach debugGER to be able to debug scheduler jobs (run in separate threads)
|
||||
# debugpy.listen(('localhost', 5678))
|
||||
# print("Waiting for debugger to attach...")
|
||||
# debugpy.wait_for_client() # Script will pause here until debugger is attached
|
||||
|
||||
#init scheduled tasks from schedule table
|
||||
#Add APS scheduler job refresh
|
||||
res, result = aps.initialize_jobs()
|
||||
if res < 0:
|
||||
#raise exception
|
||||
raise Exception(f"Error {res} initializing APS jobs, error {result}")
|
||||
|
||||
uvicorn.run("__main__:app", host="0.0.0.0", port=8000, reload=False)
|
||||
except Exception as e:
|
||||
print("Error intializing app: " + str(e) + format_exc())
|
||||
aps.scheduler.shutdown(wait=False)
|
||||
finally:
|
||||
print("closing insert_conn connection")
|
||||
insert_conn.close()
|
||||
|
||||
0
v2realbot/scheduler/__init__.py
Normal file
0
v2realbot/scheduler/__init__.py
Normal file
307
v2realbot/scheduler/ap_scheduler.py
Normal file
307
v2realbot/scheduler/ap_scheduler.py
Normal file
@ -0,0 +1,307 @@
|
||||
from uuid import UUID
|
||||
from typing import Any, List, Tuple
|
||||
from uuid import UUID, uuid4
|
||||
from v2realbot.enums.enums import Moddus, SchedulerStatus, RecordType, StartBarAlign, Mode, Account, OrderSide
|
||||
from v2realbot.common.model import RunManagerRecord, StrategyInstance, RunDay, StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem, InstantIndicator, DataTablesRequest
|
||||
from v2realbot.utils.utils import validate_and_format_time, AttributeDict, zoneNY, zonePRG, safe_get, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram, concatenate_weekdays, transform_data
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType
|
||||
from datetime import datetime
|
||||
from v2realbot.config import JOB_LOG_FILE, STRATVARS_UNCHANGEABLES, ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR,BT_FILL_CONS_TRADES_REQUIRED,BT_FILL_LOG_SURROUNDING_TRADES,BT_FILL_CONDITION_BUY_LIMIT,BT_FILL_CONDITION_SELL_LIMIT, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN, MEDIA_DIRECTORY, RUNNER_DETAIL_DIRECTORY, OFFLINE_MODE
|
||||
import numpy as np
|
||||
from rich import print as richprint
|
||||
import v2realbot.controller.services as cs
|
||||
import v2realbot.controller.run_manager as rm
|
||||
import v2realbot.scheduler.scheduler as sch
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.job import Job
|
||||
|
||||
#NOTE zatím není podporováno spouštění strategie přes půlnoc - musí se dořešit weekday_filter
|
||||
#který je zatím jen jeden jak pro start_time tak stop_time - což by v případě strategií běžících
|
||||
#přes půlnoc nezafungovalo (stop by byl následující den a scheduler by jej nespustil)
|
||||
|
||||
def format_apscheduler_jobs(jobs: list[Job]) -> list[dict]:
|
||||
if not jobs:
|
||||
print("No scheduled jobs.")
|
||||
return
|
||||
|
||||
jobs_info = []
|
||||
|
||||
for job in jobs:
|
||||
job_info = {
|
||||
"Job ID": job.id,
|
||||
"Next Run Time": job.next_run_time,
|
||||
"Job Function": job.func.__name__,
|
||||
"Trigger": str(job.trigger),
|
||||
"Job Args": ', '.join(map(str, job.args)),
|
||||
"Job Kwargs": ', '.join(f"{k}={v}" for k, v in job.kwargs.items())
|
||||
}
|
||||
jobs_info.append(job_info)
|
||||
|
||||
return jobs_info
|
||||
|
||||
def get_day_of_week(weekdays_filter):
|
||||
if not weekdays_filter:
|
||||
return '*' # All days of the week
|
||||
return ','.join(map(str, weekdays_filter))
|
||||
|
||||
#initialize_jobs se spousti
|
||||
#- pri spusteni
|
||||
#- triggerovano z add/update a delete
|
||||
|
||||
#zatim cely refresh, v budoucnu upravime jen na zmene menene polozky - viz
|
||||
#https://chat.openai.com/c/2a1423ee-59df-47ff-b073-0c49ade51ed7
|
||||
|
||||
#pomocna funkce, ktera vraci strat_id, ktera jsou v scheduleru vickrat (logika pro ne se lisi)
|
||||
def stratin_occurences(all_records: list[RunManagerRecord]):
|
||||
# Count occurrences
|
||||
strat_id_counts = {}
|
||||
for record in all_records:
|
||||
if record.strat_id in strat_id_counts:
|
||||
strat_id_counts[record.strat_id] += 1
|
||||
else:
|
||||
strat_id_counts[record.strat_id] = 1
|
||||
|
||||
# Find strat_id values that appear twice or more
|
||||
repeated_strat_ids = [strat_id for strat_id, count in strat_id_counts.items() if count >= 2]
|
||||
|
||||
return 0, repeated_strat_ids
|
||||
|
||||
|
||||
def initialize_jobs(run_manager_records: RunManagerRecord = None):
|
||||
"""
|
||||
Initialize all scheduled jobs from RunManagerRecords with moddus = "schedule"
|
||||
Triggered on app init and update of table
|
||||
It deleted all "schedule_" prefixed jobs and schedule new ones base on runmanager table
|
||||
prefiX of "schedule_" in aps scheduler allows to distinguisd schedule types jobs and allows more jobs categories
|
||||
|
||||
Parameters
|
||||
----------
|
||||
run_manager_records : RunManagerRecord, optional
|
||||
RunManagerRecords to initialize the jobs from, by default None
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[int, Union[List[dict], str]]
|
||||
A tuple containing an error code and a message. If there is no error, the
|
||||
message will contain a list of dictionaries with information about the
|
||||
scheduled jobs, otherwise it will contain an error message.
|
||||
"""
|
||||
if run_manager_records is None:
|
||||
res, run_manager_records = rm.fetch_all_run_manager_records()
|
||||
if res < 0:
|
||||
err_msg= f"Error {res} fetching all runmanager records, error {run_manager_records}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
|
||||
scheduled_jobs = scheduler.get_jobs()
|
||||
|
||||
#print(f"Current {len(scheduled_jobs)} scheduled jobs: {str(scheduled_jobs)}")
|
||||
for job in scheduled_jobs:
|
||||
if job.id.startswith("scheduler_"):
|
||||
scheduler.remove_job(job.id)
|
||||
record : RunManagerRecord = None
|
||||
for record in run_manager_records:
|
||||
if record.status == SchedulerStatus.ACTIVE and record.moddus == Moddus.SCHEDULE:
|
||||
day_of_week = get_day_of_week(record.weekdays_filter)
|
||||
|
||||
hour, minute = map(int, record.start_time.split(':'))
|
||||
start_trigger = CronTrigger(day_of_week=day_of_week, hour=hour, minute=minute,
|
||||
start_date=record.valid_from, end_date=record.valid_to, timezone=zoneNY)
|
||||
stop_hour, stop_minute = map(int, record.stop_time.split(':'))
|
||||
stop_trigger = CronTrigger(day_of_week=day_of_week, hour=stop_hour, minute=stop_minute,
|
||||
start_date=record.valid_from, end_date=record.valid_to, timezone=zoneNY)
|
||||
|
||||
# Schedule new jobs with the 'scheduler_' prefix
|
||||
scheduler.add_job(start_runman_record, start_trigger, id=f"scheduler_start_{record.id}", args=[record.id])
|
||||
scheduler.add_job(stop_runman_record, stop_trigger, id=f"scheduler_stop_{record.id}", args=[record.id])
|
||||
|
||||
#scheduler.add_job(print_hello, 'interval', seconds=10, id=f"scheduler_testinterval")
|
||||
scheduled_jobs = scheduler.get_jobs()
|
||||
print(f"APS jobs refreshed ({len(scheduled_jobs)})")
|
||||
current_jobs_dict = format_apscheduler_jobs(scheduled_jobs)
|
||||
richprint(current_jobs_dict)
|
||||
return 0, current_jobs_dict
|
||||
|
||||
#zastresovaci funkce resici error handling a printing
|
||||
def start_runman_record(id: UUID, market = "US", debug_date = None):
|
||||
record = None
|
||||
res, record, msg = _start_runman_record(id=id, market=market, debug_date=debug_date)
|
||||
|
||||
if record is not None:
|
||||
market_time_now = datetime.now().astimezone(zoneNY) if debug_date is None else debug_date
|
||||
record.last_processed = market_time_now
|
||||
formatted_date = market_time_now.strftime("%y.%m.%d %H:%M:%S")
|
||||
history_string = f"{formatted_date}"
|
||||
history_string += " STARTED" if res == 0 else "NOTE:" + msg if res == -1 else "ERROR:" + msg
|
||||
print(history_string)
|
||||
if record.history is None:
|
||||
record.history = history_string
|
||||
else:
|
||||
record.history += "\n" + history_string
|
||||
|
||||
rs, msg_rs = update_runman_record(record)
|
||||
if rs < 0:
|
||||
msg_rs = f"Error saving result to history: {msg_rs}"
|
||||
print(msg_rs)
|
||||
send_to_telegram(msg_rs)
|
||||
|
||||
|
||||
if res < -1:
|
||||
msg = f"START JOB: {id} ERROR\n" + msg
|
||||
send_to_telegram(msg)
|
||||
print(msg)
|
||||
else:
|
||||
print(f"START JOB: {id} FINISHED {res}")
|
||||
|
||||
|
||||
def update_runman_record(record: RunManagerRecord):
|
||||
#update record (nejspis jeste upravit - last_run a history)
|
||||
res, set = rm.update_run_manager_record(record.id, record)
|
||||
if res == 0:
|
||||
print(f"Record updated {set}")
|
||||
return 0, "OK"
|
||||
else:
|
||||
err_msg= f"STOP: Error updating {record.id} errir {set} with values {record}"
|
||||
return -2, err_msg#toto stopne zpracovani dalsich zaznamu pri chybe, zvazit continue
|
||||
|
||||
def stop_runman_record(id: UUID, market = "US", debug_date = None):
|
||||
res, record, msg = _stop_runman_record(id=id, market=market, debug_date=debug_date)
|
||||
#results : 0 - ok, -1 not running/already running/not specific, -2 error
|
||||
|
||||
#report vzdy zapiseme do history, pokud je record not None, pripadna chyba se stala po dotazeni recordu
|
||||
if record is not None:
|
||||
market_time_now = datetime.now().astimezone(zoneNY) if debug_date is None else debug_date
|
||||
record.last_processed = market_time_now
|
||||
formatted_date = market_time_now.strftime("%y.%m.%d %H:%M:%S")
|
||||
history_string = f"{formatted_date}"
|
||||
history_string += " STOPPED" if res == 0 else "NOTE:" + msg if res == -1 else "ERROR:" + msg
|
||||
print(history_string)
|
||||
if record.history is None:
|
||||
record.history = history_string
|
||||
else:
|
||||
record.history += "\n" + history_string
|
||||
|
||||
rs, msg_rs = update_runman_record(record)
|
||||
if rs < 0:
|
||||
msg_rs = f"Error saving result to history: {msg_rs}"
|
||||
print(msg_rs)
|
||||
send_to_telegram(msg_rs)
|
||||
|
||||
if res < -1:
|
||||
msg = f"STOP JOB: {id} ERROR\n" + msg
|
||||
send_to_telegram(msg)
|
||||
print(msg)
|
||||
else:
|
||||
print(f"STOP JOB: {id} FINISHED")
|
||||
|
||||
#start function that is called from the job
|
||||
def _start_runman_record(id: UUID, market = "US", debug_date = None):
|
||||
print(f"Start scheduled record {id}")
|
||||
|
||||
record : RunManagerRecord = None
|
||||
res, result = rm.fetch_run_manager_record_by_id(id)
|
||||
if res < 0:
|
||||
result = "Error fetching run manager record by id: " + str(id) + " Error: " + str(result)
|
||||
return res, record, result
|
||||
|
||||
record = result
|
||||
|
||||
res, sada = sch.get_todays_market_times(market=market, debug_date=debug_date)
|
||||
if res == 0:
|
||||
market_time_now, market_open_datetime, market_close_datetime = sada
|
||||
print(f"OPEN:{market_open_datetime} CLOSE:{market_close_datetime}")
|
||||
else:
|
||||
sada = "Error getting market times (CLOSED): " + str(sada)
|
||||
return res, record, sada
|
||||
|
||||
if cs.is_stratin_running(record.strat_id):
|
||||
return -1, record, f"Stratin {record.strat_id} is already running"
|
||||
|
||||
res, result = sch.run_scheduled_strategy(record)
|
||||
if res < 0:
|
||||
result = "Error running strategy: " + str(result)
|
||||
return res, record, result
|
||||
else:
|
||||
record.runner_id = UUID(result)
|
||||
|
||||
return 0, record, record.runner_id
|
||||
|
||||
#stop function that is called from the job
|
||||
def _stop_runman_record(id: UUID, market = "US", debug_date = None):
|
||||
record = None
|
||||
#get all records
|
||||
print(f"Stopping record {id}")
|
||||
res, all_records = rm.fetch_all_run_manager_records()
|
||||
if res < 0:
|
||||
err_msg= f"Error {res} fetching all runmanager records, error {all_records}"
|
||||
return -2, record, err_msg
|
||||
|
||||
record : RunManagerRecord = None
|
||||
for rec in all_records:
|
||||
if rec.id == id:
|
||||
record = rec
|
||||
break
|
||||
|
||||
if record is None:
|
||||
return -2, record, f"Record id {id} not found"
|
||||
|
||||
#strat_ids that are repeated
|
||||
res, repeated_strat_ids = stratin_occurences(all_records)
|
||||
if res < 0:
|
||||
err_msg= f"Error {res} finding repeated strat_ids, error {repeated_strat_ids}"
|
||||
return -2, record, err_msg
|
||||
|
||||
if record.strat_running is True:
|
||||
#stopneme na zaklade record.runner_id
|
||||
#this code
|
||||
id_to_stop = record.runner_id
|
||||
|
||||
#pokud existuje manualne spustena stejna strategie a neni jich vic - je to jednoznacne - stopneme ji
|
||||
elif cs.is_stratin_running(record.strat_id) and record.strat_id not in repeated_strat_ids:
|
||||
#stopneme na zaklade record.strat_id
|
||||
id_to_stop = record.strat_id
|
||||
|
||||
else:
|
||||
msg = f"strategy {record.strat_id} not RUNNING or not distinctive (manually launched or two strat_ids in scheduler)"
|
||||
print(msg)
|
||||
return -1, record, msg
|
||||
|
||||
print(f"Requesting STOP {id_to_stop}")
|
||||
res, msg = cs.stop_runner(id=id_to_stop)
|
||||
if res < 0:
|
||||
msg = f"ERROR while STOPPING runner_id/strat_id {id_to_stop} {msg}"
|
||||
return -2, record, msg
|
||||
else:
|
||||
record.runner_id = None
|
||||
|
||||
return 0, record, "finished"
|
||||
|
||||
# Global scheduler instance
|
||||
scheduler = BackgroundScheduler(timezone=zoneNY)
|
||||
scheduler.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#use naive datetoime
|
||||
debug_date = None
|
||||
debug_date = datetime(2024, 2, 16, 9, 37, 0, 0)
|
||||
#debug_date = datetime(2024, 2, 16, 10, 30, 0, 0)
|
||||
#debug_date = datetime(2024, 2, 16, 16, 1, 0, 0)
|
||||
|
||||
id = UUID("bc4ec7d2-249b-4799-a02f-f1ce66f83d4a")
|
||||
|
||||
if debug_date is not None:
|
||||
# Localize the naive datetime object to the Eastern timezone
|
||||
debug_date = zoneNY.localize(debug_date)
|
||||
#debugdate formatted as string in format "23.12.2024 9:30"
|
||||
formatted_date = debug_date.strftime("%d.%m.%Y %H:%M")
|
||||
print("Scheduler.py NY time: ", formatted_date)
|
||||
print("ISoformat", debug_date.isoformat())
|
||||
|
||||
# res, result = start_runman_record(id=id, market = "US", debug_date = debug_date)
|
||||
# print(f"CALL FINISHED, with {debug_date} RESULT: {res}, {result}")
|
||||
|
||||
|
||||
res, result = stop_runman_record(id=id, market = "US", debug_date = debug_date)
|
||||
print(f"CALL FINISHED, with {debug_date} RESULT: {res}, {result}")
|
||||
427
v2realbot/scheduler/scheduler.py
Normal file
427
v2realbot/scheduler/scheduler.py
Normal file
@ -0,0 +1,427 @@
|
||||
import json
|
||||
import datetime
|
||||
import v2realbot.controller.services as cs
|
||||
import v2realbot.controller.run_manager as rm
|
||||
from v2realbot.common.model import RunnerView, RunManagerRecord, StrategyInstance, Runner, RunRequest, Trade, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem, InstantIndicator, DataTablesRequest, AnalyzerInputs
|
||||
from uuid import uuid4, UUID
|
||||
from v2realbot.utils.utils import json_serial, send_to_telegram, zoneNY, zonePRG, fetch_calendar_data
|
||||
from datetime import datetime, timedelta
|
||||
from traceback import format_exc
|
||||
from rich import print
|
||||
import requests
|
||||
from v2realbot.config import WEB_API_KEY
|
||||
|
||||
#Puvodni varainta schedulera, ktera mela bezet v pravidelnych intervalech
|
||||
#a spoustet scheduled items v RunManagerRecord
|
||||
#Nově bylo zrefaktorováno a využitý apscheduler - knihovna v pythonu
|
||||
#umožňující plánování jobů, tzn. nyní je každý scheduled záznam RunManagerRecord
|
||||
#naplanovany jako samostatni job a triggerován pouze jednou v daný čas pro start a stop
|
||||
#novy kod v aps_scheduler.py
|
||||
|
||||
def get_todays_market_times(market = "US", debug_date = None):
|
||||
try:
|
||||
if market == "US":
|
||||
#zjistit vsechny podminky - mozna loopovat - podminky jsou vlevo
|
||||
if debug_date is not None:
|
||||
nowNY = debug_date
|
||||
else:
|
||||
nowNY = datetime.now().astimezone(zoneNY)
|
||||
nowNY_date = nowNY.date()
|
||||
#is market open - nyni pouze US
|
||||
cal_dates = fetch_calendar_data(nowNY_date, nowNY_date)
|
||||
|
||||
if len(cal_dates) == 0:
|
||||
print("No Market Day today")
|
||||
return -1, "Market Closed"
|
||||
#zatim podpora pouze main session
|
||||
|
||||
#pouze main session
|
||||
market_open_datetime = zoneNY.localize(cal_dates[0].open)
|
||||
market_close_datetime = zoneNY.localize(cal_dates[0].close)
|
||||
return 0, (nowNY, market_open_datetime, market_close_datetime)
|
||||
else:
|
||||
return -1, "Market not supported"
|
||||
except Exception as e:
|
||||
err_msg = f"General error in {e} {format_exc()}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
|
||||
def get_running_strategies():
|
||||
# Construct the URL for the local REST API endpoint on port 8000
|
||||
api_url = "http://localhost:8000/runners/"
|
||||
|
||||
# Headers for the request
|
||||
headers = {
|
||||
"X-API-Key": WEB_API_KEY
|
||||
}
|
||||
|
||||
try:
|
||||
# Make the GET request to the API with the headers
|
||||
response = requests.get(api_url, headers=headers)
|
||||
|
||||
# Check if the request was successful
|
||||
if response.status_code == 200:
|
||||
runners = response.json()
|
||||
print("Successfully fetched runners.")
|
||||
strat_ids = []
|
||||
ids = []
|
||||
|
||||
for runner_view in runners:
|
||||
strat_ids.append(UUID(runner_view["strat_id"]))
|
||||
ids.append(UUID(runner_view["id"]))
|
||||
|
||||
return 0, (strat_ids, ids)
|
||||
else:
|
||||
err_msg = f"Failed to fetch runners. Status Code: {response.status_code}, Response: {response.text}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
except requests.RequestException as e:
|
||||
err_msg = f"Request failed: {str(e)}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
|
||||
def stop_strategy(runner_id):
|
||||
# Construct the URL for the local REST API endpoint on port 8000 #option 127.0.0.1
|
||||
api_url = f"http://localhost:8000/runners/{runner_id}/stop"
|
||||
|
||||
# Headers for the request
|
||||
headers = {
|
||||
"X-API-Key": WEB_API_KEY
|
||||
}
|
||||
|
||||
try:
|
||||
# Make the PUT request to the API with the headers
|
||||
response = requests.put(api_url, headers=headers)
|
||||
|
||||
# Check if the request was successful
|
||||
if response.status_code == 200:
|
||||
print(f"Runner/strat_id {runner_id} stopped successfully.")
|
||||
return 0, runner_id
|
||||
else:
|
||||
err_msg = f"Failed to stop runner {runner_id}. Status Code: {response.status_code}, Response: {response.text}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
except requests.RequestException as e:
|
||||
err_msg = f"Request failed: {str(e)}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
|
||||
def fetch_stratin(stratin_id):
|
||||
# Construct the URL for the REST API endpoint
|
||||
api_url = f"http://localhost:8000/stratins/{stratin_id}"
|
||||
|
||||
# Headers for the request
|
||||
headers = {
|
||||
"X-API-Key": WEB_API_KEY
|
||||
}
|
||||
|
||||
try:
|
||||
# Make the GET request to the API with the headers
|
||||
response = requests.get(api_url, headers=headers)
|
||||
|
||||
# Check if the request was successful
|
||||
if response.status_code == 200:
|
||||
# Parse the response as a StrategyInstance object
|
||||
strategy_instance = response.json()
|
||||
#strategy_instance = response # Assuming the response is in JSON format
|
||||
print(f"StrategyInstance fetched: {stratin_id}")
|
||||
return 0, strategy_instance
|
||||
else:
|
||||
err_msg = f"Failed to fetch StrategyInstance {stratin_id}. " \
|
||||
f"Status Code: {response.status_code}, Response: {response.text}"
|
||||
print(err_msg)
|
||||
return -1, err_msg
|
||||
except requests.RequestException as e:
|
||||
err_msg = f"Request failed: {str(e)}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
|
||||
#return list of strat_ids that are in the scheduled table more than once
|
||||
#TODO toto je workaround dokud nebude canndidates logika ze selectu nyni presunuta na fetch_all_run_manager_records a logiku v pythonu
|
||||
def stratin_occurences():
|
||||
#get all records
|
||||
res, all_records = rm.fetch_all_run_manager_records()
|
||||
if res < 0:
|
||||
err_msg= f"Error {res} fetching all runmanager records, error {all_records}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
|
||||
# Count occurrences
|
||||
strat_id_counts = {}
|
||||
for record in all_records:
|
||||
if record.strat_id in strat_id_counts:
|
||||
strat_id_counts[record.strat_id] += 1
|
||||
else:
|
||||
strat_id_counts[record.strat_id] = 1
|
||||
|
||||
# Find strat_id values that appear twice or more
|
||||
repeated_strat_ids = [strat_id for strat_id, count in strat_id_counts.items() if count >= 2]
|
||||
|
||||
return 0, repeated_strat_ids
|
||||
|
||||
# in case debug_date is not provided, it takes current time of the given market
|
||||
#V budoucnu zde bude loopa pro kazdy obsluhovany market, nyni pouze US
|
||||
def startstop_scheduled(debug_date = None, market = "US") -> tuple[int, str]:
|
||||
res, sada = get_todays_market_times(market=market, debug_date=debug_date)
|
||||
if res == 0:
|
||||
market_time_now, market_open_datetime, market_close_datetime = sada
|
||||
print(f"OPEN:{market_open_datetime} CLOSE:{market_close_datetime}")
|
||||
else:
|
||||
return res, sada
|
||||
|
||||
#its market day
|
||||
res, candidates = rm.fetch_scheduled_candidates_for_start_and_stop(market_time_now, market)
|
||||
if res == 0:
|
||||
print(f"Candidates fetched, start: {len(candidates['start'])} stop: {len(candidates['stop'])}")
|
||||
else:
|
||||
return res, candidates
|
||||
|
||||
if candidates is None or (len(candidates["start"]) == 0 and len(candidates["stop"]) == 0):
|
||||
return -1, f"No candidates found for {market_time_now} and {market}"
|
||||
#do budoucna, az budou runnery persistovane, bude stav kazde strategie v RunManagerRecord
|
||||
#get current runners (mozna optimalizace, fetch per each section start/stop)
|
||||
res, sada = get_running_strategies()
|
||||
if res < 0:
|
||||
err_msg= f"Error fetching running strategies, error {sada}"
|
||||
print(err_msg)
|
||||
send_to_telegram(err_msg)
|
||||
return -2, err_msg
|
||||
strat_ids_running, runnerids_running = sada
|
||||
print(f"Currently running: {len(strat_ids_running)}")
|
||||
|
||||
#IERATE over START CAndidates
|
||||
record: RunManagerRecord = None
|
||||
print(f"START - Looping over {len(candidates['start'])} candidates")
|
||||
for record in candidates['start']:
|
||||
print("Candidate: ", record)
|
||||
|
||||
if record.weekdays_filter is not None and len(record.weekdays_filter) > 0:
|
||||
curr_weekday = market_time_now.weekday()
|
||||
if curr_weekday not in record.weekdays_filter:
|
||||
print(f"Strategy {record.strat_id} not started, today{curr_weekday} not in weekdays filter {record.weekdays_filter}")
|
||||
continue
|
||||
#one strat_id can run only once at time
|
||||
if record.strat_id in strat_ids_running:
|
||||
msg = f"strategy already {record.strat_id} is running"
|
||||
continue
|
||||
|
||||
res, result = run_scheduled_strategy(record)
|
||||
if res < 0:
|
||||
send_to_telegram(result)
|
||||
print(result)
|
||||
else:
|
||||
record.runner_id = UUID(result)
|
||||
strat_ids_running.append(record.strat_id)
|
||||
runnerids_running.append(record.runner_id)
|
||||
|
||||
record.last_processed = market_time_now
|
||||
history_string = f"{market_time_now.isoformat()} strategy STARTED" if res == 0 else "ERROR:" + result
|
||||
|
||||
if record.history is None:
|
||||
record.history = history_string
|
||||
else:
|
||||
record.history += "\n" + history_string
|
||||
|
||||
#update record (nejspis jeste upravit - last_run a history)
|
||||
res, set = rm.update_run_manager_record(record.id, record)
|
||||
if res == 0:
|
||||
print(f"Record in db updated {set}")
|
||||
#return 0, set
|
||||
else:
|
||||
err_msg= f"Error updating {record.id} errir {set} with values {record}. Process stopped."
|
||||
print(err_msg)
|
||||
send_to_telegram(msg)
|
||||
return -2, err_msg #toto stopne dalsi zpracovani, zvazit continue
|
||||
|
||||
#if stop candidates, then fetch existing runners
|
||||
stop_candidates_cnt = len(candidates['stop'])
|
||||
|
||||
if stop_candidates_cnt > 0:
|
||||
res, repeated_strat_ids = stratin_occurences()
|
||||
if res < 0:
|
||||
err_msg= f"Error {res} in callin stratin_occurences, error {repeated_strat_ids}"
|
||||
send_to_telegram(err_msg)
|
||||
return -2, err_msg
|
||||
|
||||
#dalsi OPEN ISSUE pri STOPu:
|
||||
# má STOP_TIME strategie záviset na dni v týdnu? jinými slovy pokud je strategie
|
||||
# nastavená na 9:30-10 v pondělí. Mohu si ji manuálně spustit v úterý a systém ji neshodí?
|
||||
# Zatím to je postaveno, že předpis určuje okno, kde má strategie běžet a mimo tuto dobu bude
|
||||
# automaticky shozena. Druhou možností je potom, že scheduler si striktně hlídá jen strategie,
|
||||
# které byly jím zapnuté a ostatní jsou mu putna. V tomto případě pak např. později ručně spuštěmá
|
||||
# strategie (např. kvůli opravě bugu) bude scheduler ignorovat a nevypne ji i kdyz je nastavena na vypnuti.
|
||||
# Dopady: weekdays pri stopu a stratin_occurences
|
||||
|
||||
#IERATE over STOP Candidates
|
||||
record: RunManagerRecord = None
|
||||
print(f"STOP - Looping over {stop_candidates_cnt} candidates")
|
||||
for record in candidates['stop']:
|
||||
print("Candidate: ", record)
|
||||
|
||||
#Tento šelmostroj se stratin_occurences tu je jen proto, aby scheduler zafungoval i na manualne spustene strategie (ve vetsine pripadu)
|
||||
# Při stopu evaluace kandidátů na vypnutí
|
||||
# - pokud mám v schedules jen 1 strategii s konkretnim strat_id, můžu jet přes strat_id - bezici strategie s timto strat_id bude vypnuta (i manualne startnuta)
|
||||
# - pokud jich mám více, musím jet přes runnery uložené v schedules
|
||||
# (v tomto případě je omezení: ručně pouštěna strategii nebude automaticky
|
||||
# stopnuta - systém neví, která to je)
|
||||
|
||||
#zjistime zda strategie bezi
|
||||
|
||||
#strategii mame v scheduleru pouze jednou, muzeme pouzit strat_id
|
||||
if record.strat_id not in repeated_strat_ids:
|
||||
if record.strat_id not in strat_ids_running:
|
||||
msg = f"strategy {record.strat_id} NOT RUNNING"
|
||||
print(msg)
|
||||
continue
|
||||
else:
|
||||
#do stop
|
||||
id_to_stop = record.strat_id
|
||||
#strat_id je pouzito v scheduleru vicekrat, musime pouzit runner_id
|
||||
elif record.runner_id is not None and record.runner_id in runnerids_running:
|
||||
#do stop
|
||||
id_to_stop = record.runner_id
|
||||
#no distinctive condition
|
||||
else:
|
||||
#dont do anything
|
||||
print(f"strategy {record.strat_id} not RUNNING or not distinctive (manually launched or two strat_ids in scheduler)")
|
||||
continue
|
||||
|
||||
print(f"Requesting STOP {id_to_stop}")
|
||||
res, msg = stop_strategy(id_to_stop)
|
||||
if res < 0:
|
||||
msg = f"ERROR while STOPPING runner_id/strat_id {id_to_stop} {msg}"
|
||||
send_to_telegram(msg)
|
||||
else:
|
||||
if record.strat_id in strat_ids_running:
|
||||
strat_ids_running.remove(record.strat_id)
|
||||
if record.runner_id is not None and record.runner_id in runnerids_running:
|
||||
runnerids_running.remove(record.runner_id)
|
||||
record.runner_id = None
|
||||
|
||||
record.last_processed = market_time_now
|
||||
history_string = f"{market_time_now.isoformat()} strategy {record.strat_id}" + "STOPPED" if res == 0 else "ERROR:" + msg
|
||||
if record.history is None:
|
||||
record.history = history_string
|
||||
else:
|
||||
record.history += "\n" + history_string
|
||||
|
||||
#update record (nejspis jeste upravit - last_run a history)
|
||||
res, set = rm.update_run_manager_record(record.id, record)
|
||||
if res == 0:
|
||||
print(f"Record updated {set}")
|
||||
else:
|
||||
err_msg= f"Error updating {record.id} errir {set} with values {record}"
|
||||
print(err_msg)
|
||||
send_to_telegram(err_msg)
|
||||
return -2, err_msg#toto stopne zpracovani dalsich zaznamu pri chybe, zvazit continue
|
||||
|
||||
return 0, "DONE"
|
||||
|
||||
##LIVE or PAPER
|
||||
#tato verze využívate REST API, po predelani jobu na apscheduler uz muze vyuzivat prime volani cs.run_stratin
|
||||
#TODO predelat
|
||||
def run_scheduled_strategy(record: RunManagerRecord):
|
||||
#get strat_json
|
||||
sada : StrategyInstance = None
|
||||
res, sada = fetch_stratin(record.strat_id)
|
||||
if res == 0:
|
||||
# #TODO toto overit jestli je stejny vystup jako JS
|
||||
# print("Sada", sada)
|
||||
# #strategy_instance = StrategyInstance(**sada)
|
||||
strat_json = json.dumps(sada, default=json_serial)
|
||||
# Replace escaped characters with their unescaped versions so it matches the JS output
|
||||
#strat_json = strat_json.replace('\\r\\n', '\r\n')
|
||||
#print(f"Strat_json fetched, {strat_json}")
|
||||
else:
|
||||
err_msg= f"Strategy {record.strat_id} not found. ERROR {sada}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
|
||||
#TBD mozna customizovat NOTE
|
||||
|
||||
#pokud neni batch_id pak vyhgeneruju a ulozim do db
|
||||
# if record.batch_id is None:
|
||||
# record.batch_id = str(uuid4())[:8]
|
||||
|
||||
api_url = f"http://localhost:8000/stratins/{record.strat_id}/run"
|
||||
|
||||
# Initialize RunRequest with record values
|
||||
runReq = {
|
||||
"id": str(record.strat_id),
|
||||
"strat_json": strat_json,
|
||||
"mode": record.mode,
|
||||
"account": record.account,
|
||||
"ilog_save": record.ilog_save,
|
||||
"weekdays_filter": record.weekdays_filter,
|
||||
"test_batch_id": record.testlist_id,
|
||||
"batch_id": record.batch_id or str(uuid4())[:8],
|
||||
"bt_from": record.bt_from.isoformat() if record.bt_from else None,
|
||||
"bt_to": record.bt_to.isoformat() if record.bt_to else None,
|
||||
"note": f"SCHED {record.start_time}-" + record.stop_time if record.stop_time else "" + record.note if record.note is not None else ""
|
||||
}
|
||||
|
||||
# Headers for the request
|
||||
headers = {
|
||||
"X-API-Key": WEB_API_KEY
|
||||
}
|
||||
|
||||
try:
|
||||
# Make the PUT request to the API with the headers
|
||||
response = requests.put(api_url, json=runReq, headers=headers)
|
||||
|
||||
# Check if the request was successful
|
||||
if response.status_code == 200:
|
||||
print(f"Strategy {record.strat_id} started successfully.")
|
||||
return 0, response.json()
|
||||
else:
|
||||
err_msg = f"Strategy {record.strat_id} NOT started. Status Code: {response.status_code}, Response: {response.text}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
except requests.RequestException as e:
|
||||
err_msg = f"Request failed: {str(e)}"
|
||||
print(err_msg)
|
||||
return -2, err_msg
|
||||
|
||||
# #intiializae RunRequest with record values
|
||||
# runReq = RunRequest(id=record.strat_id,
|
||||
# strat_json=strat_json,
|
||||
# mode=record.mode,
|
||||
# account=record.account,
|
||||
# ilog_save=record.ilog_save,
|
||||
# weekdays_filter=record.weekdays_filter,
|
||||
# test_batch_id=record.testlist_id,
|
||||
# batch_id=record.batch_id,
|
||||
# bt_from=record.bt_from,
|
||||
# bt_to=record.bt_to,
|
||||
# note=record.note)
|
||||
# #call rest API to start strategy
|
||||
|
||||
|
||||
# #start strategy
|
||||
# res, sada = cs.run_stratin(id=record.strat_id, runReq=runReq, inter_batch_params=None)
|
||||
# if res == 0:
|
||||
# print(f"Strategy {sada} started")
|
||||
# return 0, sada
|
||||
# else:
|
||||
# err_msg= f"Strategy {record.strat_id} NOT started. ERROR {sada}"
|
||||
# print(err_msg)
|
||||
# return -2, err_msg
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#use naive datetoime
|
||||
debug_date = None
|
||||
debug_date = datetime(2024, 2, 16, 16, 37, 0, 0)
|
||||
#debug_date = datetime(2024, 2, 16, 10, 30, 0, 0)
|
||||
#debug_date = datetime(2024, 2, 16, 16, 1, 0, 0)
|
||||
|
||||
if debug_date is not None:
|
||||
# Localize the naive datetime object to the Eastern timezone
|
||||
debug_date = zoneNY.localize(debug_date)
|
||||
#debugdate formatted as string in format "23.12.2024 9:30"
|
||||
formatted_date = debug_date.strftime("%d.%m.%Y %H:%M")
|
||||
print("Scheduler.py NY time: ", formatted_date)
|
||||
print("ISoformat", debug_date.isoformat())
|
||||
|
||||
res, msg = startstop_scheduled(debug_date=debug_date, market="US")
|
||||
print(f"CALL FINISHED, with {debug_date} RESULT: {res}, {msg}")
|
||||
@ -298,6 +298,251 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- SCHEDULER -->
|
||||
<div id="runmanager-table" class="flex-items">
|
||||
<label data-bs-toggle="collapse" data-bs-target="#runmanager-table-inner">
|
||||
<h4>Run Manager</h4>
|
||||
</label>
|
||||
<div id="runmanager-table-inner" class="collapse show collapsible-section" style="width:58%">
|
||||
<div id="controls">
|
||||
<button title="Create new" id="button_add_sched" class="btn btn-outline-success btn-sm">Add</button>
|
||||
<button title="Edit selected" id="button_edit_sched" class="btn btn-outline-success btn-sm">Edit</button>
|
||||
<button title="Delete selected" id="button_delete_sched" class="btn btn-outline-success btn-sm">Delete</button>
|
||||
<button title="History" id="button_history_sched" class="btn btn-outline-success btn-sm">History</button>
|
||||
<button title="Refresh" id="button_refresh_sched" class="btn btn-outline-success btn-sm">Refresh</button>
|
||||
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
||||
<!-- <input type="radio" class="btn-check" name="filterOptions" id="filterNone" autocomplete="off" checked>
|
||||
<label class="btn btn-outline-primary" for="filterNone">All</label> -->
|
||||
|
||||
<input type="radio" class="btn-check" name="filterOptions" id="filterSchedule" autocomplete="off" checked>
|
||||
<label class="btn btn-outline-primary" for="filterSchedule">Scheduled</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="filterOptions" id="filterQueue" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="filterQueue">Queued</label>
|
||||
</div>
|
||||
</div>
|
||||
<table id="runmanagerTable" class="table-striped table dataTable" style="width:100%; border-color: #dce1dc;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Type</th>
|
||||
<th>Strat_Id</th>
|
||||
<th>Symbol</th>
|
||||
<th>Account</th>
|
||||
<th>Mode</th>
|
||||
<th>Note</th>
|
||||
<th>Log</th>
|
||||
<th>BT_from</th>
|
||||
<th>BT_to</th>
|
||||
<th>days</th>
|
||||
<th>batch_id</th>
|
||||
<th>start</th>
|
||||
<th>stop</th>
|
||||
<th>status</th>
|
||||
<th>last_processed</th>
|
||||
<th>history</th>
|
||||
<th>valid_from</th>
|
||||
<th>valid_to</th>
|
||||
<th>testlist_id</th>
|
||||
<th>Running</th>
|
||||
<th>RunnerId</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="delModalRunmanager" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" id="delFormRunmanager">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title"><i class="fa fa-plus"></i> Delete record</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="delidrunmanager" class="form-label">Id</label>
|
||||
<!-- <div id="listofids"></div> -->
|
||||
<input type="text" class="form-control" id="delidrunmanager" name="id" placeholder="id" readonly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" name="delete" id="deleterunmanager" class="btn btn-primary" value="Delete" />
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="addeditModalRunmanager" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" id="addeditFormRunmanager">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title_run"><i class="fa fa-plus"></i> Add scheduler record</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="runmanid" class="form-label">Record Id</label>
|
||||
<input type="text" class="form-control" id="runmanid" name="id" placeholder="auto generated id" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="runmanmoddus" class="form-label">Type</label>
|
||||
<input type="text" class="form-control" id="runmanmoddus" name="moddus" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="runmanstrat_id" class="form-label">StrategyId</label>
|
||||
<input type="text" class="form-control" id="runmanstrat_id" name="strat_id" placeholder="strategy id">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="runmode" class="form-label">Mode</label>
|
||||
<select class="form-control" id="runmanmode" name="mode"><option value="paper">paper</option><option value="live">live</option><option value="backtest">backtest</option><option value="prep">prep</option></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account" class="form-label">Account</label>
|
||||
<select class="form-control" id="runmanaccount" name="account"><option value="ACCOUNT1">ACCOUNT1</option><option value="ACCOUNT2">ACCOUNT2</option></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="status" class="form-label">Status</label>
|
||||
<select class="form-control" id="runmanstatus" name="status"><option value="active">active</option><option value="suspended">suspended</option></select>
|
||||
</div>
|
||||
<div class="form-group" id="runmanstart_time_div">
|
||||
<label for="start" class="form-label">Start Time</label>
|
||||
<input type="text" class="form-control" id="runmanstart_time" name="start_time" value="9:30" step="1">
|
||||
</div>
|
||||
<div class="form-group" id="runmanstop_time_div">
|
||||
<label for="stop" class="form-label">Stop Time</label>
|
||||
<input type="text-local" class="form-control" id="runmanstop_time" name="stop_time" value="16:00" step="1">
|
||||
</div>
|
||||
|
||||
<!-- pro budouci queueing backtestu -->
|
||||
<div class="form-group" id="runmanbt_from_div">
|
||||
<label for="bt_from" class="form-label">bt_from</label>
|
||||
<input type="datetime-local" class="form-control" id="runmanbt_from" name="bt_from" placeholder="2023-04-06T09:00:00Z" step="1">
|
||||
</div>
|
||||
<div class="form-group" id="runmanbt_to_div">
|
||||
<label for="bt_to" class="form-label">bt_to</label>
|
||||
<input type="datetime-local" class="form-control" id="runmanbt_to" name="bt_to" placeholder="2023-04-06T09:00:00Z" step="1">
|
||||
</div>
|
||||
<div class="form-group" id="runmantestlist_id_div">
|
||||
<label for="test_batch_id" class="form-label">Test List ID</label>
|
||||
<input type="text" class="form-control" id="runmantestlist_id" name="testlist_id" placeholder="test intervals ID">
|
||||
</div>
|
||||
<!-- pro budouci queueing backtestu -->
|
||||
|
||||
<!-- Initial Checkbox for Enabling Weekday Selection -->
|
||||
<div class="form-group">
|
||||
<div style="display:inline-flex">
|
||||
<label for="runman_enable_weekdays" class="form-label">Limit to Weekdays</label>
|
||||
<input type="checkbox" class="form-check" id="runman_enable_weekdays" name="enable_weekdays" aria-label="Enable Weekday Selection">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Weekday Checkboxes -->
|
||||
<div class="form-group weekday-checkboxes" style="display:none;">
|
||||
<!-- <label class="form-label">Select Weekdays:</label> -->
|
||||
<div>
|
||||
<input type="checkbox" id="monday" name="weekdays" value="monday">
|
||||
<label for="monday">Monday</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="tuesday" name="weekdays" value="tuesday">
|
||||
<label for="tuesday">Tuesday</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="wednesday" name="weekdays" value="wednesday">
|
||||
<label for="wednesday">Wednesday</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="thursday" name="weekdays" value="thursday">
|
||||
<label for="thursday">Thursday</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="friday" name="weekdays" value="friday">
|
||||
<label for="friday">Friday</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="runmanvalid_from_div">
|
||||
<label for="runmanvalid_from" class="form-label">Valid from</label>
|
||||
<input type="datetime-local" class="form-control" id="runmanvalid_from" name="valid_from" placeholder="2023-04-06T09:00:00Z" step="1">
|
||||
</div>
|
||||
<div class="form-group" id="runmanvalid_to_div">
|
||||
<label for="runmanvalid_to" class="form-label">Valid to</label>
|
||||
<input type="datetime-local" class="form-control" id="runmanvalid_to" name="valid_to" placeholder="2023-04-06T09:00:00Z" step="1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="batch_id" class="form-label">Batch ID</label>
|
||||
<input type="text" class="form-control" id="runmanbatch_id" name="batch_id" placeholder="batch id">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div style="display:inline-flex">
|
||||
<label for="ilog_save" class="form-label">Enable logs</label>
|
||||
<input type="checkbox" class="form-check" id="runmanilog_save" name="ilog_save" aria-label="Enable logs">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="note" class="form-label">note</label>
|
||||
<textarea class="form-control" rows="1" id="runmannote" name="note"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="hidden" name="runner_id" id="runmanrunner_id" />
|
||||
<input type="hidden" name="history" id="runmanhistory" />
|
||||
<input type="hidden" name="last_processed" id="runmanlast_processed" />
|
||||
<!--<input type="hidden" name="action" id="action" value="" />-->
|
||||
<input type="submit" id="runmanagersubmit" class="btn btn-primary" value="Add" />
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="historyModalRunmanager" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" id="historyModalRunmanagerForm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title"><i class="fa fa-plus"></i>View History</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="RunmanId" class="form-label">Id</label>
|
||||
<input type="text" class="form-control" id="RunmanId" name="id" placeholder="id" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="Runmanlast_processed" class="form-label">Last processed</label>
|
||||
<input type="text" class="form-control" id="Runmanlast_processed" name="last_processed" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="Runmanhistory" class="form-label">History</label>
|
||||
<textarea class="form-control" rows="8" id="Runmanhistory" name="history" readonly></textarea>
|
||||
</div>
|
||||
<!-- <div class="form-group">
|
||||
<label for="metrics" class="form-label">Metrics</label>
|
||||
<textarea class="form-control" rows="8" id="metrics" name="metrics"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stratvars" class="form-label">Stratvars</label>
|
||||
<textarea class="form-control" rows="8" id="editstratvars" name="stratvars"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="strat_json" class="form-label">Strat JSON</label>
|
||||
<textarea class="form-control" rows="6" id="editstratjson" name="stratjson"></textarea>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<!-- <input type="submit" name="delete" id="editarchive" class="btn btn-primary" value="Edit" /> -->
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="archive-table" class="flex-items">
|
||||
<label data-bs-toggle="collapse" data-bs-target="#archive-table-inner">
|
||||
@ -403,27 +648,34 @@
|
||||
</div>
|
||||
<div id="logModal" class="modal fade" style="--bs-modal-width: 825px;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title"><i class="fa fa-plus"></i>Log</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title"><i class="fa fa-plus"></i>Log</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="logFileSelect" class="form-label">Select Log File</label>
|
||||
<select class="form-select" id="logFileSelect" aria-label="Log file select">
|
||||
<!-- <option selected>Select a log file</option> -->
|
||||
<option value="strat.log" selected>strat.log</option>
|
||||
<option value="job.log">job.log</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="logHere" class="form-label">Log</label>
|
||||
<div id="log-container">
|
||||
<pre id="log-content"></pre>
|
||||
</div>
|
||||
<!-- <input type="text" class="form-control" id="delidarchive" name="delidarchive" placeholder="id"> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="logRefreshButton" value="Refresh">Refresh</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<div class="form-group mt-3">
|
||||
<label for="logHere" class="form-label">Log</label>
|
||||
<div id="log-container">
|
||||
<pre id="log-content"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="logRefreshButton" value="Refresh">Refresh</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editModalArchive" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" id="editFormArchive">
|
||||
@ -911,7 +1163,11 @@
|
||||
<script src="/static/js/tables/archivetable/modals.js?v=1.06"></script>
|
||||
<script src="/static/js/tables/archivetable/handlers.js?v=1.06"></script>
|
||||
|
||||
|
||||
<!-- Runmanager functionality -->
|
||||
<script src="/static/js/tables/runmanager/init.js?v=1.08"></script>
|
||||
<script src="/static/js/tables/runmanager/functions.js?v=1.07"></script>
|
||||
<script src="/static/js/tables/runmanager/modals.js?v=1.06"></script>
|
||||
<script src="/static/js/tables/runmanager/handlers.js?v=1.06"></script>
|
||||
|
||||
<script src="/static/js/livewebsocket.js?v=1.01"></script>
|
||||
<script src="/static/js/realtimechart.js?v=1.01"></script>
|
||||
@ -920,6 +1176,6 @@
|
||||
<script src="/static/js/ml.js?v=1.02"></script>
|
||||
<script src="/static/js/common.js?v=1.01"></script>
|
||||
<script src="/static/js/configform.js?v=1.01"></script>
|
||||
|
||||
<!-- <script src="/static/js/scheduler.js?v=1.01"></script> -->
|
||||
</body>
|
||||
</html>
|
||||
@ -621,7 +621,6 @@ $(document).ready(function () {
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
//button run
|
||||
$('#button_run').click(function () {
|
||||
row = stratinRecords.row('.selected').data();
|
||||
@ -953,7 +952,18 @@ var runnerRecords =
|
||||
render: function ( data, type, row ) {
|
||||
return format_date(data)
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [4], //symbol
|
||||
render: function ( data, type, row ) {
|
||||
if (type === 'display') {
|
||||
//console.log("arch")
|
||||
var color = getColorForId(row.strat_id);
|
||||
return '<span style="color:' + color + ';">'+data+'</span>';
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
],
|
||||
// select: {
|
||||
// style: 'multi'
|
||||
|
||||
@ -453,8 +453,10 @@ function display_batch_report(batch_id) {
|
||||
}
|
||||
|
||||
function refresh_logfile() {
|
||||
logfile = $("#logFileSelect").val()
|
||||
lines = 700
|
||||
$.ajax({
|
||||
url:"/log?lines=30",
|
||||
url:"/log?lines="+lines+"&logfile="+logfile,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
|
||||
@ -363,6 +363,10 @@ $(document).ready(function () {
|
||||
refresh_logfile()
|
||||
});
|
||||
|
||||
$('#logFileSelect').change(function() {
|
||||
refresh_logfile();
|
||||
});
|
||||
|
||||
//button to open log modal
|
||||
$('#button_show_log').click(function () {
|
||||
window.$('#logModal').modal('show');
|
||||
|
||||
@ -284,13 +284,23 @@ function initialize_archiveRecords() {
|
||||
|
||||
//pokud mame batch_id podivame se zda jeho nastaveni uz nema a pokud ano pouzijeme to
|
||||
//pokud nemame tak si ho loadneme
|
||||
//Tento kod parsuje informace do header hlavicky podle notes, je to relevantni pouze pro
|
||||
//backtest batche, nikoliv pro paper a live, kde pocet dni je neznamy a poznamka se muze menit
|
||||
//do budoucna tento parsing na frontendu bude nahrazen batch tabulkou v db, ktera persistuje
|
||||
//tyto data
|
||||
if (group) {
|
||||
const existingBatch = batchHeaders.find(batch => batch.batch_id == group);
|
||||
//jeste neni v poli batchu - udelame hlavicku
|
||||
if (!existingBatch) {
|
||||
itemCount = extractNumbersFromString(firstRowData.note);
|
||||
try {profit = firstRowData.metrics.profit.batch_sum_profit;}
|
||||
if (!itemCount) {
|
||||
itemCount="NA"
|
||||
}
|
||||
|
||||
try { profit = firstRowData.metrics.profit.batch_sum_profit;}
|
||||
catch (e) {profit = 'NA'}
|
||||
|
||||
if (!profit) {profit = 'NA'}
|
||||
period = firstRowData.note ? firstRowData.note.substring(0, 14) : '';
|
||||
try {
|
||||
batch_note = firstRowData.note ? firstRowData.note.split("N:")[1].trim() : ''
|
||||
@ -298,15 +308,22 @@ function initialize_archiveRecords() {
|
||||
started = firstRowData.started
|
||||
stratinId = firstRowData.strat_id
|
||||
symbol = firstRowData.symbol
|
||||
if (period.startsWith("SCHED")) {
|
||||
period = "SCHEDULER";
|
||||
}
|
||||
var newBatchHeader = {batch_id:group, batch_note:batch_note, profit:profit, itemCount:itemCount, period:period, started:started, stratinId:stratinId, symbol:symbol};
|
||||
batchHeaders.push(newBatchHeader)
|
||||
}
|
||||
//uz je v poli, ale mame novejsi (pribyl v ramci backtestu napr.) - updatujeme
|
||||
else if (new Date(existingBatch.started) < new Date(firstRowData.started)) {
|
||||
itemCount = extractNumbersFromString(firstRowData.note);
|
||||
try {itemCount = extractNumbersFromString(firstRowData.note);}
|
||||
catch (e) {itemCount = 'NA'}
|
||||
try {profit = firstRowData.metrics.profit.batch_sum_profit;}
|
||||
catch (e) {profit = 'NA'}
|
||||
period = firstRowData.note ? firstRowData.note.substring(0, 14) : '';
|
||||
if (period.startsWith("SCHED")) {
|
||||
period = "SCHEDULER";
|
||||
}
|
||||
try {
|
||||
batch_note = firstRowData.note ? firstRowData.note.split("N:")[1].trim() : ''
|
||||
} catch (e) { batch_note = ''}
|
||||
|
||||
100
v2realbot/static/js/tables/runmanager/functions.js
Normal file
100
v2realbot/static/js/tables/runmanager/functions.js
Normal file
@ -0,0 +1,100 @@
|
||||
function refresh_runmanager_and_callback(row, callback) {
|
||||
//console.log("entering refresh")
|
||||
var request = $.ajax({
|
||||
url: "/run_manager_records/"+row.id,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"GET",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
success:function(data){
|
||||
//console.log("fetched data ok")
|
||||
//console.log(JSON.stringify(data,null,2));
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
|
||||
// Handling the responses of both requests
|
||||
$.when(request).then(function(response) {
|
||||
// Both requests have completed successfully
|
||||
//console.log("Result from request:", response);
|
||||
//console.log("Response received. calling callback")
|
||||
//call callback function
|
||||
callback(response)
|
||||
|
||||
}, function(error) {
|
||||
// Handle errors from either request here
|
||||
// Example:
|
||||
console.error("Error from first request:", error);
|
||||
console.log("requesting id error")
|
||||
});
|
||||
}
|
||||
|
||||
function delete_runmanager_row(id) {
|
||||
$.ajax({
|
||||
url:"/run_manager_records/"+id,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"DELETE",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
// data: JSON.stringify(ids),
|
||||
success:function(data){
|
||||
$('#delFormRunmanager')[0].reset();
|
||||
window.$('#delModalRunmanager').modal('hide');
|
||||
$('#deleterunmanager').attr('disabled', false);
|
||||
//console.log(data)
|
||||
runmanagerRecords.ajax.reload();
|
||||
disable_runmanager_buttons()
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#deleterunmanager').attr('disabled', false);
|
||||
//archiveRecords.ajax.reload();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//enable/disable based if row(s) selected
|
||||
function disable_runmanager_buttons() {
|
||||
//disable buttons (enable on row selection)
|
||||
//$('#button_add_sched').attr('disabled','disabled');
|
||||
$('#button_edit_sched').attr('disabled','disabled');
|
||||
$('#button_delete_sched').attr('disabled','disabled');
|
||||
$('#button_history_sched').attr('disabled','disabled');
|
||||
}
|
||||
|
||||
function enable_runmanager_buttons() {
|
||||
//enable buttons
|
||||
//$('#button_add_sched').attr('disabled',false);
|
||||
$('#button_edit_sched').attr('disabled',false);
|
||||
$('#button_delete_sched').attr('disabled',false);
|
||||
$('#button_history_sched').attr('disabled',false);
|
||||
}
|
||||
|
||||
// Function to update options
|
||||
function updateSelectOptions(type) {
|
||||
var allOptions = {
|
||||
'paper': '<option value="paper">paper</option>',
|
||||
'live': '<option value="live">live</option>',
|
||||
'backtest': '<option value="backtest">backtest</option>',
|
||||
'prep': '<option value="prep">prep</option>'
|
||||
};
|
||||
|
||||
var allowedOptions = (type === "schedule") ? ['paper', 'live'] : Object.keys(allOptions);
|
||||
|
||||
var $select = $('#runmanmode');
|
||||
$select.empty(); // Clear current options
|
||||
|
||||
allowedOptions.forEach(function(opt) {
|
||||
$select.append(allOptions[opt]); // Append allowed options
|
||||
});
|
||||
}
|
||||
296
v2realbot/static/js/tables/runmanager/handlers.js
Normal file
296
v2realbot/static/js/tables/runmanager/handlers.js
Normal file
@ -0,0 +1,296 @@
|
||||
/* <button title="Create new" id="button_add_sched" class="btn btn-outline-success btn-sm">Add</button>
|
||||
<button title="Edit selected" id="button_edit_sched" class="btn btn-outline-success btn-sm">Edit</button>
|
||||
<button title="Delete selected" id="button_delete_sched" class="btn btn-outline-success btn-sm">Delete</button>
|
||||
|
||||
|
||||
id="delModalRunmanager"
|
||||
id="addeditModalRunmanager" id="runmanagersubmit" == "Add vs Edit"
|
||||
*/
|
||||
|
||||
// Function to apply filter
|
||||
function applyFilter(filter) {
|
||||
switch (filter) {
|
||||
case 'filterSchedule':
|
||||
runmanagerRecords.column(1).search('schedule').draw();
|
||||
break;
|
||||
case 'filterQueue':
|
||||
runmanagerRecords.column(1).search('queue').draw();
|
||||
break;
|
||||
// default:
|
||||
// runmanagerRecords.search('').columns().search('').draw();
|
||||
// break;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get the ID of current active filter
|
||||
function getCurrentFilter() {
|
||||
var activeFilter = $('input[name="filterOptions"]:checked').attr('id');
|
||||
console.log("activeFilter", activeFilter)
|
||||
return activeFilter;
|
||||
}
|
||||
|
||||
// Function to show/hide input fields based on the current filter
|
||||
function updateInputFields() {
|
||||
var activeFilter = getCurrentFilter();
|
||||
|
||||
switch (activeFilter) {
|
||||
case 'filterSchedule':
|
||||
$('#runmantestlist_id_div').hide();
|
||||
$('#runmanbt_from_div').hide();
|
||||
$('#runmanbt_to_div').hide();
|
||||
|
||||
$('#runmanvalid_from_div').show();
|
||||
$('#runmanvalid_to_div').show();
|
||||
$('#runmanstart_time_div').show();
|
||||
$('#runmanstop_time_div').show();
|
||||
break;
|
||||
case 'filterQueue':
|
||||
$('#runmantestlist_id_div').show();
|
||||
$('#runmanbt_from_div').show();
|
||||
$('#runmanbt_to_div').show();
|
||||
|
||||
$('#runmanvalid_from_div').hide();
|
||||
$('#runmanvalid_to_div').hide();
|
||||
$('#runmanstart_time_div').hide();
|
||||
$('#runmanstop_time_div').hide();
|
||||
break;
|
||||
default:
|
||||
//$('#inputForSchedule, #inputForQueue').hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//event handlers for runmanager table
|
||||
$(document).ready(function () {
|
||||
initialize_runmanagerRecords();
|
||||
runmanagerRecords.ajax.reload();
|
||||
disable_runmanager_buttons();
|
||||
|
||||
//on click on #button_refresh_sched call runmanagerRecords.ajax.reload()
|
||||
$('#button_refresh_sched').click(function () {
|
||||
runmanagerRecords.ajax.reload();
|
||||
});
|
||||
|
||||
// Event listener for changes in the radio buttons
|
||||
$('input[name="filterOptions"]').on('change', function() {
|
||||
var selectedFilter = $(this).attr('id');
|
||||
applyFilter(selectedFilter);
|
||||
// Save the selected filter to local storage
|
||||
localStorage.setItem('selectedFilter', selectedFilter);
|
||||
});
|
||||
|
||||
|
||||
// Load the last selected filter from local storage and apply it
|
||||
var lastSelectedFilter = localStorage.getItem('selectedFilter');
|
||||
if (lastSelectedFilter) {
|
||||
$('#' + lastSelectedFilter).prop('checked', true).change();
|
||||
}
|
||||
|
||||
//listen for changes on weekday enabling button
|
||||
$('#runman_enable_weekdays').change(function() {
|
||||
if ($(this).is(':checked')) {
|
||||
$('.weekday-checkboxes').show();
|
||||
} else {
|
||||
$('.weekday-checkboxes').hide();
|
||||
}
|
||||
});
|
||||
|
||||
//selectable rows in runmanager table
|
||||
$('#runmanagerTable tbody').on('click', 'tr', function () {
|
||||
if ($(this).hasClass('selected')) {
|
||||
//$(this).removeClass('selected');
|
||||
//aadd here condition that disable is called only when there is no other selected class on tr[data-group-name]
|
||||
// Check if there are no other selected rows before disabling buttons
|
||||
if ($('#runmanagerTable tr.selected').length === 1) {
|
||||
disable_runmanager_buttons();
|
||||
}
|
||||
//disable_arch_buttons()
|
||||
} else {
|
||||
//archiveRecords.$('tr.selected').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
enable_runmanager_buttons()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//delete button
|
||||
$('#button_delete_sched').click(function () {
|
||||
row = runmanagerRecords.row('.selected').data();
|
||||
window.$('#delModalRunmanager').modal('show');
|
||||
$('#delidrunmanager').val(row.id);
|
||||
// $('#action').val('delRecord');
|
||||
// $('#save').val('Delete');
|
||||
});
|
||||
|
||||
//button add
|
||||
$('#button_add_sched').click(function () {
|
||||
window.$('#addeditModalRunmanager').modal('show');
|
||||
$('#addeditFormRunmanager')[0].reset();
|
||||
//$("#runmanid").prop('readonly', false);
|
||||
if (getCurrentFilter() == 'filterQueue') {
|
||||
mode = 'queue';
|
||||
} else {
|
||||
mode = 'schedule';
|
||||
}
|
||||
//set modus
|
||||
$('#runmanmoddus').val(mode);
|
||||
//updates fields according to selected type
|
||||
updateInputFields();
|
||||
updateSelectOptions(mode);
|
||||
// Initially, check the value of "batch" and enable/disable "btfrom" and "btto" accordingly
|
||||
if ($("#runmantestlist_id").val() !== "") {
|
||||
$("#runmanbt_from, #runmanbt_to").prop("disabled", true);
|
||||
} else {
|
||||
$("#runmanbt_from, #runmanbt_to").prop("disabled", false);
|
||||
}
|
||||
|
||||
// Listen for changes in the "batch" input and diasble/enable "btfrom" and "btto" accordingly
|
||||
$("#runmantestlist_id").on("input", function() {
|
||||
if ($(this).val() !== "") {
|
||||
// If "batch" is not empty, disable "from" and "to"
|
||||
$("#runmanbt_from, #runmanbt_to").prop("disabled", true);
|
||||
} else {
|
||||
// If "batch" is empty, enable "from" and "to"
|
||||
$("#runmanbt_from, #runmanbt_to").prop("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
$('.modal-title_run').html("<i class='fa fa-plus'></i> Add Record");
|
||||
$('#runmanagersubmit').val('Add');
|
||||
$('#runmanager_enable_weekdays').prop('checked', false);
|
||||
$('.weekday-checkboxes').hide();
|
||||
});
|
||||
|
||||
//edit button
|
||||
$('#button_edit_sched').click(function () {
|
||||
row = runmanagerRecords.row('.selected').data();
|
||||
if (row == undefined) {
|
||||
return
|
||||
}
|
||||
window.$('#addeditModalRunmanager').modal('show');
|
||||
//set fields as readonly
|
||||
//$("#runmanid").prop('readonly', true);
|
||||
//$("#runmanmoddus").prop('readonly', true);
|
||||
console.log("pred editem puvodni row", row)
|
||||
refresh_runmanager_and_callback(row, show_edit_modal)
|
||||
|
||||
function show_edit_modal(row) {
|
||||
console.log("pred editem refreshnuta row", row);
|
||||
$('#addeditFormRunmanager')[0].reset();
|
||||
$('.modal-title_run').html("<i class='fa fa-plus'></i> Edit Record");
|
||||
$('#runmanagersubmit').val('Edit');
|
||||
|
||||
//updates fields according to selected type
|
||||
updateInputFields();
|
||||
// get shared attributess
|
||||
$('#runmanid').val(row.id);
|
||||
$('#runmanhistory').val(row.history);
|
||||
$('#runmanlast_processed').val(row.last_processed);
|
||||
$('#runmanstrat_id').val(row.strat_id);
|
||||
$('#runmanmode').val(row.mode);
|
||||
$('#runmanmoddus').val(row.moddus);
|
||||
$('#runmanaccount').val(row.account);
|
||||
$('#runmanstatus').val(row.status);
|
||||
$('#runmanbatch_id').val(row.batch_id);
|
||||
$('#runmanrunner_id').val(row.runner_id);
|
||||
$("#runmanilog_save").prop("checked", row.ilog_save);
|
||||
$('#runmannote').val(row.note);
|
||||
|
||||
$('#runmantestlist_id').val(row.testlist_id);
|
||||
$('#runmanbt_from').val(row.bt_from);
|
||||
$('#runmanbt_to').val(row.bt_to);
|
||||
|
||||
$('#runmanvalid_from').val(row.valid_from);
|
||||
$('#runmanvalid_to').val(row.valid_to);
|
||||
$('#runmanstart_time').val(row.start_time);
|
||||
$('#runmanstop_time').val(row.stop_time);
|
||||
|
||||
// Initially, check the value of "batch" and enable/disable "from" and "to" accordingly
|
||||
if ($("#runmantestlist_id").val() !== "") {
|
||||
$("#runmanbt_from, #runmanbt_to").prop("disabled", true);
|
||||
} else {
|
||||
$("#runmanbt_from, #runmanbt_to").prop("disabled", false);
|
||||
}
|
||||
|
||||
// Listen for changes in the "batch" input
|
||||
$("#runmantestlist_id").on("input", function() {
|
||||
if ($(this).val() !== "") {
|
||||
// If "batch" is not empty, disable "from" and "to"
|
||||
$("#runmanbt_from, #runmanbt_to").prop("disabled", true);
|
||||
} else {
|
||||
// If "batch" is empty, enable "from" and "to"
|
||||
$("#runmanbt_from, #runmanbt_to").prop("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
type = $('#runmanmoddus').val();
|
||||
updateSelectOptions(type);
|
||||
|
||||
//add weekdays_filter transformation from string "1,2,3" to array [1,2,3]
|
||||
|
||||
// Assuming you have row.weekend_filter available here
|
||||
var weekdayFilter = row.weekdays_filter;
|
||||
|
||||
//
|
||||
|
||||
if (weekdayFilter) {
|
||||
$('#runman_enable_weekdays').prop('checked', true);
|
||||
$(".weekday-checkboxes").show();
|
||||
|
||||
// Map numbers to weekday names
|
||||
var dayOfWeekMap = {
|
||||
"0": "monday",
|
||||
"1": "tuesday",
|
||||
"2": "wednesday",
|
||||
"3": "thursday",
|
||||
"4": "friday",
|
||||
"5": "saturday", // Adjust if needed for your mapping
|
||||
"6": "sunday" // Adjust if needed for your mapping
|
||||
};
|
||||
|
||||
// Iterate through the selected days
|
||||
$.each(weekdayFilter, function(index, dayIndex) {
|
||||
var dayOfWeek = dayOfWeekMap[dayIndex];
|
||||
if (dayOfWeek) { // Make sure the day exists in the map
|
||||
$("#" + dayOfWeek).prop("checked", true);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#runman_enable_weekdays').prop('checked', false);
|
||||
$(".weekday-checkboxes").hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//edit button
|
||||
$('#button_history_sched').click(function () {
|
||||
row = runmanagerRecords.row('.selected').data();
|
||||
if (row == undefined) {
|
||||
return
|
||||
}
|
||||
window.$('#historyModalRunmanager').modal('show');
|
||||
//set fields as readonly
|
||||
//$("#runmanid").prop('readonly', true);
|
||||
//$("#runmanmoddus").prop('readonly', true);
|
||||
//console.log("pred editem puvodni row", row)
|
||||
refresh_runmanager_and_callback(row, show_history_modal)
|
||||
|
||||
function show_history_modal(row) {
|
||||
//console.log("pred editem refreshnuta row", row);
|
||||
$('#historyModalRunmanagerForm')[0].reset();
|
||||
// get shared attributess
|
||||
$('#RunmanId').val(row.id);
|
||||
var date = new Date(row.last_processed);
|
||||
formatted = date.toLocaleString('cs-CZ', {
|
||||
timeZone: 'America/New_York',
|
||||
})
|
||||
$('#Runmanlast_processed').val(formatted);
|
||||
$('#Runmanhistory').val(row.history);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
322
v2realbot/static/js/tables/runmanager/init.js
Normal file
322
v2realbot/static/js/tables/runmanager/init.js
Normal file
@ -0,0 +1,322 @@
|
||||
var runmanagerRecords = null
|
||||
|
||||
//ekvivalent to ready
|
||||
function initialize_runmanagerRecords() {
|
||||
|
||||
//archive table
|
||||
runmanagerRecords =
|
||||
$('#runmanagerTable').DataTable( {
|
||||
ajax: {
|
||||
url: '/run_manager_records/',
|
||||
dataSrc: '',
|
||||
method:"GET",
|
||||
contentType: "application/json",
|
||||
// dataType: "json",
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
data: function (d) {
|
||||
return JSON.stringify(d);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
//var err = eval("(" + xhr.responseText + ")");
|
||||
//window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
}
|
||||
},
|
||||
columns: [ { data: 'id' },
|
||||
{ data: 'moddus' },
|
||||
{ data: 'strat_id' },
|
||||
{data: 'symbol'},
|
||||
{data: 'account'},
|
||||
{data: 'mode'},
|
||||
{data: 'note'},
|
||||
{data: 'ilog_save'},
|
||||
{data: 'bt_from'},
|
||||
{data: 'bt_to'},
|
||||
{data: 'weekdays_filter', visible: true},
|
||||
{data: 'batch_id', visible: true},
|
||||
{data: 'start_time', visible: true},
|
||||
{data: 'stop_time', visible: true},
|
||||
{data: 'status'},
|
||||
{data: 'last_processed', visible: true},
|
||||
{data: 'history', visible: false},
|
||||
{data: 'valid_from', visible: true},
|
||||
{data: 'valid_to', visible: true},
|
||||
{data: 'testlist_id', visible: true},
|
||||
{data: 'strat_running', visible: true},
|
||||
{data: 'runner_id', visible: true},
|
||||
],
|
||||
paging: true,
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
columnDefs: [
|
||||
{ //history
|
||||
targets: [6],
|
||||
render: function(data, type, row, meta) {
|
||||
if (!data) return data;
|
||||
var stateClass = 'truncated-text';
|
||||
var uniqueId = 'note-' + row.id;
|
||||
|
||||
if (localStorage.getItem(uniqueId) === 'expanded') {
|
||||
stateClass = 'expanded-text';
|
||||
}
|
||||
|
||||
if (type === 'display') {
|
||||
return '<div class="' + stateClass + '" id="' + uniqueId + '">' + data + '</div>';
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
{ //iloc_save
|
||||
targets: [7],
|
||||
render: function ( data, type, row ) {
|
||||
//if ilog_save true
|
||||
if (data) {
|
||||
return '<span class="material-symbols-outlined">done_outline</span>'
|
||||
}
|
||||
else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [10], //weekdays
|
||||
render: function (data, type, row) {
|
||||
if (!data) return data;
|
||||
// Map each number in the array to a weekday
|
||||
var weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
|
||||
return data.map(function(dayNumber) {
|
||||
return weekdays[dayNumber];
|
||||
}).join(', ');
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [0, 21], //interni id, runner_id
|
||||
render: function ( data, type, row ) {
|
||||
if (!data) return data;
|
||||
if (type === 'display') {
|
||||
return '<div class="tdnowrap" data-bs-toggle="tooltip" data-bs-placement="top" title="'+data+'">'+data+'</div>';
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [2], //strat_id
|
||||
render: function ( data, type, row ) {
|
||||
if (type === 'display') {
|
||||
//console.log("arch")
|
||||
var color = getColorForId(data);
|
||||
return '<div class="tdnowrap" data-bs-toggle="tooltip" data-bs-placement="top" title="'+data+'"><span class="color-tag" style="background-color:' + color + ';"></span>'+data+'</div>';
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [3,12,13], //symbol, start_time, stop_time
|
||||
render: function ( data, type, row ) {
|
||||
if (type === 'display') {
|
||||
//console.log("arch")
|
||||
var color = getColorForId(row.strat_id);
|
||||
return '<span style="color:' + color + ';">'+data+'</span>';
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [16], //history
|
||||
render: function ( data, type, row ) {
|
||||
if (type === 'display') {
|
||||
if (!data) data = "";
|
||||
return '<div data-bs-toggle="tooltip" data-bs-placement="top" title="'+data+'">'+data+'</div>';
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [14], //status
|
||||
render: function ( data, type, row ) {
|
||||
if (type === 'display') {
|
||||
//console.log("arch")
|
||||
var color = data == "active" ? "#3f953f" : "#f84c4c";
|
||||
return '<span style="color:' + color + ';">'+data+'</span>';
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [20], //strat_running
|
||||
render: function ( data, type, row ) {
|
||||
if (type === 'display') {
|
||||
if (!data) data = "";
|
||||
console.log("running", data)
|
||||
//var color = data == "active" ? "#3f953f" : "#f84c4c";
|
||||
data = data ? "running" : ""
|
||||
return '<div title="' + row.runner_id + '" style="color:#3f953f;">'+data+'</div>';
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
// {
|
||||
// targets: [0,17],
|
||||
// render: function ( data, type, row ) {
|
||||
// if (!data) return data
|
||||
// return '<div class="tdnowrap" title="'+data+'">'+data+'</i>'
|
||||
// },
|
||||
// },
|
||||
{
|
||||
targets: [15,17, 18, 8, 9], //start, stop, valid_from, valid_to, bt_from, bt_to, last_proccessed
|
||||
render: function ( data, type, row ) {
|
||||
if (!data) return data
|
||||
now = new Date(data)
|
||||
if (type == "sort") {
|
||||
return new Date(data).getTime();
|
||||
}
|
||||
var date = new Date(data);
|
||||
tit = date.toLocaleString('cs-CZ', {
|
||||
timeZone: 'America/New_York',
|
||||
})
|
||||
|
||||
if (isToday(now)) {
|
||||
//return local time only
|
||||
return '<div title="'+tit+'">'+ 'dnes ' + format_date(data,true,true)+'</div>'
|
||||
}
|
||||
else
|
||||
{
|
||||
//return local datetime
|
||||
return '<div title="'+tit+'">'+ format_date(data,true,false)+'</div>'
|
||||
}
|
||||
},
|
||||
},
|
||||
// {
|
||||
// targets: [6],
|
||||
// render: function ( data, type, row ) {
|
||||
// now = new Date(data)
|
||||
// if (type == "sort") {
|
||||
// return new Date(data).getTime();
|
||||
// }
|
||||
// var date = new Date(data);
|
||||
// tit = date.toLocaleString('cs-CZ', {
|
||||
// timeZone: 'America/New_York',
|
||||
// })
|
||||
|
||||
// if (isToday(now)) {
|
||||
// //return local time only
|
||||
// return '<div title="'+tit+'" class="token level comment">'+ 'dnes ' + format_date(data,false,true)+'</div>'
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //return local datetime
|
||||
// return '<div title="'+tit+'" class="token level number">'+ format_date(data,false,false)+'</div>'
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// targets: [9,10],
|
||||
// render: function ( data, type, row ) {
|
||||
// if (type == "sort") {
|
||||
// return new Date(data).getTime();
|
||||
// }
|
||||
// //console.log(data)
|
||||
// //market datetime
|
||||
// return data ? format_date(data, true) : data
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// targets: [2],
|
||||
// render: function ( data, type, row ) {
|
||||
// return '<div class="tdname tdnowrap" title="'+data+'">'+data+'</div>'
|
||||
// },
|
||||
// },
|
||||
// // {
|
||||
// // targets: [4],
|
||||
// // render: function ( data, type, row ) {
|
||||
// // return '<div class="tdname tdnowrap" title="'+data+'">'+data+'</div>'
|
||||
// // },
|
||||
// // },
|
||||
// {
|
||||
// targets: [16],
|
||||
// render: function ( data, type, row ) {
|
||||
// //console.log("metrics", data)
|
||||
// try {
|
||||
// data = JSON.parse(data)
|
||||
// }
|
||||
// catch (error) {
|
||||
// //console.log(error)
|
||||
// }
|
||||
// var res = JSON.stringify(data)
|
||||
// var unquoted = res.replace(/"([^"]+)":/g, '$1:')
|
||||
|
||||
// //zobrazujeme jen kratkou summary pokud mame, jinak davame vse, do titlu davame vzdy vse
|
||||
// //console.log(data)
|
||||
// short = null
|
||||
// if ((data) && (data.profit) && (data.profit.sum)) {
|
||||
// short = data.profit.sum
|
||||
// }
|
||||
// else {
|
||||
// short = unquoted
|
||||
// }
|
||||
// return '<div class="tdmetrics" title="'+unquoted+'">'+short+'</div>'
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// targets: [4],
|
||||
// render: function ( data, type, row ) {
|
||||
// return '<div class="tdnote" title="'+data+'">'+data+'</div>'
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// targets: [13,14,15],
|
||||
// render: function ( data, type, row ) {
|
||||
// return '<div class="tdsmall">'+data+'</div>'
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// targets: [11],
|
||||
// render: function ( data, type, row ) {
|
||||
// //if ilog_save true
|
||||
// if (data) {
|
||||
// return '<span class="material-symbols-outlined">done_outline</span>'
|
||||
// }
|
||||
// else {
|
||||
// return null
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
{
|
||||
targets: [4], //account
|
||||
render: function ( data, type, row ) {
|
||||
//if ilog_save true
|
||||
if (data == "ACCOUNT1") {
|
||||
res="ACC1"
|
||||
}
|
||||
else if (data == "ACCOUNT2") {
|
||||
res="ACC2"
|
||||
}
|
||||
else { res=data}
|
||||
return res
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [5], //mode
|
||||
render: function ( data, type, row ) {
|
||||
//if ilog_save true
|
||||
if (data == "backtest") {
|
||||
res="bt"
|
||||
}
|
||||
else { res=data}
|
||||
return res
|
||||
},
|
||||
}
|
||||
],
|
||||
order: [[1, 'asc']],
|
||||
select: {
|
||||
info: true,
|
||||
style: 'multi',
|
||||
//selector: 'tbody > tr:not(.group-header)'
|
||||
selector: 'tbody > tr:not(.group-header)'
|
||||
},
|
||||
paging: true
|
||||
});
|
||||
|
||||
}
|
||||
195
v2realbot/static/js/tables/runmanager/modals.js
Normal file
195
v2realbot/static/js/tables/runmanager/modals.js
Normal file
@ -0,0 +1,195 @@
|
||||
//delete modal
|
||||
$("#delModalRunmanager").on('submit','#delFormRunmanager', function(event){
|
||||
event.preventDefault();
|
||||
$('#deleterunmanager').attr('disabled','disabled');
|
||||
|
||||
//get val from #delidrunmanager
|
||||
id = $('#delidrunmanager').val();
|
||||
delete_runmanager_row(id);
|
||||
});
|
||||
|
||||
//add api
|
||||
// fetch(`/run_manager_records/`, {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'X-API-Key': API_KEY
|
||||
// },
|
||||
// body: JSON.stringify(newRecord)
|
||||
// })
|
||||
|
||||
// fetch(`/run_manager_records/${recordId}`, {
|
||||
// method: 'PATCH',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'X-API-Key': API_KEY
|
||||
// },
|
||||
// body: JSON.stringify(updatedData)
|
||||
// })
|
||||
|
||||
function getCheckedWeekdays() {
|
||||
const checkboxes = document.querySelectorAll('input[name="weekdays_filter[]"]:checked');
|
||||
const selectedDays = Array.from(checkboxes).map(checkbox => checkbox.value);
|
||||
return selectedDays;
|
||||
}
|
||||
|
||||
|
||||
//submit form
|
||||
$("#addeditModalRunmanager").on('submit','#addeditFormRunmanager', function(event){
|
||||
//event.preventDefault();
|
||||
//code for add
|
||||
if ($('#runmanagersubmit').val() == "Add") {
|
||||
|
||||
event.preventDefault();
|
||||
//set id as editable
|
||||
$('#runmanagersubmit').attr('disabled','disabled');
|
||||
//trow = runmanagerRecords.row('.selected').data();
|
||||
//note = $('#editnote').val()
|
||||
|
||||
// Handle weekdays functionality
|
||||
var weekdays = [];
|
||||
if ($('#runman_enable_weekdays').is(':checked')) {
|
||||
$('#addeditFormRunmanager input[name="weekdays"]:checked').each(function() {
|
||||
var weekday = $(this).val();
|
||||
switch(weekday) {
|
||||
case 'monday': weekdays.push(0); break;
|
||||
case 'tuesday': weekdays.push(1); break;
|
||||
case 'wednesday': weekdays.push(2); break;
|
||||
case 'thursday': weekdays.push(3); break;
|
||||
case 'friday': weekdays.push(4); break;
|
||||
// Add cases for Saturday and Sunday if needed
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log("weekdays pole", weekdays)
|
||||
|
||||
var formData = $(this).serializeJSON();
|
||||
console.log("formData", formData)
|
||||
|
||||
delete formData["enable_weekdays"]
|
||||
delete formData["weekdays"]
|
||||
|
||||
//pokud je zatrzeno tak aplikujeme filter, jinak nevyplnujeme
|
||||
if (weekdays.length > 0) {
|
||||
formData.weekdays_filter = weekdays
|
||||
}
|
||||
console.log(formData)
|
||||
if ($('#runmanilog_save').prop('checked')) {
|
||||
formData.ilog_save = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
formData.ilog_save = false;
|
||||
}
|
||||
|
||||
//if (formData.batch_id == "") {delete formData["batch_id"];}
|
||||
|
||||
//projede vsechny atributy a kdyz jsou "" tak je smaze, default nahradi backend
|
||||
for (let key in formData) {
|
||||
if (formData.hasOwnProperty(key) && formData[key] === "") {
|
||||
delete formData[key];
|
||||
}
|
||||
}
|
||||
|
||||
jsonString = JSON.stringify(formData);
|
||||
console.log("json string pro formData pred odeslanim", jsonString)
|
||||
$.ajax({
|
||||
url:"/run_manager_records/",
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"POST",
|
||||
contentType: "application/json",
|
||||
// dataType: "json",
|
||||
data: jsonString,
|
||||
success:function(data){
|
||||
$('#addeditFormRunmanager')[0].reset();
|
||||
window.$('#addeditModalRunmanager').modal('hide');
|
||||
$('#runmanagersubmit').attr('disabled', false);
|
||||
runmanagerRecords.ajax.reload();
|
||||
disable_runmanager_buttons();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#runmanagersubmit').attr('disabled', false);
|
||||
}
|
||||
})
|
||||
}
|
||||
//code for edit
|
||||
else {
|
||||
event.preventDefault();
|
||||
$('#runmanagersubmit').attr('disabled','disabled');
|
||||
//trow = runmanagerRecords.row('.selected').data();
|
||||
//note = $('#editnote').val()
|
||||
|
||||
// Handle weekdays functionality
|
||||
var weekdays = [];
|
||||
if ($('#runman_enable_weekdays').is(':checked')) {
|
||||
$('#addeditFormRunmanager input[name="weekdays"]:checked').each(function() {
|
||||
var weekday = $(this).val();
|
||||
switch(weekday) {
|
||||
case 'monday': weekdays.push(0); break;
|
||||
case 'tuesday': weekdays.push(1); break;
|
||||
case 'wednesday': weekdays.push(2); break;
|
||||
case 'thursday': weekdays.push(3); break;
|
||||
case 'friday': weekdays.push(4); break;
|
||||
// Add cases for Saturday and Sunday if needed
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var formData = $(this).serializeJSON();
|
||||
delete formData["enable_weekdays"]
|
||||
delete formData["weekdays"]
|
||||
|
||||
//pokud je zatrzeno tak aplikujeme filter, jinak nevyplnujeme
|
||||
if (weekdays.length > 0) {
|
||||
formData.weekdays_filter = weekdays
|
||||
}
|
||||
console.log(formData)
|
||||
if ($('#runmanilog_save').prop('checked')) {
|
||||
formData.ilog_save = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
formData.ilog_save = false;
|
||||
}
|
||||
|
||||
//projede formatributy a kdyz jsou "" tak je smaze, default nahradi backend - tzn. smaze se puvodni hodnota
|
||||
for (let key in formData) {
|
||||
if (formData.hasOwnProperty(key) && formData[key] === "") {
|
||||
delete formData[key];
|
||||
}
|
||||
}
|
||||
|
||||
jsonString = JSON.stringify(formData);
|
||||
console.log("EDIT json string pro formData pred odeslanim", jsonString);
|
||||
$.ajax({
|
||||
url:"/run_manager_records/"+formData.id,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"PATCH",
|
||||
contentType: "application/json",
|
||||
// dataType: "json",
|
||||
data: jsonString,
|
||||
success:function(data){
|
||||
console.log("EDIT success data", data);
|
||||
$('#addeditFormRunmanager')[0].reset();
|
||||
window.$('#addeditModalRunmanager').modal('hide');
|
||||
$('#runmanagersubmit').attr('disabled', false);
|
||||
runmanagerRecords.ajax.reload();
|
||||
disable_runmanager_buttons();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#runmanagersubmit').attr('disabled', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@ -250,6 +250,17 @@ strong {
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
.btn-check:checked+.btn, .btn.active, .btn.show, .btn:first-child:active, :not(.btn-check)+.btn:active {
|
||||
color: var(--bs-btn-active-color);
|
||||
background-color: #3a5962;
|
||||
border-color: #3a5962;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
--bs-btn-color: #94b1b3;
|
||||
--bs-btn-border-color: #3a5a62;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
margin-top: 0.5em;
|
||||
color: var(--bs-emphasis-color);
|
||||
|
||||
@ -424,7 +424,7 @@ class Strategy:
|
||||
|
||||
#main strat loop
|
||||
print(self.name, "Waiting for DATA",self.q1.qsize())
|
||||
with tqdm(total=self.q1.qsize()) as pbar:
|
||||
with tqdm(total=self.q1.qsize(), desc=self.name + "-Ingesting Aggregated") as pbar:
|
||||
while True:
|
||||
try:
|
||||
#block 5s, after that check signals
|
||||
@ -655,7 +655,7 @@ class Strategy:
|
||||
if len(self.state.iter_log_list) > 0:
|
||||
rt_out["iter_log"] = self.state.iter_log_list
|
||||
|
||||
#print(rt_out)
|
||||
printnow(rt_out)
|
||||
|
||||
print("RTQUEUE INSERT")
|
||||
#send current values to Realtime display on frontend
|
||||
|
||||
@ -4,9 +4,9 @@ from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Foll
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists
|
||||
from v2realbot.utils.directive_utils import get_conditions_from_configuration
|
||||
import mlroom.utils.mlutils as ml
|
||||
#import mlroom.utils.mlutils as ml
|
||||
from v2realbot.common.model import SLHistory
|
||||
from v2realbot.config import KW, MODEL_DIR
|
||||
from v2realbot.config import KW, MODEL_DIR, _ml_module_loaded
|
||||
from uuid import uuid4
|
||||
from datetime import datetime
|
||||
#import random
|
||||
@ -17,8 +17,14 @@ from rich import print as printanyway
|
||||
from threading import Event
|
||||
from traceback import format_exc
|
||||
|
||||
def load_ml_model(modelname, modelversion, MODEL_DIR):
|
||||
global ml
|
||||
import mlroom.utils.mlutils as ml
|
||||
return ml.load_model(modelname, modelversion, None, MODEL_DIR)
|
||||
|
||||
def initialize_dynamic_indicators(state):
|
||||
#pro vsechny indikatory, ktere maji ve svych stratvars TYPE inicializujeme
|
||||
|
||||
##ßprintanyway(state.vars, state)
|
||||
dict_copy = state.vars.indicators.copy()
|
||||
for indname, indsettings in dict_copy.items():
|
||||
@ -68,7 +74,8 @@ def initialize_dynamic_indicators(state):
|
||||
modelname = safe_get(indsettings["cp"], 'name', None)
|
||||
modelversion = safe_get(indsettings["cp"], 'version', "1")
|
||||
if modelname is not None:
|
||||
state.vars.loaded_models[modelname] = ml.load_model(modelname, modelversion, None, MODEL_DIR)
|
||||
state.vars.loaded_models[modelname] = load_ml_model(modelname, modelversion, MODEL_DIR)
|
||||
# state.vars.loaded_models[modelname] = ml.load_model(modelname, modelversion, None, MODEL_DIR)
|
||||
if state.vars.loaded_models[modelname] is not None:
|
||||
printanyway(f"model {modelname} loaded")
|
||||
else:
|
||||
|
||||
@ -30,6 +30,33 @@ from alpaca.trading.requests import GetCalendarRequest
|
||||
from alpaca.trading.client import TradingClient
|
||||
import time as timepkg
|
||||
from traceback import format_exc
|
||||
import re
|
||||
import tempfile
|
||||
import shutil
|
||||
from filelock import FileLock
|
||||
|
||||
def validate_and_format_time(time_string):
|
||||
"""
|
||||
Validates if the given time string is in the format HH:MM or H:MM.
|
||||
If valid, returns the standardized time string in HH:MM format.
|
||||
|
||||
Args:
|
||||
time_string (str): The time string to validate.
|
||||
|
||||
Returns:
|
||||
str or None: Standardized time string in HH:MM format if valid,
|
||||
None otherwise.
|
||||
"""
|
||||
# Regular expression for matching the time format H:MM or HH:MM
|
||||
time_pattern = re.compile(r'^([0-1]?[0-9]|2[0-3]):([0-5][0-9])$')
|
||||
|
||||
# Checking if the time string matches the pattern
|
||||
if time_pattern.match(time_string):
|
||||
# Standardize the time format to HH:MM
|
||||
standardized_time = datetime.strptime(time_string, '%H:%M').strftime('%H:%M')
|
||||
return standardized_time
|
||||
else:
|
||||
return None
|
||||
|
||||
#Alpaca Calendar wrapper with retry
|
||||
def fetch_calendar_data(start, end, max_retries=5, backoff_factor=1):
|
||||
@ -587,6 +614,7 @@ def json_serial(obj):
|
||||
Intervals: lambda obj: obj.__dict__,
|
||||
SLHistory: lambda obj: obj.__dict__,
|
||||
InstantIndicator: lambda obj: obj.__dict__,
|
||||
StrategyInstance: lambda obj: obj.__dict__,
|
||||
}
|
||||
|
||||
serializer = type_map.get(type(obj))
|
||||
@ -620,18 +648,30 @@ def parse_toml_string(tomlst: str):
|
||||
return (0, dict_replace_value(tomlst,"None",None))
|
||||
|
||||
#class to persist
|
||||
|
||||
# A FileLock is used to prevent concurrent access to the cache file.
|
||||
# The __init__ method reads the existing cache file within the lock to ensure it's not being written to simultaneously by another process.
|
||||
# The save method writes to a temporary file first and then atomically moves it to the desired file location. This prevents the issue of partial file writes in case the process is interrupted during the write.
|
||||
#Zatim temporary fix, aby nezapisoval jiny process
|
||||
#predtim nez bude implementovano ukladani do db
|
||||
#pro ostatni processy je dostupne rest api get stratin
|
||||
class Store:
|
||||
stratins : List[StrategyInstance] = []
|
||||
stratins: List[StrategyInstance] = []
|
||||
runners: List[Runner] = []
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.lock = FileLock(DATA_DIR + "/strategyinstances.lock")
|
||||
self.db_file = DATA_DIR + "/strategyinstances.cache"
|
||||
if os.path.exists(self.db_file):
|
||||
with open (self.db_file, 'rb') as fp:
|
||||
with self.lock, open(self.db_file, 'rb') as fp:
|
||||
self.stratins = pickle.load(fp)
|
||||
|
||||
def save(self):
|
||||
with open(self.db_file, 'wb') as fp:
|
||||
pickle.dump(self.stratins, fp)
|
||||
with self.lock:
|
||||
temp_fd, temp_path = tempfile.mkstemp(dir=DATA_DIR)
|
||||
with os.fdopen(temp_fd, 'wb') as temp_file:
|
||||
pickle.dump(self.stratins, temp_file)
|
||||
shutil.move(temp_path, self.db_file)
|
||||
|
||||
qu = Queue()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user