pridan draft batch runu

This commit is contained in:
David Brazda
2023-09-03 22:01:28 +02:00
parent e067a43dda
commit 7fabc5887c
14 changed files with 264 additions and 37 deletions

View File

@ -6,7 +6,7 @@ from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Orde
from v2realbot.indicators.indicators import ema
from v2realbot.indicators.oscillators import rsi
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType
from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, round2five, is_open_rush, is_close_rush, eval_cond_dict, Average, crossed_down, crossed_up, crossed, is_pivot, json_serial
from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, round2five, is_open_rush, is_close_rush, is_window_open, eval_cond_dict, Average, crossed_down, crossed_up, crossed, is_pivot, json_serial
from v2realbot.utils.directive_utils import get_conditions_from_configuration
from v2realbot.common.model import SLHistory
from datetime import datetime
@ -1108,13 +1108,17 @@ def next(data, state: StrategyState):
#obecne precondition preds vstupem - platne jak pro condition based tak pro plugin
def common_go_preconditions_check(signalname: str, options: dict):
#ZAKLADNI KONTROLY ATRIBUTU s fallbackem na obecné
#check working windows
close_rush = safe_get(options, "close_rush",safe_get(state.vars, "open_rush",0))
open_rush = safe_get(options, "open_rush",safe_get(state.vars, "close_rush",0))
#check working windows (open - close, in minutes from the start of marker)
window_open = safe_get(options, "window_open",safe_get(state.vars, "window_open",0))
window_close = safe_get(options, "window_close",safe_get(state.vars, "window_close",390))
if is_open_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), open_rush) or is_close_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), close_rush):
state.ilog(e=f"SIGNAL {signalname} - RUSH STANDBY", msg=f"{open_rush=} {close_rush=} ")
return False
if is_window_open(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), window_open, window_close) is False:
state.ilog(e=f"SIGNAL {signalname} - WINDOW CLOSED", msg=f"{window_open=} {window_close=} ")
return False
# if is_open_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), open_rush) or is_close_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), close_rush):
# state.ilog(e=f"SIGNAL {signalname} - WINDOW CLOSED", msg=f"{open_rush=} {close_rush=} ")
# return False
#natvrdo nebo na podminku
activated = safe_get(options, "activated", True)

View File

@ -27,6 +27,7 @@ from alpaca.data.enums import Exchange
class Intervals(BaseModel):
start: str
end: str
note: Optional[str] = None
# Define the data model for the TestLists
class TestList(BaseModel):
@ -71,6 +72,7 @@ class RunRequest(BaseModel):
ilog_save: bool = False
bt_from: datetime = None
bt_to: datetime = None
test_batch_id: Optional[str] = None
cash: int = 100000

View File

@ -6,7 +6,7 @@ from alpaca.data.requests import StockTradesRequest, StockBarsRequest
from alpaca.data.enums import DataFeed
from alpaca.data.timeframe import TimeFrame
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide
from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent
from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals
from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram
from v2realbot.utils.ilog import delete_logs
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType
@ -19,6 +19,7 @@ from tinydb import TinyDB, Query, where
from tinydb.operations import set
import json
from numpy import ndarray
from rich import print
import pandas as pd
from traceback import format_exc
from datetime import timedelta, time
@ -309,8 +310,94 @@ def capsule(target: object, db: object):
db.runners.remove(i)
print("Runner STOPPED")
#vrátí konkrétní sadu testlistu
def get_testlist_byID(record_id: str):
conn = pool.get_connection()
try:
cursor = conn.cursor()
cursor.execute("SELECT id, name, dates FROM test_list WHERE id = ?", (record_id,))
row = cursor.fetchone()
finally:
pool.release_connection(conn)
if row is None:
return -2, "not found"
else:
return 0, TestList(id=row[0], name=row[1], dates=json.loads(row[2]))
#volano pro batchove spousteni (BT,)
def run_batch_stratin(id: UUID, runReq: RunRequest):
if runReq.test_batch_id is None:
return (-1, "batch_id required for batch run")
if runReq.mode != Mode.BT:
return (-1, "batch run only for backtest")
print("request values:", runReq)
print("getting intervals")
testlist: TestList
res, testlist = get_testlist_byID(record_id=runReq.test_batch_id)
if res < 0:
return (-1, f"not existing ID of testlists with {runReq.test_batch_id}")
#spousti se vlakno s paralelnim behem a vracime ok
ridici_vlakno = Thread(target=batch_run_manager, args=(id, runReq, testlist), name=f"Batch run controll thread started.")
ridici_vlakno.start()
print(enumerate())
return 0, f"Batch run started"
#thread, ktery bude ridit paralelni spousteni
# bud ceka na dokonceni v runners nebo to bude ridit jinak a bude mit jednoho runnera?
# nejak vymyslet.
# logovani zatim jen do print
def batch_run_manager(id: UUID, runReq: RunRequest, testlist: TestList):
#zde muzu iterovat nad intervaly
#cekat az dobehne jeden interval a pak spustit druhy
#pripadne naplanovat beh - to uvidim
#domyslet kompatibilitu s budoucim automatickym "dennim" spousteni strategii
#taky budu mit nejaky konfiguracni RUN MANAGER, tak by krome rizeniho denniho runu
#mohl podporovat i BATCH RUNy.
batch_id = str(uuid4())[:8]
print("generated batch_ID", batch_id)
print("test batch", testlist)
print("test date", testlist.dates)
interval: Intervals
cnt_max = len(testlist.dates)
cnt = 0
for intrvl in testlist.dates:
cnt += 1
interval = intrvl
if interval.note is not None:
print("mame zde note")
print("Datum od", interval.start)
print("Datum do", interval.end)
print("starting")
#předání atributů datetime.fromisoformat
runReq.bt_from = datetime.fromisoformat(interval.start)
runReq.bt_to = datetime.fromisoformat(interval.end)
runReq.note = f"Batch {batch_id} run #{cnt}/{cnt_max} Note:{interval.note}"
#protoze jsme v ridicim vlaknu, poustime za sebou jednotlive stratiny v synchronnim modu
res, id_val = run_stratin(id=id, runReq=runReq, synchronous=True)
if res < 0:
print(f"CHyba v runu #{cnt} od:{runReq.bt_from} do {runReq.bt_to} -> {id_val}")
break
print("Batch manager FINISHED")
#stratin run
def run_stratin(id: UUID, runReq: RunRequest):
def run_stratin(id: UUID, runReq: RunRequest, synchronous: bool = False):
if runReq.mode == Mode.BT:
if runReq.bt_from is None:
return (-1, "start date required for BT")
@ -405,6 +492,12 @@ def run_stratin(id: UUID, runReq: RunRequest):
print(db.runners)
print(i)
print(enumerate())
#pokud spoustime v batch módu, tak čekáme na výsledek a pak pouštíme další run
if synchronous:
print(f"waiting for thread {vlakno} to finish")
vlakno.join()
return (0, id)
except Exception as e:
return (-2, "Exception: "+str(e)+format_exc())

View File

@ -226,7 +226,11 @@ def _get_stratin(stratin_id) -> StrategyInstance:
@app.put("/stratins/{stratin_id}/run", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK)
def _run_stratin(stratin_id: UUID, runReq: RunRequest):
res, id = cs.run_stratin(id=stratin_id, runReq=runReq)
print(runReq)
if runReq.test_batch_id is not None:
res, id = cs.run_batch_stratin(id=stratin_id, runReq=runReq)
else:
res, id = cs.run_stratin(id=stratin_id, runReq=runReq)
if res == 0: return id
elif res < 0:
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {res}:{id}")
@ -336,8 +340,8 @@ def _get_alpaca_history_bars(symbol: str, datetime_object_from: datetime, dateti
@app.post('/testlists/', dependencies=[Depends(api_key_auth)])
def create_record(testlist: TestList):
# Generate a new UUID for the record
testlist.id = str(uuid4())
testlist.id = str(uuid4())[:8]
# Insert the record into the database
conn = pool.get_connection()
cursor = conn.cursor()
@ -366,17 +370,12 @@ def get_testlists():
# API endpoint to retrieve a single record by ID
@app.get('/testlists/{record_id}')
def get_testlist(record_id: str):
conn = pool.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT id, name, dates FROM test_list WHERE id = ?", (record_id,))
row = cursor.fetchone()
pool.release_connection(conn)
if row is None:
res, testlist = cs.get_testlist_byID(record_id=record_id)
if res == 0:
return testlist
elif res < 0:
raise HTTPException(status_code=404, detail='Record not found')
testlist = TestList(id=row[0], name=row[1], dates=json.loads(row[2]))
return testlist
# API endpoint to update a record
@app.put('/testlists/{record_id}')

View File

@ -38,7 +38,10 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.4.6/mousetrap.min.js"></script>
<!-- <script src="https://cdn.datatables.net/select/1.6.2/js/dataTables.select.min.js"></script> -->
<script src="/static/js/fast-toml.js" type="text/javascript"></script>
<!-- <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.41.0/min/vs/editor/editor.main.js"></script> -->
<!-- <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.41.0/min/vs/loader.min.js"></script> -->
</head>
<body>
<div id="main" class="mainConteiner flex-container content">
@ -112,6 +115,7 @@
<button id="button_refresh" class="refresh btn btn-outline-success btn-sm">Refresh</button>
<button id="button_connect" class="btn btn-outline-success btn-sm">Connect</button>
</div>
<div>status info</div>
<table id="runnerTable" class="table-striped table dataTable" style="width:100%; border-color: #dce1dc;">
<thead>
<tr>
@ -340,7 +344,16 @@
<div class="form-group">
<label for="close_rush" class="form-label">close_rush</label>
<input type="number" class="form-control" id="close_rush" name="close_rush" placeholder="close_rush" value=0 required>
</div>
</div>
<div class="form-group">
<div id="editor"></div>
</div>
<!-- Create a textarea for saving content -->
<!-- <textarea id="content" style="display: none;"></textarea> -->
<div class="form-group">
<label for="stratvars_conf" class="form-label">stratvars_conf</label>
<textarea class="form-control" rows="16" id="stratvars_conf" name="stratvars_conf" required></textarea>
@ -459,7 +472,6 @@
<label for="account" class="form-label">Account</label>
<select class="form-control" id="account" name="account"><option value="ACCOUNT1">ACCOUNT1</option><option value="ACCOUNT2">ACCOUNT2</option></select>
</div>
<div class="form-group">
<label for="debug" class="form-label">debug</label>
<select class="form-control" id="debug" name="debug"><option value="true">true</option><option value="false" selected>false</option></select>
@ -472,6 +484,10 @@
<label for="bt_to" class="form-label">bt_to</label>
<input type="datetime-local" class="form-control" id="bt_to" name="bt_to" placeholder="2023-04-06T09:00:00Z" step="1">
</div>
<div class="form-group">
<label for="test_batch_id" class="form-label">Test Batch ID</label>
<input type="text" class="form-control" id="test_batch_id" name="test_batch_id" placeholder="test intervals ID">
</div>
<div class="form-group">
<label for="cash" class="form-label">cash</label>
<input type="number" class="form-control" id="cash" name="cash" placeholder="cash" value="100000">
@ -521,6 +537,8 @@
<input type="datetime-local" id="datepickerstart" step="1">
<label for="datepickerend">End:</label>
<input type="datetime-local" id="datepickerend" step="1">
<label for="datenote">Note:</label>
<input type="text" id="datenote">
<button type="button" id="addTagBtn" class="btn btn-outline-success btn-sm">Add Date</button>
<br>
<div id="tagContainer"></div>

View File

@ -24,6 +24,8 @@ function transform_data(data) {
var sl_line_markers_sada = []
//console.log(JSON.stringify(data.ext_data.sl_history, null, 2))
prev_id = 0
//cas of first record, nekdy jsou stejny - musim pridat setinku
prev_cas = 0
data.ext_data.sl_history.forEach((histRecord, index, array) => {
console.log("plnime")
@ -41,8 +43,16 @@ function transform_data(data) {
}
prev_id = histRecord.id
//prevedeme iso data na timestampy
cas = histRecord.time
if (cas == prev_cas) {
cas = cas + 0.001
}
prev_cas = cas
//line pro buy/sell markery
sline = {}
sline["time"] = cas
@ -599,6 +609,8 @@ function chart_archived_run(archRecord, data, oneMinuteBars) {
slLine.forEach((series, index, array) => {
chart.removeSeries(series)
})
slLine=[]
}
// if (slLine) {
// chart.removeSeries(slLine)
@ -627,7 +639,6 @@ function chart_archived_run(archRecord, data, oneMinuteBars) {
});
slLine_temp.setData(slRecord);
//zatim nemame markery pro sl history
slLine_temp.setMarkers(transformed_data["sl_line_markers"][index]);
slLine.push(slLine_temp)

View File

@ -406,11 +406,32 @@ $(document).ready(function () {
//console.log(localStorage.getItem("bt_from"))
$('#bt_to').val(localStorage.getItem("bt_to"));
//console.log(localStorage.getItem("bt_to"))
$('#test_batch_id').val(localStorage.getItem("test_batch_id"));
$('#mode').val(localStorage.getItem("mode"));
$('#account').val(localStorage.getItem("account"));
$('#debug').val(localStorage.getItem("debug"));
$('#ilog_save').val(localStorage.getItem("ilog_save"));
$('#runid').val(row.id);
// Initially, check the value of "batch" and enable/disable "from" and "to" accordingly
if ($("#test_batch_id").val() !== "") {
$("#bt_from, #bt_to").prop("disabled", true);
} else {
$("#bt_from, #bt_to").prop("disabled", false);
}
// Listen for changes in the "batch" input
$("#test_batch_id").on("input", function() {
if ($(this).val() !== "") {
// If "batch" is not empty, disable "from" and "to"
$("#bt_from, #bt_to").prop("disabled", true);
} else {
// If "batch" is empty, enable "from" and "to"
$("#bt_from, #bt_to").prop("disabled", false);
}
});
});
//button add
@ -441,6 +462,23 @@ $(document).ready(function () {
$('.modal-title').html(" Edit Records");
$('#action').val('updateRecord');
$('#save').val('Save');
//code for toml editor
// Initialize Monaco Editor
// require.config({ paths: { vs: 'monaco-editor/min/vs' } });
// require(['vs/editor/editor.main'], function () {
// var editor = $("#editor").monacoEditor({
// language: "toml",
// value: row.stratvars_conf
// });
// // Get content from Monaco Editor and set it to the textarea using jQuery
// editor.getModels()[0].onDidChangeContent(function() {
// var tomlContent = monaco.editor.getModels()[0].getValue();
// $('#stratvars_conf').val(tomlContent);
// });
// });
});
//delete button
$('#button_delete').click(function () {
@ -576,6 +614,7 @@ var runnerRecords =
$("#runModal").on('submit','#runForm', function(event){
localStorage.setItem("bt_from", $('#bt_from').val());
localStorage.setItem("bt_to", $('#bt_to').val());
localStorage.setItem("test_batch_id", $('#test_batch_id').val());
localStorage.setItem("mode", $('#mode').val());
localStorage.setItem("account", $('#account').val());
localStorage.setItem("debug", $('#debug').val());
@ -587,7 +626,7 @@ $("#runModal").on('submit','#runForm', function(event){
//rename runid to id
Object.defineProperty(formData, "id", Object.getOwnPropertyDescriptor(formData, "runid"));
delete formData["runid"];
//console.log(formData)
console.log(formData)
if ($('#ilog_save').prop('checked')) {
formData.ilog_save = true;
}
@ -599,6 +638,8 @@ $("#runModal").on('submit','#runForm', function(event){
if (formData.bt_from == "") {delete formData["bt_from"];}
if (formData.bt_to == "") {delete formData["bt_to"];}
if (formData.test_batch_id == "") {delete formData["test_batch_id"];}
//create strat_json - snapshot of stratin
row = stratinRecords.row('.selected').data();
const rec = new Object()

View File

@ -16,7 +16,15 @@ $(document).ready(function() {
datesArray.forEach(function(dates) {
var tag = $('<div class="tag">' + dates.start + " --- " + dates.end + '<span class="close">X</span></div>');
tag.find('.close').click(function() {
var dateText = tag.text();
console.log("clicked")
datesArray = datesArray.filter(function(date) {
tagcontent = date.start + " --- " + date.end + "X"
console.log(tagcontent,dateText)
return tagcontent !== dateText;
});
$(this).parent().remove();
console.log("ccclickd")
});
$('#tagContainer').append(tag);
});
@ -29,9 +37,13 @@ $(document).ready(function() {
records.forEach(function(record) {
var recordItem = $('<div class="recordItem"></div>');
var recordDetails = $('<div class="recordDetails"></div>').html('<strong>ID:</strong> ' + record.id + '<br><strong>Name:</strong> ' + record.name + '<br><strong>Dates:</strong> ');
record.dates.forEach(function(interval) {
var intervalItem = $('<div class="intervalContainer"></div>').html('<strong>Start:</strong> ' + interval.start + '<br><strong>End:</strong> ' + interval.end);
var note = ""
if (interval.note !== null) {
var note = '<br><strong>Note:</strong> ' + interval.note
}
var intervalItem = $('<div class="intervalContainer"></div>').html('<strong>Start:</strong> ' + interval.start + '<br><strong>End:</strong> ' + interval.end + note);
recordDetails.append(intervalItem);
});
@ -98,15 +110,24 @@ $(document).ready(function() {
$('#addTagBtn').click(function() {
var dateTextStart = $('#datepickerstart').val().trim();
var dateTextEnd = $('#datepickerend').val().trim();
var datenote = $('#datenote').val();
if ((dateTextStart !== '') && (dateTextEnd !== '')) {
var tag = $('<div class="tag">' + dateTextStart + " --- " + dateTextEnd + '<span class="close">X</span></div>');
tag.find('.close').click(function() {
var dateText = tag.text();
console.log("clicked")
datesArray = datesArray.filter(function(date) {
tagcontent = date.start + " --- " + date.end + "X"
console.log(tagcontent,dateText)
return tagcontent !== dateText;
});
$(this).parent().remove();
});
$('#tagContainer').append(tag);
var interval = {}
interval["start"] = dateTextStart
interval["end"] = dateTextEnd
interval["note"] = datenote
datesArray.push(interval);
$('#datepicker').val('');
}
@ -145,15 +166,17 @@ $(document).ready(function() {
cancelEdit();
});
$('#tagContainer').on('click', '.tag .close', function() {
var tag = $(this).parent();
var dateText = tag.text();
datesArray = datesArray.filter(function(date) {
tagcontent = date.start + " --- " + date.end
return tagcontent !== dateText;
});
tag.remove();
});
// $('#tagContainer').on('click', '.tag .close', function() {
// var tag = $(this).parent();
// var dateText = tag.text();
// console.log("clicked")
// datesArray = datesArray.filter(function(date) {
// tagcontent = date.start + " --- " + date.end
// console.log(tagcontent,dateText)
// return tagcontent !== dateText;
// });
// tag.remove();
// });
function getRecords() {
$.ajax({

View File

@ -223,6 +223,7 @@ function cleanup_chart() {
clear_status_header()
chart = null
indList = [];
slLine = []
markersLine = null
avgBuyLine = null
volumeSeries = null

View File

@ -398,6 +398,13 @@ pre {
'opsz' 24
}
.intervalContainer {
float: left;
border-width: thick;
border: beige;
display: inline-table;
padding: 10px;
}
/* TestList part generated by ChatGPT */
.recordItem {

View File

@ -284,6 +284,34 @@ def is_open_rush(dt: datetime, mins: int = 30):
rushtime = (datetime.combine(date.today(), business_hours["from"]) + timedelta(minutes=mins)).time()
return business_hours["from"] <= dt.time() < rushtime
#TODO market time pro dany den si dotahnout z Alpaca
def is_window_open(dt: datetime, start: int = 0, end: int = 390):
""""
Returns true if time (start in minutes and end in minutes) is in working window
"""
if start < 0 or start > 389:
return False
if end < 0 or end > 389:
return False
dt = dt.astimezone(zoneNY)
business_hours = {
"from": time(hour=9, minute=30),
"to": time(hour=16, minute=0)
}
startime = (datetime.combine(date.today(), business_hours["from"]) + timedelta(minutes=start)).time()
endtime = (datetime.combine(date.today(), business_hours["from"]) + timedelta(minutes=end)).time()
#time not within business hours
if not business_hours["from"] <= dt.time() <= business_hours["to"]:
return False
if startime <= dt.time() <= endtime:
return True
else:
return False
def is_close_rush(dt: datetime, mins: int = 30):
""""
Returns true if time is within afternoon rush (close-mins)