Compare commits
5 Commits
master
...
feature/mu
| Author | SHA1 | Date | |
|---|---|---|---|
| 30931f60d4 | |||
| 79a033a633 | |||
| a10d5b8a64 | |||
| 30044dc4ea | |||
| 30048364bb |
File diff suppressed because one or more lines are too long
@ -1691,7 +1691,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.10"
|
||||
"version": "3.10.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
932
research/strat_SUPERTREND/SUPERTREND_v1_MULTI.ipynb
Normal file
932
research/strat_SUPERTREND/SUPERTREND_v1_MULTI.ipynb
Normal file
@ -0,0 +1,932 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from v2realbot.tools.loadbatch import load_batch\n",
|
||||
"from v2realbot.utils.utils import zoneNY\n",
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"import vectorbtpro as vbt\n",
|
||||
"from itables import init_notebook_mode, show\n",
|
||||
"import datetime\n",
|
||||
"from itertools import product\n",
|
||||
"from v2realbot.config import ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, DATA_DIR\n",
|
||||
"\n",
|
||||
"init_notebook_mode(all_interactive=True)\n",
|
||||
"\n",
|
||||
"vbt.settings.set_theme(\"dark\")\n",
|
||||
"vbt.settings['plotting']['layout']['width'] = 1280\n",
|
||||
"vbt.settings.plotting.auto_rangebreaks = True\n",
|
||||
"# Set the option to display with pagination\n",
|
||||
"pd.set_option('display.notebook_repr_html', True)\n",
|
||||
"pd.set_option('display.max_rows', 10) # Number of rows per page\n",
|
||||
"\n",
|
||||
"# Define the market open and close times\n",
|
||||
"market_open = datetime.time(9, 30)\n",
|
||||
"market_close = datetime.time(16, 0)\n",
|
||||
"entry_window_opens = 1\n",
|
||||
"entry_window_closes = 370\n",
|
||||
"\n",
|
||||
"forced_exit_start = 380\n",
|
||||
"forced_exit_end = 390\n",
|
||||
"\n",
|
||||
"#LOAD FROM PARQUET\n",
|
||||
"#list all files is dir directory with parquet extension\n",
|
||||
"dir = DATA_DIR + \"/notebooks/\"\n",
|
||||
"import os\n",
|
||||
"files = [f for f in os.listdir(dir) if f.endswith(\".parquet\")]\n",
|
||||
"#print('\\n'.join(map(str, files)))\n",
|
||||
"file_name = \"ohlcv_df-SPY-2024-01-01T09:30:00-2024-05-14T16:00:00.parquet\"\n",
|
||||
"ohlcv_df = pd.read_parquet(dir+file_name,engine='pyarrow')\n",
|
||||
"basic_data = vbt.Data.from_data(vbt.symbol_dict({\"SPY\": ohlcv_df}), tz_convert=zoneNY)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#parameters (primary y line, secondary y line, close)\n",
|
||||
"def plot_2y_close(priminds, secinds, close):\n",
|
||||
" fig = vbt.make_subplots(rows=1, cols=1, shared_xaxes=True, specs=[[{\"secondary_y\": True}]], vertical_spacing=0.02, subplot_titles=(\"MOM\", \"Price\" ))\n",
|
||||
" close.vbt.plot(fig=fig, add_trace_kwargs=dict(secondary_y=False), trace_kwargs=dict(line=dict(color=\"blue\")))\n",
|
||||
" for ind in priminds:\n",
|
||||
" ind.plot(fig=fig, add_trace_kwargs=dict(secondary_y=False))\n",
|
||||
" for ind in secinds:\n",
|
||||
" ind.plot(fig=fig, add_trace_kwargs=dict(secondary_y=True))\n",
|
||||
" return fig\n",
|
||||
"\n",
|
||||
"# close = basic_data.xloc[\"09:30\":\"10:00\"].close"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#PIPELINE - FOR - LOOP\n",
|
||||
"\n",
|
||||
"#indicator parameters\n",
|
||||
"mom_timeperiod = list(range(2, 12))\n",
|
||||
"\n",
|
||||
"#uzavreni okna od 1 do 200\n",
|
||||
"#entry_window_closes = list(range(2, 50, 3))\n",
|
||||
"entry_window_closes = [5, 10, 30, 45]\n",
|
||||
"#entry_window_closes = 30\n",
|
||||
"#threshold entries parameters\n",
|
||||
"#long\n",
|
||||
"mom_th = np.round(np.arange(0.01, 0.5 + 0.02, 0.02),4).tolist()#-0.02\n",
|
||||
"# short\n",
|
||||
"#mom_th = np.round(np.arange(-0.01, -0.3 - 0.02, -0.02),4).tolist()#-0.02\n",
|
||||
"roc_th = np.round(np.arange(-0.2, -0.8 - 0.05, -0.05),4).tolist()#-0.2\n",
|
||||
"#print(mom_th, roc_th)\n",
|
||||
"\n",
|
||||
"#portfolio simulation parameters\n",
|
||||
"sl_stop =np.round(np.arange(0.02/100, 0.7/100, 0.05/100),4).tolist()\n",
|
||||
"tp_stop = np.round(np.arange(0.02/100, 0.7/100, 0.05/100),4).tolist()\n",
|
||||
"\n",
|
||||
"combs = list(product(mom_timeperiod, mom_th, roc_th, sl_stop, tp_stop))\n",
|
||||
"\n",
|
||||
"@vbt.parameterized(merge_func = \"concat\", random_subset = 2000, show_progress=True) \n",
|
||||
"def test_strat(entry_window_closes=60,\n",
|
||||
" mom_timeperiod=2,\n",
|
||||
" mom_th=-0.04,\n",
|
||||
" #roc_th=-0.2,\n",
|
||||
" sl_stop=0.19/100,\n",
|
||||
" tp_stop=0.19/100):\n",
|
||||
" # mom_timeperiod=2\n",
|
||||
" # mom_th=-0.06\n",
|
||||
" # roc_th=-0.2\n",
|
||||
" # sl_stop=0.04/100\n",
|
||||
" # tp_stop=0.04/100\n",
|
||||
"\n",
|
||||
" momshort = vbt.indicator(\"talib:MOM\").run(basic_data.close, timeperiod=mom_timeperiod, short_name = \"slope_short\")\n",
|
||||
" rocp = vbt.indicator(\"talib:ROC\").run(basic_data.close, short_name = \"rocp\")\n",
|
||||
" #rate of change + momentum\n",
|
||||
"\n",
|
||||
" #momshort.plot rocp.real_crossed_below(roc_th) & \n",
|
||||
" #short_signal = momshort.real_crossed_below(mom_th)\n",
|
||||
" long_signal = momshort.real_crossed_above(mom_th)\n",
|
||||
" # print(\"short signal\")\n",
|
||||
" # print(short_signal.value_counts())\n",
|
||||
"\n",
|
||||
" #forced_exit = pd.Series(False, index=close.index)\n",
|
||||
" forced_exit = basic_data.symbol_wrapper.fill(False)\n",
|
||||
" #entry_window_open = pd.Series(False, index=close.index)\n",
|
||||
" entry_window_open= basic_data.symbol_wrapper.fill(False)\n",
|
||||
"\n",
|
||||
" #print(entry_window_closes, \"entry window closes\")\n",
|
||||
" # Calculate the time difference in minutes from market open for each timestamp\n",
|
||||
" elapsed_min_from_open = (forced_exit.index.hour - market_open.hour) * 60 + (forced_exit.index.minute - market_open.minute)\n",
|
||||
"\n",
|
||||
" entry_window_open[(elapsed_min_from_open >= entry_window_opens) & (elapsed_min_from_open < entry_window_closes)] = True\n",
|
||||
"\n",
|
||||
" #print(entry_window_open.value_counts())\n",
|
||||
"\n",
|
||||
" forced_exit[(elapsed_min_from_open >= forced_exit_start) & (elapsed_min_from_open < forced_exit_end)] = True\n",
|
||||
" #short_entries = (short_signal & entry_window_open)\n",
|
||||
" #short_exits = forced_exit\n",
|
||||
" entries = (long_signal & entry_window_open)\n",
|
||||
" exits = forced_exit\n",
|
||||
" #long_entries.info()\n",
|
||||
" #number of trues and falses in long_entries\n",
|
||||
" #print(short_exits.value_counts())\n",
|
||||
" #print(short_entries.value_counts())\n",
|
||||
"\n",
|
||||
" #fig = plot_2y_close([],[momshort, rocp], close)\n",
|
||||
" #short_signal.vbt.signals.plot_as_entries(close, fig=fig, add_trace_kwargs=dict(secondary_y=False))\n",
|
||||
" #print(sl_stop)\n",
|
||||
" #tsl_th=sl_stop, \n",
|
||||
" #short_entries=short_entries, short_exits=short_exits,\n",
|
||||
" pf = vbt.Portfolio.from_signals(close=basic_data.close, entries=entries, exits=exits, tsl_stop=sl_stop, tp_stop = tp_stop, fees=0.0167/100, freq=\"1s\", price=\"close\") #sl_stop=sl_stop, tp_stop = sl_stop,\n",
|
||||
" \n",
|
||||
" return pf.stats([\n",
|
||||
" 'total_return',\n",
|
||||
" 'max_dd', \n",
|
||||
" 'total_trades', \n",
|
||||
" 'win_rate', \n",
|
||||
" 'expectancy'\n",
|
||||
" ])\n",
|
||||
"\n",
|
||||
"pf_results = test_strat(vbt.Param(entry_window_closes),\n",
|
||||
" vbt.Param(mom_timeperiod),\n",
|
||||
" vbt.Param(mom_th),\n",
|
||||
" #vbt.Param(roc_th)\n",
|
||||
" vbt.Param(sl_stop),\n",
|
||||
" vbt.Param(tp_stop, condition=\"tp_stop > sl_stop\"))\n",
|
||||
"pf_results = pf_results.unstack(level=-1)\n",
|
||||
"pf_results.sort_values(by=[\"Total Return [%]\", \"Max Drawdown [%]\"], ascending=[False, True])\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#pf_results.load(\"10tiscomb.pickle\")\n",
|
||||
"#pf_results.info()\n",
|
||||
"\n",
|
||||
"vbt.save(pf_results, \"8tiscomb_tsl.pickle\")\n",
|
||||
"\n",
|
||||
"# pf_results = vbt.load(\"8tiscomb_tsl.pickle\")\n",
|
||||
"# pf_results\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# parallel_coordinates method¶\n",
|
||||
"\n",
|
||||
"# attach_px_methods.<locals>.plot_func(\n",
|
||||
"# *args,\n",
|
||||
"# layout=None,\n",
|
||||
"# **kwargs\n",
|
||||
"# )\n",
|
||||
"\n",
|
||||
"# pf_results.vbt.px.parallel_coordinates() #ocdf\n",
|
||||
"\n",
|
||||
"res = pf_results.reset_index()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf_results"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"from sklearn.decomposition import PCA\n",
|
||||
"from sklearn.preprocessing import StandardScaler\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"\n",
|
||||
"# Assuming pf_results is your DataFrame\n",
|
||||
"# Convert columns to numeric, assuming NaNs where conversion fails\n",
|
||||
"metrics = ['Total Return [%]', 'Max Drawdown [%]', 'Total Trades']\n",
|
||||
"for metric in metrics:\n",
|
||||
" pf_results[metric] = pd.to_numeric(pf_results[metric], errors='coerce')\n",
|
||||
"\n",
|
||||
"# Handle missing values, for example filling with the median\n",
|
||||
"pf_results['Max Drawdown [%]'].fillna(pf_results['Max Drawdown [%]'].median(), inplace=True)\n",
|
||||
"\n",
|
||||
"# Extract the metrics into a new DataFrame\n",
|
||||
"data_for_pca = pf_results[metrics]\n",
|
||||
"\n",
|
||||
"# Standardize the data before applying PCA\n",
|
||||
"scaler = StandardScaler()\n",
|
||||
"data_scaled = scaler.fit_transform(data_for_pca)\n",
|
||||
"\n",
|
||||
"# Apply PCA\n",
|
||||
"pca = PCA(n_components=2) # Adjust components as needed\n",
|
||||
"principal_components = pca.fit_transform(data_scaled)\n",
|
||||
"\n",
|
||||
"# Create a DataFrame with the principal components\n",
|
||||
"pca_results = pd.DataFrame(data=principal_components, columns=['PC1', 'PC2'])\n",
|
||||
"\n",
|
||||
"# Visualize the results\n",
|
||||
"plt.figure(figsize=(8,6))\n",
|
||||
"plt.scatter(pca_results['PC1'], pca_results['PC2'], alpha=0.5)\n",
|
||||
"plt.xlabel('Principal Component 1')\n",
|
||||
"plt.ylabel('Principal Component 2')\n",
|
||||
"plt.title('PCA of Strategy Optimization Results')\n",
|
||||
"plt.grid(True)\n",
|
||||
"plt.savefig(\"ddd.png\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Check if there is any unnamed level and rename it\n",
|
||||
"if None in df.index.names:\n",
|
||||
" # Generate new names list replacing None with 'stat'\n",
|
||||
" new_names = ['stat' if name is None else name for name in df.index.names]\n",
|
||||
" df.index.set_names(new_names, inplace=True)\n",
|
||||
"\n",
|
||||
"rs= df\n",
|
||||
"\n",
|
||||
"rs.info()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# # Now, 'stat' is the name of the previously unnamed level\n",
|
||||
"\n",
|
||||
"# # Filter for 'Total Return' assuming it is a correct identifier in the 'stat' level\n",
|
||||
"# total_return_series = df.xs('Total Return [%]', level='stat')\n",
|
||||
"\n",
|
||||
"# # Sort the Series to get the largest 'Total Return' values\n",
|
||||
"# sorted_series = total_return_series.sort_values(ascending=False)\n",
|
||||
"\n",
|
||||
"# # Print the sorted filtered data\n",
|
||||
"# sorted_series.head(20)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sorted_series.vbt.save()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#df.info()\n",
|
||||
"total_return_series = df.xs('Total Return [%]')\n",
|
||||
"sorted_series = total_return_series.sort_values(ascending=False)\n",
|
||||
"\n",
|
||||
"# Display the top N entries, e.g., top 5\n",
|
||||
"sorted_series.head(5)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"comb_stats_df.nlargest(10, 'Total Return [%]')\n",
|
||||
"#stats_df.info()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"8\t-0.06\t-0.2\t0.0028\t0.0048\t4.156254\n",
|
||||
"4 -0.02 -0.25 0.0028 0.0048 0.84433\n",
|
||||
"3 -0.02 -0.25 0.0033 0.0023 Total Return [%] 0.846753\n",
|
||||
"#2\t-0.04\t-0.2\t0.0019\t0.0019\n",
|
||||
"# 2\t-0.04\t-0.2\t0.0019\t0.0019\t0.556919\t91\t60.43956\t0.00612\n",
|
||||
"# 2\t-0.04\t-0.25\t0.0019\t0.0019\t0.556919\t91\t60.43956\t0.00612\n",
|
||||
"# 2\t-0.04\t-0.3\t0.0019\t0.0019\t0.556919\t91\t60.43956\t0.00612\n",
|
||||
"# 2\t-0.04\t-0.35\t0.0019\t0.0019\t0.556919\t91\t60.43956\t0.00612\n",
|
||||
"# 2\t-0.04\t-0.4\t0.0019\t0.0019\t0.556919\t91\t60.43956\t0.00612\n",
|
||||
"# 2\t-0.04\t-0.2\t0.0019\t0.0017\t0.451338\t93\t63.44086\t0.004853\n",
|
||||
"# 2\t-0.04\t-0.25\t0.0019\t0.0017\t0.451338\t93\t63.44086\t0.004853\n",
|
||||
"# 2\t-0.04\t-0.3\t0.0019\t0.0017\t0.451338\t93\t63.44086\t0.004853\n",
|
||||
"# 2\t-0.04\t-0.35\t0.0019\t0.0017\t0.451338\t93\t63.44086\t0.004853\n",
|
||||
"# 2\t-0.04\t-0.4\t0.0019\t0.0017\t0.451338\t93\t63.44086\t0.004853"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"basic_data.symbols"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
">>> def apply_func(ts, entries, exits, fastw, sloww, minp=None):\n",
|
||||
"... fast_ma = vbt.nb.rolling_mean_nb(ts, fastw, minp=minp)\n",
|
||||
"... slow_ma = vbt.nb.rolling_mean_nb(ts, sloww, minp=minp)\n",
|
||||
"... entries[:] = vbt.nb.crossed_above_nb(fast_ma, slow_ma) \n",
|
||||
"... exits[:] = vbt.nb.crossed_above_nb(slow_ma, fast_ma)\n",
|
||||
"... return (fast_ma, slow_ma) \n",
|
||||
"\n",
|
||||
">>> CrossSig = vbt.IF(\n",
|
||||
"... class_name=\"CrossSig\",\n",
|
||||
"... input_names=['ts'],\n",
|
||||
"... in_output_names=['entries', 'exits'],\n",
|
||||
"... param_names=['fastw', 'sloww'],\n",
|
||||
"... output_names=['fast_ma', 'slow_ma']\n",
|
||||
"... ).with_apply_func(\n",
|
||||
"... apply_func,\n",
|
||||
"... in_output_settings=dict(\n",
|
||||
"... entries=dict(dtype=np.bool_), #initialize output with bool\n",
|
||||
"... exits=dict(dtype=np.bool_)\n",
|
||||
"... )\n",
|
||||
"... )\n",
|
||||
">>> cross_sig = CrossSig.run(ts2, 2, 4)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#PIPELINE - parameters in one go\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"#TOTO prepsat do FOR-LOOPu\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"#indicator parameters\n",
|
||||
"mom_timeperiod = list(range(2, 6))\n",
|
||||
"\n",
|
||||
"#threshold entries parameters\n",
|
||||
"mom_th = np.round(np.arange(-0.02, -0.1 - 0.02, -0.02),4).tolist()#-0.02\n",
|
||||
"roc_th = np.round(np.arange(-0.2, -0.4 - 0.05, -0.05),4).tolist()#-0.2\n",
|
||||
"#print(mom_th, roc_th)\n",
|
||||
"#jejich product\n",
|
||||
"# mom_th_prod, roc_th_prod = zip(*product(mom_th, roc_th))\n",
|
||||
"\n",
|
||||
"# #convert threshold to vbt param\n",
|
||||
"# mom_th_index = vbt.Param(mom_th_prod, name='mom_th_th') \n",
|
||||
"# roc_th_index = vbt.Param(roc_th_prod, name='roc_th_th')\n",
|
||||
"\n",
|
||||
"mom_th = vbt.Param(mom_th, name='mom_th')\n",
|
||||
"roc_th = vbt.Param(roc_th, name='roc_th')\n",
|
||||
"\n",
|
||||
"#portfolio simulation parameters\n",
|
||||
"sl_stop = np.arange(0.03/100, 0.2/100, 0.02/100).tolist()\n",
|
||||
"# Using the round function\n",
|
||||
"sl_stop = [round(val, 4) for val in sl_stop]\n",
|
||||
"tp_stop = np.arange(0.03/100, 0.2/100, 0.02/100).tolist()\n",
|
||||
"# Using the round function\n",
|
||||
"tp_stop = [round(val, 4) for val in tp_stop]\n",
|
||||
"sl_stop = vbt.Param(sl_stop) #np.nan mean s no stoploss\n",
|
||||
"tp_stop = vbt.Param(tp_stop) #np.nan mean s no stoploss\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"#def test_mom(window=14, mom_th=0.2, roc_th=0.2, sl_stop=0.03/100, tp_stop=0.03/100):\n",
|
||||
"#close = basic_data.xloc[\"09:30\":\"10:00\"].close\n",
|
||||
"momshort = vbt.indicator(\"talib:MOM\").run(basic_data.get(\"Close\"), timeperiod=mom_timeperiod, short_name = \"slope_short\")\n",
|
||||
"\n",
|
||||
"#ht_trendline = vbt.indicator(\"talib:HT_TRENDLINE\").run(close, short_name = \"httrendline\")\n",
|
||||
"rocp = vbt.indicator(\"talib:ROC\").run(basic_data.get(\"Close\"), short_name = \"rocp\")\n",
|
||||
"#rate of change + momentum\n",
|
||||
"\n",
|
||||
"rocp_signal = rocp.real_crossed_below(mom_th)\n",
|
||||
"mom_signal = momshort.real_crossed_below(roc_th)\n",
|
||||
"\n",
|
||||
"#mom_signal\n",
|
||||
"print(rocp_signal.info())\n",
|
||||
"print(mom_signal.info())\n",
|
||||
"#print(rocp.real)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"short_signal = (mom_signal.vbt & rocp_signal)\n",
|
||||
"\n",
|
||||
"# #short_signal = (rocp.real_crossed_below(roc_th_index) & momshort.real_crossed_below(mom_th_index))\n",
|
||||
"# forced_exit = m1_data.symbol_wrapper.fill(False)\n",
|
||||
"# entry_window_open= m1_data.symbol_wrapper.fill(False)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# # Calculate the time difference in minutes from market open for each timestamp\n",
|
||||
"# elapsed_min_from_open = (forced_exit.index.hour - market_open.hour) * 60 + (forced_exit.index.minute - market_open.minute)\n",
|
||||
"\n",
|
||||
"# entry_window_open[(elapsed_min_from_open >= entry_window_opens) & (elapsed_min_from_open < entry_window_closes)] = True\n",
|
||||
"# forced_exit[(elapsed_min_from_open >= forced_exit_start) & (elapsed_min_from_open < forced_exit_end)] = True\n",
|
||||
"# short_entries = (short_signal & entry_window_open)\n",
|
||||
"# short_exits = forced_exit\n",
|
||||
"# #long_entries.info()\n",
|
||||
"# #number of trues and falses in long_entries\n",
|
||||
"# #short_exits.value_counts()\n",
|
||||
"# #short_entries.value_counts()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# pf = vbt.Portfolio.from_signals(close=close, short_entries=short_entries, short_exits=short_exits, sl_stop=sl_stop, tp_stop = tp_stop, fees=0.0167/100, freq=\"1s\") #sl_stop=sl_stop, tp_stop = sl_stop,\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# filter dates"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#filter na dny\n",
|
||||
"dates_of_interest = pd.to_datetime(['2024-04-22']).tz_localize('US/Eastern')\n",
|
||||
"filtered_df = df.loc[df.index.normalize().isin(dates_of_interest)]\n",
|
||||
"\n",
|
||||
"df = filtered_df\n",
|
||||
"df.info()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# import plotly.io as pio\n",
|
||||
"# pio.renderers.default = 'notebook'\n",
|
||||
"\n",
|
||||
"#naloadujeme do vbt symbol as column\n",
|
||||
"basic_data = vbt.Data.from_data({\"BAC\": df}, tz_convert=zoneNY)\n",
|
||||
"\n",
|
||||
"vbt.settings.plotting.auto_rangebreaks = True\n",
|
||||
"#basic_data.data[\"BAC\"].vbt.ohlcv.plot()\n",
|
||||
"\n",
|
||||
"#basic_data.data[\"BAC\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"m1_data = basic_data[['Open', 'High', 'Low', 'Close', 'Volume']]\n",
|
||||
"\n",
|
||||
"m1_data.data[\"BAC\"]\n",
|
||||
"#m5_data = m1_data.resample(\"5T\")\n",
|
||||
"\n",
|
||||
"#m5_data.data[\"BAC\"].head(10)\n",
|
||||
"\n",
|
||||
"# m15_data = m1_data.resample(\"15T\")\n",
|
||||
"\n",
|
||||
"# m15 = m15_data.data[\"BAC\"]\n",
|
||||
"\n",
|
||||
"# m15.vbt.ohlcv.plot()\n",
|
||||
"\n",
|
||||
"# m1_data.wrapper.index\n",
|
||||
"\n",
|
||||
"# m1_resampler = m1_data.wrapper.get_resampler(\"1T\")\n",
|
||||
"# m1_resampler.index_difference(reverse=True)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# m5_resampler.prettify()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# MOM indicator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vbt.phelp(vbt.indicator(\"talib:ROCP\").run)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"vyuzití rychleho klesani na sekundove urovni behem open rush\n",
|
||||
"- MOM + ROC during open rush\n",
|
||||
"- short signal\n",
|
||||
"- pipeline kombinace thresholdu pro vstup mom_th, roc_th + hodnota sl_stop a tp_stop (pripadne trailing) - nalezeni optimalni kombinace atributu"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# fig = plot_2y_close([ht_trendline],[momshort, rocp], close)\n",
|
||||
"# short_signal.vbt.signals.plot_as_entries(close, fig=fig, add_trace_kwargs=dict(secondary_y=False)) \n",
|
||||
"\n",
|
||||
"#parameters (primary y line, secondary y line, close)\n",
|
||||
"def plot_2y_close(priminds, secinds, close):\n",
|
||||
" fig = vbt.make_subplots(rows=1, cols=1, shared_xaxes=True, specs=[[{\"secondary_y\": True}]], vertical_spacing=0.02, subplot_titles=(\"MOM\", \"Price\" ))\n",
|
||||
" close.vbt.plot(fig=fig, add_trace_kwargs=dict(secondary_y=False), trace_kwargs=dict(line=dict(color=\"blue\")))\n",
|
||||
" for ind in priminds:\n",
|
||||
" ind.plot(fig=fig, add_trace_kwargs=dict(secondary_y=False))\n",
|
||||
" for ind in secinds:\n",
|
||||
" ind.plot(fig=fig, add_trace_kwargs=dict(secondary_y=True))\n",
|
||||
" return fig\n",
|
||||
"\n",
|
||||
"close = m1_data.xloc[\"09:30\":\"10:00\"].close\n",
|
||||
"momshort = vbt.indicator(\"talib:MOM\").run(close, timeperiod=3, short_name = \"slope_short\")\n",
|
||||
"ht_trendline = vbt.indicator(\"talib:HT_TRENDLINE\").run(close, short_name = \"httrendline\")\n",
|
||||
"rocp = vbt.indicator(\"talib:ROC\").run(close, short_name = \"rocp\")\n",
|
||||
"#rate of change + momentum\n",
|
||||
"short_signal = (rocp.real_crossed_below(-0.2) & momshort.real_crossed_below(-0.02))\n",
|
||||
"#indlong = vbt.indicator(\"talib:MOM\").run(close, timeperiod=10, short_name = \"slope_long\")\n",
|
||||
"fig = plot_2y_close([ht_trendline],[momshort, rocp], close)\n",
|
||||
"short_signal.vbt.signals.plot_as_entries(close, fig=fig, add_trace_kwargs=dict(secondary_y=False)) "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"close = m1_data.close\n",
|
||||
"#vbt.phelp(vbt.OLS.run)\n",
|
||||
"\n",
|
||||
"#oer steepmnes of regression line\n",
|
||||
"#talib.LINEARREG_SLOPE(close, timeperiod=timeperiod)\n",
|
||||
"#a také ON BALANCE VOLUME - http://5.161.179.223:8000/static/js/vbt/api/indicators/custom/obv/index.html\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"mom_ind = vbt.indicator(\"talib:MOM\") \n",
|
||||
"#vbt.phelp(mom_ind.run)\n",
|
||||
"\n",
|
||||
"mom = mom_ind.run(close, timeperiod=10)\n",
|
||||
"\n",
|
||||
"plot_2y_close(mom, close)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# defining ENTRY WINDOW and forced EXIT window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#m1_data.data[\"BAC\"].info()\n",
|
||||
"import datetime\n",
|
||||
"# Define the market open and close times\n",
|
||||
"market_open = datetime.time(9, 30)\n",
|
||||
"market_close = datetime.time(16, 0)\n",
|
||||
"entry_window_opens = 2\n",
|
||||
"entry_window_closes = 30\n",
|
||||
"\n",
|
||||
"forced_exit_start = 380\n",
|
||||
"forced_exit_end = 390\n",
|
||||
"\n",
|
||||
"forced_exit = m1_data.symbol_wrapper.fill(False)\n",
|
||||
"entry_window_open= m1_data.symbol_wrapper.fill(False)\n",
|
||||
"\n",
|
||||
"# Calculate the time difference in minutes from market open for each timestamp\n",
|
||||
"elapsed_min_from_open = (forced_exit.index.hour - market_open.hour) * 60 + (forced_exit.index.minute - market_open.minute)\n",
|
||||
"\n",
|
||||
"entry_window_open[(elapsed_min_from_open >= entry_window_opens) & (elapsed_min_from_open < entry_window_closes)] = True\n",
|
||||
"forced_exit[(elapsed_min_from_open >= forced_exit_start) & (elapsed_min_from_open < forced_exit_end)] = True\n",
|
||||
"\n",
|
||||
"#entry_window_open.info()\n",
|
||||
"# forced_exit.tail(100)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"close = m1_data.close\n",
|
||||
"\n",
|
||||
"#rsi = vbt.RSI.run(close, window=14)\n",
|
||||
"\n",
|
||||
"short_entries = (short_signal & entry_window_open)\n",
|
||||
"short_exits = forced_exit\n",
|
||||
"#long_entries.info()\n",
|
||||
"#number of trues and falses in long_entries\n",
|
||||
"#short_exits.value_counts()\n",
|
||||
"short_entries.value_counts()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def plot_rsi(close, entries, exits):\n",
|
||||
" fig = vbt.make_subplots(rows=1, cols=1, shared_xaxes=True, specs=[[{\"secondary_y\": True}]], vertical_spacing=0.02, subplot_titles=(\"RSI\", \"Price\" ))\n",
|
||||
" close.vbt.plot(fig=fig, add_trace_kwargs=dict(secondary_y=True))\n",
|
||||
" #rsi.plot(fig=fig, add_trace_kwargs=dict(secondary_y=False))\n",
|
||||
" entries.vbt.signals.plot_as_entries(close, fig=fig, add_trace_kwargs=dict(secondary_y=False)) \n",
|
||||
" exits.vbt.signals.plot_as_exits(close, fig=fig, add_trace_kwargs=dict(secondary_y=False)) \n",
|
||||
" return fig\n",
|
||||
"\n",
|
||||
"plot_rsi(close, short_entries, short_exits)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vbt.phelp(vbt.Portfolio.from_signals)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sl_stop = np.arange(0.03/100, 0.2/100, 0.02/100).tolist()\n",
|
||||
"# Using the round function\n",
|
||||
"sl_stop = [round(val, 4) for val in sl_stop]\n",
|
||||
"print(sl_stop)\n",
|
||||
"sl_stop = vbt.Param(sl_stop) #np.nan mean s no stoploss\n",
|
||||
"\n",
|
||||
"pf = vbt.Portfolio.from_signals(close=close, short_entries=short_entries, short_exits=short_exits, sl_stop=0.03/100, tp_stop = 0.03/100, fees=0.0167/100, freq=\"1s\") #sl_stop=sl_stop, tp_stop = sl_stop,\n",
|
||||
"\n",
|
||||
"#pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#list of orders\n",
|
||||
"#pf.orders.records_readable\n",
|
||||
"#pf.orders.plots()\n",
|
||||
"#pf.stats()\n",
|
||||
"pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf[(0.0015,0.0013)].plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf[0.03].plot_trade_signals()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# pristup k pf jako multi index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#pf[0.03].plot()\n",
|
||||
"#pf.order_records\n",
|
||||
"pf[(0.03)].stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#zgrupovane statistiky\n",
|
||||
"stats_df = pf.stats([\n",
|
||||
" 'total_return',\n",
|
||||
" 'total_trades',\n",
|
||||
" 'win_rate',\n",
|
||||
" 'expectancy'\n",
|
||||
"], agg_func=None)\n",
|
||||
"stats_df\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"stats_df.nlargest(10, 'Total Return [%]')\n",
|
||||
"#stats_df.info()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf[(0.0011,0.0013000000000000002)].plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pandas.tseries.offsets import DateOffset\n",
|
||||
"\n",
|
||||
"temp_data = basic_data['2024-4-22']\n",
|
||||
"temp_data\n",
|
||||
"res1m = temp_data[[\"Open\", \"High\", \"Low\", \"Close\", \"Volume\"]]\n",
|
||||
"\n",
|
||||
"# Define a custom date offset that starts at 9:30 AM and spans 4 hours\n",
|
||||
"custom_offset = DateOffset(hours=4, minutes=30)\n",
|
||||
"\n",
|
||||
"# res1m = res1m.get().resample(\"4H\").agg({ \n",
|
||||
"# \"Open\": \"first\",\n",
|
||||
"# \"High\": \"max\",\n",
|
||||
"# \"Low\": \"min\",\n",
|
||||
"# \"Close\": \"last\",\n",
|
||||
"# \"Volume\": \"sum\"\n",
|
||||
"# })\n",
|
||||
"\n",
|
||||
"res4h = res1m.resample(\"1h\", resample_kwargs=dict(origin=\"start\"))\n",
|
||||
"\n",
|
||||
"res4h.data\n",
|
||||
"\n",
|
||||
"res15m = res1m.resample(\"15T\", resample_kwargs=dict(origin=\"start\"))\n",
|
||||
"\n",
|
||||
"res15m.data[\"BAC\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@vbt.njit\n",
|
||||
"def long_entry_place_func_nb(c, low, close, time_in_ns, rsi14, window_open, window_close):\n",
|
||||
" market_open_minutes = 570 # 9 hours * 60 minutes + 30 minutes\n",
|
||||
"\n",
|
||||
" for out_i in range(len(c.out)):\n",
|
||||
" i = c.from_i + out_i\n",
|
||||
"\n",
|
||||
" current_minutes = vbt.dt_nb.hour_nb(time_in_ns[i]) * 60 + vbt.dt_nb.minute_nb(time_in_ns[i])\n",
|
||||
" #print(\"current_minutes\", current_minutes)\n",
|
||||
" # Calculate elapsed minutes since market open at 9:30 AM\n",
|
||||
" elapsed_from_open = current_minutes - market_open_minutes\n",
|
||||
" elapsed_from_open = elapsed_from_open if elapsed_from_open >= 0 else 0\n",
|
||||
" #print( \"elapsed_from_open\", elapsed_from_open)\n",
|
||||
"\n",
|
||||
" #elapsed_from_open = elapsed_minutes_from_open_nb(time_in_ns) \n",
|
||||
" in_window = elapsed_from_open > window_open and elapsed_from_open < window_close\n",
|
||||
" #print(\"in_window\", in_window)\n",
|
||||
" # if in_window:\n",
|
||||
" # print(\"in window\")\n",
|
||||
"\n",
|
||||
" if in_window and rsi14[i] > 60: # and low[i, c.col] <= hit_price: # and hour == 9: # (4)!\n",
|
||||
" return out_i\n",
|
||||
" return -1\n",
|
||||
"\n",
|
||||
"@vbt.njit\n",
|
||||
"def long_exit_place_func_nb(c, high, close, time_index, tp, sl): # (5)!\n",
|
||||
" entry_i = c.from_i - c.wait\n",
|
||||
" entry_price = close[entry_i, c.col]\n",
|
||||
" hit_price = entry_price * (1 + tp)\n",
|
||||
" stop_price = entry_price * (1 - sl)\n",
|
||||
" for out_i in range(len(c.out)):\n",
|
||||
" i = c.from_i + out_i\n",
|
||||
" last_bar_of_day = vbt.dt_nb.day_changed_nb(time_index[i], time_index[i + 1])\n",
|
||||
"\n",
|
||||
" #print(next_day)\n",
|
||||
" if last_bar_of_day: #pokud je dalsi next day, tak zavirame posledni\n",
|
||||
" print(\"ted\",out_i)\n",
|
||||
" return out_i\n",
|
||||
" if close[i, c.col] >= hit_price or close[i, c.col] <= stop_price :\n",
|
||||
" return out_i\n",
|
||||
" return -1\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df = pd.DataFrame(np.random.random(size=(5, 10)), columns=list('abcdefghij'))\n",
|
||||
"\n",
|
||||
"df"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df.sum()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
782
research/strat_SUPERTREND/SUPERTREND_v1_SINGLE.ipynb
Normal file
782
research/strat_SUPERTREND/SUPERTREND_v1_SINGLE.ipynb
Normal file
@ -0,0 +1,782 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# SUPERTREND\n",
|
||||
"\n",
|
||||
"* kombinace supertrendu na vice urovnich"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from dotenv import load_dotenv\n",
|
||||
"\n",
|
||||
"#as V2realbot is client , load env variables here\n",
|
||||
"env_file = \"/Users/davidbrazda/Documents/Development/python/.env\"\n",
|
||||
"# Load the .env file\n",
|
||||
"load_dotenv(env_file)\n",
|
||||
"\n",
|
||||
"from v2realbot.utils.utils import zoneNY\n",
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"import vectorbtpro as vbt\n",
|
||||
"# from itables import init_notebook_mode, show\n",
|
||||
"import datetime\n",
|
||||
"from itertools import product\n",
|
||||
"from v2realbot.config import DATA_DIR\n",
|
||||
"from lightweight_charts import JupyterChart, chart, Panel, PlotAccessor\n",
|
||||
"from IPython.display import display\n",
|
||||
"\n",
|
||||
"# init_notebook_mode(all_interactive=True)\n",
|
||||
"\n",
|
||||
"vbt.settings.set_theme(\"dark\")\n",
|
||||
"vbt.settings['plotting']['layout']['width'] = 1280\n",
|
||||
"vbt.settings.plotting.auto_rangebreaks = True\n",
|
||||
"# Set the option to display with pagination\n",
|
||||
"pd.set_option('display.notebook_repr_html', True)\n",
|
||||
"pd.set_option('display.max_rows', 10) # Number of rows per page"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"trades_df-BAC-2024-01-01T09_30_00-2024-05-14T16_00_00-CO4B7VPWUZF-100.parquet\n",
|
||||
"trades_df-BAC-2024-01-11T09:30:00-2024-01-12T16:00:00.parquet\n",
|
||||
"trades_df-SPY-2024-01-01T09:30:00-2024-05-14T16:00:00.parquet\n",
|
||||
"trades_df-BAC-2023-01-01T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-BAC-2024-01-11T09:30:00-2024-01-12T16:00:00.parquet\n",
|
||||
"trades_df-BAC-2024-05-15T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-BAC-2024-01-01T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-SPY-2024-01-01T09:30:00-2024-05-14T16:00:00.parquet\n",
|
||||
"ohlcv_df-BAC-2024-01-01T09_30_00-2024-05-14T16_00_00-CO4B7VPWUZF-100.parquet\n",
|
||||
"ohlcv_df-BAC-2023-01-01T09_30_00-2024-05-25T16_00_00-47BCFOPUVWZ-100.parquet\n",
|
||||
"ohlcv_df-BAC-2023-01-01T09_30_00-2024-05-25T15_30_00-47BCFOPUVWZ-100.parquet\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"351"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Define the market open and close times\n",
|
||||
"market_open = datetime.time(9, 30)\n",
|
||||
"market_close = datetime.time(16, 0)\n",
|
||||
"entry_window_opens = 1\n",
|
||||
"entry_window_closes = 370\n",
|
||||
"forced_exit_start = 380\n",
|
||||
"forced_exit_end = 390\n",
|
||||
"\n",
|
||||
"#LOAD FROM PARQUET\n",
|
||||
"#list all files is dir directory with parquet extension\n",
|
||||
"dir = DATA_DIR + \"/notebooks/\"\n",
|
||||
"import os\n",
|
||||
"files = [f for f in os.listdir(dir) if f.endswith(\".parquet\")]\n",
|
||||
"print('\\n'.join(map(str, files)))\n",
|
||||
"file_name = \"ohlcv_df-BAC-2023-01-01T09_30_00-2024-05-25T15_30_00-47BCFOPUVWZ-100.parquet\"\n",
|
||||
"ohlcv_df = pd.read_parquet(dir+file_name,engine='pyarrow')\n",
|
||||
"#filter ohlcv_df to certain date range (assuming datetime index)\n",
|
||||
"#ohlcv_df = ohlcv_df.loc[\"2024-02-12 9:30\":\"2024-02-14 16:00\"]\n",
|
||||
"\n",
|
||||
"#add vwap column to ohlcv_df\n",
|
||||
"#ohlcv_df[\"hlcc4\"] = (ohlcv_df[\"close\"] + ohlcv_df[\"high\"] + ohlcv_df[\"low\"] + ohlcv_df[\"close\"]) / 4\n",
|
||||
"\n",
|
||||
"basic_data = vbt.Data.from_data(vbt.symbol_dict({\"BAC\": ohlcv_df}), tz_convert=zoneNY)\n",
|
||||
"ohlcv_df= None\n",
|
||||
"basic_data.wrapper.index.normalize().nunique()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"<class 'pandas.core.frame.DataFrame'>\n",
|
||||
"DatetimeIndex: 4549772 entries, 2023-01-03 09:30:01-05:00 to 2024-05-24 15:59:59-04:00\n",
|
||||
"Data columns (total 10 columns):\n",
|
||||
" # Column Dtype \n",
|
||||
"--- ------ ----- \n",
|
||||
" 0 open float64 \n",
|
||||
" 1 high float64 \n",
|
||||
" 2 low float64 \n",
|
||||
" 3 close float64 \n",
|
||||
" 4 volume float64 \n",
|
||||
" 5 trades float64 \n",
|
||||
" 6 updated datetime64[ns, US/Eastern]\n",
|
||||
" 7 vwap float64 \n",
|
||||
" 8 buyvolume float64 \n",
|
||||
" 9 sellvolume float64 \n",
|
||||
"dtypes: datetime64[ns, US/Eastern](1), float64(9)\n",
|
||||
"memory usage: 381.8 MB\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"basic_data.data[\"BAC\"].info()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Add resample function to custom columns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from vectorbtpro.utils.config import merge_dicts, Config, HybridConfig\n",
|
||||
"from vectorbtpro import _typing as tp\n",
|
||||
"from vectorbtpro.generic import nb as generic_nb\n",
|
||||
"\n",
|
||||
"_feature_config: tp.ClassVar[Config] = HybridConfig(\n",
|
||||
" {\n",
|
||||
" \"buyvolume\": dict(\n",
|
||||
" resample_func=lambda self, obj, resampler: obj.vbt.resample_apply(\n",
|
||||
" resampler,\n",
|
||||
" generic_nb.sum_reduce_nb,\n",
|
||||
" )\n",
|
||||
" ),\n",
|
||||
" \"sellvolume\": dict(\n",
|
||||
" resample_func=lambda self, obj, resampler: obj.vbt.resample_apply(\n",
|
||||
" resampler,\n",
|
||||
" generic_nb.sum_reduce_nb,\n",
|
||||
" )\n",
|
||||
" ),\n",
|
||||
" \"trades\": dict(\n",
|
||||
" resample_func=lambda self, obj, resampler: obj.vbt.resample_apply(\n",
|
||||
" resampler,\n",
|
||||
" generic_nb.sum_reduce_nb,\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
" }\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"basic_data._feature_config = _feature_config"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"s1data = basic_data[['open', 'high', 'low', 'close', 'volume','vwap','buyvolume','trades','sellvolume']]\n",
|
||||
"\n",
|
||||
"s5data = s1data.resample(\"5s\")\n",
|
||||
"s5data = s5data.transform(lambda df: df.between_time('09:30', '16:00').dropna())\n",
|
||||
"\n",
|
||||
"t1data = basic_data[['open', 'high', 'low', 'close', 'volume','vwap','buyvolume','trades','sellvolume']].resample(\"1T\")\n",
|
||||
"t1data = t1data.transform(lambda df: df.between_time('09:30', '16:00').dropna())\n",
|
||||
"# t1data.data[\"BAC\"].info()\n",
|
||||
"\n",
|
||||
"t30data = basic_data[['open', 'high', 'low', 'close', 'volume','vwap','buyvolume','trades','sellvolume']].resample(\"30T\")\n",
|
||||
"t30data = t30data.transform(lambda df: df.between_time('09:30', '16:00').dropna())\n",
|
||||
"# t30data.data[\"BAC\"].info()\n",
|
||||
"\n",
|
||||
"s1close = s1data.close\n",
|
||||
"t1close = t1data.close\n",
|
||||
"t30close = t30data.close\n",
|
||||
"t30volume = t30data.volume\n",
|
||||
"\n",
|
||||
"#resample on specific index \n",
|
||||
"resampler = vbt.Resampler(t30data.index, s1data.index, source_freq=\"30T\", target_freq=\"1s\")\n",
|
||||
"t30close_realigned = t30close.vbt.realign_closing(resampler)\n",
|
||||
"\n",
|
||||
"#resample 1min to s\n",
|
||||
"resampler_s = vbt.Resampler(t1data.index, s1data.index, source_freq=\"1T\", target_freq=\"1s\")\n",
|
||||
"t1close_realigned = t1close.vbt.realign_closing(resampler_s)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"datetime64[ns, US/Eastern]\n",
|
||||
"datetime64[ns, US/Eastern]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(t30data.index.dtype)\n",
|
||||
"print(s1data.index.dtype)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"<class 'pandas.core.frame.DataFrame'>\n",
|
||||
"DatetimeIndex: 4551 entries, 2023-01-03 09:30:00-05:00 to 2024-05-24 15:30:00-04:00\n",
|
||||
"Data columns (total 9 columns):\n",
|
||||
" # Column Non-Null Count Dtype \n",
|
||||
"--- ------ -------------- ----- \n",
|
||||
" 0 open 4551 non-null float64\n",
|
||||
" 1 high 4551 non-null float64\n",
|
||||
" 2 low 4551 non-null float64\n",
|
||||
" 3 close 4551 non-null float64\n",
|
||||
" 4 volume 4551 non-null float64\n",
|
||||
" 5 vwap 4551 non-null float64\n",
|
||||
" 6 buyvolume 4551 non-null float64\n",
|
||||
" 7 trades 4551 non-null float64\n",
|
||||
" 8 sellvolume 4551 non-null float64\n",
|
||||
"dtypes: float64(9)\n",
|
||||
"memory usage: 355.5 KB\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"t30data.data[\"BAC\"].info()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vbt.IF.list_indicators(\"*vwap\")\n",
|
||||
"vbt.phelp(vbt.VWAP.run)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# VWAP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\n",
|
||||
"t1vwap_h = vbt.VWAP.run(t1data.high, t1data.low, t1data.close, t1data.volume, anchor=\"H\")\n",
|
||||
"t1vwap_d = vbt.VWAP.run(t1data.high, t1data.low, t1data.close, t1data.volume, anchor=\"D\")\n",
|
||||
"t1vwap_t = vbt.VWAP.run(t1data.high, t1data.low, t1data.close, t1data.volume, anchor=\"T\")\n",
|
||||
"\n",
|
||||
"t1vwap_h_real = t1vwap_h.vwap.vbt.realign_closing(resampler_s)\n",
|
||||
"t1vwap_d_real = t1vwap_d.vwap.vbt.realign_closing(resampler_s)\n",
|
||||
"t1vwap_t_real = t1vwap_t.vwap.vbt.realign_closing(resampler_s)\n",
|
||||
"\n",
|
||||
"#t1vwap_5t.xloc[\"2024-01-3 09:30:00\":\"2024-01-03 16:00:00\"].plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#m30data.close.lw.plot()\n",
|
||||
"#quick few liner\n",
|
||||
"pane1 = Panel(\n",
|
||||
" histogram=[\n",
|
||||
" #(s1data.volume, \"volume\",None, 0.8),\n",
|
||||
" #(m30volume, \"m30volume\",None, 1)\n",
|
||||
" ], # [(series, name, \"rgba(53, 94, 59, 0.6)\", opacity)]\n",
|
||||
" right=[\n",
|
||||
" (s1data.close, \"1s close\"),\n",
|
||||
" (t1data.close, \"1min close\"),\n",
|
||||
" (t1vwap_t, \"1mvwap_t\"),\n",
|
||||
" (t1vwap_h, \"1mvwap_h\"),\n",
|
||||
" (t1vwap_d, \"1mvwap_d\"),\n",
|
||||
" (t1vwap_t_real, \"1mvwap_t_real\"),\n",
|
||||
" (t1vwap_h_real, \"1mvwap_h_real\"),\n",
|
||||
" (t1vwap_d_real, \"1mvwap_d_real\")\n",
|
||||
" # (t1close_realigned, \"1min close realigned\"),\n",
|
||||
" # (m30data.close, \"30min-close\"),\n",
|
||||
" # (m30close_realigned, \"30min close realigned\"),\n",
|
||||
" ],\n",
|
||||
")\n",
|
||||
"ch = chart([pane1], size=\"s\", xloc=slice(\"2024-05-1 09:30:00\",\"2024-05-25 16:00:00\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# SUPERTREND"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"supertrend_s1 = vbt.SUPERTREND.run(s1data.high, s1data.low, s1data.close, period=5, multiplier=3)\n",
|
||||
"direction_series_s1 = supertrend_s1.direction\n",
|
||||
"supertrend_t1 = vbt.SUPERTREND.run(t1data.high, t1data.low, t1data.close, period=14, multiplier=3)\n",
|
||||
"direction_series_t1 = supertrend_t1.direction\n",
|
||||
"supertrend_t30 = vbt.SUPERTREND.run(t30data.high, t30data.low, t30data.close, period=14, multiplier=3)\n",
|
||||
"direction_series_t30 = supertrend_t30.direction\n",
|
||||
"\n",
|
||||
"resampler_1t_sec = vbt.Resampler(direction_series_t1.index, direction_series_s1.index, source_freq=\"1T\", target_freq=\"1s\")\n",
|
||||
"resampler_30t_sec = vbt.Resampler(direction_series_t30.index, direction_series_s1.index, source_freq=\"30T\", target_freq=\"1s\")\n",
|
||||
"direction_series_t1_realigned = direction_series_t1.vbt.realign_closing(resampler_1t_sec)\n",
|
||||
"direction_series_t30_realigned = direction_series_t30.vbt.realign_closing(resampler_30t_sec)\n",
|
||||
"\n",
|
||||
"#supertrend_s1.xloc[\"2024-01-3 09:30:00\":\"2024-01-03 16:00:00\"].plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# aligned_ups= pd.Series(False, index=direction_real.index)\n",
|
||||
"# aligned_downs= pd.Series(False, index=direction_real.index)\n",
|
||||
"\n",
|
||||
"# aligned_ups = direction_real == 1 & supertrend.direction == 1\n",
|
||||
"# aligned_ups"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"s5close = s5data.data[\"BAC\"].close\n",
|
||||
"s5open = s5data.data[\"BAC\"].open\n",
|
||||
"s5high = s5data.data[\"BAC\"].high\n",
|
||||
"s5close_prev = s5close.shift(1)\n",
|
||||
"s5open_prev = s5open.shift(1)\n",
|
||||
"s5high_prev = s5high.shift(1)\n",
|
||||
"#gap nahoru od byci svicky a nevraci se zpet na jeji uroven\n",
|
||||
"entry_ups = (s5close_prev > s5open_prev) & (s5open > s5high_prev + 0.010) & (s5close > s5close_prev)\n",
|
||||
"\n",
|
||||
"entry_ups.value_counts()\n",
|
||||
"\n",
|
||||
"#entry_ups.info()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Entry window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"market_open = datetime.time(9, 30)\n",
|
||||
"market_close = datetime.time(16, 0)\n",
|
||||
"entry_window_opens = 10\n",
|
||||
"entry_window_closes = 370"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"entry_window_open= pd.Series(False, index=entry_ups.index)\n",
|
||||
"# Calculate the time difference in minutes from market open for each timestamp\n",
|
||||
"elapsed_min_from_open = (entry_ups.index.hour - market_open.hour) * 60 + (entry_ups.index.minute - market_open.minute)\n",
|
||||
"entry_window_open[(elapsed_min_from_open >= entry_window_opens) & (elapsed_min_from_open < entry_window_closes)] = True\n",
|
||||
"#entry_window_open\n",
|
||||
"\n",
|
||||
"entry_ups = entry_ups & entry_window_open\n",
|
||||
"# entry_ups\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"s5vwap_h = vbt.VWAP.run(s5data.high, s5data.low, s5data.close, s5data.volume, anchor=\"H\")\n",
|
||||
"s5vwap_d = vbt.VWAP.run(s5data.high, s5data.low, s5data.close, s5data.volume, anchor=\"D\")\n",
|
||||
"\n",
|
||||
"# s5vwap_h_real = s5vwap_h.vwap.vbt.realign_closing(resampler_s)\n",
|
||||
"# s5vwap_d_real = s5vwap_d.vwap.vbt.realign_closing(resampler_s)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pane1 = Panel(\n",
|
||||
" ohlcv=(s5data.data[\"BAC\"],), #(series, entries, exits, other_markers)\n",
|
||||
" histogram=[], # [(series, name, \"rgba(53, 94, 59, 0.6), opacity\")]\n",
|
||||
" right=[#(bbands,), #[(series, name, entries, exits, other_markers)]\n",
|
||||
" (s5data.data[\"BAC\"].close, \"close\", entry_ups),\n",
|
||||
" (s5data.data[\"BAC\"].open, \"open\"),\n",
|
||||
" (s5vwap_h, \"vwap5s_H\",),\n",
|
||||
" (s5vwap_d, \"vwap5s_D\",)\n",
|
||||
" # (t1data.data[\"BAC\"].vwap, \"vwap\"),\n",
|
||||
" # (t1data.close, \"1min close\"),\n",
|
||||
" # (supertrend_s1.trend,\"STtrend\"),\n",
|
||||
" # (supertrend_s1.long,\"STlong\"),\n",
|
||||
" # (supertrend_s1.short,\"STshort\")\n",
|
||||
" ],\n",
|
||||
" left = [\n",
|
||||
" #(direction_series_s1,\"direction_s1\"),\n",
|
||||
" # (direction_series_t1,\"direction_t1\"),\n",
|
||||
" # (direction_series_t30,\"direction_t30\")\n",
|
||||
" \n",
|
||||
" ],\n",
|
||||
" # right=[(bbands.upperband, \"upperband\",),\n",
|
||||
" # (bbands.lowerband, \"lowerband\",),\n",
|
||||
" # (bbands.middleband, \"middleband\",)\n",
|
||||
" # ], #[(series, name, entries, exits, other_markers)]\n",
|
||||
" middle1=[],\n",
|
||||
" middle2=[],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# pane2 = Panel(\n",
|
||||
"# ohlcv=(t1data.data[\"BAC\"],uptrend_m30, downtrend_m30), #(series, entries, exits, other_markers)\n",
|
||||
"# histogram=[], # [(series, name, \"rgba(53, 94, 59, 0.6), opacity\")]\n",
|
||||
"# left=[#(bbands,), #[(series, name, entries, exits, other_markers)]\n",
|
||||
"# (direction_real,\"direction30min_real\"),\n",
|
||||
"# ],\n",
|
||||
"# # left = [(supertrendm30.direction,\"STdirection30\")],\n",
|
||||
"# # # right=[(bbands.upperband, \"upperband\",),\n",
|
||||
"# # # (bbands.lowerband, \"lowerband\",),\n",
|
||||
"# # # (bbands.middleband, \"middleband\",)\n",
|
||||
"# # # ], #[(series, name, entries, exits, other_markers)]\n",
|
||||
"# middle1=[],\n",
|
||||
"# middle2=[],\n",
|
||||
"# title = \"1m\")\n",
|
||||
"\n",
|
||||
"ch = chart([pane1], sync=True, size=\"s\", xloc=slice(\"2024-02-20 09:30:00\",\"2024-02-22 16:00:00\"), precision=6)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# data = s5data.xloc[\"2024-01-03 09:30:00\":\"2024-03-10 16:00:00\"]\n",
|
||||
"# entry = entry_ups.vbt.xloc[\"2024-01-03 09:30:00\":\"2024-03-10 16:00:00\"].obj\n",
|
||||
"\n",
|
||||
"pf = vbt.Portfolio.from_signals(close=s5data, entries=entry_ups, direction=\"longonly\", sl_stop=0.05/100, tp_stop = 0.05/100, fees=0.0167/100, freq=\"5s\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.xloc[\"2024-01-26 09:30:00\":\"2024-02-28 16:00:00\"].positions.plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.xloc[\"2024-01-26 09:30:00\":\"2024-01-28 16:00:00\"].plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pd.set_option('display.max_rows', None)\n",
|
||||
"pf.stats()\n",
|
||||
"# pf.xloc[\"monday\"].stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"buyvolume = t1data.data[\"BAC\"].buyvolume\n",
|
||||
"sellvolume = t1data.data[\"BAC\"].sellvolume\n",
|
||||
"totalvolume = buyvolume + sellvolume\n",
|
||||
"\n",
|
||||
"#adjust to minimal value to avoid division by zero\n",
|
||||
"sellvolume_adjusted = sellvolume.replace(0, 1e-10)\n",
|
||||
"oibratio = buyvolume / sellvolume\n",
|
||||
"\n",
|
||||
"#cumulative order flow (net difference)\n",
|
||||
"cof = buyvolume - sellvolume\n",
|
||||
"\n",
|
||||
"# Calculate the order imbalance (net differene) normalize the order imbalance by calculating the difference between buy and sell volumes and then scaling it by the total volume.\n",
|
||||
"order_imbalance = cof / totalvolume\n",
|
||||
"order_imbalance = order_imbalance.fillna(0) #nan nahradime 0\n",
|
||||
"\n",
|
||||
"order_imbalance_allvolume = cof / t1data.data[\"BAC\"].volume\n",
|
||||
"\n",
|
||||
"order_imbalance_sma = vbt.indicator(\"talib:EMA\").run(order_imbalance, timeperiod=5)\n",
|
||||
"short_signals = order_imbalance.vbt < -0.5\n",
|
||||
"#short_entries = oibratio.vbt < 0.01\n",
|
||||
"short_signals.value_counts()\n",
|
||||
"short_signals.name = \"short_entries\"\n",
|
||||
"#.fillna(False)\n",
|
||||
"short_exits = short_signals.shift(-2).fillna(False).astype(bool)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pane1 = Panel(\n",
|
||||
" ohlcv=(t1data.data[\"BAC\"],), #(series, entries, exits, other_markers)\n",
|
||||
" histogram=[(order_imbalance_allvolume, \"oib_allvolume\", \"rgba(53, 94, 59, 0.6)\",0.5),\n",
|
||||
" (t1data.data[\"BAC\"].trades, \"trades\",None,0.4),\n",
|
||||
" ], # [(series, name, \"rgba(53, 94, 59, 0.6)\", opacity)]\n",
|
||||
" # right=[\n",
|
||||
" # (supertrend.trend,\"STtrend\"),\n",
|
||||
" # (supertrend.long,\"STlong\"),\n",
|
||||
" # (supertrend.short,\"STshort\")\n",
|
||||
" # ],\n",
|
||||
" # left = [(supertrend.direction,\"STdirection\")],\n",
|
||||
" # right=[(bbands.upperband, \"upperband\",),\n",
|
||||
" # (bbands.lowerband, \"lowerband\",),\n",
|
||||
" # (bbands.middleband, \"middleband\",)\n",
|
||||
" # ], #[(series, name, entries, exits, other_markers)]\n",
|
||||
" middle1=[],\n",
|
||||
" middle2=[],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"pane2 = Panel(\n",
|
||||
" ohlcv=(basic_data.data[\"BAC\"],), #(series, entries, exits, other_markers)\n",
|
||||
" left=[(basic_data.data[\"BAC\"].trades, \"trades\")],\n",
|
||||
" histogram=[(basic_data.data[\"BAC\"].trades, \"trades_hist\", \"white\", 0.5)], #\"rgba(53, 94, 59, 0.6)\"\n",
|
||||
" # ], # [(series, name, \"rgba(53, 94, 59, 0.6)\")]\n",
|
||||
" # right=[\n",
|
||||
" # (supertrend.trend,\"STtrend\"),\n",
|
||||
" # (supertrend.long,\"STlong\"),\n",
|
||||
" # (supertrend.short,\"STshort\")\n",
|
||||
" # ],\n",
|
||||
" # left = [(supertrend.direction,\"STdirection\")],\n",
|
||||
" # right=[(bbands.upperband, \"upperband\",),\n",
|
||||
" # (bbands.lowerband, \"lowerband\",),\n",
|
||||
" # (bbands.middleband, \"middleband\",)\n",
|
||||
" # ], #[(series, name, entries, exits, other_markers)]\n",
|
||||
" middle1=[],\n",
|
||||
" middle2=[],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"ch = chart([pane1, pane2], size=\"m\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#short_signal = t1slope.real_below(t1_th) & t2slope.real_below(t2_th) & t3slope.real_below(t3_th) & t4slope.real_below(t4_th)\n",
|
||||
"#long_signal = t1slope.real_above(t1_th) & t2slope.real_above(t2_th) & t3slope.real_above(t3_th) & t4slope.real_above(t4_th)\n",
|
||||
"\n",
|
||||
"#test na daily s reversem crossed 0\n",
|
||||
"short_signal = t2slope.vbt < -0.01 & t3slope.vbt < -0.01 #min value of threshold\n",
|
||||
"long_signal = t2slope.vbt > 0.01 & t3slope.vbt > 0.01 #min\n",
|
||||
"\n",
|
||||
"# thirty_up_signal = t3slope.vbt.crossed_above(0.01)\n",
|
||||
"# thirty_down_signal = t3slope.vbt.crossed_below(-0.01)\n",
|
||||
"\n",
|
||||
"fig = plot_2y_close(priminds=[], secinds=[t3slope], close=t1data.close)\n",
|
||||
"#short_signal.vbt.signals.plot_as_entries(basic_data.close, fig=fig)\n",
|
||||
"\n",
|
||||
"short_signal.vbt.signals.plot_as_entries(t1data.close, fig=fig, trace_kwargs=dict(name=\"SHORTS\",\n",
|
||||
" line=dict(color=\"#ffe476\"),\n",
|
||||
" marker=dict(color=\"red\", symbol=\"triangle-down\"),\n",
|
||||
" fill=None,\n",
|
||||
" connectgaps=True,\n",
|
||||
" ))\n",
|
||||
"long_signal.vbt.signals.plot_as_entries(t1data.close, fig=fig, trace_kwargs=dict(name=\"LONGS\",\n",
|
||||
" line=dict(color=\"#ffe476\"),\n",
|
||||
" marker=dict(color=\"limegreen\"),\n",
|
||||
" fill=None,\n",
|
||||
" connectgaps=True,\n",
|
||||
" ))\n",
|
||||
"\n",
|
||||
"# thirty_down_signal.vbt.signals.plot_as_entries(t1data.close, fig=fig, trace_kwargs=dict(name=\"DOWN30\",\n",
|
||||
"# line=dict(color=\"#ffe476\"),\n",
|
||||
"# marker=dict(color=\"yellow\", symbol=\"triangle-down\"),\n",
|
||||
"# fill=None,\n",
|
||||
"# connectgaps=True,\n",
|
||||
"# ))\n",
|
||||
"# thirty_up_signal.vbt.signals.plot_as_entries(t1data.close, fig=fig, trace_kwargs=dict(name=\"UP30\",\n",
|
||||
"# line=dict(color=\"#ffe476\"),\n",
|
||||
"# marker=dict(color=\"grey\"),\n",
|
||||
"# fill=None,\n",
|
||||
"# connectgaps=True,\n",
|
||||
"# ))\n",
|
||||
"\n",
|
||||
"# thirtymin_slope_to_compare.vbt.plot(fig=fig, add_trace_kwargs=dict(secondary_y=True), trace_kwargs=dict(name=\"30min slope\",\n",
|
||||
"# line=dict(color=\"yellow\"), \n",
|
||||
"# fill=None,\n",
|
||||
"# connectgaps=True,\n",
|
||||
"# ))\n",
|
||||
"\n",
|
||||
"fig.show()\n",
|
||||
"# print(\"short signal\")\n",
|
||||
"# print(short_signal.value_counts())\n",
|
||||
"\n",
|
||||
"#forced_exit = pd.Series(False, index=close.index)\n",
|
||||
"forced_exit = basic_data.symbol_wrapper.fill(False)\n",
|
||||
"#entry_window_open = pd.Series(False, index=close.index)\n",
|
||||
"entry_window_open= basic_data.symbol_wrapper.fill(False)\n",
|
||||
"\n",
|
||||
"# Calculate the time difference in minutes from market open for each timestamp\n",
|
||||
"elapsed_min_from_open = (forced_exit.index.hour - market_open.hour) * 60 + (forced_exit.index.minute - market_open.minute)\n",
|
||||
"\n",
|
||||
"entry_window_open[(elapsed_min_from_open >= entry_window_opens) & (elapsed_min_from_open < entry_window_closes)] = True\n",
|
||||
"\n",
|
||||
"#print(entry_window_open.value_counts())\n",
|
||||
"\n",
|
||||
"forced_exit[(elapsed_min_from_open >= forced_exit_start) & (elapsed_min_from_open < forced_exit_end)] = True\n",
|
||||
"short_entries = (short_signal & entry_window_open)\n",
|
||||
"short_exits = forced_exit\n",
|
||||
"\n",
|
||||
"entries = (long_signal & entry_window_open)\n",
|
||||
"exits = forced_exit\n",
|
||||
"#long_entries.info()\n",
|
||||
"#number of trues and falses in long_entries\n",
|
||||
"# print(short_exits.value_counts())\n",
|
||||
"# print(short_entries.value_counts())\n",
|
||||
"\n",
|
||||
"#fig = plot_2y_close([],[momshort, rocp], close)\n",
|
||||
"#short_signal.vbt.signals.plot_as_entries(close, fig=fig, add_trace_kwargs=dict(secondary_y=False))\n",
|
||||
"#print(sl_stop)\n",
|
||||
"#short_entries=short_entries, short_exits=short_exits,\n",
|
||||
"# pf = vbt.Portfolio.from_signals(close=basic_data, entries=short_entries, exits=exits, tsl_stop=0.005, tp_stop = 0.05, fees=0.0167/100, freq=\"1s\") #sl_stop=sl_stop, tp_stop = sl_stop,\n",
|
||||
"\n",
|
||||
"# pf.stats()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"forced_exit = t1data.symbol_wrapper.fill(False)\n",
|
||||
"#entry_window_open = pd.Series(False, index=close.index)\n",
|
||||
"entry_window_open= t1data.symbol_wrapper.fill(False)\n",
|
||||
"\n",
|
||||
"# Calculate the time difference in minutes from market open for each timestamp\n",
|
||||
"elapsed_min_from_open = (forced_exit.index.hour - market_open.hour) * 60 + (forced_exit.index.minute - market_open.minute)\n",
|
||||
"\n",
|
||||
"entry_window_open[(elapsed_min_from_open >= entry_window_opens) & (elapsed_min_from_open < entry_window_closes)] = True\n",
|
||||
"\n",
|
||||
"#print(entry_window_open.value_counts())\n",
|
||||
"\n",
|
||||
"forced_exit[(elapsed_min_from_open >= forced_exit_start) & (elapsed_min_from_open < forced_exit_end)] = True\n",
|
||||
"short_entries = (short_signals & entry_window_open)\n",
|
||||
"short_exits = forced_exit\n",
|
||||
"\n",
|
||||
"entries = (long_signals & entry_window_open)\n",
|
||||
"exits = forced_exit\n",
|
||||
"\n",
|
||||
"pf = vbt.Portfolio.from_signals(close=t1data, entries=entries, exits=exits, short_entries=short_entries, short_exits=exits,\n",
|
||||
"td_stop=2, time_delta_format=\"rows\",\n",
|
||||
"tsl_stop=0.005, tp_stop = 0.005, fees=0.0167/100)#, freq=\"1s\") #sl_stop=sl_stop, tp_stop = sl_stop,\n",
|
||||
"\n",
|
||||
"pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.get_drawdowns().records_readable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.orders.records_readable"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import numpy as np
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model import Trade, TradeDirection, TradeStatus
|
||||
from typing import Tuple
|
||||
from copy import deepcopy
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_override_for_active_trade, keyword_conditions_met
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_signal_section_directive, keyword_conditions_met
|
||||
from v2realbot.utils.utils import safe_get
|
||||
# FIBONACCI PRO PROFIT A SL
|
||||
|
||||
@ -63,10 +63,10 @@ class SLOptimizer:
|
||||
|
||||
def initialize_levels(self, state):
|
||||
directive_name = 'SL_opt_exit_levels_'+str(self.direction)
|
||||
SL_opt_exit_levels = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
SL_opt_exit_levels = get_signal_section_directive(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
|
||||
directive_name = 'SL_opt_exit_sizes_'+str(self.direction)
|
||||
SL_opt_exit_sizes = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
SL_opt_exit_sizes = get_signal_section_directive(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
|
||||
if SL_opt_exit_levels is None or SL_opt_exit_sizes is not None:
|
||||
print("no directives found: SL_opt_exit_levels/SL_opt_exit_sizes")
|
||||
|
||||
2
testy/testtt.py
Normal file
2
testy/testtt.py
Normal file
@ -0,0 +1,2 @@
|
||||
import locale
|
||||
print(locale.getdefaultlocale())
|
||||
@ -59,24 +59,11 @@ Hlavní loop:
|
||||
|
||||
"""
|
||||
def next(data, state: StrategyState):
|
||||
##print(10*"*","NEXT START",10*"*")
|
||||
# important vars state.avgp, state.positions, state.vars, data
|
||||
print(10*"*", state.account_variables)
|
||||
|
||||
#indicators moved to call_next in upper class
|
||||
|
||||
#pokud mame prazdne pozice a neceka se na nic
|
||||
if state.positions == 0 and state.vars.pending is None:
|
||||
#vykoname trady ve fronte
|
||||
execute_prescribed_trades(state, data)
|
||||
#pokud se neaktivoval nejaky trade, poustime signal search - ale jen jednou za bar?
|
||||
#if conf_bar == 1:
|
||||
if state.vars.pending is None:
|
||||
signal_search(state, data)
|
||||
#pro jistotu ihned zpracujeme
|
||||
execute_prescribed_trades(state, data)
|
||||
|
||||
#mame aktivni trade a neceka se n anic
|
||||
elif state.vars.activeTrade and state.vars.pending is None:
|
||||
execute_prescribed_trades(state, data) #pro jistotu ihned zpracujeme
|
||||
manage_active_trade(state, data)
|
||||
|
||||
def init(state: StrategyState):
|
||||
@ -88,13 +75,13 @@ def init(state: StrategyState):
|
||||
|
||||
#nove atributy na rizeni tradu
|
||||
#identifikuje provedenou změnu na Tradu (neděláme změny dokud nepřijde potvrzeni z notifikace)
|
||||
state.vars.pending = None
|
||||
#state.vars.pending = None #nahrazeno pebnding pod accountem state.account_variables[account.name].pending
|
||||
#obsahuje aktivni Trade a jeho nastaveni
|
||||
state.vars.activeTrade = None #pending/Trade
|
||||
#state.vars.activeTrade = None #pending/Trade moved to account_variables
|
||||
#obsahuje pripravene Trady ve frontě
|
||||
state.vars.prescribedTrades = []
|
||||
#flag pro reversal
|
||||
state.vars.requested_followup = None
|
||||
#state.vars.requested_followup = None #nahrazeno pod accountem
|
||||
|
||||
#TODO presunout inicializaci work_dict u podminek - sice hodnoty nepujdou zmenit, ale zlepsi se performance
|
||||
#pripadne udelat refresh kazdych x-iterací
|
||||
@ -102,9 +89,8 @@ def init(state: StrategyState):
|
||||
state.vars.mode = None
|
||||
state.vars.last_50_deltas = []
|
||||
state.vars.next_new = 0
|
||||
state.vars.last_buy_index = None
|
||||
state.vars.last_exit_index = None
|
||||
state.vars.last_in_index = None
|
||||
state.vars.last_entry_index = None #mponechano obecne pro vsechny accounty
|
||||
state.vars.last_exit_index = None #obecna varianta ponechana
|
||||
state.vars.last_update_time = 0
|
||||
state.vars.reverse_position_waiting_amount = 0
|
||||
#INIT promenne, ktere byly zbytecne ve stratvars
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
"""
|
||||
from uuid import UUID, uuid4
|
||||
from alpaca.trading.enums import OrderSide, OrderStatus, TradeEvent, OrderType
|
||||
from v2realbot.common.model import TradeUpdate, Order
|
||||
from v2realbot.common.model import TradeUpdate, Order, Account
|
||||
from rich import print as printanyway
|
||||
import threading
|
||||
import asyncio
|
||||
@ -61,6 +61,7 @@ import dash_bootstrap_components as dbc
|
||||
from dash.dependencies import Input, Output
|
||||
from dash import dcc, html, dash_table, Dash
|
||||
import v2realbot.utils.config_handler as cfh
|
||||
from typing import Set
|
||||
""""
|
||||
LATENCY DELAYS
|
||||
.000 trigger - last_trade_time (.4246266)
|
||||
@ -74,7 +75,20 @@ lock = threading.Lock
|
||||
#todo nejspis dat do classes, aby se mohlo backtestovat paralelne
|
||||
#ted je globalni promena last_time_now a self.account a cash
|
||||
class Backtester:
|
||||
def __init__(self, symbol: str, order_fill_callback: callable, btdata: list, bp_from: datetime, bp_to: datetime, cash: float = 100000):
|
||||
"""
|
||||
Initializes a new instance of the Backtester class.
|
||||
Args:
|
||||
symbol (str): The symbol of the security being backtested.
|
||||
accounts (set): A set of accounts to use for backtesting.
|
||||
order_fill_callback (callable): A callback function to handle order fills.
|
||||
btdata (list): A list of backtesting data.
|
||||
bp_from (datetime): The start date of the backtesting period.
|
||||
bp_to (datetime): The end date of the backtesting period.
|
||||
cash (float, optional): The initial cash balance. Defaults to 100000.
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
def __init__(self, symbol: str, accounts: Set, order_fill_callback: callable, btdata: list, bp_from: datetime, bp_to: datetime, cash: float = 100000):
|
||||
#this TIME value determines true time for submit, replace, cancel order should happen (allowing past)
|
||||
#it is set by every iteration of BT or before fill callback - allowing past events to happen
|
||||
self.time = None
|
||||
@ -83,6 +97,7 @@ class Backtester:
|
||||
self.btdata = btdata
|
||||
self.backtest_start = None
|
||||
self.backtest_end = None
|
||||
self.accounts = accounts
|
||||
self.cash_init = cash
|
||||
#backtesting period
|
||||
self.bp_from = bp_from
|
||||
@ -90,9 +105,10 @@ class Backtester:
|
||||
self.cash = cash
|
||||
self.cash_reserved_for_shorting = 0
|
||||
self.trades = []
|
||||
self.account = { "BAC": [0, 0] }
|
||||
# { "BAC": [avgp, size] }
|
||||
self.open_orders =[]
|
||||
self.internal_account = { account.name:{self.symbol: [0, 0]} for account in accounts }
|
||||
# { "ACCOUNT1": {}"BAC": [avgp, size]}, .... }
|
||||
self.open_orders =[] #open orders shared for all accounts, account being an attribute
|
||||
|
||||
# self.open_orders = [Order(id=uuid4(),
|
||||
# submitted_at = datetime(2023, 3, 17, 9, 30, 0, 0, tzinfo=zoneNY),
|
||||
# symbol = "BAC",
|
||||
@ -110,6 +126,8 @@ class Backtester:
|
||||
# side = OrderSide.BUY)]
|
||||
|
||||
#
|
||||
|
||||
|
||||
def execute_orders_and_callbacks(self, intime: float):
|
||||
"""""
|
||||
Voláno ze strategie před každou iterací s časem T.
|
||||
@ -166,7 +184,7 @@ class Backtester:
|
||||
|
||||
for order in self.open_orders:
|
||||
#pokud je vyplneny symbol, tak jedeme jen tyto, jinak vsechny
|
||||
print(order.id, datetime.timestamp(order.submitted_at), order.symbol, order.side, order.order_type, order.qty, order.limit_price, order.submitted_at)
|
||||
print(order.account.name, order.id, datetime.timestamp(order.submitted_at), order.symbol, order.side, order.order_type, order.qty, order.limit_price, order.submitted_at)
|
||||
if order.canceled_at:
|
||||
#ic("deleting canceled order",order.id)
|
||||
todel.append(order)
|
||||
@ -348,21 +366,22 @@ class Backtester:
|
||||
|
||||
#ic(o.filled_at, o.filled_avg_price)
|
||||
|
||||
a = self.update_account(o = o)
|
||||
a = self.update_internal_account(o = o)
|
||||
if a < 0:
|
||||
tlog("update_account ERROR")
|
||||
return -1
|
||||
|
||||
trade = TradeUpdate(order = o,
|
||||
trade = TradeUpdate(account=o.account,
|
||||
order = o,
|
||||
event = TradeEvent.FILL,
|
||||
execution_id = str(uuid4()),
|
||||
timestamp = datetime.fromtimestamp(fill_time),
|
||||
position_qty= self.account[o.symbol][0],
|
||||
position_qty= self.internal_account[o.account.name][o.symbol][0],
|
||||
price=float(fill_price),
|
||||
qty = o.qty,
|
||||
value = float(o.qty*fill_price),
|
||||
cash = self.cash,
|
||||
pos_avg_price = self.account[o.symbol][1])
|
||||
pos_avg_price = self.internal_account[o.account.name][o.symbol][1])
|
||||
|
||||
self.trades.append(trade)
|
||||
|
||||
@ -379,49 +398,49 @@ class Backtester:
|
||||
self.time = time + float(cfh.config_handler.get_val('BT_DELAYS','fill_to_not'))
|
||||
print("current bt.time",self.time)
|
||||
#print("FILL NOTIFICATION: ", tradeupdate)
|
||||
res = asyncio.run(self.order_fill_callback(tradeupdate))
|
||||
res = asyncio.run(self.order_fill_callback(tradeupdate, tradeupdate.account))
|
||||
return 0
|
||||
|
||||
def update_account(self, o: Order):
|
||||
def update_internal_account(self, o: Order):
|
||||
#updatujeme self.account
|
||||
#pokud neexistuje klic v accountu vytvorime si ho
|
||||
if o.symbol not in self.account:
|
||||
if o.symbol not in self.internal_account[o.account.name]:
|
||||
# { "BAC": [size, avgp] }
|
||||
self.account[o.symbol] = [0,0]
|
||||
self.internal_account[o.account.name][o.symbol] = [0,0]
|
||||
|
||||
if o.side == OrderSide.BUY:
|
||||
#[size, avgp]
|
||||
newsize = (self.account[o.symbol][0] + o.qty)
|
||||
newsize = (self.internal_account[o.account.name][o.symbol][0] + o.qty)
|
||||
#JPLNE UZAVRENI SHORT (avgp 0)
|
||||
if newsize == 0: newavgp = 0
|
||||
#CASTECNE UZAVRENI SHORT (avgp puvodni)
|
||||
elif newsize < 0: newavgp = self.account[o.symbol][1]
|
||||
elif newsize < 0: newavgp = self.internal_account[o.account.name][o.symbol][1]
|
||||
#JDE O LONG (avgp nove)
|
||||
else:
|
||||
newavgp = ((self.account[o.symbol][0] * self.account[o.symbol][1]) + (o.qty * o.filled_avg_price)) / (self.account[o.symbol][0] + o.qty)
|
||||
newavgp = ((self.internal_account[o.account.name][o.symbol][0] * self.internal_account[o.account.name][o.symbol][1]) + (o.qty * o.filled_avg_price)) / (self.internal_account[o.account.name][o.symbol][0] + o.qty)
|
||||
|
||||
self.account[o.symbol] = [newsize, newavgp]
|
||||
self.internal_account[o.account.name][o.symbol] = [newsize, newavgp]
|
||||
self.cash = self.cash - (o.qty * o.filled_avg_price)
|
||||
return 1
|
||||
#sell
|
||||
elif o.side == OrderSide.SELL:
|
||||
newsize = self.account[o.symbol][0]-o.qty
|
||||
newsize = self.internal_account[o.account.name][o.symbol][0]-o.qty
|
||||
#UPLNE UZAVRENI LONGU (avgp 0)
|
||||
if newsize == 0: newavgp = 0
|
||||
#CASTECNE UZAVRENI LONGU (avgp puvodni)
|
||||
elif newsize > 0: newavgp = self.account[o.symbol][1]
|
||||
elif newsize > 0: newavgp = self.internal_account[o.account.name][o.symbol][1]
|
||||
#jde o SHORT (avgp nove)
|
||||
else:
|
||||
#pokud je predchozi 0 - tzn. jde o prvni short
|
||||
if self.account[o.symbol][1] == 0:
|
||||
if self.internal_account[o.account.name][o.symbol][1] == 0:
|
||||
newavgp = o.filled_avg_price
|
||||
else:
|
||||
newavgp = ((abs(self.account[o.symbol][0]) * self.account[o.symbol][1]) + (o.qty * o.filled_avg_price)) / (abs(self.account[o.symbol][0]) + o.qty)
|
||||
newavgp = ((abs(self.internal_account[o.account.name][o.symbol][0]) * self.internal_account[o.account.name][o.symbol][1]) + (o.qty * o.filled_avg_price)) / (abs(self.internal_account[o.account.name][o.symbol][0]) + o.qty)
|
||||
|
||||
self.account[o.symbol] = [newsize, newavgp]
|
||||
self.internal_account[o.account.name][o.symbol] = [newsize, newavgp]
|
||||
|
||||
#pokud jde o prodej longu(nova pozice je>=0) upravujeme cash
|
||||
if self.account[o.symbol][0] >= 0:
|
||||
if self.internal_account[o.account.name][o.symbol][0] >= 0:
|
||||
self.cash = float(self.cash + (o.qty * o.filled_avg_price))
|
||||
print("uprava cashe, jde o prodej longu")
|
||||
else:
|
||||
@ -466,7 +485,7 @@ class Backtester:
|
||||
# #ic("get last price")
|
||||
# return self.btdata[i-1][1]
|
||||
|
||||
def submit_order(self, time: float, symbol: str, side: OrderSide, size: int, order_type: OrderType, price: float = None):
|
||||
def submit_order(self, time: float, symbol: str, side: OrderSide, size: int, order_type: OrderType, account: Account, price: float = None):
|
||||
"""submit order
|
||||
- zakladni validace
|
||||
- vloží do self.open_orders s daným časem
|
||||
@ -499,9 +518,9 @@ class Backtester:
|
||||
return -1
|
||||
|
||||
#pokud neexistuje klic v accountu vytvorime si ho
|
||||
if symbol not in self.account:
|
||||
if symbol not in self.internal_account[account.name]:
|
||||
# { "BAC": [size, avgp] }
|
||||
self.account[symbol] = [0,0]
|
||||
self.internal_account[account.name][symbol] = [0,0]
|
||||
|
||||
#check for available quantity
|
||||
if side == OrderSide.SELL:
|
||||
@ -509,15 +528,15 @@ class Backtester:
|
||||
reserved_price = 0
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
if o.side == OrderSide.SELL and o.symbol == symbol and o.canceled_at is None:
|
||||
if o.side == OrderSide.SELL and o.symbol == symbol and o.canceled_at is None and o.account==account:
|
||||
reserved += o.qty
|
||||
cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol)
|
||||
reserved_price += o.qty * cena
|
||||
print("blokovano v open orders pro sell qty: ", reserved, "celkem:", reserved_price)
|
||||
|
||||
actual_minus_reserved = int(self.account[symbol][0]) - reserved
|
||||
actual_minus_reserved = int(self.internal_account[account.name][symbol][0]) - reserved
|
||||
if actual_minus_reserved > 0 and actual_minus_reserved - int(size) < 0:
|
||||
printanyway("not enough shares available to sell or shorting while long position",self.account[symbol][0],"reserved",reserved,"available",int(self.account[symbol][0]) - reserved,"selling",size)
|
||||
printanyway("not enough shares available to sell or shorting while long position",self.internal_account[account.name][symbol][0],"reserved",reserved,"available",int(self.internal_account[account.name][symbol][0]) - reserved,"selling",size)
|
||||
return -1
|
||||
|
||||
#if is shorting - check available cash to short
|
||||
@ -533,13 +552,13 @@ class Backtester:
|
||||
reserved_price = 0
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
if o.side == OrderSide.BUY and o.canceled_at is None:
|
||||
if o.side == OrderSide.BUY and o.canceled_at is None and o.account==account:
|
||||
cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol)
|
||||
reserved_price += o.qty * cena
|
||||
reserved_qty += o.qty
|
||||
print("blokovano v open orders for buy: qty, price", reserved_qty, reserved_price)
|
||||
|
||||
actual_plus_reserved_qty = int(self.account[symbol][0]) + reserved_qty
|
||||
actual_plus_reserved_qty = int(self.internal_account[account.name][symbol][0]) + reserved_qty
|
||||
|
||||
#jde o uzavreni shortu
|
||||
if actual_plus_reserved_qty < 0 and (actual_plus_reserved_qty + int(size)) > 0:
|
||||
@ -555,6 +574,7 @@ class Backtester:
|
||||
|
||||
id = str(uuid4())
|
||||
order = Order(id=id,
|
||||
account=account,
|
||||
submitted_at = datetime.fromtimestamp(float(time)),
|
||||
symbol=symbol,
|
||||
order_type = order_type,
|
||||
@ -569,7 +589,7 @@ class Backtester:
|
||||
return id
|
||||
|
||||
|
||||
def replace_order(self, id: str, time: float, size: int = None, price: float = None):
|
||||
def replace_order(self, id: str, time: float, account: Account, size: int = None, price: float = None):
|
||||
"""replace order
|
||||
- zakladni validace vrací synchronně
|
||||
- vrací číslo nové objednávky
|
||||
@ -586,7 +606,7 @@ class Backtester:
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
print(o.id)
|
||||
if str(o.id) == str(id):
|
||||
if str(o.id) == str(id) and o.account == account:
|
||||
newid = str(uuid4())
|
||||
o.id = newid
|
||||
o.submitted_at = datetime.fromtimestamp(time)
|
||||
@ -597,7 +617,7 @@ class Backtester:
|
||||
print("BT: replacement order doesnt exist")
|
||||
return 0
|
||||
|
||||
def cancel_order(self, time: float, id: str):
|
||||
def cancel_order(self, time: float, id: str, account: Account):
|
||||
"""cancel order
|
||||
- základní validace vrací synchronně
|
||||
- vymaže objednávku z open orders
|
||||
@ -613,22 +633,22 @@ class Backtester:
|
||||
return 0
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
if str(o.id) == id:
|
||||
if str(o.id) == id and o.account == account:
|
||||
o.canceled_at = time
|
||||
print("set as canceled in self.open_orders")
|
||||
return 1
|
||||
print("BTC: cantchange. open order doesnt exist")
|
||||
return 0
|
||||
|
||||
def get_open_position(self, symbol: str):
|
||||
def get_open_position(self, symbol: str, account: Account):
|
||||
"""get positions ->(avg,size)"""
|
||||
#print("BT:get open positions entry")
|
||||
try:
|
||||
return self.account[symbol][1], self.account[symbol][0]
|
||||
return self.internal_account[account.name][symbol][1], self.internal_account[account.name][symbol][0]
|
||||
except:
|
||||
return (0,0)
|
||||
|
||||
def get_open_orders(self, side: OrderSide, symbol: str):
|
||||
def get_open_orders(self, side: OrderSide, symbol: str, account: Account):
|
||||
"""get open orders ->list(OrderNotification)"""
|
||||
print("BT:get open orders entry")
|
||||
if len(self.open_orders) == 0:
|
||||
@ -638,7 +658,7 @@ class Backtester:
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
#print(o)
|
||||
if o.symbol == symbol and o.canceled_at is None:
|
||||
if o.symbol == symbol and o.canceled_at is None and o.account == account:
|
||||
if side is None or o.side == side:
|
||||
res.append(o)
|
||||
return res
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
from typing import Any, Optional, List, Union
|
||||
from uuid import UUID
|
||||
class TradeStatus(str, Enum):
|
||||
READY = "ready"
|
||||
ACTIVATED = "activated"
|
||||
CLOSED = "closed"
|
||||
#FINISHED = "finished"
|
||||
|
||||
class TradeDirection(str, Enum):
|
||||
LONG = "long"
|
||||
SHORT = "short"
|
||||
|
||||
class TradeStoplossType(str, Enum):
|
||||
FIXED = "fixed"
|
||||
TRAILING = "trailing"
|
||||
|
||||
#Predpis obchodu vygenerovany signalem, je to zastresujici jednotka
|
||||
#ke kteremu jsou pak navazany jednotlivy FILLy (reprezentovany model.TradeUpdate) - napr. castecne exity atp.
|
||||
class Trade(BaseModel):
|
||||
id: UUID
|
||||
last_update: datetime
|
||||
entry_time: Optional[datetime] = None
|
||||
exit_time: Optional[datetime] = None
|
||||
status: TradeStatus
|
||||
generated_by: Optional[str] = None
|
||||
direction: TradeDirection
|
||||
entry_price: Optional[float] = None
|
||||
goal_price: Optional[float] = None
|
||||
size: Optional[int] = None
|
||||
# size_multiplier je pomocna promenna pro pocitani relativniho denniho profit
|
||||
size_multiplier: Optional[float] = None
|
||||
# stoploss_type: TradeStoplossType
|
||||
stoploss_value: Optional[float] = None
|
||||
profit: Optional[float] = 0
|
||||
profit_sum: Optional[float] = 0
|
||||
rel_profit: Optional[float] = 0
|
||||
rel_profit_cum: Optional[float] = 0
|
||||
|
||||
@ -5,10 +5,75 @@ from rich import print
|
||||
from typing import Any, Optional, List, Union
|
||||
from datetime import datetime, date
|
||||
from pydantic import BaseModel, Field
|
||||
from v2realbot.enums.enums import Mode, Account, SchedulerStatus, Moddus, Market
|
||||
from v2realbot.enums.enums import Mode, Account, SchedulerStatus, Moddus, Market, Followup
|
||||
from alpaca.data.enums import Exchange
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
from typing import Any, Optional, List, Union
|
||||
from uuid import UUID
|
||||
|
||||
|
||||
#prescribed model
|
||||
#from prescribed model
|
||||
class InstantIndicator(BaseModel):
|
||||
name: str
|
||||
toml: str
|
||||
|
||||
|
||||
class TradeStatus(str, Enum):
|
||||
READY = "ready"
|
||||
ACTIVATED = "activated"
|
||||
CLOSED = "closed"
|
||||
#FINISHED = "finished"
|
||||
|
||||
class TradeDirection(str, Enum):
|
||||
LONG = "long"
|
||||
SHORT = "short"
|
||||
|
||||
class TradeStoplossType(str, Enum):
|
||||
FIXED = "fixed"
|
||||
TRAILING = "trailing"
|
||||
|
||||
#Predpis obchodu vygenerovany signalem, je to zastresujici jednotka
|
||||
#ke kteremu jsou pak navazany jednotlivy FILLy (reprezentovany model.TradeUpdate) - napr. castecne exity atp.
|
||||
class Trade(BaseModel):
|
||||
account: Account
|
||||
id: UUID
|
||||
last_update: datetime
|
||||
entry_time: Optional[datetime] = None
|
||||
exit_time: Optional[datetime] = None
|
||||
status: TradeStatus
|
||||
generated_by: Optional[str] = None
|
||||
direction: TradeDirection
|
||||
entry_price: Optional[float] = None
|
||||
goal_price: Optional[float] = None
|
||||
size: Optional[int] = None
|
||||
# size_multiplier je pomocna promenna pro pocitani relativniho denniho profit
|
||||
size_multiplier: Optional[float] = None
|
||||
# stoploss_type: TradeStoplossType
|
||||
stoploss_value: Optional[float] = None
|
||||
profit: Optional[float] = 0
|
||||
profit_sum: Optional[float] = 0
|
||||
rel_profit: Optional[float] = 0
|
||||
rel_profit_cum: Optional[float] = 0
|
||||
|
||||
#account variables that can be accessed by ACCOUNT key dictionary
|
||||
class AccountVariables(BaseModel):
|
||||
positions: float = 0
|
||||
avgp: float = 0
|
||||
pending: str = None
|
||||
blockbuy: int = 0
|
||||
wait_for_fill: float = None
|
||||
profit: float = 0
|
||||
docasny_rel_profit: list = []
|
||||
rel_profit_cum: list = []
|
||||
last_entry_index: int = None #acc varianta, mame taky obnecnou state.vars.last_entry_index
|
||||
requested_followup: Followup = None
|
||||
activeTrade: Trade = None
|
||||
dont_exit_already_activated: bool = False
|
||||
#activeTrade, prescribedTrades
|
||||
#tbd transferables?
|
||||
|
||||
|
||||
#models for server side datatables
|
||||
@ -91,7 +156,7 @@ class TestList(BaseModel):
|
||||
dates: List[Intervals]
|
||||
|
||||
#for GUI to fetch historical trades on given symbol
|
||||
class Trade(BaseModel):
|
||||
class TradeView(BaseModel):
|
||||
symbol: str
|
||||
timestamp: datetime
|
||||
exchange: Optional[Union[Exchange, str]] = None
|
||||
@ -189,8 +254,8 @@ class RunnerView(BaseModel):
|
||||
run_symbol: Optional[str] = None
|
||||
run_trade_count: Optional[int] = 0
|
||||
run_profit: Optional[float] = 0
|
||||
run_positions: Optional[int] = 0
|
||||
run_avgp: Optional[float] = 0
|
||||
run_positions: Optional[dict] = 0
|
||||
run_avgp: Optional[dict] = 0
|
||||
run_stopped: Optional[datetime] = None
|
||||
run_paused: Optional[datetime] = None
|
||||
|
||||
@ -208,8 +273,8 @@ class Runner(BaseModel):
|
||||
run_ilog_save: Optional[bool] = False
|
||||
run_trade_count: Optional[int] = None
|
||||
run_profit: Optional[float] = None
|
||||
run_positions: Optional[int] = None
|
||||
run_avgp: Optional[float] = None
|
||||
run_positions: Optional[dict] = None
|
||||
run_avgp: Optional[dict] = None
|
||||
run_strat_json: Optional[str] = None
|
||||
run_stopped: Optional[datetime] = None
|
||||
run_paused: Optional[datetime] = None
|
||||
@ -247,6 +312,7 @@ class Bar(BaseModel):
|
||||
vwap: Optional[float] = 0
|
||||
|
||||
class Order(BaseModel):
|
||||
account: Account
|
||||
id: UUID
|
||||
submitted_at: datetime
|
||||
filled_at: Optional[datetime] = None
|
||||
@ -262,6 +328,7 @@ class Order(BaseModel):
|
||||
|
||||
#entita pro kazdy kompletni FILL, je navazana na prescribed_trade
|
||||
class TradeUpdate(BaseModel):
|
||||
account: Account
|
||||
event: Union[TradeEvent, str]
|
||||
execution_id: Optional[UUID] = None
|
||||
order: Order
|
||||
@ -307,8 +374,8 @@ class RunArchive(BaseModel):
|
||||
ilog_save: Optional[bool] = False
|
||||
profit: float = 0
|
||||
trade_count: int = 0
|
||||
end_positions: int = 0
|
||||
end_positions_avgp: float = 0
|
||||
end_positions: Union[dict,str] = None
|
||||
end_positions_avgp: Union[dict,str] = None
|
||||
metrics: Union[dict, str] = None
|
||||
stratvars_toml: Optional[str] = None
|
||||
|
||||
@ -329,8 +396,8 @@ class RunArchiveView(BaseModel):
|
||||
ilog_save: Optional[bool] = False
|
||||
profit: float = 0
|
||||
trade_count: int = 0
|
||||
end_positions: int = 0
|
||||
end_positions_avgp: float = 0
|
||||
end_positions: Union[dict,int] = None
|
||||
end_positions_avgp: Union[dict,float] = None
|
||||
metrics: Union[dict, str] = None
|
||||
batch_profit: float = 0 # Total profit for the batch - now calculated during query
|
||||
batch_count: int = 0 # Count of runs in the batch - now calculated during query
|
||||
@ -347,6 +414,8 @@ class SLHistory(BaseModel):
|
||||
id: Optional[UUID] = None
|
||||
time: datetime
|
||||
sl_val: float
|
||||
direction: TradeDirection
|
||||
account: Account
|
||||
|
||||
#Contains archive of running strategies (runner) - detail data
|
||||
class RunArchiveDetail(BaseModel):
|
||||
@ -359,9 +428,3 @@ class RunArchiveDetail(BaseModel):
|
||||
trades: List[TradeUpdate]
|
||||
ext_data: Optional[dict] = None
|
||||
|
||||
|
||||
class InstantIndicator(BaseModel):
|
||||
name: str
|
||||
toml: str
|
||||
|
||||
|
||||
|
||||
@ -51,8 +51,8 @@ def row_to_runarchiveview(row: dict) -> RunArchiveView:
|
||||
ilog_save=bool(row['ilog_save']),
|
||||
profit=float(row['profit']),
|
||||
trade_count=int(row['trade_count']),
|
||||
end_positions=int(row['end_positions']),
|
||||
end_positions_avgp=float(row['end_positions_avgp']),
|
||||
end_positions=orjson.loads(row['end_positions']),
|
||||
end_positions_avgp=orjson.loads(row['end_positions_avgp']),
|
||||
metrics=orjson.loads(row['metrics']) if row['metrics'] else None,
|
||||
batch_profit=int(row['batch_profit']) if row['batch_profit'] and row['batch_id'] else 0,
|
||||
batch_count=int(row['batch_count']) if row['batch_count'] and row['batch_id'] else 0,
|
||||
@ -79,8 +79,8 @@ def row_to_runarchive(row: dict) -> RunArchive:
|
||||
ilog_save=bool(row['ilog_save']),
|
||||
profit=float(row['profit']),
|
||||
trade_count=int(row['trade_count']),
|
||||
end_positions=int(row['end_positions']),
|
||||
end_positions_avgp=float(row['end_positions_avgp']),
|
||||
end_positions=str(row['end_positions']),
|
||||
end_positions_avgp=str(row['end_positions_avgp']),
|
||||
metrics=orjson.loads(row['metrics']),
|
||||
stratvars_toml=row['stratvars_toml'],
|
||||
transferables=orjson.loads(row['transferables']) if row['transferables'] else None
|
||||
|
||||
@ -17,9 +17,6 @@ RUNNER_DETAIL_DIRECTORY = Path(__file__).parent.parent.parent / "runner_detail"
|
||||
LOG_PATH = Path(__file__).parent.parent
|
||||
LOG_FILE = Path(__file__).parent.parent / "strat.log"
|
||||
JOB_LOG_FILE = Path(__file__).parent.parent / "job.log"
|
||||
DOTENV_DIRECTORY = Path(__file__).parent.parent.parent
|
||||
ENV_FILE = DOTENV_DIRECTORY / '.env'
|
||||
|
||||
|
||||
#stratvars that cannot be changed in gui
|
||||
STRATVARS_UNCHANGEABLES = ['pendingbuys', 'blockbuy', 'jevylozeno', 'limitka']
|
||||
@ -30,6 +27,26 @@ MODEL_DIR = Path(DATA_DIR)/"models"
|
||||
PROFILING_NEXT_ENABLED = False
|
||||
PROFILING_OUTPUT_DIR = DATA_DIR
|
||||
|
||||
def find_dotenv(start_path):
|
||||
"""
|
||||
Searches for a .env file in the given directory or its parents and returns the path.
|
||||
|
||||
Args:
|
||||
start_path: The directory to start searching from.
|
||||
|
||||
Returns:
|
||||
Path to the .env file if found, otherwise None.
|
||||
"""
|
||||
current_path = Path(start_path)
|
||||
for _ in range(6): # Limit search depth to 5 levels
|
||||
dotenv_path = current_path / '.env'
|
||||
if dotenv_path.exists():
|
||||
return dotenv_path
|
||||
current_path = current_path.parent
|
||||
return None
|
||||
|
||||
ENV_FILE = find_dotenv(__file__)
|
||||
|
||||
#NALOADUJEME DOTENV ENV VARIABLES
|
||||
if load_dotenv(ENV_FILE, verbose=True) is False:
|
||||
print(f"Error loading.env file {ENV_FILE}. Now depending on ENV VARIABLES set externally.")
|
||||
@ -66,10 +83,10 @@ def get_key(mode: Mode, account: Account):
|
||||
return None
|
||||
dict = globals()
|
||||
try:
|
||||
API_KEY = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_API_KEY" ]
|
||||
SECRET_KEY = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_SECRET_KEY" ]
|
||||
PAPER = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_PAPER" ]
|
||||
FEED = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_FEED" ]
|
||||
API_KEY = dict[str.upper(str(account.name)) + "_" + str.upper(str(mode.name)) + "_API_KEY" ]
|
||||
SECRET_KEY = dict[str.upper(str(account.name)) + "_" + str.upper(str(mode.name)) + "_SECRET_KEY" ]
|
||||
PAPER = dict[str.upper(str(account.name)) + "_" + str.upper(str(mode.name)) + "_PAPER" ]
|
||||
FEED = dict[str.upper(str(account.name)) + "_" + str.upper(str(mode.name)) + "_FEED" ]
|
||||
return Keys(API_KEY, SECRET_KEY, PAPER, FEED)
|
||||
except KeyError:
|
||||
print("Not valid combination to get keys for", mode, account)
|
||||
@ -93,7 +110,7 @@ data_feed_type_str = os.environ.get('ACCOUNT1_PAPER_FEED', 'iex') # Default to
|
||||
# Convert the string to DataFeed enum
|
||||
try:
|
||||
ACCOUNT1_PAPER_FEED = DataFeed(data_feed_type_str)
|
||||
except ValueError:
|
||||
except nameError:
|
||||
# Handle the case where the environment variable does not match any enum member
|
||||
print(f"Invalid data feed type: {data_feed_type_str} in ACCOUNT1_PAPER_FEED defaulting to 'iex'")
|
||||
ACCOUNT1_PAPER_FEED = DataFeed.SIP
|
||||
@ -111,7 +128,7 @@ data_feed_type_str = os.environ.get('ACCOUNT1_LIVE_FEED', 'iex') # Default to '
|
||||
# Convert the string to DataFeed enum
|
||||
try:
|
||||
ACCOUNT1_LIVE_FEED = DataFeed(data_feed_type_str)
|
||||
except ValueError:
|
||||
except nameError:
|
||||
# Handle the case where the environment variable does not match any enum member
|
||||
print(f"Invalid data feed type: {data_feed_type_str} in ACCOUNT1_LIVE_FEED defaulting to 'iex'")
|
||||
ACCOUNT1_LIVE_FEED = DataFeed.IEX
|
||||
@ -129,7 +146,7 @@ data_feed_type_str = os.environ.get('ACCOUNT2_PAPER_FEED', 'iex') # Default to
|
||||
# Convert the string to DataFeed enum
|
||||
try:
|
||||
ACCOUNT2_PAPER_FEED = DataFeed(data_feed_type_str)
|
||||
except ValueError:
|
||||
except nameError:
|
||||
# Handle the case where the environment variable does not match any enum member
|
||||
print(f"Invalid data feed type: {data_feed_type_str} in ACCOUNT2_PAPER_FEED defaulting to 'iex'")
|
||||
ACCOUNT2_PAPER_FEED = DataFeed.IEX
|
||||
@ -148,7 +165,7 @@ except ValueError:
|
||||
# # Convert the string to DataFeed enum
|
||||
# try:
|
||||
# ACCOUNT2_LIVE_FEED = DataFeed(data_feed_type_str)
|
||||
# except ValueError:
|
||||
# except nameError:
|
||||
# # Handle the case where the environment variable does not match any enum member
|
||||
# print(f"Invalid data feed type: {data_feed_type_str} in ACCOUNT2_LIVE_FEED defaulting to 'iex'")
|
||||
# ACCOUNT2_LIVE_FEED = DataFeed.IEX
|
||||
|
||||
@ -3,7 +3,7 @@ from uuid import UUID, uuid4
|
||||
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 v2realbot.common.model 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
|
||||
|
||||
@ -8,9 +8,9 @@ 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 RunDay, StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem, InstantIndicator, DataTablesRequest
|
||||
from v2realbot.utils.utils import 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.utils import AttributeDict, zoneNY, zonePRG, safe_get, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram, concatenate_weekdays, transform_data, gaka
|
||||
from v2realbot.utils.ilog import delete_logs
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType
|
||||
from v2realbot.common.model 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
|
||||
@ -71,8 +71,8 @@ def get_all_runners():
|
||||
if i.run_instance:
|
||||
i.run_profit = round(float(i.run_instance.state.profit),2)
|
||||
i.run_trade_count = len(i.run_instance.state.tradeList)
|
||||
i.run_positions = i.run_instance.state.positions
|
||||
i.run_avgp = round(float(i.run_instance.state.avgp),3)
|
||||
i.run_positions = gaka(i.run_instance.state.account_variables, "positions")
|
||||
i.run_avgp = gaka(i.run_instance.state.account_variables, "avgp", lambda x: round(float(x),3))
|
||||
return (0, db.runners)
|
||||
else:
|
||||
return (0, [])
|
||||
@ -94,8 +94,8 @@ def get_runner(id: UUID):
|
||||
if str(i.id) == str(id):
|
||||
i.run_profit = round(float(i.run_instance.state.profit),2)
|
||||
i.run_trade_count = len(i.run_instance.state.tradeList)
|
||||
i.run_positions = i.run_instance.state.positions
|
||||
i.run_avgp = round(float(i.run_instance.state.avgp),3)
|
||||
i.run_positions =gaka(i.run_instance.state.account_variables, "positions")
|
||||
i.run_avgp = gaka(i.run_instance.state.account_variables, "avgp", lambda x: round(float(x),3))
|
||||
return (0, i)
|
||||
return (-2, "not found")
|
||||
|
||||
@ -738,13 +738,14 @@ def populate_metrics_output_directory(strat: StrategyInstance, inter_batch_param
|
||||
|
||||
tradeList = strat.state.tradeList
|
||||
|
||||
trade_dict = AttributeDict(orderid=[],timestamp=[],symbol=[],side=[],order_type=[],qty=[],price=[],position_qty=[])
|
||||
trade_dict = AttributeDict(account=[],orderid=[],timestamp=[],symbol=[],side=[],order_type=[],qty=[],price=[],position_qty=[])
|
||||
if strat.mode == Mode.BT:
|
||||
trade_dict["value"] = []
|
||||
trade_dict["cash"] = []
|
||||
trade_dict["pos_avg_price"] = []
|
||||
for t in tradeList:
|
||||
if t.event == TradeEvent.FILL:
|
||||
trade_dict.account.append(t.account)
|
||||
trade_dict.orderid.append(str(t.order.id))
|
||||
trade_dict.timestamp.append(t.timestamp)
|
||||
trade_dict.symbol.append(t.order.symbol)
|
||||
@ -768,10 +769,12 @@ def populate_metrics_output_directory(strat: StrategyInstance, inter_batch_param
|
||||
max_positions = max_positions[max_positions['side'] == OrderSide.SELL]
|
||||
max_positions = max_positions.drop(columns=['side'], axis=1)
|
||||
|
||||
res = dict(profit={})
|
||||
res = dict(account_variables={}, profit={})
|
||||
#filt = max_positions['side'] == 'OrderSide.BUY'
|
||||
res["pos_cnt"] = dict(zip(str(max_positions['qty']), max_positions['count']))
|
||||
|
||||
res["account_variables"] = transform_data(strat.state.account_variables, json_serial)
|
||||
|
||||
res["pos_cnt"] = dict(zip(str(max_positions['qty']), max_positions['count']))
|
||||
#naplneni batch sum profitu
|
||||
if inter_batch_params is not None:
|
||||
res["profit"]["batch_sum_profit"] = int(inter_batch_params["batch_profit"])
|
||||
@ -923,8 +926,8 @@ def archive_runner(runner: Runner, strat: StrategyInstance, inter_batch_params:
|
||||
settings = settings,
|
||||
profit=round(float(strat.state.profit),2),
|
||||
trade_count=len(strat.state.tradeList),
|
||||
end_positions=strat.state.positions,
|
||||
end_positions_avgp=round(float(strat.state.avgp),3),
|
||||
end_positions=gaka(strat.state.account_variables, "positions"),
|
||||
end_positions_avgp=gaka(strat.state.account_variables, "avgp", lambda x: round(float(x),3)),
|
||||
metrics=results_metrics,
|
||||
stratvars_toml=runner.run_stratvars_toml,
|
||||
transferables=strat.state.vars["transferables"]
|
||||
@ -1264,6 +1267,7 @@ def insert_archive_header(archeader: RunArchive):
|
||||
try:
|
||||
c = conn.cursor()
|
||||
#json_string = orjson.dumps(archeader, default=json_serial, option=orjson.OPT_PASSTHROUGH_DATETIME)
|
||||
#print(archeader)
|
||||
|
||||
res = c.execute("""
|
||||
INSERT INTO runner_header
|
||||
@ -1271,7 +1275,7 @@ def insert_archive_header(archeader: RunArchive):
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(str(archeader.id), str(archeader.strat_id), archeader.batch_id, archeader.symbol, archeader.name, archeader.note, archeader.started, archeader.stopped, archeader.mode, archeader.account, archeader.bt_from, archeader.bt_to, orjson.dumps(archeader.strat_json).decode('utf-8'), orjson.dumps(archeader.settings).decode('utf-8'), archeader.ilog_save, archeader.profit, archeader.trade_count, archeader.end_positions, archeader.end_positions_avgp, orjson.dumps(archeader.metrics, default=json_serial, option=orjson.OPT_PASSTHROUGH_DATETIME).decode('utf-8'), archeader.stratvars_toml, orjson.dumps(archeader.transferables).decode('utf-8')))
|
||||
(str(archeader.id), str(archeader.strat_id), archeader.batch_id, archeader.symbol, archeader.name, archeader.note, archeader.started, archeader.stopped, archeader.mode, archeader.account, archeader.bt_from, archeader.bt_to, orjson.dumps(archeader.strat_json).decode('utf-8'), orjson.dumps(archeader.settings).decode('utf-8'), archeader.ilog_save, archeader.profit, archeader.trade_count, orjson.dumps(archeader.end_positions).decode('utf-8'), orjson.dumps(archeader.end_positions_avgp).decode('utf-8'), orjson.dumps(archeader.metrics, default=json_serial, option=orjson.OPT_PASSTHROUGH_DATETIME).decode('utf-8'), archeader.stratvars_toml, orjson.dumps(archeader.transferables).decode('utf-8')))
|
||||
|
||||
#retry not yet supported for statement format above
|
||||
#res = execute_with_retry(c,statement)
|
||||
@ -1646,13 +1650,14 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool =
|
||||
new_inds = AttributeDict(**new_inds)
|
||||
new_tick_inds = {key: [] for key in detail.indicators[1].keys()}
|
||||
new_tick_inds = AttributeDict(**new_tick_inds)
|
||||
interface = BacktestInterface(symbol="X", bt=None)
|
||||
def_account = Account("ACCOUNT1")
|
||||
interface = BacktestInterface(symbol="X", bt=None, account=def_account)
|
||||
|
||||
##dame nastaveni indikatoru do tvaru, ktery stratvars ocekava (pro dynmaicke inicializace)
|
||||
stratvars = AttributeDict(indicators=AttributeDict(**{jmeno:toml_parsed}))
|
||||
#print("stratvars", stratvars)
|
||||
|
||||
state = StrategyState(name="XX", symbol = "X", stratvars = AttributeDict(**stratvars), interface=interface)
|
||||
state = StrategyState(name="XX", symbol = "X", stratvars = AttributeDict(**stratvars), interface=interface, accounts=[def_account], account=def_account)
|
||||
|
||||
#inicializujeme stavove promenne a novy indikator v cilovem dict
|
||||
if output == "bar":
|
||||
|
||||
@ -5,6 +5,7 @@ from v2realbot.backtesting.backtester import Backtester
|
||||
from datetime import datetime
|
||||
from v2realbot.utils.utils import zoneNY
|
||||
import v2realbot.utils.config_handler as cfh
|
||||
from v2realbot.common.model import Account
|
||||
|
||||
""""
|
||||
backtester methods can be called
|
||||
@ -16,8 +17,9 @@ both should be backtestable
|
||||
if method are called for the past self.time must be set accordingly
|
||||
"""
|
||||
class BacktestInterface(GeneralInterface):
|
||||
def __init__(self, symbol, bt: Backtester) -> None:
|
||||
def __init__(self, symbol, bt: Backtester, account: Account) -> None:
|
||||
self.symbol = symbol
|
||||
self.account = account
|
||||
self.bt = bt
|
||||
self.count_api_requests = cfh.config_handler.get_val('COUNT_API_REQUESTS')
|
||||
self.mincnt = list([dict(minute=0,count=0)])
|
||||
@ -43,48 +45,48 @@ class BacktestInterface(GeneralInterface):
|
||||
def buy(self, size = 1, repeat: bool = False):
|
||||
self.count()
|
||||
#add REST API latency
|
||||
return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.BUY,size=size,order_type = OrderType.MARKET)
|
||||
return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.BUY,size=size,order_type = OrderType.MARKET, account=self.account)
|
||||
|
||||
"""buy limit"""
|
||||
def buy_l(self, price: float, size: int = 1, repeat: bool = False, force: int = 0):
|
||||
self.count()
|
||||
return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.BUY,size=size,price=price,order_type = OrderType.LIMIT)
|
||||
return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.BUY,size=size,price=price,order_type = OrderType.LIMIT, account=self.account)
|
||||
|
||||
"""sell market"""
|
||||
def sell(self, size = 1, repeat: bool = False):
|
||||
self.count()
|
||||
return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.SELL,size=size,order_type = OrderType.MARKET)
|
||||
return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.SELL,size=size,order_type = OrderType.MARKET, account=self.account)
|
||||
|
||||
"""sell limit"""
|
||||
async def sell_l(self, price: float, size = 1, repeat: bool = False):
|
||||
self.count()
|
||||
return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.SELL,size=size,price=price,order_type = OrderType.LIMIT)
|
||||
return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.SELL,size=size,price=price,order_type = OrderType.LIMIT, account=self.account)
|
||||
|
||||
"""replace order"""
|
||||
async def repl(self, orderid: str, price: float = None, size: int = None, repeat: bool = False):
|
||||
self.count()
|
||||
return self.bt.replace_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),id=orderid,size=size,price=price)
|
||||
return self.bt.replace_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),id=orderid,size=size,price=price, account=self.account)
|
||||
|
||||
"""cancel order"""
|
||||
#TBD exec predtim?
|
||||
def cancel(self, orderid: str):
|
||||
self.count()
|
||||
return self.bt.cancel_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'), id=orderid)
|
||||
return self.bt.cancel_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'), id=orderid, account=self.account)
|
||||
|
||||
"""get positions ->(size,avgp)"""
|
||||
#TBD exec predtim?
|
||||
def pos(self):
|
||||
self.count()
|
||||
return self.bt.get_open_position(symbol=self.symbol)
|
||||
return self.bt.get_open_position(symbol=self.symbol, account=self.account)
|
||||
|
||||
"""get open orders ->list(Order)"""
|
||||
def get_open_orders(self, side: OrderSide, symbol: str):
|
||||
self.count()
|
||||
return self.bt.get_open_orders(side=side, symbol=symbol)
|
||||
return self.bt.get_open_orders(side=side, symbol=symbol, account=self.account)
|
||||
|
||||
def get_last_price(self, symbol: str):
|
||||
self.count()
|
||||
return self.bt.get_last_price(time=self.bt.time)
|
||||
return self.bt.get_last_price(time=self.bt.time, account=self.account)
|
||||
|
||||
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ class LiveInterface(GeneralInterface):
|
||||
return -1
|
||||
|
||||
"""sell limit"""
|
||||
async def sell_l(self, price: float, size = 1, repeat: bool = False):
|
||||
def sell_l(self, price: float, size = 1, repeat: bool = False):
|
||||
self.size = size
|
||||
self.repeat = repeat
|
||||
|
||||
@ -124,7 +124,7 @@ class LiveInterface(GeneralInterface):
|
||||
return -1
|
||||
|
||||
"""order replace"""
|
||||
async def repl(self, orderid: str, price: float = None, size: int = None, repeatl: bool = False):
|
||||
def repl(self, orderid: str, price: float = None, size: int = None, repeatl: bool = False):
|
||||
|
||||
if not price and not size:
|
||||
print("price or size has to be filled")
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from threading import Thread
|
||||
from threading import Thread, current_thread
|
||||
from alpaca.trading.stream import TradingStream
|
||||
from v2realbot.config import Keys
|
||||
from v2realbot.common.model import Account
|
||||
|
||||
#jelikoz Alpaca podporuje pripojeni libovolneho poctu websocket instanci na order updates
|
||||
#vytvorime pro kazdou bezici instanci vlastni webservisu (jinak bychom museli delat instanci pro kombinaci ACCOUNT1 - LIVE, ACCOUNT1 - PAPER, ACCOUNT2 - PAPER ..)
|
||||
@ -14,15 +15,16 @@ As Alpaca supports connecting of any number of trade updates clients
|
||||
new instance of this websocket thread is created for each strategy instance.
|
||||
"""""
|
||||
class LiveOrderUpdatesStreamer(Thread):
|
||||
def __init__(self, key: Keys, name: str) -> None:
|
||||
def __init__(self, key: Keys, name: str, account: Account) -> None:
|
||||
self.key = key
|
||||
self.account = account
|
||||
self.strategy = None
|
||||
self.client = TradingStream(api_key=key.API_KEY, secret_key=key.SECRET_KEY, paper=key.PAPER)
|
||||
Thread.__init__(self, name=name)
|
||||
|
||||
#notif dispatcher - pouze 1 strategie
|
||||
async def distributor(self,data):
|
||||
if self.strategy.symbol == data.order.symbol: await self.strategy.order_updates(data)
|
||||
if self.strategy.symbol == data.order.symbol: await self.strategy.order_updates(data, self.account)
|
||||
|
||||
# connects callback to interface object - responses for given symbol are routed to interface callback
|
||||
def connect_callback(self, st):
|
||||
@ -39,6 +41,6 @@ class LiveOrderUpdatesStreamer(Thread):
|
||||
print("connect strategy first")
|
||||
return
|
||||
self.client.subscribe_trade_updates(self.distributor)
|
||||
print("*"*10, "WS Order Update Streamer started for", self.strategy.name, "*"*10)
|
||||
print("*"*10, "WS Order Update Streamer started for", current_thread().name,"*"*10)
|
||||
self.client.run()
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ from fastapi.security import APIKeyHeader
|
||||
import uvicorn
|
||||
from uuid import UUID
|
||||
from v2realbot.utils.ilog import get_log_window
|
||||
from v2realbot.common.model import RunManagerRecord, StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem, InstantIndicator, DataTablesRequest, AnalyzerInputs
|
||||
from v2realbot.common.model import RunManagerRecord, StrategyInstance, RunnerView, RunRequest, TradeView, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem, InstantIndicator, DataTablesRequest, AnalyzerInputs
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query, Request
|
||||
from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@ -334,7 +334,7 @@ def stop_all_runners():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}")
|
||||
|
||||
@app.get("/tradehistory/{symbol}", dependencies=[Depends(api_key_auth)])
|
||||
def get_trade_history(symbol: str, timestamp_from: float, timestamp_to:float) -> list[Trade]:
|
||||
def get_trade_history(symbol: str, timestamp_from: float, timestamp_to:float) -> list[TradeView]:
|
||||
res, set = cs.get_trade_history(symbol, timestamp_from, timestamp_to)
|
||||
if res == 0:
|
||||
return set
|
||||
|
||||
@ -12,7 +12,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -12,7 +12,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -11,7 +11,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -11,7 +11,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -11,7 +11,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -11,7 +11,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -11,7 +11,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -11,7 +11,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -12,7 +12,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -10,7 +10,7 @@ from enum import Enum
|
||||
import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -10,7 +10,7 @@ from enum import Enum
|
||||
import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -10,7 +10,7 @@ from enum import Enum
|
||||
import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -11,7 +11,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
@ -23,7 +23,7 @@ from collections import defaultdict
|
||||
from scipy.stats import zscore
|
||||
from io import BytesIO
|
||||
from typing import Tuple, Optional, List
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
|
||||
def load_trades(runner_ids: List = None, batch_id: str = None) -> Tuple[int, List[Trade], int]:
|
||||
if runner_ids is None and batch_id is None:
|
||||
|
||||
@ -10,7 +10,7 @@ from enum import Enum
|
||||
import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -4,7 +4,7 @@ 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, Market
|
||||
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 v2realbot.common.model 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, MEDIA_DIRECTORY, RUNNER_DETAIL_DIRECTORY
|
||||
import numpy as np
|
||||
|
||||
@ -11,6 +11,44 @@ var markersLine = null
|
||||
var avgBuyLine = null
|
||||
var profitLine = null
|
||||
var slLine = []
|
||||
|
||||
//create function which for each ACCOUNT1, ACCOUNT2 or ACCOUNT3 returns color for buy and color for sell - which can be strings representing color
|
||||
//HELPERS FUNCTION - will go to utils
|
||||
/**
|
||||
* Returns an object containing the colors for buy and sell for the specified account.
|
||||
*
|
||||
* Parameters:
|
||||
* account (string): The account for which to retrieve the colors (ACCOUNT1, ACCOUNT2, or ACCOUNT3).
|
||||
*
|
||||
* Returns:
|
||||
* object: An object with 'buy' and 'sell' properties containing the corresponding color strings.
|
||||
*
|
||||
* Account 1:
|
||||
#FF6B6B, #FF9999
|
||||
Account 2:
|
||||
#4ECDC4, #83E8E1
|
||||
Account 3:
|
||||
#FFD93D, #FFE787
|
||||
Account 4:
|
||||
#6C5CE7, #A29BFE
|
||||
Another option for colors:
|
||||
|
||||
#1F77B4 (Entry) and #AEC7E8 (Exit)
|
||||
#FF7F0E (Entry) and #FFBB78 (Exit)
|
||||
#2CA02C (Entry) and #98DF8A (Exit)
|
||||
#D62728 (Entry) and #FF9896 (Exit)
|
||||
*/
|
||||
function getAccountColors(account) {
|
||||
const accountColors = {
|
||||
ACCOUNT1: { accid: 'A1', buy: '#FF7F0E', sell: '#FFBB78' },
|
||||
ACCOUNT2: { accid: 'A2',buy: '#1F77B4', sell: '#AEC7E8' },
|
||||
ACCOUNT3: { accid: 'A3',buy: '#2CA02C', sell: '#98DF8A' },
|
||||
ACCOUNT4: { accid: 'A4',buy: '#D62728', sell: '#FF9896' },
|
||||
ACCOUNT5: { accid: 'A5',buy: 'purple', sell: 'orange' }
|
||||
};
|
||||
return accountColors[account] || { buy: '#37cade', sell: 'red' };
|
||||
}
|
||||
|
||||
//TRANSFORM object returned from REST API get_arch_run_detail
|
||||
//to series and markers required by lightweigth chart
|
||||
//input array object bars = { high: [1,2,3], time: [1,2,3], close: [2,2,2]...}
|
||||
@ -34,6 +72,11 @@ function transform_data(data) {
|
||||
//cas of first record, nekdy jsou stejny - musim pridat setinku
|
||||
prev_cas = 0
|
||||
if ((data.ext_data !== null) && (data.ext_data.sl_history)) {
|
||||
///sort sl_history according to order id string - i need all same order id together
|
||||
data.ext_data.sl_history.sort(function (a, b) {
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
|
||||
data.ext_data.sl_history.forEach((histRecord, index, array) => {
|
||||
|
||||
//console.log("plnime")
|
||||
@ -48,6 +91,7 @@ function transform_data(data) {
|
||||
//init nova sada
|
||||
sl_line_sada = []
|
||||
sl_line_markers_sada = []
|
||||
sline_color = "#f5aa42"
|
||||
}
|
||||
|
||||
prev_id = histRecord.id
|
||||
@ -65,12 +109,21 @@ function transform_data(data) {
|
||||
sline = {}
|
||||
sline["time"] = cas
|
||||
sline["value"] = histRecord.sl_val
|
||||
if (histRecord.account) {
|
||||
const accColors = getAccountColors(histRecord.account)
|
||||
sline_color = histRecord.direction == "long" ? accColors.buy : accColors.sell //idealne
|
||||
sline["color"] = sline_color
|
||||
}
|
||||
|
||||
sl_line_sada.push(sline)
|
||||
|
||||
//ZDE JSEM SKONCIL
|
||||
//COLOR SE NASTAVUJE V SERIES OPTIONS POZDEJI - nejak vymyslet
|
||||
|
||||
sline_markers = {}
|
||||
sline_markers["time"] = cas
|
||||
sline_markers["position"] = "inBar"
|
||||
sline_markers["color"] = "#f5aa42"
|
||||
sline_markers["color"] = sline_color
|
||||
//sline_markers["shape"] = "circle"
|
||||
//console.log("SHOW_SL_DIGITS",SHOW_SL_DIGITS)
|
||||
sline_markers["text"] = SHOW_SL_DIGITS ? histRecord.sl_val.toFixed(3) : ""
|
||||
@ -241,29 +294,31 @@ function transform_data(data) {
|
||||
}
|
||||
}
|
||||
|
||||
const { accid: accountId,buy: buyColor, sell: sellColor } = getAccountColors(trade.account);
|
||||
|
||||
//buy sell markery
|
||||
marker = {}
|
||||
marker["time"] = timestamp;
|
||||
// marker["position"] = (trade.order.side == "buy") ? "belowBar" : "aboveBar"
|
||||
marker["position"] = (trade.order.side == "buy") ? "aboveBar" : "aboveBar"
|
||||
marker["color"] = (trade.order.side == "buy") ? "#37cade" : "red"
|
||||
marker["color"] = (trade.order.side == "buy") ? buyColor : sellColor
|
||||
//marker["shape"] = (trade.order.side == "buy") ? "arrowUp" : "arrowDown"
|
||||
marker["shape"] = (trade.order.side == "buy") ? "arrowUp" : "arrowDown"
|
||||
//marker["text"] = trade.qty + "/" + trade.price
|
||||
qt_optimized = (trade.order.qty % 1000 === 0) ? (trade.order.qty / 1000).toFixed(1) + 'K' : trade.order.qty
|
||||
|
||||
marker["text"] = accountId + " " //account shortcut
|
||||
if (CHART_SHOW_TEXT) {
|
||||
//včetně qty
|
||||
//marker["text"] = qt_optimized + "@" + trade.price
|
||||
|
||||
//bez qty
|
||||
marker["text"] = trade.price
|
||||
marker["text"] += trade.price
|
||||
closed_trade_marker_and_profit = (trade.profit) ? "c" + trade.profit.toFixed(1) + "/" + trade.profit_sum.toFixed(1) : "c"
|
||||
marker["text"] += (trade.position_qty == 0) ? closed_trade_marker_and_profit : ""
|
||||
} else {
|
||||
closed_trade_marker_and_profit = (trade.profit) ? "c" + trade.profit.toFixed(1) + "/" + trade.profit_sum.toFixed(1) : "c"
|
||||
marker["text"] = (trade.position_qty == 0) ? closed_trade_marker_and_profit : trade.price.toFixed(3)
|
||||
marker["text"] += (trade.position_qty == 0) ? closed_trade_marker_and_profit : trade.price.toFixed(3)
|
||||
}
|
||||
|
||||
markers.push(marker)
|
||||
@ -844,7 +899,7 @@ function display_buy_markers(data) {
|
||||
//console.log("uvnitr")
|
||||
slLine_temp = chart.addLineSeries({
|
||||
// title: "avgpbuyline",
|
||||
color: '#e4c76d',
|
||||
color: slRecord[0]["color"] ? slRecord[0]["color"] : '#e4c76d',
|
||||
// color: 'transparent',
|
||||
lineWidth: 1,
|
||||
lastValueVisible: false
|
||||
|
||||
@ -172,7 +172,7 @@ function initialize_archiveRecords() {
|
||||
{
|
||||
targets: [13,14,15],
|
||||
render: function ( data, type, row ) {
|
||||
return '<div class="tdsmall">'+data+'</div>'
|
||||
return '<div class="tdsmall">'+JSON.stringify(data, null, 2)+'</div>'
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -4,7 +4,7 @@ from v2realbot.utils.tlog import tlog, tlog_exception
|
||||
from v2realbot.enums.enums import Mode, Order, Account, RecordType, Followup
|
||||
#from alpaca.trading.models import TradeUpdate
|
||||
from v2realbot.common.model import TradeUpdate
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model import Trade, TradeDirection, TradeStatus
|
||||
from alpaca.trading.enums import TradeEvent, OrderStatus
|
||||
from v2realbot.indicators.indicators import ema
|
||||
import orjson
|
||||
@ -44,7 +44,8 @@ class StrategyClassicSL(Strategy):
|
||||
msg = f"QUITTING {hard_cutoff=} MAX SUM REL PROFIT REACHED {max_sum_profit_to_quit_rel=} {self.state.profit=} {rel_profit=} relprofits:{str(self.state.rel_profit_cum)}"
|
||||
printanyway(msg)
|
||||
self.state.ilog(e=msg)
|
||||
self.state.vars.pending = "max_sum_profit_to_quit_rel"
|
||||
for account in self.accounts:
|
||||
self.state.account_variables[account.name].pending = "max_sum_profit_to_quit_rel"
|
||||
if self.mode not in [Mode.BT, Mode.PREP]:
|
||||
send_to_telegram(msg)
|
||||
if hard_cutoff:
|
||||
@ -57,7 +58,8 @@ class StrategyClassicSL(Strategy):
|
||||
msg=f"QUITTING {hard_cutoff=} MAX SUM REL LOSS REACHED {max_sum_loss_to_quit_rel=} {self.state.profit=} {rel_profit=} relprofits:{str(self.state.rel_profit_cum)}"
|
||||
printanyway(msg)
|
||||
self.state.ilog(e=msg)
|
||||
self.state.vars.pending = "max_sum_loss_to_quit_rel"
|
||||
for account in self.accounts:
|
||||
self.state.account_variables[account.name].pending = "max_sum_loss_to_quit_rel"
|
||||
if self.mode not in [Mode.BT, Mode.PREP]:
|
||||
send_to_telegram(msg)
|
||||
if hard_cutoff:
|
||||
@ -71,7 +73,8 @@ class StrategyClassicSL(Strategy):
|
||||
msg = f"QUITTING {hard_cutoff=} MAX SUM ABS PROFIT REACHED {max_sum_profit_to_quit=} {self.state.profit=} {rel_profit=} relprofits:{str(self.state.rel_profit_cum)}"
|
||||
printanyway(msg)
|
||||
self.state.ilog(e=msg)
|
||||
self.state.vars.pending = "max_sum_profit_to_quit"
|
||||
for account in self.accounts:
|
||||
self.state.account_variables[account.name].pending = "max_sum_profit_to_quit"
|
||||
if self.mode not in [Mode.BT, Mode.PREP]:
|
||||
send_to_telegram(msg)
|
||||
if hard_cutoff:
|
||||
@ -84,7 +87,8 @@ class StrategyClassicSL(Strategy):
|
||||
msg = f"QUITTING {hard_cutoff=} MAX SUM ABS LOSS REACHED {max_sum_loss_to_quit=} {self.state.profit=} {rel_profit=} relprofits:{str(self.state.rel_profit_cum)}"
|
||||
printanyway(msg)
|
||||
self.state.ilog(e=msg)
|
||||
self.state.vars.pending = "max_sum_loss_to_quit"
|
||||
for account in self.accounts:
|
||||
self.state.account_variables[account.name].pending = "max_sum_loss_to_quit"
|
||||
if self.mode not in [Mode.BT, Mode.PREP]:
|
||||
send_to_telegram(msg)
|
||||
if hard_cutoff:
|
||||
@ -95,9 +99,10 @@ class StrategyClassicSL(Strategy):
|
||||
|
||||
return False
|
||||
|
||||
async def add_followup(self, direction: TradeDirection, size: int, signal_name: str):
|
||||
async def add_followup(self, direction: TradeDirection, size: int, signal_name: str, account: Account):
|
||||
trade_to_add = Trade(
|
||||
id=uuid4(),
|
||||
account=account,
|
||||
last_update=datetime.fromtimestamp(self.state.time).astimezone(zoneNY),
|
||||
status=TradeStatus.READY,
|
||||
size=size,
|
||||
@ -108,45 +113,48 @@ class StrategyClassicSL(Strategy):
|
||||
|
||||
self.state.vars.prescribedTrades.append(trade_to_add)
|
||||
|
||||
self.state.vars.requested_followup = None
|
||||
self.state.account_variables[account.name].requested_followup = None
|
||||
|
||||
self.state.ilog(e=f"FOLLOWUP {direction} added to prescr.trades {signal_name=} {size=}", trade=trade_to_add)
|
||||
self.state.ilog(e=f"FOLLOWUP {direction} - {account} added to prescr.trades {signal_name=} {size=}", trade=trade_to_add)
|
||||
|
||||
async def orderUpdateBuy(self, data: TradeUpdate):
|
||||
o: Order = data.order
|
||||
signal_name = None
|
||||
account = data.account
|
||||
##nejak to vymyslet, aby se dal poslat cely Trade a serializoval se
|
||||
self.state.ilog(e="Příchozí BUY notif", msg=o.status, trade=transform_data(data, json_serial))
|
||||
self.state.ilog(e="Příchozí BUY notif"+account, msg=o.status, trade=transform_data(data, json_serial))
|
||||
|
||||
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
|
||||
|
||||
#pokud jde o fill pred kterym je partail, muze se stat, ze uz budou vynulovany pozice, toto je pojistka
|
||||
#jde o uzavření short pozice - počítáme PROFIT
|
||||
if int(self.state.positions) < 0 or (int(self.state.positions) == 0 and self.state.wait_for_fill is not None):
|
||||
if int(self.state.account_variables[account.name].positions) < 0 or (int(self.state.account_variables[account.name].positions) == 0 and self.state.account_variables[account.name].wait_for_fill is not None):
|
||||
|
||||
if data.event == TradeEvent.PARTIAL_FILL and self.state.wait_for_fill is None:
|
||||
if data.event == TradeEvent.PARTIAL_FILL and self.state.account_variables[account.name].wait_for_fill is None:
|
||||
#timto si oznacime, ze po partialu s vlivem na PROFIT musime cekat na FILL a zaroven ukladame prum cenu, kterou potrebujeme na vypocet profitu u fillu
|
||||
self.state.wait_for_fill = float(self.state.avgp)
|
||||
self.state.account_variables[account.name].wait_for_fill = float(self.state.account_variables[account.name].avgp)
|
||||
|
||||
#PROFIT pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena
|
||||
#naklady vypocteme z prumerne ceny, kterou mame v pozicich
|
||||
bought_amount = data.qty * data.price
|
||||
#podle prumerne vstupni ceny, kolik stalo toto mnozstvi
|
||||
if float(self.state.avgp) > 0:
|
||||
vstup_cena = float(self.state.avgp)
|
||||
elif float(self.state.avgp) == 0 and self.state.wait_for_fill is not None:
|
||||
vstup_cena = float(self.state.wait_for_fill)
|
||||
if float(self.state.account_variables[account.name].avgp) > 0:
|
||||
vstup_cena = float(self.state.account_variables[account.name].avgp)
|
||||
elif float(self.state.account_variables[account.name].avgp) == 0 and self.state.account_variables[account.name].wait_for_fill is not None:
|
||||
vstup_cena = float(self.state.account_variables[account.name].wait_for_fill)
|
||||
else:
|
||||
vstup_cena = 0
|
||||
|
||||
avg_costs = vstup_cena * float(data.qty)
|
||||
|
||||
if avg_costs == 0:
|
||||
self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.")
|
||||
self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0"+account, msg="naklady=utrzena cena. TBD opravit.")
|
||||
avg_costs = bought_amount
|
||||
|
||||
trade_profit = round((avg_costs-bought_amount),2)
|
||||
self.state.profit += trade_profit
|
||||
#celkovy profit
|
||||
self.state.profit += trade_profit #overall abs profit
|
||||
self.state.account_variables[account.name].profit += trade_profit #account profit
|
||||
|
||||
rel_profit = 0
|
||||
#spoctene celkovy relativni profit za trade v procentech ((trade_profit/vstup_naklady)*100)
|
||||
@ -160,30 +168,32 @@ class StrategyClassicSL(Strategy):
|
||||
if data.event == TradeEvent.FILL:
|
||||
#jde o partial EXIT dvááme si rel.profit do docasne promenne, po poslednim exitu z nich vypocteme skutecny rel.profit
|
||||
if data.position_qty != 0:
|
||||
self.state.docasny_rel_profit.append(rel_profit)
|
||||
self.state.account_variables[account.name].docasny_rel_profit.append(rel_profit)
|
||||
partial_exit = True
|
||||
else:
|
||||
#jde o posledni z PARTIAL EXITU tzn.data.position_qty == 0
|
||||
if len(self.state.docasny_rel_profit) > 0:
|
||||
if len(self.state.account_variables[account.name].docasny_rel_profit) > 0:
|
||||
#pricteme aktualni rel profit
|
||||
self.state.docasny_rel_profit.append(rel_profit)
|
||||
self.state.account_variables[account.name].docasny_rel_profit.append(rel_profit)
|
||||
#a z rel profitu tohoto tradu vypocteme prumer, ktery teprve ulozime
|
||||
rel_profit = round(np.mean(self.state.docasny_rel_profit),5)
|
||||
self.state.docasny_rel_profit = []
|
||||
rel_profit = round(np.mean(self.state.account_variables[account.name].docasny_rel_profit),5)
|
||||
self.state.account_variables[account.name].docasny_rel_profit = []
|
||||
partial_last = True
|
||||
|
||||
self.state.rel_profit_cum.append(rel_profit)
|
||||
self.state.rel_profit_cum.append(rel_profit) #overall cum rel profit
|
||||
self.state.account_variables[account.name].rel_profit_cum.append(rel_profit) #account cum rel profit
|
||||
|
||||
rel_profit_cum_calculated = round(np.sum(self.state.rel_profit_cum),5)
|
||||
|
||||
#pro martingale updatujeme loss_series_cnt
|
||||
#pro martingale updatujeme loss_series_cnt -
|
||||
self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"] = 0 if rel_profit > 0 else self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"]+1
|
||||
self.state.ilog(lvl=1, e=f"update cont_loss_series_cnt na {self.state.vars['transferables']['martingale']['cont_loss_series_cnt']}")
|
||||
|
||||
self.state.ilog(e=f"BUY notif - SHORT PROFIT: {partial_exit=} {partial_last=} {round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)} rel:{float(rel_profit)} rel_cum:{round(rel_profit_cum_calculated,7)}", msg=str(data.event), rel_profit_cum=str(self.state.rel_profit_cum), bought_amount=bought_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id))
|
||||
self.state.ilog(e=f"BUY notif {account} - SHORT PROFIT: {partial_exit=} {partial_last=} {round(float(trade_profit),3)} celkem abs:{round(float(self.state.profit),3)} rel:{float(rel_profit)} rel_cum:{round(rel_profit_cum_calculated,7)}", msg=str(data.event), rel_profit_cum=str(self.state.rel_profit_cum), bought_amount=bought_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id))
|
||||
|
||||
#zapsat profit do prescr.trades
|
||||
for trade in self.state.vars.prescribedTrades:
|
||||
if trade.id == self.state.vars.pending:
|
||||
if trade.id == self.state.account_variables[account.name].pending:
|
||||
trade.last_update = datetime.fromtimestamp(self.state.time).astimezone(zoneNY)
|
||||
trade.profit += trade_profit
|
||||
#pro ulozeni do tradeData scitame vsechen zisk z tohoto tradu (kvuli partialum)
|
||||
@ -201,7 +211,7 @@ class StrategyClassicSL(Strategy):
|
||||
|
||||
if data.event == TradeEvent.FILL:
|
||||
#mazeme self.state.
|
||||
self.state.wait_for_fill = None
|
||||
self.state.account_variables[account.name].wait_for_fill = None
|
||||
#zapsat update profitu do tradeList
|
||||
for tradeData in self.state.tradeList:
|
||||
if tradeData.execution_id == data.execution_id:
|
||||
@ -209,7 +219,7 @@ class StrategyClassicSL(Strategy):
|
||||
setattr(tradeData, "profit", trade_profit)
|
||||
setattr(tradeData, "profit_sum", self.state.profit)
|
||||
setattr(tradeData, "signal_name", signal_name)
|
||||
setattr(tradeData, "prescribed_trade_id", self.state.vars.pending)
|
||||
setattr(tradeData, "prescribed_trade_id", self.state.account_variables[account.name].pending)
|
||||
#self.state.ilog(f"updatnut tradeList o profit", tradeData=orjson.loads(orjson.dumps(tradeData, default=json_serial, option=orjson.OPT_PASSTHROUGH_DATETIME)))
|
||||
setattr(tradeData, "rel_profit", rel_profit)
|
||||
setattr(tradeData, "rel_profit_cum", rel_profit_cum_calculated)
|
||||
@ -220,16 +230,16 @@ class StrategyClassicSL(Strategy):
|
||||
|
||||
#pIF REVERSAL REQUIRED - reverse position is added to prescr.Trades with same signal name
|
||||
#jen při celém FILLU
|
||||
if data.event == TradeEvent.FILL and self.state.vars.requested_followup is not None:
|
||||
if self.state.vars.requested_followup == Followup.REVERSE:
|
||||
await self.add_followup(direction=TradeDirection.LONG, size=o.qty, signal_name=signal_name)
|
||||
elif self.state.vars.requested_followup == Followup.ADD:
|
||||
if data.event == TradeEvent.FILL and self.state.account_variables[account.name].requested_followup is not None:
|
||||
if self.state.account_variables[account.name].requested_followup == Followup.REVERSE:
|
||||
await self.add_followup(direction=TradeDirection.LONG, size=o.qty, signal_name=signal_name, account=account)
|
||||
elif self.state.account_variables[account.name].requested_followup == Followup.ADD:
|
||||
#zatim stejna SIZE
|
||||
await self.add_followup(direction=TradeDirection.SHORT, size=o.qty, signal_name=signal_name)
|
||||
await self.add_followup(direction=TradeDirection.SHORT, size=o.qty, signal_name=signal_name, account=account)
|
||||
else:
|
||||
#zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano
|
||||
for trade in self.state.vars.prescribedTrades:
|
||||
if trade.id == self.state.vars.pending:
|
||||
if trade.id == self.state.account_variables[account.name].pending:
|
||||
signal_name = trade.generated_by
|
||||
#zapiseme entry_time (jen pokud to neni partial add) - tzn. jen poprvé
|
||||
if data.event == TradeEvent.FILL and trade.entry_time is None:
|
||||
@ -239,7 +249,7 @@ class StrategyClassicSL(Strategy):
|
||||
for tradeData in self.state.tradeList:
|
||||
if tradeData.execution_id == data.execution_id:
|
||||
setattr(tradeData, "signal_name", signal_name)
|
||||
setattr(tradeData, "prescribed_trade_id", self.state.vars.pending)
|
||||
setattr(tradeData, "prescribed_trade_id", self.state.account_variables[account.name].pending)
|
||||
|
||||
self.state.ilog(e="BUY: Jde o LONG nakuú nepocitame profit zatim")
|
||||
|
||||
@ -248,43 +258,47 @@ class StrategyClassicSL(Strategy):
|
||||
self.state.last_entry_price["long"] = data.price
|
||||
|
||||
#pokud neni nastaveno goal_price tak vyplnujeme defaultem
|
||||
if self.state.vars.activeTrade.goal_price is None:
|
||||
if self.state.account_variables[account.name].activeTrade.goal_price is None:
|
||||
dat = dict(close=data.price)
|
||||
self.state.vars.activeTrade.goal_price = get_profit_target_price(self.state, dat, TradeDirection.LONG)
|
||||
self.state.account_variables[account.name].activeTrade.goal_price = get_profit_target_price(self.state, dat, self.state.account_variables[account.name].activeTrade, TradeDirection.LONG)
|
||||
|
||||
#ic("vstupujeme do orderupdatebuy")
|
||||
print(data)
|
||||
#dostavame zde i celkové akutální množství - ukládáme
|
||||
self.state.positions = data.position_qty
|
||||
self.state.avgp, self.state.positions = self.state.interface.pos()
|
||||
self.state.account_variables[account.name].positions = data.position_qty
|
||||
self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = self.state.interface[account.name].pos()
|
||||
|
||||
if o.status == OrderStatus.FILLED or o.status == OrderStatus.CANCELED:
|
||||
#davame pryc pending
|
||||
self.state.vars.pending = None
|
||||
self.state.account_variables[account.name].pending = None
|
||||
|
||||
|
||||
async def orderUpdateSell(self, data: TradeUpdate):
|
||||
|
||||
self.state.ilog(e="Příchozí SELL notif", msg=data.order.status, trade=transform_data(data, json_serial))
|
||||
account = data.account
|
||||
#TODO tady jsem skoncil, pak projit vsechen kod na state.avgp a prehodit
|
||||
|
||||
|
||||
self.state.ilog(e=f"Příchozí SELL notif - {account}", msg=data.order.status, trade=transform_data(data, json_serial))
|
||||
|
||||
#naklady vypocteme z prumerne ceny, kterou mame v pozicich
|
||||
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
|
||||
|
||||
#pokud jde o fill pred kterym je partail, muze se stat, ze uz budou vynulovany pozice, toto je pojistka
|
||||
#jde o uzavření long pozice - počítáme PROFIT
|
||||
if int(self.state.positions) > 0 or (int(self.state.positions) == 0 and self.state.wait_for_fill is not None):
|
||||
if int(self.state.account_variables[account.name].positions) > 0 or (int(self.state.account_variables[account.name].positions) == 0 and self.state.account_variables[account.name].wait_for_fill is not None):
|
||||
|
||||
if data.event == TradeEvent.PARTIAL_FILL and self.state.wait_for_fill is None:
|
||||
if data.event == TradeEvent.PARTIAL_FILL and self.state.account_variables[account.name].wait_for_fill is None:
|
||||
#timto si oznacime, ze po partialu s vlivem na PROFIT musime cekat na FILL a zaroven ukladame prum cenu, kterou potrebujeme na vypocet profitu u fillu
|
||||
self.state.wait_for_fill = float(self.state.avgp)
|
||||
self.state.account_variables[account.name].wait_for_fill = float(self.state.account_variables[account.name].avgp)
|
||||
|
||||
#PROFIT pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena
|
||||
#naklady vypocteme z prumerne ceny, kterou mame v pozicich
|
||||
sold_amount = data.qty * data.price
|
||||
if float(self.state.avgp) > 0:
|
||||
vstup_cena = float(self.state.avgp)
|
||||
elif float(self.state.avgp) == 0 and self.state.wait_for_fill is not None:
|
||||
vstup_cena = float(self.state.wait_for_fill)
|
||||
if float(self.state.account_variables[account.name].avgp) > 0:
|
||||
vstup_cena = float(self.state.account_variables[account.name].avgp)
|
||||
elif float(self.state.account_variables[account.name].avgp) == 0 and self.state.account_variables[account.name].wait_for_fill is not None:
|
||||
vstup_cena = float(self.state.account_variables[account.name].wait_for_fill)
|
||||
else:
|
||||
vstup_cena = 0
|
||||
|
||||
@ -292,11 +306,12 @@ class StrategyClassicSL(Strategy):
|
||||
avg_costs = vstup_cena * float(data.qty)
|
||||
|
||||
if avg_costs == 0:
|
||||
self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.")
|
||||
self.state.ilog(e=f"ERR: {account} Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.")
|
||||
avg_costs = sold_amount
|
||||
|
||||
trade_profit = round((sold_amount - avg_costs),2)
|
||||
self.state.profit += trade_profit
|
||||
self.state.profit += trade_profit #celkový profit
|
||||
self.state.account_variables[account.name].profit += trade_profit #account specific profit
|
||||
|
||||
rel_profit = 0
|
||||
#spoctene celkovy relativni profit za trade v procentech ((trade_profit/vstup_naklady)*100)
|
||||
@ -309,30 +324,31 @@ class StrategyClassicSL(Strategy):
|
||||
if data.event == TradeEvent.FILL:
|
||||
#jde o partial EXIT dvááme si rel.profit do docasne promenne, po poslednim exitu z nich vypocteme skutecny rel.profit
|
||||
if data.position_qty != 0:
|
||||
self.state.docasny_rel_profit.append(rel_profit)
|
||||
self.state.account_variables[account.name].docasny_rel_profit.append(rel_profit)
|
||||
partial_exit = True
|
||||
else:
|
||||
#jde o posledni z PARTIAL EXITU tzn.data.position_qty == 0
|
||||
if len(self.state.docasny_rel_profit) > 0:
|
||||
if len(self.state.account_variables[account.name].docasny_rel_profit) > 0:
|
||||
#pricteme aktualni rel profit
|
||||
self.state.docasny_rel_profit.append(rel_profit)
|
||||
self.state.account_variables[account.name].docasny_rel_profit.append(rel_profit)
|
||||
#a z rel profitu tohoto tradu vypocteme prumer, ktery teprve ulozime
|
||||
rel_profit = round(np.mean(self.state.docasny_rel_profit),5)
|
||||
self.state.docasny_rel_profit = []
|
||||
rel_profit = round(np.mean(self.state.account_variables[account.name].docasny_rel_profit),5)
|
||||
self.state.account_variables[account.name].docasny_rel_profit = []
|
||||
partial_last = True
|
||||
|
||||
self.state.rel_profit_cum.append(rel_profit)
|
||||
self.state.rel_profit_cum.append(rel_profit) #overall rel profit
|
||||
self.state.account_variables[account.name].rel_profit_cum.append(rel_profit) #account cum rel profit
|
||||
rel_profit_cum_calculated = round(np.sum(self.state.rel_profit_cum),5)
|
||||
|
||||
#pro martingale updatujeme loss_series_cnt
|
||||
self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"] = 0 if rel_profit > 0 else self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"]+1
|
||||
self.state.ilog(lvl=1, e=f"update cont_loss_series_cnt na {self.state.vars['transferables']['martingale']['cont_loss_series_cnt']}")
|
||||
|
||||
self.state.ilog(e=f"SELL notif - LONG PROFIT {partial_exit=} {partial_last=}:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)} rel:{float(rel_profit)} rel_cum:{round(rel_profit_cum_calculated,7)}", msg=str(data.event), rel_profit_cum = str(self.state.rel_profit_cum), sold_amount=sold_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id))
|
||||
self.state.ilog(e=f"SELL notif {account.name}- LONG PROFIT {partial_exit=} {partial_last=}:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)} rel:{float(rel_profit)} rel_cum:{round(rel_profit_cum_calculated,7)}", msg=str(data.event), rel_profit_cum = str(self.state.rel_profit_cum), sold_amount=sold_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id))
|
||||
|
||||
#zapsat profit do prescr.trades
|
||||
for trade in self.state.vars.prescribedTrades:
|
||||
if trade.id == self.state.vars.pending:
|
||||
if trade.id == self.state.account_variables[account.name].pending:
|
||||
trade.last_update = datetime.fromtimestamp(self.state.time).astimezone(zoneNY)
|
||||
trade.profit += trade_profit
|
||||
#pro ulozeni do tradeData scitame vsechen zisk z tohoto tradu (kvuli partialum)
|
||||
@ -349,7 +365,7 @@ class StrategyClassicSL(Strategy):
|
||||
|
||||
if data.event == TradeEvent.FILL:
|
||||
#mazeme self.state.
|
||||
self.state.wait_for_fill = None
|
||||
self.state.account_variables[account.name].wait_for_fill = None
|
||||
#zapsat update profitu do tradeList
|
||||
for tradeData in self.state.tradeList:
|
||||
if tradeData.execution_id == data.execution_id:
|
||||
@ -357,7 +373,7 @@ class StrategyClassicSL(Strategy):
|
||||
setattr(tradeData, "profit", trade_profit)
|
||||
setattr(tradeData, "profit_sum", self.state.profit)
|
||||
setattr(tradeData, "signal_name", signal_name)
|
||||
setattr(tradeData, "prescribed_trade_id", self.state.vars.pending)
|
||||
setattr(tradeData, "prescribed_trade_id", self.state.account_variables[account.name].pending)
|
||||
#self.state.ilog(f"updatnut tradeList o profi {str(tradeData)}")
|
||||
setattr(tradeData, "rel_profit", rel_profit)
|
||||
setattr(tradeData, "rel_profit_cum", rel_profit_cum_calculated)
|
||||
@ -367,17 +383,17 @@ class StrategyClassicSL(Strategy):
|
||||
if data.event == TradeEvent.FILL and await self.stop_when_max_profit_loss() is False:
|
||||
|
||||
#IF REVERSAL REQUIRED - reverse position is added to prescr.Trades with same signal name
|
||||
if data.event == TradeEvent.FILL and self.state.vars.requested_followup is not None:
|
||||
if self.state.vars.requested_followup == Followup.REVERSE:
|
||||
if data.event == TradeEvent.FILL and self.state.account_variables[account.name].requested_followup is not None:
|
||||
if self.state.account_variables[account.name].requested_followup == Followup.REVERSE:
|
||||
await self.add_followup(direction=TradeDirection.SHORT, size=data.order.qty, signal_name=signal_name)
|
||||
elif self.state.vars.requested_followup == Followup.ADD:
|
||||
elif self.state.account_variables[account.name].requested_followup == Followup.ADD:
|
||||
#zatim stejna SIZE
|
||||
await self.add_followup(direction=TradeDirection.LONG, size=data.order.qty, signal_name=signal_name)
|
||||
|
||||
else:
|
||||
#zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano
|
||||
for trade in self.state.vars.prescribedTrades:
|
||||
if trade.id == self.state.vars.pending:
|
||||
if trade.id == self.state.account_variables[account.name].pending:
|
||||
signal_name = trade.generated_by
|
||||
#zapiseme entry_time (jen pokud to neni partial add) - tzn. jen poprvé
|
||||
if data.event == TradeEvent.FILL and trade.entry_time is None:
|
||||
@ -387,7 +403,7 @@ class StrategyClassicSL(Strategy):
|
||||
for tradeData in self.state.tradeList:
|
||||
if tradeData.execution_id == data.execution_id:
|
||||
setattr(tradeData, "signal_name", signal_name)
|
||||
setattr(tradeData, "prescribed_trade_id", self.state.vars.pending)
|
||||
setattr(tradeData, "prescribed_trade_id", self.state.account_variables[account.name].pending)
|
||||
|
||||
self.state.ilog(e="SELL: Jde o SHORT nepocitame profit zatim")
|
||||
|
||||
@ -395,32 +411,32 @@ class StrategyClassicSL(Strategy):
|
||||
#zapisujeme last entry price
|
||||
self.state.last_entry_price["short"] = data.price
|
||||
#pokud neni nastaveno goal_price tak vyplnujeme defaultem
|
||||
if self.state.vars.activeTrade.goal_price is None:
|
||||
if self.state.account_variables[account.name].activeTrade.goal_price is None:
|
||||
dat = dict(close=data.price)
|
||||
self.state.vars.activeTrade.goal_price = get_profit_target_price(self.state, dat, TradeDirection.SHORT)
|
||||
self.state.account_variables[account.name].activeTrade.goal_price = get_profit_target_price(self.state, dat, self.state.account_variables[account.name].activeTrade, TradeDirection.SHORT)
|
||||
#sem v budoucnu dat i update SL
|
||||
#if self.state.vars.activeTrade.stoploss_value is None:
|
||||
|
||||
|
||||
#update pozic, v trade update je i pocet zbylych pozic
|
||||
old_avgp = self.state.avgp
|
||||
old_pos = self.state.positions
|
||||
self.state.positions = int(data.position_qty)
|
||||
old_avgp = self.state.account_variables[account.name].avgp
|
||||
old_pos = self.state.account_variables[account.name].positions
|
||||
self.state.account_variables[account.name].positions = int(data.position_qty)
|
||||
if int(data.position_qty) == 0:
|
||||
self.state.avgp = 0
|
||||
self.state.account_variables[account.name].avgp = 0
|
||||
|
||||
self.state.ilog(e="SELL notifikace "+str(data.order.status), msg="update pozic", old_avgp=old_avgp, old_pos=old_pos, avgp=self.state.avgp, pos=self.state.positions, orderid=str(data.order.id))
|
||||
#self.state.avgp, self.state.positions = self.interface.pos()
|
||||
self.state.ilog(e="SELL notifikace "+str(data.order.status), msg="update pozic", old_avgp=old_avgp, old_pos=old_pos, avgp=self.state.account_variables[account.name].avgp, pos=self.state.account_variables[account.name].positions, orderid=str(data.order.id))
|
||||
#self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = self.interface.pos()
|
||||
|
||||
if data.event == TradeEvent.FILL or data.event == TradeEvent.CANCELED:
|
||||
print("Příchozí SELL notifikace - complete FILL nebo CANCEL", data.event)
|
||||
self.state.vars.pending = None
|
||||
a,p = self.interface.pos()
|
||||
self.state.account_variables[account.name].pending = None
|
||||
a,p = self.interface[account.name].pos() #TBD maybe optimize for speed
|
||||
#pri chybe api nechavame puvodni hodnoty
|
||||
if a != -1:
|
||||
self.state.avgp, self.state.positions = a,p
|
||||
self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = a,p
|
||||
else: self.state.ilog(e=f"Chyba pri dotažení self.interface.pos() {a}")
|
||||
#ic(self.state.avgp, self.state.positions)
|
||||
#ic(self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions)
|
||||
|
||||
#this parent method is called by strategy just once before waiting for first data
|
||||
def strat_init(self):
|
||||
@ -441,48 +457,47 @@ class StrategyClassicSL(Strategy):
|
||||
else:
|
||||
self.next(item, self.state)
|
||||
|
||||
|
||||
#overidden methods
|
||||
# pouziva se pri vstupu long nebo exitu short
|
||||
# osetrit uzavreni s vice nez mam
|
||||
def buy(self, size = None, repeat: bool = False):
|
||||
def buy(self, account: Account, size = None, repeat: bool = False):
|
||||
print("overriden buy method")
|
||||
if size is None:
|
||||
sizer = self.state.vars.chunk
|
||||
else:
|
||||
sizer = size
|
||||
#jde o uzavreni short pozice
|
||||
if int(self.state.positions) < 0 and (int(self.state.positions) + int(sizer)) > 0:
|
||||
self.state.ilog(e="buy nelze nakoupit vic nez shortuji", positions=self.state.positions, size=size)
|
||||
if int(self.state.account_variables[account.name].positions) < 0 and (int(self.state.account_variables[account.name].positions) + int(sizer)) > 0:
|
||||
self.state.ilog(e="buy nelze nakoupit vic nez shortuji", positions=self.state.account_variables[account.name].positions, size=size)
|
||||
printanyway("buy nelze nakoupit vic nez shortuji")
|
||||
return -2
|
||||
|
||||
if int(self.state.positions) >= self.state.vars.maxpozic:
|
||||
self.state.ilog(e="buy Maxim mnozstvi naplneno", positions=self.state.positions)
|
||||
if int(self.state.account_variables[account.name].positions) >= self.state.vars.maxpozic:
|
||||
self.state.ilog(e="buy Maxim mnozstvi naplneno", positions=self.state.account_variables[account.name].positions)
|
||||
printanyway("max mnostvi naplneno")
|
||||
return 0
|
||||
|
||||
self.state.blockbuy = 1
|
||||
self.state.vars.lastbuyindex = self.state.bars['index'][-1]
|
||||
#self.state.ilog(e="send MARKET buy to if", msg="S:"+str(size), ltp=self.state.interface.get_last_price(self.state.symbol))
|
||||
self.state.ilog(e="send MARKET buy to if", msg="S:"+str(size), ltp=self.state.bars['close'][-1])
|
||||
return self.state.interface.buy(size=sizer)
|
||||
return self.state.interface[account.name].buy(size=sizer)
|
||||
|
||||
#overidden methods
|
||||
# pouziva se pri vstupu short nebo exitu long
|
||||
def sell(self, size = None, repeat: bool = False):
|
||||
def sell(self, account: Account, size = None, repeat: bool = False):
|
||||
print("overriden sell method")
|
||||
if size is None:
|
||||
size = abs(int(self.state.positions))
|
||||
size = abs(int(self.state.account_variables[account.name].positions))
|
||||
|
||||
#jde o uzavreni long pozice
|
||||
if int(self.state.positions) > 0 and (int(self.state.positions) - int(size)) < 0:
|
||||
self.state.ilog(e="nelze prodat vic nez longuji", positions=self.state.positions, size=size)
|
||||
if int(self.state.account_variables[account.name].positions) > 0 and (int(self.state.account_variables[account.name].positions) - int(size)) < 0:
|
||||
self.state.ilog(e="nelze prodat vic nez longuji", positions=self.state.account_variables[account.name].positions, size=size)
|
||||
printanyway("nelze prodat vic nez longuji")
|
||||
return -2
|
||||
|
||||
#pokud shortuji a mam max pozic
|
||||
if int(self.state.positions) < 0 and abs(int(self.state.positions)) >= self.state.vars.maxpozic:
|
||||
self.state.ilog(e="short - Maxim mnozstvi naplneno", positions=self.state.positions, size=size)
|
||||
if int(self.state.account_variables[account.name].positions) < 0 and abs(int(self.state.account_variables[account.name].positions)) >= self.state.vars.maxpozic:
|
||||
self.state.ilog(e="short - Maxim mnozstvi naplneno", positions=self.state.account_variables[account.name].positions, size=size)
|
||||
printanyway("short - Maxim mnozstvi naplneno")
|
||||
return 0
|
||||
|
||||
@ -490,4 +505,4 @@ class StrategyClassicSL(Strategy):
|
||||
#self.state.vars.lastbuyindex = self.state.bars['index'][-1]
|
||||
#self.state.ilog(e="send MARKET SELL to if", msg="S:"+str(size), ltp=self.state.interface.get_last_price(self.state.symbol))
|
||||
self.state.ilog(e="send MARKET SELL to if", msg="S:"+str(size), ltp=self.state.bars['close'][-1])
|
||||
return self.state.interface.sell(size=size)
|
||||
return self.state.interface[account.name].sell(size=size)
|
||||
@ -2,7 +2,7 @@
|
||||
Strategy base class
|
||||
"""
|
||||
from datetime import datetime
|
||||
from v2realbot.utils.utils import AttributeDict, zoneNY, is_open_rush, is_close_rush, json_serial, print
|
||||
from v2realbot.utils.utils import AttributeDict, zoneNY, is_open_rush, is_close_rush, json_serial, print, gaka
|
||||
from v2realbot.utils.tlog import tlog
|
||||
from v2realbot.utils.ilog import insert_log, insert_log_multiple_queue
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Order, Account
|
||||
@ -16,11 +16,11 @@ from v2realbot.loader.trade_ws_streamer import Trade_WS_Streamer
|
||||
from v2realbot.interfaces.general_interface import GeneralInterface
|
||||
from v2realbot.interfaces.backtest_interface import BacktestInterface
|
||||
from v2realbot.interfaces.live_interface import LiveInterface
|
||||
import v2realbot.common.PrescribedTradeModel as ptm
|
||||
import v2realbot.common.model as ptm
|
||||
from alpaca.trading.enums import OrderSide
|
||||
from v2realbot.backtesting.backtester import Backtester
|
||||
#from alpaca.trading.models import TradeUpdate
|
||||
from v2realbot.common.model import TradeUpdate
|
||||
from v2realbot.common.model import TradeUpdate, AccountVariables
|
||||
from alpaca.trading.enums import TradeEvent, OrderStatus
|
||||
from threading import Event, current_thread
|
||||
import orjson
|
||||
@ -30,6 +30,7 @@ from collections import defaultdict
|
||||
import v2realbot.strategyblocks.activetrade.sl.optimsl as optimsl
|
||||
from tqdm import tqdm
|
||||
import v2realbot.utils.config_handler as cfh
|
||||
from typing import Dict, Set
|
||||
|
||||
if PROFILING_NEXT_ENABLED:
|
||||
from pyinstrument import Profiler
|
||||
@ -50,7 +51,8 @@ class Strategy:
|
||||
self.rectype: RecordType = None
|
||||
self.nextnew = 1
|
||||
self.btdata: list = []
|
||||
self.interface: GeneralInterface = None
|
||||
self.interface: Dict[str, GeneralInterface] = {}
|
||||
self.order_notifs: Dict[str, LiveOrderUpdatesStreamer] = {}
|
||||
self.state: StrategyState = None
|
||||
self.bt: Backtester = None
|
||||
self.debug = False
|
||||
@ -60,8 +62,8 @@ class Strategy:
|
||||
self.open_rush = open_rush
|
||||
self.close_rush = close_rush
|
||||
self._streams = []
|
||||
#primary account from runReqs
|
||||
self.account = account
|
||||
self.key = get_key(mode=self.mode, account=self.account)
|
||||
self.rtqueue = None
|
||||
self.runner_id = runner_id
|
||||
self.ilog_save = ilog_save
|
||||
@ -69,6 +71,9 @@ class Strategy:
|
||||
self.secondary_res_start_index = dict()
|
||||
self.last_index = -1
|
||||
|
||||
#set of all accounts (Account) including those from stratvars
|
||||
self.accounts = self.get_accounts_in_stratvars_and_reqs()
|
||||
|
||||
#TODO predelat na dynamické queues
|
||||
self.q1 = queue.Queue()
|
||||
self.q2 = queue.Queue()
|
||||
@ -83,6 +88,25 @@ class Strategy:
|
||||
self.hard_stop = False #indikuje hard stop, tedy vypnuti strategie
|
||||
self.soft_stop = False #indikuje soft stop (napr. při dosažení max zisku/ztráty), tedy pokracovani strategie, vytvareni dat, jen bez obchodu
|
||||
|
||||
def get_accounts_in_stratvars_and_reqs(self) -> Set:
|
||||
"""
|
||||
Helper that retrieves distinct account values used in stratvars and in runRequest.
|
||||
|
||||
Returns:
|
||||
set: A set of unique account values.
|
||||
"""
|
||||
account_keywords = ['account', 'account_long', 'account_short']
|
||||
account_values = set()
|
||||
|
||||
for signal_value in self.stratvars.get('signals', {}).values():
|
||||
for key in account_keywords:
|
||||
if key in signal_value:
|
||||
account_values.add(Account(signal_value[key]))
|
||||
|
||||
account_values.add(Account(self.account))
|
||||
printnow("Distinct account values:", account_values)
|
||||
return account_values
|
||||
|
||||
#prdelat queue na dynamic - podle toho jak bud uchtit pracovat s multiresolutions
|
||||
#zatim jen jedna q1
|
||||
#TODO zaroven strategie musi vedet o rectypu, protoze je zpracovava
|
||||
@ -116,25 +140,33 @@ class Strategy:
|
||||
return -1
|
||||
|
||||
self.debug = debug
|
||||
self.key = get_key(mode=mode, account=self.account)
|
||||
|
||||
if mode == Mode.LIVE or mode == Mode.PAPER:
|
||||
#data loader thread
|
||||
self.dataloader = Trade_WS_Streamer(name="WS-LDR-"+self.name)
|
||||
self.interface = LiveInterface(symbol=self.symbol, key=self.key)
|
||||
#populate interfaces for each account
|
||||
for account in self.accounts:
|
||||
#get key for account
|
||||
key = get_key(mode=mode, account=Account(account))
|
||||
self.interface[account.name] = LiveInterface(symbol=self.symbol, key=key)
|
||||
# order notif thread
|
||||
self.order_notifs = LiveOrderUpdatesStreamer(key=self.key, name="WS-STRMR-" + self.name)
|
||||
self.order_notifs[account.name] = LiveOrderUpdatesStreamer(key=key, name="WS-STRMR-" + account.name + "-" + self.name, account=account)
|
||||
#propojujeme notifice s interfacem (pro callback)
|
||||
self.order_notifs.connect_callback(self)
|
||||
self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, ilog_save=self.ilog_save)
|
||||
self.order_notifs[account.name].connect_callback(self)
|
||||
|
||||
self.state = StrategyState(name=self.name, accounts=self.accounts, account=self.account, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, ilog_save=self.ilog_save)
|
||||
|
||||
elif mode == Mode.BT:
|
||||
|
||||
self.dataloader = Trade_Offline_Streamer(start, end, btdata=self.btdata)
|
||||
self.bt = Backtester(symbol = self.symbol, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end)
|
||||
self.bt = Backtester(symbol = self.symbol, accounts=self.accounts, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end)
|
||||
|
||||
self.interface = BacktestInterface(symbol=self.symbol, bt=self.bt)
|
||||
self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, bt=self.bt, ilog_save=self.ilog_save)
|
||||
#populate interfaces for each account
|
||||
for account in self.accounts:
|
||||
#pro backtest volame stejne oklicujeme interface
|
||||
self.interface[account.name] = BacktestInterface(symbol=self.symbol, bt=self.bt, account=account)
|
||||
self.state = StrategyState(name=self.name, accounts=self.accounts, account=self.account, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, bt=self.bt, ilog_save=self.ilog_save)
|
||||
#no callback from bt, it is called directly
|
||||
self.order_notifs = None
|
||||
|
||||
##streamer bude plnit trady do listu trades - nad kterym bude pracovat paper trade
|
||||
@ -142,10 +174,10 @@ class Strategy:
|
||||
self.dataloader.add_stream(TradeAggregator2List(symbol=self.symbol,btdata=self.btdata,rectype=RecordType.TRADE))
|
||||
elif mode == Mode.PREP:
|
||||
#bt je zde jen pro udrzeni BT casu v logu atp. JInak jej nepouzivame.
|
||||
self.bt = Backtester(symbol = self.symbol, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end)
|
||||
self.bt = Backtester(symbol = self.symbol, accounts=self.accounts, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end)
|
||||
self.interface = None
|
||||
#self.interface = BacktestInterface(symbol=self.symbol, bt=self.bt)
|
||||
self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, bt=self.bt, ilog_save=self.ilog_save)
|
||||
self.state = StrategyState(name=self.name, accounts=self.accounts, account=self.account, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, bt=self.bt, ilog_save=self.ilog_save)
|
||||
self.order_notifs = None
|
||||
|
||||
else:
|
||||
@ -314,13 +346,15 @@ class Strategy:
|
||||
""""refresh positions and avgp - for CBAR once per confirmed, for BARS each time"""
|
||||
def refresh_positions(self, item):
|
||||
if self.rectype == RecordType.BAR:
|
||||
a,p = self.interface.pos()
|
||||
for account in self.accounts:
|
||||
a,p = self.interface[account.name].pos()
|
||||
if a != -1:
|
||||
self.state.avgp, self.state.positions = a,p
|
||||
self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = a, p
|
||||
elif self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME, RecordType.CBARDOLLAR, RecordType.CBARRENKO) and item['confirmed'] == 1:
|
||||
a,p = self.interface.pos()
|
||||
for account in self.accounts:
|
||||
a,p = self.interface[account.name].pos()
|
||||
if a != -1:
|
||||
self.state.avgp, self.state.positions = a,p
|
||||
self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = a, p
|
||||
|
||||
"""update state.last_trade_time a time of iteration"""
|
||||
def update_times(self, item):
|
||||
@ -416,7 +450,11 @@ class Strategy:
|
||||
|
||||
if self.mode == Mode.LIVE or self.mode == Mode.PAPER:
|
||||
#live notification thread
|
||||
self.order_notifs.start()
|
||||
#for all keys in self.order_notifs call start()
|
||||
for key in self.order_notifs:
|
||||
self.order_notifs[key].start()
|
||||
|
||||
#self.order_notifs.start()
|
||||
elif self.mode == Mode.BT or self.mode == Mode.PREP:
|
||||
self.bt.backtest_start = datetime.now()
|
||||
|
||||
@ -486,7 +524,7 @@ class Strategy:
|
||||
self.stop()
|
||||
|
||||
if self.mode == Mode.BT:
|
||||
print("REQUEST COUNT:", self.interface.mincnt)
|
||||
print("REQUEST COUNT:", {account_str:self.interface[account_str].mincnt for account_str in self.interface})
|
||||
|
||||
self.bt.backtest_end = datetime.now()
|
||||
#print(40*"*",self.mode, "BACKTEST RESULTS",40*"*")
|
||||
@ -500,7 +538,9 @@ class Strategy:
|
||||
|
||||
#disconnect strategy from websocket trader updates
|
||||
if self.mode == Mode.LIVE or self.mode == Mode.PAPER:
|
||||
self.order_notifs.disconnect_callback(self)
|
||||
for key in self.order_notifs:
|
||||
self.order_notifs[key].disconnect_callback(self)
|
||||
#self.order_notifs.disconnect_callback(self)
|
||||
|
||||
#necessary only for shared loaders (to keep it running for other stratefies)
|
||||
for i in self._streams:
|
||||
@ -541,11 +581,11 @@ class Strategy:
|
||||
#for order updates from LIVE or BACKTEST
|
||||
#updates are sent only for SYMBOL of strategy
|
||||
|
||||
async def order_updates(self, data: TradeUpdate):
|
||||
async def order_updates(self, data: TradeUpdate, account: Account):
|
||||
if self.mode == Mode.LIVE or self.mode == Mode.PAPER:
|
||||
now = datetime.now().timestamp()
|
||||
#z alpakýho TradeEvent si udelame svuj rozsireny TradeEvent (obsahujici navic profit atp.)
|
||||
data = TradeUpdate(**data.dict())
|
||||
data = TradeUpdate(**data.dict(), account=account)
|
||||
else:
|
||||
now = self.bt.time
|
||||
|
||||
@ -633,16 +673,13 @@ class Strategy:
|
||||
for key, value in self.state.statinds.items():
|
||||
rt_out["statinds"][key] = value
|
||||
|
||||
#vkladame average price and positions, pokud existuji
|
||||
#self.state.avgp , self.state.positions
|
||||
|
||||
#pro typ strategie Classic, posilame i vysi stoploss
|
||||
try:
|
||||
sl_value = self.state.vars["activeTrade"].stoploss_value
|
||||
sl_value = gaka(self.state.account_variables, "activeTrade", lambda x: x.stoploss_value)
|
||||
except (KeyError, AttributeError):
|
||||
sl_value = None
|
||||
|
||||
rt_out["positions"] = dict(time=self.state.time, positions=self.state.positions, avgp=self.state.avgp, sl_value=sl_value)
|
||||
rt_out["positions"] = dict(time=self.state.time, positions=gaka(self.state.account_variables, "positions"), avgp=gaka(self.state.account_variables,), sl_value=sl_value)
|
||||
|
||||
#vkladame limitku a pendingbuys
|
||||
try:
|
||||
@ -718,17 +755,21 @@ class StrategyState:
|
||||
"""Strategy Stat object that is passed to callbacks
|
||||
note:
|
||||
state.time
|
||||
state.interface.time
|
||||
state.interface[account.name].time
|
||||
accounts = set of all accounts (strings)
|
||||
account = enum of primary account (Account)
|
||||
většinou mají stejnou hodnotu, ale lišit se mužou např. v případě BT callbacku - kdy se v rámci okna končící state.time realizují objednávky, které
|
||||
triggerují callback, který následně vyvolá např. buy (ten se musí ale udít v čase fillu, tzn. callback si nastaví čas interfacu na filltime)
|
||||
po dokončení bt kroků před zahájením iterace "NEXT" se časy znovu updatnout na původni state.time
|
||||
"""
|
||||
def __init__(self, name: str, symbol: str, stratvars: AttributeDict, bars: AttributeDict = {}, trades: AttributeDict = {}, interface: GeneralInterface = None, rectype: RecordType = RecordType.BAR, runner_id: UUID = None, bt: Backtester = None, ilog_save: bool = False):
|
||||
def __init__(self, name: str, symbol: str, accounts: set, account: Account, stratvars: AttributeDict, bars: AttributeDict = {}, trades: AttributeDict = {}, interface: GeneralInterface = None, rectype: RecordType = RecordType.BAR, runner_id: UUID = None, bt: Backtester = None, ilog_save: bool = False):
|
||||
self.vars = stratvars
|
||||
self.interface = interface
|
||||
self.positions = 0
|
||||
self.avgp = 0
|
||||
self.blockbuy = 0
|
||||
self.account = account #primary account
|
||||
self.accounts = accounts
|
||||
#populate account variables dictionary
|
||||
self.account_variables: Dict[str, AccountVariables] = {account.name: AccountVariables() for account in self.accounts}
|
||||
|
||||
self.name = name
|
||||
self.symbol = symbol
|
||||
self.rectype = rectype
|
||||
@ -737,11 +778,10 @@ class StrategyState:
|
||||
self.time = None
|
||||
#time of last trade processed
|
||||
self.last_trade_time = 0
|
||||
self.last_entry_price=dict(long=0,short=999)
|
||||
self.last_entry_price={key:dict(long=0,short=999) for key in self.accounts}
|
||||
self.resolution = None
|
||||
self.runner_id = runner_id
|
||||
self.bt = bt
|
||||
self.dont_exit_already_activated = False
|
||||
self.docasny_rel_profit = []
|
||||
self.ilog_save = ilog_save
|
||||
self.sl_optimizer_short = optimsl.SLOptimizer(ptm.TradeDirection.SHORT)
|
||||
@ -779,18 +819,14 @@ class StrategyState:
|
||||
#secondary resolution indicators
|
||||
#self.secondary_indicators = AttributeDict(time=[], sec_price=[])
|
||||
self.statinds = AttributeDict()
|
||||
#these methods can be overrided by StrategyType (to add or alter its functionality)
|
||||
self.buy = self.interface.buy
|
||||
self.buy_l = self.interface.buy_l
|
||||
self.sell = self.interface.sell
|
||||
self.sell_l = self.interface.sell_l
|
||||
|
||||
self.cancel_pending_buys = None
|
||||
self.iter_log_list = []
|
||||
self.dailyBars = defaultdict(dict)
|
||||
#celkovy profit (prejmennovat na profit_cum)
|
||||
self.profit = 0
|
||||
self.profit = 0 #TODO key by account?
|
||||
#celkovy relativni profit (obsahuje pole relativnich zisku, z jeho meanu se spocita celkovy rel_profit_cu,)
|
||||
self.rel_profit_cum = []
|
||||
self.rel_profit_cum = []#TODO key by account?
|
||||
self.tradeList = []
|
||||
#nova promenna pro externi data do ArchiveDetaili, napr. pro zobrazeni v grafu, je zde např. SL history
|
||||
self.extData = defaultdict(dict)
|
||||
@ -799,6 +835,25 @@ class StrategyState:
|
||||
self.today_market_close = None
|
||||
self.classed_indicators = {}
|
||||
|
||||
#quick interface actions to access from state without having to write interface[account.name].buy_l
|
||||
def buy_l(self, account: Account, price: float, size: int = 1, repeat: bool = False, force: int = 0):
|
||||
self.interface[account.name].buy_l(price, size, repeat, force)
|
||||
|
||||
def buy(self, account: Account, size = 1, repeat: bool = False):
|
||||
self.interface[account.name].buy(size, repeat)
|
||||
|
||||
def sell_l(self, account: Account, price: float, size: int = 1, repeat: bool = False):
|
||||
self.interface[account.name].sell_l(price, size, repeat)
|
||||
|
||||
def sell(self, account: Account, size = 1, repeat: bool = False):
|
||||
self.interface[account.name].sell(size, repeat)
|
||||
|
||||
def repl(self, account: Account, orderid: str, price: float = None, size: int = 1, repeat: bool = False):
|
||||
self.interface[account.name].repl(orderid, price, size, repeat)
|
||||
|
||||
def cancel(self, account: Account, orderid: str):
|
||||
self.interface[account.name].cancel(orderid)
|
||||
|
||||
def release(self):
|
||||
#release large variables
|
||||
self.bars = None
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
from v2realbot.strategyblocks.activetrade.sl.trailsl import trail_SL_management
|
||||
from v2realbot.strategyblocks.activetrade.close.evaluate_close import eval_close_position
|
||||
|
||||
from v2realbot.utils.utils import gaka
|
||||
def manage_active_trade(state, data):
|
||||
trade = state.vars.activeTrade
|
||||
if trade is None:
|
||||
return -1
|
||||
trail_SL_management(state, data)
|
||||
eval_close_position(state, data)
|
||||
accountsWithActiveTrade = gaka(state.account_variables, "activeTrade", None, lambda x: x is not None)
|
||||
# {"account1": activeTrade,
|
||||
# "account2": activeTrade}
|
||||
|
||||
if len(accountsWithActiveTrade.values()) == 0:
|
||||
return
|
||||
|
||||
trail_SL_management(state, accountsWithActiveTrade, data)
|
||||
eval_close_position(state, accountsWithActiveTrade, data)
|
||||
@ -1,7 +1,7 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model 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
|
||||
from v2realbot.common.model import SLHistory
|
||||
@ -18,52 +18,61 @@ import os
|
||||
from traceback import format_exc
|
||||
from v2realbot.strategyblocks.activetrade.helpers import insert_SL_history
|
||||
|
||||
#TODO tady jsem taky skoncil a pak zpetna evaluate_close (mozna zde staci jen account?)
|
||||
|
||||
# - close means change status in prescribed Trends,update profit, delete from activeTrade
|
||||
def close_position(state, data, direction: TradeDirection, reason: str, followup: Followup = None):
|
||||
def close_position(state: StrategyState, activeTrade: Trade, data, direction: TradeDirection, reason: str, followup: Followup = None):
|
||||
followup_text = str(followup) if followup is not None else ""
|
||||
state.ilog(lvl=1,e=f"CLOSING TRADE {followup_text} {reason} {str(direction)}", curr_price=data["close"], trade=state.vars.activeTrade)
|
||||
positions = state.account_variables[activeTrade.account.name].positions
|
||||
state.ilog(lvl=1,e=f"CLOSING TRADE {followup_text} {reason} {str(direction)}", curr_price=data["close"], trade=activeTrade)
|
||||
if direction == TradeDirection.SHORT:
|
||||
res = state.buy(size=abs(int(state.positions)))
|
||||
res = state.buy(account=activeTrade.account, size=abs(int(positions)))
|
||||
if isinstance(res, int) and res < 0:
|
||||
raise Exception(f"error in required operation {reason} {res}")
|
||||
|
||||
elif direction == TradeDirection.LONG:
|
||||
res = state.sell(size=state.positions)
|
||||
res = state.sell(account=activeTrade.account, size=positions)
|
||||
if isinstance(res, int) and res < 0:
|
||||
raise Exception(f"error in required operation STOPLOSS SELL {res}")
|
||||
raise Exception(f"error in required operation STOPLOSS SELL {res}") #TBD error handling
|
||||
|
||||
else:
|
||||
raise Exception(f"unknow TradeDirection in close_position")
|
||||
|
||||
#pri uzavreni tradu zapisujeme SL history - lepsi zorbazeni v grafu
|
||||
insert_SL_history(state)
|
||||
state.dont_exit_already_activated = False
|
||||
state.vars.pending = state.vars.activeTrade.id
|
||||
state.vars.activeTrade = None
|
||||
insert_SL_history(state, activeTrade)
|
||||
state.account_variables[activeTrade.account.name].pending = activeTrade.id
|
||||
state.account_variables[activeTrade.account.name].activeTrade = None
|
||||
#state.account_variables[activeTrade.account.name].last_exit_index = data["index"]
|
||||
state.vars.last_exit_index = data["index"]
|
||||
state.account_variables[activeTrade.account.name].dont_exit_already_activated = False
|
||||
if followup is not None:
|
||||
state.vars.requested_followup = followup
|
||||
state.account_variables[activeTrade.account.name].requested_followup = followup
|
||||
|
||||
#close only partial position - no followup here, size multiplier must be between 0 and 1
|
||||
def close_position_partial(state, data, direction: TradeDirection, reason: str, size: float):
|
||||
def close_position_partial(state, activeTrade: Trade,data, direction: TradeDirection, reason: str, size: float):
|
||||
positions = state.account_variables[activeTrade.account.name].positions
|
||||
if size <= 0 or size >=1:
|
||||
raise Exception(f"size must be betweem 0 and 1")
|
||||
size_abs = abs(int(int(state.positions)*size))
|
||||
state.ilog(lvl=1,e=f"CLOSING TRADE PART: {size_abs} {size} {reason} {str(direction)}", curr_price=data["close"], trade=state.vars.activeTrade)
|
||||
size_abs = abs(int(int(positions)*size))
|
||||
state.ilog(lvl=1,e=f"CLOSING TRADE PART: {size_abs} {size} {reason} {str(direction)}", curr_price=data["close"], trade=activeTrade)
|
||||
if direction == TradeDirection.SHORT:
|
||||
res = state.buy(size=size_abs)
|
||||
res = state.buy(account=activeTrade.account, size=size_abs)
|
||||
if isinstance(res, int) and res < 0:
|
||||
raise Exception(f"error in required operation STOPLOSS PARTIAL BUY {reason} {res}")
|
||||
|
||||
elif direction == TradeDirection.LONG:
|
||||
res = state.sell(size=size_abs)
|
||||
res = state.sell(account=activeTrade.account, size=size_abs)
|
||||
if isinstance(res, int) and res < 0:
|
||||
raise Exception(f"error in required operation STOPLOSS PARTIAL SELL {res}")
|
||||
else:
|
||||
raise Exception(f"unknow TradeDirection in close_position")
|
||||
|
||||
#pri uzavreni tradu zapisujeme SL history - lepsi zorbazeni v grafu
|
||||
insert_SL_history(state)
|
||||
state.vars.pending = state.vars.activeTrade.id
|
||||
insert_SL_history(state, activeTrade)
|
||||
state.account_variables[activeTrade.account.name].pending = activeTrade.id
|
||||
state.account_variables[activeTrade.account.name].activeTrade = None
|
||||
state.account_variables[activeTrade.account.name].dont_exit_already_activated = False
|
||||
#state.account_variables[activeTrade.account.name].last_exit_index = data["index"]
|
||||
|
||||
#state.vars.activeTrade = None
|
||||
#state.vars.last_exit_index = data["index"]
|
||||
state.vars.last_exit_index = data["index"] #ponechano mimo account
|
||||
@ -1,7 +1,7 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model 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
|
||||
from v2realbot.common.model import SLHistory
|
||||
@ -12,9 +12,9 @@ from threading import Event
|
||||
import os
|
||||
from traceback import format_exc
|
||||
from v2realbot.strategyblocks.indicators.helpers import evaluate_directive_conditions
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_override_for_active_trade, normalize_tick
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_signal_section_directive, normalize_tick
|
||||
|
||||
def dontexit_protection_met(state, data, direction: TradeDirection):
|
||||
def dontexit_protection_met(state, activeTrade: Trade, data, direction: TradeDirection):
|
||||
if direction == TradeDirection.LONG:
|
||||
smer = "long"
|
||||
else:
|
||||
@ -24,58 +24,64 @@ def dontexit_protection_met(state, data, direction: TradeDirection):
|
||||
#vyreseno pri kazde aktivaci se vyplni flag already_activated
|
||||
#pri naslednem false podminky se v pripade, ze je aktivovany flag posle True -
|
||||
#take se vyrusi v closu
|
||||
def process_result(result):
|
||||
def process_result(result, account):
|
||||
if result:
|
||||
state.dont_exit_already_activated = True
|
||||
state.account_variables[account.name].dont_exit_already_activated = True
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def evaluate_result():
|
||||
mother_signal = state.vars.activeTrade.generated_by
|
||||
mother_signal = activeTrade.generated_by
|
||||
dont_exit_already_activated = state.account_variables[activeTrade.account.name].dont_exit_already_activated
|
||||
|
||||
if mother_signal is not None:
|
||||
#TESTUJEME DONT_EXIT_
|
||||
cond_dict = state.vars.conditions[KW.dont_exit][mother_signal][smer]
|
||||
#OR
|
||||
result, conditions_met = evaluate_directive_conditions(state, cond_dict, "OR")
|
||||
state.ilog(lvl=1,e=f"DONT_EXIT {mother_signal} {smer} =OR= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(state.dont_exit_already_activated))
|
||||
state.ilog(lvl=1,e=f"DONT_EXIT {mother_signal} {smer} =OR= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(dont_exit_already_activated))
|
||||
if result:
|
||||
return True
|
||||
|
||||
#OR neprosly testujeme AND
|
||||
result, conditions_met = evaluate_directive_conditions(state, cond_dict, "AND")
|
||||
state.ilog(lvl=1,e=f"DONT_EXIT {mother_signal} {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(state.dont_exit_already_activated))
|
||||
state.ilog(lvl=1,e=f"DONT_EXIT {mother_signal} {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(dont_exit_already_activated))
|
||||
if result:
|
||||
return True
|
||||
|
||||
cond_dict = state.vars.conditions[KW.dont_exit]["common"][smer]
|
||||
#OR
|
||||
result, conditions_met = evaluate_directive_conditions(state, cond_dict, "OR")
|
||||
state.ilog(lvl=1,e=f"DONT_EXIT common {smer} =OR= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(state.dont_exit_already_activated))
|
||||
state.ilog(lvl=1,e=f"DONT_EXIT common {smer} =OR= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(dont_exit_already_activated))
|
||||
if result:
|
||||
return True
|
||||
|
||||
#OR neprosly testujeme AND
|
||||
result, conditions_met = evaluate_directive_conditions(state, cond_dict, "AND")
|
||||
state.ilog(lvl=1,e=f"DONT_EXIT common {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(state.dont_exit_already_activated))
|
||||
state.ilog(lvl=1,e=f"DONT_EXIT common {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(dont_exit_already_activated))
|
||||
return result
|
||||
|
||||
#nejprve evaluujeme vsechny podminky
|
||||
result = evaluate_result()
|
||||
|
||||
#pak evaluujeme vysledek a vracíme
|
||||
return process_result(result)
|
||||
return process_result(result, activeTrade.account)
|
||||
|
||||
|
||||
def exit_conditions_met(state, data, direction: TradeDirection):
|
||||
def exit_conditions_met(state: StrategyState, activeTrade: Trade, data, direction: TradeDirection):
|
||||
if direction == TradeDirection.LONG:
|
||||
smer = "long"
|
||||
else:
|
||||
smer = "short"
|
||||
|
||||
signal_name = activeTrade.generated_by
|
||||
last_entry_index = state.account_variables[activeTrade.account.name].last_entry_index
|
||||
avgp = state.account_variables[activeTrade.account.name].avgp
|
||||
positions = state.account_variables[activeTrade.account.name].positions
|
||||
|
||||
directive_name = "exit_cond_only_on_confirmed"
|
||||
exit_cond_only_on_confirmed = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
exit_cond_only_on_confirmed = get_signal_section_directive(state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
|
||||
if exit_cond_only_on_confirmed and data['confirmed'] == 0:
|
||||
state.ilog(lvl=0,e="EXIT COND ONLY ON CONFIRMED BAR")
|
||||
@ -83,20 +89,20 @@ def exit_conditions_met(state, data, direction: TradeDirection):
|
||||
|
||||
## minimální počet barů od vstupu
|
||||
directive_name = "exit_cond_req_bars"
|
||||
exit_cond_req_bars = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 1))
|
||||
exit_cond_req_bars = get_signal_section_directive(state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 1))
|
||||
|
||||
if state.vars.last_in_index is not None:
|
||||
index_to_compare = int(state.vars.last_in_index)+int(exit_cond_req_bars)
|
||||
if last_entry_index is not None:
|
||||
index_to_compare = int(last_entry_index)+int(exit_cond_req_bars)
|
||||
if int(data["index"]) < index_to_compare:
|
||||
state.ilog(lvl=1,e=f"EXIT COND WAITING on required bars from IN {exit_cond_req_bars} TOO SOON", currindex=data["index"], index_to_compare=index_to_compare, last_in_index=state.vars.last_in_index)
|
||||
state.ilog(lvl=1,e=f"EXIT COND WAITING on required bars from IN {exit_cond_req_bars} TOO SOON", currindex=data["index"], index_to_compare=index_to_compare, last_entry_index=last_entry_index)
|
||||
return False
|
||||
|
||||
#POKUD je nastaven MIN PROFIT, zkontrolujeme ho a az pripadne pustime CONDITIONY
|
||||
directive_name = "exit_cond_min_profit"
|
||||
exit_cond_min_profit_nodir = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
exit_cond_min_profit_nodir = get_signal_section_directive(state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
|
||||
directive_name = "exit_cond_min_profit_" + str(smer)
|
||||
exit_cond_min_profit = get_override_for_active_trade(state, directive_name=directive_name, default_value=exit_cond_min_profit_nodir)
|
||||
exit_cond_min_profit = get_signal_section_directive(state, signal_name=signal_name,directive_name=directive_name, default_value=exit_cond_min_profit_nodir)
|
||||
|
||||
|
||||
#máme nastavený exit_cond_min_profit
|
||||
@ -105,10 +111,10 @@ def exit_conditions_met(state, data, direction: TradeDirection):
|
||||
|
||||
if exit_cond_min_profit is not None:
|
||||
exit_cond_min_profit_normalized = normalize_tick(state, data, float(exit_cond_min_profit))
|
||||
exit_cond_goal_price = price2dec(float(state.avgp)+exit_cond_min_profit_normalized,3) if int(state.positions) > 0 else price2dec(float(state.avgp)-exit_cond_min_profit_normalized,3)
|
||||
exit_cond_goal_price = price2dec(float(avgp)+exit_cond_min_profit_normalized,3) if int(positions) > 0 else price2dec(float(avgp)-exit_cond_min_profit_normalized,3)
|
||||
curr_price = float(data["close"])
|
||||
state.ilog(lvl=1,e=f"EXIT COND min profit {exit_cond_goal_price=} {exit_cond_min_profit=} {exit_cond_min_profit_normalized=} {curr_price=}")
|
||||
if (int(state.positions) < 0 and curr_price<=exit_cond_goal_price) or (int(state.positions) > 0 and curr_price>=exit_cond_goal_price):
|
||||
if (int(positions) < 0 and curr_price<=exit_cond_goal_price) or (int(positions) > 0 and curr_price>=exit_cond_goal_price):
|
||||
state.ilog(lvl=1,e=f"EXIT COND min profit PASS - POKRACUJEME")
|
||||
else:
|
||||
state.ilog(lvl=1,e=f"EXIT COND min profit NOT PASS")
|
||||
@ -137,10 +143,10 @@ def exit_conditions_met(state, data, direction: TradeDirection):
|
||||
#bereme bud exit condition signalu, ktery activeTrade vygeneroval+ fallback na general
|
||||
state.ilog(lvl=0,e=f"EXIT CONDITIONS ENTRY {smer}", conditions=state.vars.conditions[KW.exit])
|
||||
|
||||
mother_signal = state.vars.activeTrade.generated_by
|
||||
mother_signal = signal_name
|
||||
|
||||
if mother_signal is not None:
|
||||
cond_dict = state.vars.conditions[KW.exit][state.vars.activeTrade.generated_by][smer]
|
||||
cond_dict = state.vars.conditions[KW.exit][signal_name][smer]
|
||||
result, conditions_met = evaluate_directive_conditions(state, cond_dict, "OR")
|
||||
state.ilog(lvl=1,e=f"EXIT CONDITIONS of {mother_signal} =OR= {result}", **conditions_met, cond_dict=cond_dict)
|
||||
if result:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model 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
|
||||
from v2realbot.common.model import SLHistory
|
||||
@ -18,10 +18,10 @@ import os
|
||||
from traceback import format_exc
|
||||
from v2realbot.strategyblocks.activetrade.helpers import insert_SL_history
|
||||
from v2realbot.strategyblocks.activetrade.close.conditions import dontexit_protection_met, exit_conditions_met
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_override_for_active_trade, keyword_conditions_met
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_signal_section_directive
|
||||
|
||||
|
||||
def eod_exit_activated(state: StrategyState, data, direction: TradeDirection):
|
||||
def eod_exit_activated(state: StrategyState, activeTrade: Trade, data, direction: TradeDirection):
|
||||
"""
|
||||
Function responsible for end of day management
|
||||
|
||||
@ -38,8 +38,10 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection):
|
||||
- 1 min forced immediate
|
||||
"""
|
||||
|
||||
avgp = state.account_variables[activeTrade.account.name].avgp
|
||||
|
||||
directive_name = "forced_exit_window_start"
|
||||
forced_exit_window_start = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
forced_exit_window_start = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
|
||||
if forced_exit_window_start is None:
|
||||
state.ilog(lvl=0,e="Forced exit not required.")
|
||||
@ -47,7 +49,7 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection):
|
||||
|
||||
|
||||
directive_name = "forced_exit_window_end"
|
||||
forced_exit_window_end = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 389))
|
||||
forced_exit_window_end = get_signal_section_directive(state, signal_name=activeTrade.generated_by,directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 389))
|
||||
|
||||
if forced_exit_window_start>389:
|
||||
state.ilog(lvl=0,e="Forced exit window end max is 389")
|
||||
@ -60,7 +62,7 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection):
|
||||
|
||||
# #dokdy konci okno snizujiciho se profitu (zbytek je breakeven a posledni minuta forced) - default pulka okna
|
||||
# directive_name = "forced_exit_decreasing_profit_window_end"
|
||||
# forced_exit_decreasing_profit_window_end = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, (forced_exit_window_end-forced_exit_window_end)/2))
|
||||
# forced_exit_decreasing_profit_window_end = get_signal_section_directive(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, (forced_exit_window_end-forced_exit_window_end)/2))
|
||||
|
||||
# if forced_exit_decreasing_profit_window_end > forced_exit_window_end-1:
|
||||
# state.ilog(lvl=0,e="Decreasing profit window must be less than window end -1.")
|
||||
@ -72,7 +74,7 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection):
|
||||
state.ilog(lvl=1,e=f"Forced Exit Window OPEN - breakeven check", msg=f"{forced_exit_window_start=} {forced_exit_window_end=} ", time=str(datetime.fromtimestamp(data['updated']).astimezone(zoneNY)))
|
||||
|
||||
directive_name = "forced_exit_breakeven_period"
|
||||
forced_exit_breakeven_period = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, True))
|
||||
forced_exit_breakeven_period = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, True))
|
||||
|
||||
if forced_exit_breakeven_period is False:
|
||||
return False
|
||||
@ -80,11 +82,11 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection):
|
||||
#zatim krom posledni minuty cekame alespon na breakeven
|
||||
curr_price = float(data['close'])
|
||||
#short smer
|
||||
if direction == TradeDirection.SHORT and curr_price<=float(state.avgp):
|
||||
if direction == TradeDirection.SHORT and curr_price<=float(avgp):
|
||||
state.ilog(lvl=1,e=f"Forced Exit - price better than avgp, dir SHORT")
|
||||
return True
|
||||
|
||||
if direction == TradeDirection.LONG and curr_price>=float(state.avgp):
|
||||
if direction == TradeDirection.LONG and curr_price>=float(avgp):
|
||||
state.ilog(lvl=1,e=f"Forced Exit - price better than avgp, dir LONG")
|
||||
return True
|
||||
|
||||
|
||||
@ -1,89 +1,98 @@
|
||||
from v2realbot.strategyblocks.activetrade.close.close_position import close_position, close_position_partial
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.enums.enums import Followup
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.utils.utils import safe_get
|
||||
from v2realbot.config import KW
|
||||
#from icecream import install, ic
|
||||
from rich import print as printanyway
|
||||
from threading import Event
|
||||
#import gaka
|
||||
from v2realbot.utils.utils import gaka
|
||||
import os
|
||||
from traceback import format_exc
|
||||
from v2realbot.strategyblocks.activetrade.close.eod_exit import eod_exit_activated
|
||||
from v2realbot.strategyblocks.activetrade.close.conditions import dontexit_protection_met, exit_conditions_met
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_override_for_active_trade, keyword_conditions_met
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_signal_section_directive, keyword_conditions_met
|
||||
from v2realbot.strategyblocks.activetrade.sl.optimsl import SLOptimizer
|
||||
|
||||
def eval_close_position(state: StrategyState, data):
|
||||
curr_price = float(data['close'])
|
||||
state.ilog(lvl=0,e="Eval CLOSE", price=curr_price, pos=state.positions, avgp=state.avgp, pending=state.vars.pending, activeTrade=str(state.vars.activeTrade))
|
||||
#TODO tady odsud
|
||||
def eval_close_position(state: StrategyState, accountsWithActiveTrade, data):
|
||||
|
||||
if int(state.positions) != 0 and float(state.avgp)>0 and state.vars.pending is None:
|
||||
curr_price = float(data['close'])
|
||||
state.ilog(lvl=0,e="Eval CLOSE", price=curr_price, pos=gaka(state.account_variables, "positions"), avgp=gaka(state.account_variables, "avgp"), pending=gaka(state.account_variables, "pending"), activeTrade=str(gaka(state.account_variables, "activeTrade")))
|
||||
|
||||
#iterate over accountsWithActiveTrade
|
||||
for account_str, activeTrade in accountsWithActiveTrade.items():
|
||||
positions = state.account_variables[account_str].positions
|
||||
avgp = state.account_variables[account_str].avgp
|
||||
pending = state.account_variables[account_str].pending
|
||||
if int(positions) != 0 and float(avgp)>0 and pending is None:
|
||||
|
||||
#close position handling
|
||||
#TBD pridat OPTIMALIZACI POZICE - EXIT 1/2
|
||||
|
||||
#mame short pozice - (IDEA: rozlisovat na zaklade aktivniho tradu - umozni mi spoustet i pri soucasne long pozicemi)
|
||||
if int(state.positions) < 0:
|
||||
if int(positions) < 0:
|
||||
#get TARGET PRICE pro dany smer a signal
|
||||
|
||||
#pokud existujeme bereme z nastaveni tradu a nebo z defaultu
|
||||
if state.vars.activeTrade.goal_price is not None:
|
||||
goal_price = state.vars.activeTrade.goal_price
|
||||
if activeTrade.goal_price is not None:
|
||||
goal_price = activeTrade.goal_price
|
||||
else:
|
||||
goal_price = get_profit_target_price(state, data, TradeDirection.SHORT)
|
||||
goal_price = get_profit_target_price(state, data, activeTrade, TradeDirection.SHORT)
|
||||
|
||||
max_price = get_max_profit_price(state, data, TradeDirection.SHORT)
|
||||
max_price = get_max_profit_price(state, activeTrade, data, TradeDirection.SHORT)
|
||||
state.ilog(lvl=1,e=f"Def Goal price {str(TradeDirection.SHORT)} {goal_price} max price {max_price}")
|
||||
|
||||
#SL OPTIMALIZATION - PARTIAL EXIT
|
||||
level_met, exit_adjustment = state.sl_optimizer_short.eval_position(state, data)
|
||||
level_met, exit_adjustment = state.sl_optimizer_short.eval_position(state, data, activeTrade)
|
||||
if level_met is not None and exit_adjustment is not None:
|
||||
position = state.positions * exit_adjustment
|
||||
state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.SHORT)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_short.get_initial_abs_levels(state)), rem_levels=str(state.sl_optimizer_short.get_remaining_abs_levels(state)), exit_levels=str(state.sl_optimizer_short.exit_levels), exit_sizes=str(state.sl_optimizer_short.exit_sizes))
|
||||
position = positions * exit_adjustment
|
||||
state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.SHORT)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_short.get_initial_abs_levels(state, activeTrade)), rem_levels=str(state.sl_optimizer_short.get_remaining_abs_levels(state, activeTrade)), exit_levels=str(state.sl_optimizer_short.exit_levels), exit_sizes=str(state.sl_optimizer_short.exit_sizes))
|
||||
printanyway(f"SL OPTIMIZATION ENGAGED {str(TradeDirection.SHORT)} {position=} {level_met=} {exit_adjustment}")
|
||||
close_position_partial(state=state, data=data, direction=TradeDirection.SHORT, reason=F"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment)
|
||||
close_position_partial(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason=F"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment)
|
||||
return
|
||||
|
||||
#FULL SL reached - execution
|
||||
if curr_price > state.vars.activeTrade.stoploss_value:
|
||||
if curr_price > activeTrade.stoploss_value:
|
||||
|
||||
directive_name = 'reverse_for_SL_exit_short'
|
||||
reverse_for_SL_exit = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no"))
|
||||
reverse_for_SL_exit = get_signal_section_directive(state=state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no"))
|
||||
|
||||
if reverse_for_SL_exit == "always":
|
||||
followup_action = Followup.REVERSE
|
||||
elif reverse_for_SL_exit == "cond":
|
||||
followup_action = Followup.REVERSE if keyword_conditions_met(state, data, direction=TradeDirection.SHORT, keyword=KW.slreverseonly, skip_conf_validation=True) else None
|
||||
followup_action = Followup.REVERSE if keyword_conditions_met(state, data=data, activeTrade=activeTrade, direction=TradeDirection.SHORT, keyword=KW.slreverseonly, skip_conf_validation=True) else None
|
||||
else:
|
||||
followup_action = None
|
||||
close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="SL REACHED", followup=followup_action)
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason="SL REACHED", followup=followup_action)
|
||||
return
|
||||
|
||||
|
||||
#REVERSE BASED ON REVERSE CONDITIONS
|
||||
if keyword_conditions_met(state, data, direction=TradeDirection.SHORT, keyword=KW.reverse):
|
||||
close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="REVERSE COND MET", followup=Followup.REVERSE)
|
||||
if keyword_conditions_met(state, data, activeTrade=activeTrade, direction=TradeDirection.SHORT, keyword=KW.reverse):
|
||||
close_position(state=state, activeTrade=activeTrade,data=data, direction=TradeDirection.SHORT, reason="REVERSE COND MET", followup=Followup.REVERSE)
|
||||
return
|
||||
|
||||
#EXIT ADD CONDITIONS MET (exit and add)
|
||||
if keyword_conditions_met(state, data, direction=TradeDirection.SHORT, keyword=KW.exitadd):
|
||||
close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="EXITADD COND MET", followup=Followup.ADD)
|
||||
if keyword_conditions_met(state, data, activeTrade=activeTrade, direction=TradeDirection.SHORT, keyword=KW.exitadd):
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason="EXITADD COND MET", followup=Followup.ADD)
|
||||
return
|
||||
|
||||
#CLOSING BASED ON EXIT CONDITIONS
|
||||
if exit_conditions_met(state, data, TradeDirection.SHORT):
|
||||
if exit_conditions_met(state, activeTrade, data, TradeDirection.SHORT):
|
||||
directive_name = 'reverse_for_cond_exit_short'
|
||||
reverse_for_cond_exit_short = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
reverse_for_cond_exit_short = get_signal_section_directive(state=state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
directive_name = 'add_for_cond_exit_short'
|
||||
add_for_cond_exit_short = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
add_for_cond_exit_short = get_signal_section_directive(state=state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
if reverse_for_cond_exit_short:
|
||||
followup_action = Followup.REVERSE
|
||||
elif add_for_cond_exit_short:
|
||||
followup_action = Followup.ADD
|
||||
else:
|
||||
followup_action = None
|
||||
close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="EXIT COND MET", followup=followup_action)
|
||||
close_position(state=state, activeTrae=activeTrade, data=data, direction=TradeDirection.SHORT, reason="EXIT COND MET", followup=followup_action)
|
||||
return
|
||||
|
||||
#PROFIT
|
||||
@ -93,85 +102,85 @@ def eval_close_position(state: StrategyState, data):
|
||||
#TODO pripadne pokud dosahne TGTBB prodat ihned
|
||||
max_price_signal = curr_price<=max_price
|
||||
#OPTIMALIZACE pri stoupajícím angle
|
||||
if max_price_signal or dontexit_protection_met(state=state, data=data,direction=TradeDirection.SHORT) is False:
|
||||
close_position(state=state, data=data, direction=TradeDirection.SHORT, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}")
|
||||
if max_price_signal or dontexit_protection_met(state=state, activeTrade=activeTrade, data=data,direction=TradeDirection.SHORT) is False:
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}")
|
||||
return
|
||||
#pokud je cena horsi, ale byla uz dont exit aktivovany - pak prodavame také
|
||||
elif state.dont_exit_already_activated == True:
|
||||
elif state.account_variables[activeTrade.account.name].dont_exit_already_activated == True:
|
||||
#TODO toto mozna take na direktivu, timto neprodavame pokud porkacuje trend - EXIT_PROT_BOUNCE_IMMEDIATE
|
||||
#if dontexit_protection_met(state=state, data=data,direction=TradeDirection.SHORT) is False:
|
||||
close_position(state=state, data=data, direction=TradeDirection.SHORT, reason=f"EXIT PROTECTION BOUNCE {state.dont_exit_already_activated=}")
|
||||
state.dont_exit_already_activated = False
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason=f"EXIT PROTECTION BOUNCE {state.account_variables[activeTrade.account.name].dont_exit_already_activated=}")
|
||||
state.account_variables[activeTrade.account.name].dont_exit_already_activated = False
|
||||
return
|
||||
|
||||
#FORCED EXIT PRI KONCI DNE
|
||||
if eod_exit_activated(state, data, TradeDirection.SHORT):
|
||||
close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="EOD EXIT ACTIVATED")
|
||||
if eod_exit_activated(state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT):
|
||||
close_position(state=state, activeTrade=activeTrade,data=data, direction=TradeDirection.SHORT, reason="EOD EXIT ACTIVATED")
|
||||
return
|
||||
|
||||
#mame long
|
||||
elif int(state.positions) > 0:
|
||||
elif int(positions) > 0:
|
||||
|
||||
#get TARGET PRICE pro dany smer a signal
|
||||
#pokud existujeme bereme z nastaveni tradu a nebo z defaultu
|
||||
if state.vars.activeTrade.goal_price is not None:
|
||||
goal_price = state.vars.activeTrade.goal_price
|
||||
if activeTrade.goal_price is not None:
|
||||
goal_price = activeTrade.goal_price
|
||||
else:
|
||||
goal_price = get_profit_target_price(state, data, TradeDirection.LONG)
|
||||
goal_price = get_profit_target_price(state, data, activeTrade, TradeDirection.LONG)
|
||||
|
||||
max_price = get_max_profit_price(state, data, TradeDirection.LONG)
|
||||
max_price = get_max_profit_price(state, activeTrade, data, TradeDirection.LONG)
|
||||
state.ilog(lvl=1,e=f"Goal price {str(TradeDirection.LONG)} {goal_price} max price {max_price}")
|
||||
|
||||
#SL OPTIMALIZATION - PARTIAL EXIT
|
||||
level_met, exit_adjustment = state.sl_optimizer_long.eval_position(state, data)
|
||||
level_met, exit_adjustment = state.sl_optimizer_long.eval_position(state, data, activeTrade)
|
||||
if level_met is not None and exit_adjustment is not None:
|
||||
position = state.positions * exit_adjustment
|
||||
state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_long.get_initial_abs_levels(state)), rem_levels=str(state.sl_optimizer_long.get_remaining_abs_levels(state)), exit_levels=str(state.sl_optimizer_long.exit_levels), exit_sizes=str(state.sl_optimizer_long.exit_sizes))
|
||||
position = positions * exit_adjustment
|
||||
state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_long.get_initial_abs_levels(state, activeTrade)), rem_levels=str(state.sl_optimizer_long.get_remaining_abs_levels(state, activeTrade)), exit_levels=str(state.sl_optimizer_long.exit_levels), exit_sizes=str(state.sl_optimizer_long.exit_sizes))
|
||||
printanyway(f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}")
|
||||
close_position_partial(state=state, data=data, direction=TradeDirection.LONG, reason=f"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment)
|
||||
close_position_partial(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason=f"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment)
|
||||
return
|
||||
|
||||
#SL FULL execution
|
||||
if curr_price < state.vars.activeTrade.stoploss_value:
|
||||
if curr_price < activeTrade.stoploss_value:
|
||||
directive_name = 'reverse_for_SL_exit_long'
|
||||
reverse_for_SL_exit = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no"))
|
||||
reverse_for_SL_exit = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no"))
|
||||
|
||||
state.ilog(lvl=1, e=f"reverse_for_SL_exit {reverse_for_SL_exit}")
|
||||
|
||||
if reverse_for_SL_exit == "always":
|
||||
followup_action = Followup.REVERSE
|
||||
elif reverse_for_SL_exit == "cond":
|
||||
followup_action = Followup.REVERSE if keyword_conditions_met(state, data, direction=TradeDirection.LONG, keyword=KW.slreverseonly, skip_conf_validation=True) else None
|
||||
followup_action = Followup.REVERSE if keyword_conditions_met(state, data, activeTrade, direction=TradeDirection.LONG, keyword=KW.slreverseonly, skip_conf_validation=True) else None
|
||||
else:
|
||||
followup_action = None
|
||||
|
||||
close_position(state=state, data=data, direction=TradeDirection.LONG, reason="SL REACHED", followup=followup_action)
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="SL REACHED", followup=followup_action)
|
||||
return
|
||||
|
||||
|
||||
#REVERSE BASED ON REVERSE CONDITIONS
|
||||
if keyword_conditions_met(state, data,TradeDirection.LONG, KW.reverse):
|
||||
close_position(state=state, data=data, direction=TradeDirection.LONG, reason="REVERSE COND MET", followup=Followup.REVERSE)
|
||||
if keyword_conditions_met(state, data, activeTrade, TradeDirection.LONG, KW.reverse):
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="REVERSE COND MET", followup=Followup.REVERSE)
|
||||
return
|
||||
|
||||
#EXIT ADD CONDITIONS MET (exit and add)
|
||||
if keyword_conditions_met(state, data, TradeDirection.LONG, KW.exitadd):
|
||||
close_position(state=state, data=data, direction=TradeDirection.LONG, reason="EXITADD COND MET", followup=Followup.ADD)
|
||||
if keyword_conditions_met(state, data, activeTrade, TradeDirection.LONG, KW.exitadd):
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="EXITADD COND MET", followup=Followup.ADD)
|
||||
return
|
||||
|
||||
#EXIT CONDITIONS
|
||||
if exit_conditions_met(state, data, TradeDirection.LONG):
|
||||
if exit_conditions_met(state, activeTrade, data, TradeDirection.LONG):
|
||||
directive_name = 'reverse_for_cond_exit_long'
|
||||
reverse_for_cond_exit_long = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
reverse_for_cond_exit_long = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
directive_name = 'add_for_cond_exit_long'
|
||||
add_for_cond_exit_long = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
add_for_cond_exit_long = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
if reverse_for_cond_exit_long:
|
||||
followup_action = Followup.REVERSE
|
||||
elif add_for_cond_exit_long:
|
||||
followup_action = Followup.ADD
|
||||
else:
|
||||
followup_action = None
|
||||
close_position(state=state, data=data, direction=TradeDirection.LONG, reason="EXIT CONDS MET", followup=followup_action)
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="EXIT CONDS MET", followup=followup_action)
|
||||
return
|
||||
|
||||
#PROFIT
|
||||
@ -181,18 +190,18 @@ def eval_close_position(state: StrategyState, data):
|
||||
#TODO pripadne pokud dosahne TGTBB prodat ihned
|
||||
max_price_signal = curr_price>=max_price
|
||||
#OPTIMALIZACE pri stoupajícím angle
|
||||
if max_price_signal or dontexit_protection_met(state, data, direction=TradeDirection.LONG) is False:
|
||||
close_position(state=state, data=data, direction=TradeDirection.LONG, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}")
|
||||
if max_price_signal or dontexit_protection_met(state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG) is False:
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}")
|
||||
return
|
||||
#pokud je cena horsi, ale byl uz dont exit aktivovany - pak prodavame také
|
||||
elif state.dont_exit_already_activated == True:
|
||||
elif state.account_variables[activeTrade.account.name].dont_exit_already_activated == True:
|
||||
#TODO toto mozna take na direktivu, timto neprodavame pokud porkacuje trend - EXIT_PROT_BOUNCE_IMMEDIATE
|
||||
# if dontexit_protection_met(state=state, data=data,direction=TradeDirection.LONG) is False:
|
||||
close_position(state=state, data=data, direction=TradeDirection.LONG, reason=f"EXIT PROTECTION BOUNCE {state.dont_exit_already_activated=}")
|
||||
state.dont_exit_already_activated = False
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason=f"EXIT PROTECTION BOUNCE {state.account_variables[activeTrade.account.name].dont_exit_already_activated=}")
|
||||
state.account_variables[activeTrade.account.name].dont_exit_already_activated = False
|
||||
return
|
||||
|
||||
#FORCED EXIT PRI KONCI DNE
|
||||
if eod_exit_activated(state, data, TradeDirection.LONG):
|
||||
close_position(state=state, data=data, direction=TradeDirection.LONG, reason="EOD EXIT ACTIVATED")
|
||||
if eod_exit_activated(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG):
|
||||
close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="EOD EXIT ACTIVATED")
|
||||
return
|
||||
@ -1,5 +1,5 @@
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model 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
|
||||
from v2realbot.common.model import SLHistory
|
||||
@ -18,17 +18,20 @@ from traceback import format_exc
|
||||
from v2realbot.strategyblocks.helpers import normalize_tick
|
||||
from v2realbot.strategyblocks.indicators.helpers import evaluate_directive_conditions
|
||||
|
||||
#TODO zde dodelat viz nize get get_signal_section_directive a pak pokracovat v close positions
|
||||
#otestuje keyword podminky (napr. reverse_if, nebo exitadd_if)
|
||||
def keyword_conditions_met(state, data, direction: TradeDirection, keyword: KW, skip_conf_validation: bool = False):
|
||||
def keyword_conditions_met(state, data, activeTrade: Trade, direction: TradeDirection, keyword: KW, skip_conf_validation: bool = False):
|
||||
action = str(keyword).upper()
|
||||
if direction == TradeDirection.LONG:
|
||||
smer = "long"
|
||||
else:
|
||||
smer = "short"
|
||||
|
||||
mother_signal = activeTrade.generated_by
|
||||
|
||||
if skip_conf_validation is False:
|
||||
directive_name = "exit_cond_only_on_confirmed"
|
||||
exit_cond_only_on_confirmed = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
exit_cond_only_on_confirmed = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
|
||||
|
||||
if exit_cond_only_on_confirmed and data['confirmed'] == 0:
|
||||
state.ilog(lvl=0,e=f"{action} CHECK COND ONLY ON CONFIRMED BAR")
|
||||
@ -37,7 +40,7 @@ def keyword_conditions_met(state, data, direction: TradeDirection, keyword: KW,
|
||||
#TOTO zatim u REVERSU neresime
|
||||
# #POKUD je nastaven MIN PROFIT, zkontrolujeme ho a az pripadne pustime CONDITIONY
|
||||
# directive_name = "exit_cond_min_profit"
|
||||
# exit_cond_min_profit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
# exit_cond_min_profit = get_signal_section_directive(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
|
||||
# #máme nastavený exit_cond_min_profit
|
||||
# # zjistíme, zda jsme v daném profit a případně nepustíme dál
|
||||
@ -77,8 +80,6 @@ def keyword_conditions_met(state, data, direction: TradeDirection, keyword: KW,
|
||||
#bereme bud exit condition signalu, ktery activeTrade vygeneroval+ fallback na general
|
||||
state.ilog(lvl=0,e=f"{action} CONDITIONS ENTRY {smer}", conditions=state.vars.conditions[KW.reverse])
|
||||
|
||||
mother_signal = state.vars.activeTrade.generated_by
|
||||
|
||||
if mother_signal is not None:
|
||||
cond_dict = state.vars.conditions[keyword][mother_signal][smer]
|
||||
result, conditions_met = evaluate_directive_conditions(state, cond_dict, "OR")
|
||||
@ -108,12 +109,12 @@ def keyword_conditions_met(state, data, direction: TradeDirection, keyword: KW,
|
||||
|
||||
|
||||
#mozna do SL helpers tuto
|
||||
def insert_SL_history(state):
|
||||
def insert_SL_history(state, activeTrade: Trade):
|
||||
#insert stoploss history as key sl_history into runner archive extended data
|
||||
state.extData["sl_history"].append(SLHistory(id=state.vars.activeTrade.id, time=state.time, sl_val=state.vars.activeTrade.stoploss_value))
|
||||
state.extData["sl_history"].append(SLHistory(id=activeTrade.id, time=state.time, sl_val=activeTrade.stoploss_value, direction=activeTrade.direction, account=activeTrade.account))
|
||||
|
||||
|
||||
def get_default_sl_value(state, direction: TradeDirection):
|
||||
def get_default_sl_value(state, signal_name, direction: TradeDirection):
|
||||
|
||||
if direction == TradeDirection.LONG:
|
||||
smer = "long"
|
||||
@ -128,15 +129,16 @@ def get_default_sl_value(state, direction: TradeDirection):
|
||||
state.ilog(lvl=1,e="No options for exit in stratvars. Fallback.")
|
||||
return 0.01
|
||||
directive_name = 'SL_defval_'+str(smer)
|
||||
val = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01))
|
||||
val = get_signal_section_directive(state, signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01))
|
||||
return val
|
||||
#funkce pro direktivy, ktere muzou byt overridnute v signal sekci
|
||||
#tato funkce vyhleda signal sekci aktivniho tradu a pokusi se danou direktivu vyhledat tam,
|
||||
#pokud nenajde tak vrati default, ktery byl poskytnut
|
||||
def get_override_for_active_trade(state, directive_name: str, default_value: str):
|
||||
#TODO toto predelat na jiny nazev get_overide_for_directive_section (vstup muze byt opuze signal_name)
|
||||
def get_signal_section_directive(state, signal_name: str, directive_name: str, default_value: str):
|
||||
val = default_value
|
||||
override = "NO"
|
||||
mother_signal = state.vars.activeTrade.generated_by
|
||||
mother_signal = signal_name
|
||||
|
||||
if mother_signal is not None:
|
||||
override = "YES "+mother_signal
|
||||
@ -145,30 +147,30 @@ def get_override_for_active_trade(state, directive_name: str, default_value: str
|
||||
state.ilog(lvl=0,e=f"{directive_name} OVERRIDE {override} NEWVAL:{val} ORIGINAL:{default_value} {mother_signal}", mother_signal=mother_signal,default_value=default_value)
|
||||
return val
|
||||
|
||||
def get_profit_target_price(state, data, direction: TradeDirection):
|
||||
def get_profit_target_price(state, data, activeTrade, direction: TradeDirection):
|
||||
if direction == TradeDirection.LONG:
|
||||
smer = "long"
|
||||
else:
|
||||
smer = "short"
|
||||
|
||||
directive_name = "profit"
|
||||
def_profit_both_directions = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.50))
|
||||
def_profit_both_directions = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.50))
|
||||
|
||||
#profit pro dany smer
|
||||
directive_name = 'profit_'+str(smer)
|
||||
def_profit = get_override_for_active_trade(state, directive_name=directive_name, default_value=def_profit_both_directions)
|
||||
def_profit = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=def_profit_both_directions)
|
||||
|
||||
#mame v direktivve ticky
|
||||
if isinstance(def_profit, (float, int)):
|
||||
to_return = get_normalized_profitprice_from_tick(state, data, def_profit, direction)
|
||||
to_return = get_normalized_profitprice_from_tick(state, data, def_profit, activeTrade.account, direction)
|
||||
#mame v direktive indikator
|
||||
elif isinstance(def_profit, str):
|
||||
to_return = float(value_or_indicator(state, def_profit))
|
||||
|
||||
#min profit (ochrana extremnich hodnot indikatoru)
|
||||
directive_name = 'profit_min_ind_tick_value'
|
||||
profit_min_ind_tick_value = get_override_for_active_trade(state, directive_name=directive_name, default_value=def_profit_both_directions)
|
||||
profit_min_ind_price_value = get_normalized_profitprice_from_tick(state, data, profit_min_ind_tick_value, direction)
|
||||
profit_min_ind_tick_value = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=def_profit_both_directions)
|
||||
profit_min_ind_price_value = get_normalized_profitprice_from_tick(state, data, profit_min_ind_tick_value, activeTrade.account, direction)
|
||||
|
||||
#ochrana pri nastaveni profitu prilis nizko
|
||||
if direction == TradeDirection.LONG and to_return < profit_min_ind_price_value or direction == TradeDirection.SHORT and to_return > profit_min_ind_price_value:
|
||||
@ -179,28 +181,32 @@ def get_profit_target_price(state, data, direction: TradeDirection):
|
||||
return to_return
|
||||
|
||||
##based on tick a direction, returns normalized prfoit price (LONG = avgp(nebo currprice)+norm.tick, SHORT=avgp(or currprice)-norm.tick)
|
||||
def get_normalized_profitprice_from_tick(state, data, tick, direction: TradeDirection):
|
||||
def get_normalized_profitprice_from_tick(state, data, tick, account: Account, direction: TradeDirection):
|
||||
avgp = state.account_variables[account.name].avgp
|
||||
normalized_tick = normalize_tick(state, data, float(tick))
|
||||
base_price = state.avgp if state.avgp != 0 else data["close"]
|
||||
base_price = avgp if avgp != 0 else data["close"]
|
||||
returned_price = price2dec(float(base_price)+normalized_tick,3) if direction == TradeDirection.LONG else price2dec(float(base_price)-normalized_tick,3)
|
||||
state.ilog(lvl=0,e=f"NORMALIZED TICK {tick=} {normalized_tick=} NORM.PRICE {returned_price}")
|
||||
return returned_price
|
||||
|
||||
def get_max_profit_price(state, data, direction: TradeDirection):
|
||||
def get_max_profit_price(state, activeTrade: Trade, data, direction: TradeDirection):
|
||||
if direction == TradeDirection.LONG:
|
||||
smer = "long"
|
||||
else:
|
||||
smer = "short"
|
||||
|
||||
directive_name = "max_profit"
|
||||
max_profit_both_directions = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.35))
|
||||
max_profit_both_directions = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.35))
|
||||
|
||||
avgp = state.account_variables[activeTrade.account.name].avgp
|
||||
positions = state.account_variables[activeTrade.account.name].positions
|
||||
|
||||
#max profit pro dany smer, s fallbackem na bez smeru
|
||||
directive_name = 'max_profit_'+str(smer)
|
||||
max_profit = get_override_for_active_trade(state, directive_name=directive_name, default_value=max_profit_both_directions)
|
||||
max_profit = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=max_profit_both_directions)
|
||||
|
||||
normalized_max_profit = normalize_tick(state,data,float(max_profit))
|
||||
|
||||
state.ilog(lvl=0,e=f"MAX PROFIT {max_profit=} {normalized_max_profit=}")
|
||||
|
||||
return price2dec(float(state.avgp)+normalized_max_profit,3) if int(state.positions) > 0 else price2dec(float(state.avgp)-normalized_max_profit,3)
|
||||
return price2dec(float(avgp)+normalized_max_profit,3) if int(positions) > 0 else price2dec(float(avgp)-normalized_max_profit,3)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import numpy as np
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model import Trade, TradeDirection, TradeStatus
|
||||
from typing import Tuple
|
||||
from copy import deepcopy
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_override_for_active_trade
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_signal_section_directive
|
||||
from v2realbot.utils.utils import safe_get
|
||||
# FIBONACCI PRO PROFIT A SL
|
||||
|
||||
@ -49,23 +49,23 @@ class SLOptimizer:
|
||||
# self.exit_levels = self.init_exit_levels
|
||||
# self.exit_sizes = self.init_exit_sizes
|
||||
|
||||
def get_trade_details(self, state):
|
||||
trade: Trade = state.vars.activeTrade
|
||||
def get_trade_details(self, state, activeTrade):
|
||||
trade: Trade = activeTrade
|
||||
#jde o novy trade - resetujeme levely
|
||||
if trade.id != self.last_trade:
|
||||
#inicializujeme a vymazeme pripadne puvodni
|
||||
if self.initialize_levels(state) is False:
|
||||
if self.initialize_levels(state, activeTrade) is False:
|
||||
return None, None
|
||||
self.last_trade = trade.id
|
||||
#return cost_price, sl_price
|
||||
return state.avgp, trade.stoploss_value
|
||||
return state.account_variables[trade.account.name].avgp, trade.stoploss_value
|
||||
|
||||
def initialize_levels(self, state):
|
||||
def initialize_levels(self, state, activeTrade):
|
||||
directive_name = 'SL_opt_exit_levels_'+str(self.direction.value)
|
||||
SL_opt_exit_levels = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
SL_opt_exit_levels = get_signal_section_directive(state=state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
|
||||
directive_name = 'SL_opt_exit_sizes_'+str(self.direction.value)
|
||||
SL_opt_exit_sizes = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
SL_opt_exit_sizes = get_signal_section_directive(state=state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
|
||||
|
||||
if SL_opt_exit_levels is None or SL_opt_exit_sizes is None:
|
||||
#print("no directives found: SL_opt_exit_levels/SL_opt_exit_sizes")
|
||||
@ -83,11 +83,11 @@ class SLOptimizer:
|
||||
print(f"new levels initialized {self.exit_levels=} {self.exit_sizes=}")
|
||||
return True
|
||||
|
||||
def get_initial_abs_levels(self, state):
|
||||
def get_initial_abs_levels(self, state, activeTrade):
|
||||
"""
|
||||
Returns price levels corresponding to initial setting of exit_levels
|
||||
"""
|
||||
cost_price, sl_price = self.get_trade_details(state)
|
||||
cost_price, sl_price = self.get_trade_details(state, activeTrade)
|
||||
if cost_price is None or sl_price is None:
|
||||
return []
|
||||
curr_sl_distance = np.abs(cost_price - sl_price)
|
||||
@ -96,11 +96,11 @@ class SLOptimizer:
|
||||
else:
|
||||
return [cost_price - exit_level * curr_sl_distance for exit_level in self.init_exit_levels]
|
||||
|
||||
def get_remaining_abs_levels(self, state):
|
||||
def get_remaining_abs_levels(self, state, activeTrade):
|
||||
"""
|
||||
Returns price levels corresponding to remaing exit_levels for current trade
|
||||
"""
|
||||
cost_price, sl_price = self.get_trade_details(state)
|
||||
cost_price, sl_price = self.get_trade_details(state, activeTrade)
|
||||
if cost_price is None or sl_price is None:
|
||||
return []
|
||||
curr_sl_distance = np.abs(cost_price - sl_price)
|
||||
@ -109,11 +109,11 @@ class SLOptimizer:
|
||||
else:
|
||||
return [cost_price - exit_level * curr_sl_distance for exit_level in self.exit_levels]
|
||||
|
||||
def eval_position(self, state, data) -> Tuple[float, float]:
|
||||
def eval_position(self, state, data, activeTrade) -> Tuple[float, float]:
|
||||
"""Evaluates optimalization for current position and returns if the given level was
|
||||
met and how to adjust exit position.
|
||||
"""
|
||||
cost_price, sl_price = self.get_trade_details(state)
|
||||
cost_price, sl_price = self.get_trade_details(state, activeTrade)
|
||||
if cost_price is None or sl_price is None:
|
||||
#print("no settings found")
|
||||
return (None, None)
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get
|
||||
from v2realbot.common.model import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.utils.utils import gaka, isrising, isfalling,zoneNY, price2dec, print, safe_get
|
||||
#from icecream import install, ic
|
||||
from rich import print as printanyway
|
||||
from threading import Event
|
||||
import os
|
||||
from traceback import format_exc
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_override_for_active_trade, normalize_tick, insert_SL_history
|
||||
|
||||
from v2realbot.strategyblocks.activetrade.helpers import get_signal_section_directive, normalize_tick, insert_SL_history
|
||||
|
||||
#pokud se cena posouva nasim smerem olespon o (0.05) nad (SL + 0.09val), posuneme SL o offset
|
||||
#+ varianta - skoncit breakeven
|
||||
@ -25,10 +24,17 @@ from v2realbot.strategyblocks.activetrade.helpers import get_override_for_active
|
||||
# SL_trailing_stop_at_breakeven_short = true
|
||||
# SL_trailing_stop_at_breakeven_long = true
|
||||
|
||||
def trail_SL_management(state: StrategyState, data):
|
||||
if int(state.positions) != 0 and float(state.avgp)>0 and state.vars.pending is None:
|
||||
def trail_SL_management(state: StrategyState, accountsWithActiveTrade, data):
|
||||
#iterate over accountsWithActiveTrade
|
||||
for account_str, activeTrade in accountsWithActiveTrade.items():
|
||||
positions = state.account_variables[account_str].positions
|
||||
avgp = state.account_variables[account_str].avgp
|
||||
pending = state.account_variables[account_str].pending
|
||||
signal_name = activeTrade.generated_by
|
||||
last_entry_index = state.account_variables[account_str].last_entry_index
|
||||
if int(positions) != 0 and float(avgp)>0 and pending is None:
|
||||
|
||||
if int(state.positions) < 0:
|
||||
if int(positions) < 0:
|
||||
direction = TradeDirection.SHORT
|
||||
smer = "short"
|
||||
else:
|
||||
@ -43,32 +49,32 @@ def trail_SL_management(state: StrategyState, data):
|
||||
return
|
||||
|
||||
directive_name = 'SL_trailing_enabled_'+str(smer)
|
||||
sl_trailing_enabled = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, False))
|
||||
sl_trailing_enabled = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, False))
|
||||
|
||||
|
||||
#SL_trailing_protection_window_short
|
||||
directive_name = 'SL_trailing_protection_window_'+str(smer)
|
||||
SL_trailing_protection_window = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0))
|
||||
index_to_compare = int(state.vars.last_in_index)+int(SL_trailing_protection_window)
|
||||
SL_trailing_protection_window = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, 0))
|
||||
index_to_compare = int(last_entry_index)+int(SL_trailing_protection_window)
|
||||
if index_to_compare > int(data["index"]):
|
||||
state.ilog(lvl=1,e=f"SL trail PROTECTION WINDOW {SL_trailing_protection_window} - TOO SOON", currindex=data["index"], index_to_compare=index_to_compare, last_in_index=state.vars.last_in_index)
|
||||
state.ilog(lvl=1,e=f"SL trail PROTECTION WINDOW {SL_trailing_protection_window} - TOO SOON", currindex=data["index"], index_to_compare=index_to_compare, last_entry_index=last_entry_index)
|
||||
return
|
||||
|
||||
|
||||
|
||||
if sl_trailing_enabled is True:
|
||||
directive_name = 'SL_trailing_stop_at_breakeven_'+str(smer)
|
||||
stop_breakeven = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, False))
|
||||
stop_breakeven = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, False))
|
||||
directive_name = 'SL_defval_'+str(smer)
|
||||
def_SL = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01))
|
||||
def_SL = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01))
|
||||
directive_name = "SL_trailing_offset_"+str(smer)
|
||||
offset = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01))
|
||||
offset = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01))
|
||||
directive_name = "SL_trailing_step_"+str(smer)
|
||||
step = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, offset))
|
||||
step = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, offset))
|
||||
|
||||
#pokud je pozadovan trail jen do breakeven a uz prekroceno
|
||||
if (direction == TradeDirection.LONG and stop_breakeven and state.vars.activeTrade.stoploss_value >= float(state.avgp)) or (direction == TradeDirection.SHORT and stop_breakeven and state.vars.activeTrade.stoploss_value <= float(state.avgp)):
|
||||
state.ilog(lvl=1,e=f"SL trail STOP at breakeven {str(smer)} SL:{state.vars.activeTrade.stoploss_value} UNCHANGED", stop_breakeven=stop_breakeven)
|
||||
if (direction == TradeDirection.LONG and stop_breakeven and activeTrade.stoploss_value >= float(avgp)) or (direction == TradeDirection.SHORT and stop_breakeven and activeTrade.stoploss_value <= float(avgp)):
|
||||
state.ilog(lvl=1,e=f"SL trail STOP at breakeven {str(smer)} SL:{activeTrade.stoploss_value} UNCHANGED", stop_breakeven=stop_breakeven)
|
||||
return
|
||||
|
||||
#Aktivace SL pokud vystoupa na "offset", a nasledne posunuti o "step"
|
||||
@ -77,16 +83,16 @@ def trail_SL_management(state: StrategyState, data):
|
||||
step_normalized = normalize_tick(state, data, step)
|
||||
def_SL_normalized = normalize_tick(state, data, def_SL)
|
||||
if direction == TradeDirection.LONG:
|
||||
move_SL_threshold = state.vars.activeTrade.stoploss_value + offset_normalized + def_SL_normalized
|
||||
state.ilog(lvl=1,e=f"SL TRAIL EVAL {smer} SL:{round(state.vars.activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized)
|
||||
move_SL_threshold = activeTrade.stoploss_value + offset_normalized + def_SL_normalized
|
||||
state.ilog(lvl=1,e=f"SL TRAIL EVAL {smer} SL:{round(activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized)
|
||||
if (move_SL_threshold) < data['close']:
|
||||
state.vars.activeTrade.stoploss_value += step_normalized
|
||||
insert_SL_history(state)
|
||||
state.ilog(lvl=1,e=f"SL TRAIL TH {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized)
|
||||
activeTrade.stoploss_value += step_normalized
|
||||
insert_SL_history(state, activeTrade)
|
||||
state.ilog(lvl=1,e=f"SL TRAIL TH {smer} reached {move_SL_threshold} SL moved to {activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized)
|
||||
elif direction == TradeDirection.SHORT:
|
||||
move_SL_threshold = state.vars.activeTrade.stoploss_value - offset_normalized - def_SL_normalized
|
||||
state.ilog(lvl=0,e=f"SL TRAIL EVAL {smer} SL:{round(state.vars.activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized)
|
||||
move_SL_threshold = activeTrade.stoploss_value - offset_normalized - def_SL_normalized
|
||||
state.ilog(lvl=0,e=f"SL TRAIL EVAL {smer} SL:{round(activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized)
|
||||
if (move_SL_threshold) > data['close']:
|
||||
state.vars.activeTrade.stoploss_value -= step_normalized
|
||||
insert_SL_history(state)
|
||||
state.ilog(lvl=1,e=f"SL TRAIL GOAL {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized)
|
||||
activeTrade.stoploss_value -= step_normalized
|
||||
insert_SL_history(state, activeTrade)
|
||||
state.ilog(lvl=1,e=f"SL TRAIL GOAL {smer} reached {move_SL_threshold} SL moved to {activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized)
|
||||
|
||||
@ -55,7 +55,11 @@ def populate_all_indicators(data, state: StrategyState):
|
||||
#TODO tento lof patri spis do nextu classic SL - je poplatny typu stratefie
|
||||
#TODO na toto se podivam, nejak moc zajasonovani a zpatky -
|
||||
#PERF PROBLEM
|
||||
state.ilog(lvl=1,e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} SL:{state.vars.activeTrade.stoploss_value if state.vars.activeTrade is not None else None} GP:{state.vars.activeTrade.goal_price if state.vars.activeTrade is not None else None} profit:{round(float(state.profit),2)} profit_rel:{round(np.sum(state.rel_profit_cum),6) if len(state.rel_profit_cum)>0 else 0} Trades:{len(state.tradeList)} pend:{state.vars.pending}", rel_profit_cum=str(state.rel_profit_cum), activeTrade=transform_data(state.vars.activeTrade, json_serial), prescribedTrades=transform_data(state.vars.prescribedTrades, json_serial), pending=str(state.vars.pending))
|
||||
positions = state.account_variables[state.account].positions
|
||||
avgp = state.account_variables[state.account].avgp
|
||||
#state.ilog(lvl=1,e="ENTRY", msg=f"LP:{lp} P:{positions}/{round(float(avgp),3)} SL:{state.vars.activeTrade.stoploss_value if state.vars.activeTrade is not None else None} GP:{state.vars.activeTrade.goal_price if state.vars.activeTrade is not None else None} profit:{round(float(state.profit),2)} profit_rel:{round(np.sum(state.rel_profit_cum),6) if len(state.rel_profit_cum)>0 else 0} Trades:{len(state.tradeList)} pend:{state.vars.pending}", rel_profit_cum=str(state.rel_profit_cum), activeTrade=transform_data(state.vars.activeTrade, json_serial), prescribedTrades=transform_data(state.vars.prescribedTrades, json_serial), pending=str(state.vars.pending))
|
||||
|
||||
state.ilog(lvl=1,e="ENTRY", msg=f"LP:{lp} ", accountVars=transform_data(state.account_variables, json_serial), prescribedTrades=transform_data(state.vars.prescribedTrades, json_serial))
|
||||
|
||||
#kroky pro CONFIRMED BAR only
|
||||
if conf_bar == 1:
|
||||
|
||||
@ -46,6 +46,10 @@ def populate_dynamic_slopeLP_indicator(data, state: StrategyState, name):
|
||||
#typ leveho bodu [lastbuy - cena posledniho nakupu, baropen - cena otevreni baru]
|
||||
leftpoint = safe_get(options, 'leftpoint', "lastbuy")
|
||||
|
||||
#REFACTOR multiaccount
|
||||
#avgp bereme z primarni accountu (state.account)
|
||||
avgp = state.account_variables[state.account].avgp
|
||||
|
||||
#lookback has to be even
|
||||
if lookback_offset % 2 != 0:
|
||||
lookback_offset += 1
|
||||
@ -65,8 +69,8 @@ def populate_dynamic_slopeLP_indicator(data, state: StrategyState, name):
|
||||
|
||||
#pokud mame aktivni pozice, nastavime lookbackprice a time podle posledniho tradu
|
||||
#pokud se ale dlouho nenakupuje (uplynulo od posledniho nakupu vic nez back_to_standard_after baru), tak se vracime k prumeru
|
||||
if state.avgp > 0 and state.bars.index[-1] < int(state.vars.last_buy_index)+back_to_standard_after:
|
||||
lb_index = -1 - (state.bars.index[-1] - int(state.vars.last_buy_index))
|
||||
if avgp > 0 and state.bars.index[-1] < int(state.vars.last_entry_index)+back_to_standard_after:
|
||||
lb_index = -1 - (state.bars.index[-1] - int(state.vars.last_entry_index))
|
||||
lookbackprice = state.bars.vwap[lb_index]
|
||||
state.ilog(lvl=0,e=f"IND {name} slope {leftpoint}- LEFT POINT OVERRIDE bereme ajko cenu lastbuy {lookbackprice=} {lookbacktime=} {lb_index=}")
|
||||
else:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model 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
|
||||
from v2realbot.common.model import SLHistory
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model 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
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model 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
|
||||
from v2realbot.common.model import SLHistory
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus
|
||||
from v2realbot.utils.utils import zoneNY, json_serial,transform_data
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus
|
||||
from v2realbot.utils.utils import zoneNY, json_serial,transform_data, gaka
|
||||
from datetime import datetime
|
||||
#import random
|
||||
import orjson
|
||||
@ -11,96 +11,108 @@ from v2realbot.strategyblocks.indicators.helpers import value_or_indicator
|
||||
def execute_prescribed_trades(state: StrategyState, data):
|
||||
##evaluate prescribed trade, prvni eligible presuneme do activeTrade, zmenime stav and vytvorime objednavky
|
||||
|
||||
if state.vars.activeTrade is not None or len(state.vars.prescribedTrades) == 0:
|
||||
#for multiaccount setup we check if there is active trade for each account
|
||||
|
||||
if len(state.vars.prescribedTrades) == 0 :
|
||||
return
|
||||
|
||||
accountsWithNoActiveTrade = gaka(state.account_variables, "activeTrade", None, lambda x: x is None)
|
||||
|
||||
if len(accountsWithNoActiveTrade.values()) == 0:
|
||||
#print("active trades on all accounts")
|
||||
return
|
||||
|
||||
#returns true if all values are not None
|
||||
#all(v is not None for v in d.keys())
|
||||
|
||||
#evaluate long (price/market)
|
||||
#support multiaccount trades
|
||||
state.ilog(lvl=1,e="evaluating prescr trades", trades=transform_data(state.vars.prescribedTrades, json_serial))
|
||||
for trade in state.vars.prescribedTrades:
|
||||
if trade.account.name not in accountsWithNoActiveTrade.keys() or state.account_variables[trade.account.name].pending is not None: #availability or pending
|
||||
continue
|
||||
if trade.status == TradeStatus.READY and trade.direction == TradeDirection.LONG and (trade.entry_price is None or trade.entry_price >= data['close']):
|
||||
trade.status = TradeStatus.ACTIVATED
|
||||
trade.last_update = datetime.fromtimestamp(state.time).astimezone(zoneNY)
|
||||
state.ilog(lvl=1,e=f"evaluated LONG", trade=transform_data(trade, json_serial), prescrTrades=transform_data(state.vars.prescribedTrades, json_serial))
|
||||
state.vars.activeTrade = trade
|
||||
state.vars.last_buy_index = data["index"]
|
||||
state.vars.last_in_index = data["index"]
|
||||
break
|
||||
execute_trade(state, data, trade) #TBD ERROR HANDLING
|
||||
del accountsWithNoActiveTrade[trade.account.name] #to avoid other entries on the same account
|
||||
continue
|
||||
#evaluate shorts
|
||||
if not state.vars.activeTrade:
|
||||
for trade in state.vars.prescribedTrades:
|
||||
if trade.status == TradeStatus.READY and trade.direction == TradeDirection.SHORT and (trade.entry_price is None or trade.entry_price <= data['close']):
|
||||
state.ilog(lvl=1,e=f"evaluaed SHORT", trade=transform_data(trade, json_serial), prescrTrades=transform_data(state.vars.prescribedTrades, json_serial))
|
||||
trade.status = TradeStatus.ACTIVATED
|
||||
trade.last_update = datetime.fromtimestamp(state.time).astimezone(zoneNY)
|
||||
state.vars.activeTrade = trade
|
||||
state.vars.last_buy_index = data["index"]
|
||||
state.vars.last_in_index = data["index"]
|
||||
break
|
||||
execute_trade(state, data, trade) #TBD ERROR HANDLING
|
||||
del accountsWithNoActiveTrade[trade.account.name] #to avoid other entries on the same account
|
||||
continue
|
||||
|
||||
#odeslani ORDER + NASTAVENI STOPLOSS (zatim hardcoded)
|
||||
if state.vars.activeTrade:
|
||||
if state.vars.activeTrade.direction == TradeDirection.LONG:
|
||||
state.ilog(lvl=1,e="odesilame LONG ORDER", trade=transform_data(state.vars.activeTrade, json_serial))
|
||||
if state.vars.activeTrade.size is not None:
|
||||
size = state.vars.activeTrade.size
|
||||
else:
|
||||
size = state.vars.chunk
|
||||
res = state.buy(size=size)
|
||||
|
||||
#TODO konzolidovat nize na spolecny kod pro short a long
|
||||
#odeslani ORDER + NASTAVENI STOPLOSS (zatim hardcoded)
|
||||
#TODO doplnit error management
|
||||
def execute_trade(state, data, trade):
|
||||
if trade.direction == TradeDirection.LONG:
|
||||
state.ilog(lvl=1,e="odesilame LONG ORDER", trade=transform_data(trade, json_serial))
|
||||
size = trade.size if trade.size is not None else state.vars.chunk
|
||||
res = state.buy(size=size, account=trade.account)
|
||||
#TODO ukládáme někam ID objednávky? už zde je vráceno v res
|
||||
#TODO error handling
|
||||
if isinstance(res, int) and res < 0:
|
||||
raise Exception(f"error in required operation LONG {res}")
|
||||
|
||||
#TODO error handling
|
||||
#defaultni goal price pripadne nastavujeme az v notifikaci
|
||||
state.account_variables[trade.account.name].activeTrade = trade
|
||||
|
||||
#TODO nastaveni SL az do notifikace, kdy je známá
|
||||
#pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars
|
||||
if state.vars.activeTrade.stoploss_value is None:
|
||||
sl_defvalue = get_default_sl_value(state, direction=state.vars.activeTrade.direction)
|
||||
if trade.stoploss_value is None:
|
||||
sl_defvalue = get_default_sl_value(state=state, signal_name=trade.generated_by, direction=trade.direction)
|
||||
|
||||
if isinstance(sl_defvalue, (float, int)):
|
||||
#normalizuji dle aktualni ceny
|
||||
sl_defvalue_normalized = normalize_tick(state, data,sl_defvalue)
|
||||
state.vars.activeTrade.stoploss_value = float(data['close']) - sl_defvalue_normalized
|
||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }")
|
||||
state.account_variables[trade.account.name].activeTrade.stoploss_value = float(data['close']) - sl_defvalue_normalized
|
||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.account_variables[trade.account.name].activeTrade.stoploss_value }")
|
||||
elif isinstance(sl_defvalue, str):
|
||||
#from indicator
|
||||
ind = sl_defvalue
|
||||
sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue))
|
||||
if sl_defvalue_abs >= float(data['close']):
|
||||
raise Exception(f"error in stoploss {ind} {sl_defvalue_abs} >= curr price")
|
||||
state.vars.activeTrade.stoploss_value = sl_defvalue_abs
|
||||
state.account_variables[trade.account.name].activeTrade.stoploss_value = sl_defvalue_abs
|
||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}")
|
||||
insert_SL_history(state)
|
||||
state.vars.pending = state.vars.activeTrade.id
|
||||
elif state.vars.activeTrade.direction == TradeDirection.SHORT:
|
||||
state.ilog(lvl=1,e="odesilame SHORT ORDER", trade=transform_data(state.vars.activeTrade, json_serial))
|
||||
if state.vars.activeTrade.size is not None:
|
||||
size = state.vars.activeTrade.size
|
||||
else:
|
||||
size = state.vars.chunk
|
||||
res = state.sell(size=size)
|
||||
insert_SL_history(state, state.account_variables[trade.account.name].activeTrade)
|
||||
elif trade.direction == TradeDirection.SHORT:
|
||||
state.ilog(lvl=1,e="odesilame SHORT ORDER", trade=transform_data(trade, json_serial))
|
||||
size = trade.size if trade.size is not None else state.vars.chunk
|
||||
res = state.sell(size=size, account=trade.account)
|
||||
if isinstance(res, int) and res < 0:
|
||||
print(f"error in required operation SHORT {res}")
|
||||
raise Exception(f"error in required operation SHORT {res}")
|
||||
#defaultní goalprice nastavujeme az v notifikaci
|
||||
|
||||
state.account_variables[trade.account.name].activeTrade = trade
|
||||
#pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars
|
||||
if state.vars.activeTrade.stoploss_value is None:
|
||||
sl_defvalue = get_default_sl_value(state, direction=state.vars.activeTrade.direction)
|
||||
if trade.stoploss_value is None:
|
||||
sl_defvalue = get_default_sl_value(state, signal_name=trade.generated_by,direction=trade.direction)
|
||||
|
||||
if isinstance(sl_defvalue, (float, int)):
|
||||
#normalizuji dle aktualni ceny
|
||||
sl_defvalue_normalized = normalize_tick(state, data,sl_defvalue)
|
||||
state.vars.activeTrade.stoploss_value = float(data['close']) + sl_defvalue_normalized
|
||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }")
|
||||
state.account_variables[trade.account.name].activeTrade.stoploss_value = float(data['close']) + sl_defvalue_normalized
|
||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.account_variables[trade.account.name].activeTrade.stoploss_value }")
|
||||
elif isinstance(sl_defvalue, str):
|
||||
#from indicator
|
||||
ind = sl_defvalue
|
||||
sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue))
|
||||
if sl_defvalue_abs <= float(data['close']):
|
||||
raise Exception(f"error in stoploss {ind} {sl_defvalue_abs} <= curr price")
|
||||
state.vars.activeTrade.stoploss_value = sl_defvalue_abs
|
||||
state.account_variables[trade.account.name].activeTrade.stoploss_value = sl_defvalue_abs
|
||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}")
|
||||
insert_SL_history(state)
|
||||
state.vars.pending = state.vars.activeTrade.id
|
||||
else:
|
||||
state.ilog(lvl=1,e="unknow direction")
|
||||
state.vars.activeTrade = None
|
||||
insert_SL_history(state, state.account_variables[trade.account.name].activeTrade)
|
||||
|
||||
state.account_variables[trade.account.name].pending = trade.id
|
||||
state.account_variables[trade.account.name].activeTrade = trade
|
||||
state.account_variables[trade.account.name].last_entry_index =data["index"] #last_entry_index per account
|
||||
state.vars.last_entry_index = data["index"] #spolecne pro vsechny accounty
|
||||
@ -1,6 +1,6 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get
|
||||
from v2realbot.common.model import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, gaka
|
||||
from v2realbot.config import KW
|
||||
from uuid import uuid4
|
||||
from datetime import datetime
|
||||
@ -31,6 +31,12 @@ def signal_search(state: StrategyState, data):
|
||||
# slope10.out_short_if_above = 0
|
||||
# ema.AND.short_if_below = 28
|
||||
|
||||
accountsWithNoActiveTrade = gaka(state.account_variables, "activeTrade", None, lambda x: x is None)
|
||||
|
||||
if len(accountsWithNoActiveTrade.values()) == 0:
|
||||
#print("active trades on all accounts")
|
||||
return
|
||||
|
||||
for signalname, signalsettings in state.vars.signals.items():
|
||||
execute_signal_generator(state, data, signalname)
|
||||
|
||||
@ -38,14 +44,21 @@ def signal_search(state: StrategyState, data):
|
||||
# pokud je s cenou ceka se na cenu, pokud immmediate tak se hned provede
|
||||
# to vse za predpokladu, ze neni aktivni trade
|
||||
|
||||
def execute_signal_generator(state, data, name):
|
||||
def execute_signal_generator(state: StrategyState, data, name):
|
||||
state.ilog(lvl=1,e=f"SIGNAL SEARCH for {name}", cond_go=state.vars.conditions[KW.go][name], cond_dontgo=state.vars.conditions[KW.dont_go][name], cond_activate=state.vars.conditions[KW.activate][name] )
|
||||
options = safe_get(state.vars.signals, name, None)
|
||||
|
||||
#add account from stratvars (if there) or default to self.state.account
|
||||
|
||||
if options is None:
|
||||
state.ilog(lvl=1,e=f"No options for {name} in stratvars")
|
||||
return
|
||||
|
||||
#get account of the signal, fallback to default
|
||||
account = safe_get(options, "account", state.account)
|
||||
account_long = safe_get(options, "account_long", account)
|
||||
account_short = safe_get(options, "account_short", account)
|
||||
|
||||
if common_go_preconditions_check(state, data, signalname=name, options=options) is False:
|
||||
return
|
||||
|
||||
@ -71,9 +84,12 @@ def execute_signal_generator(state, data, name):
|
||||
state.ilog(lvl=1,e=f"{name} SHORT DISABLED")
|
||||
if long_enabled is False:
|
||||
state.ilog(lvl=1,e=f"{name} LONG DISABLED")
|
||||
if long_enabled and go_conditions_met(state, data,signalname=name, direction=TradeDirection.LONG):
|
||||
trade_made = None
|
||||
#predkontroloa zda neni pending na accountu nebo aktivni trade
|
||||
if state.account_variables[account_long].pending is None and state.account_variables[account_long].activeTrade is None and long_enabled and go_conditions_met(state, data,signalname=name, direction=TradeDirection.LONG):
|
||||
multiplier = get_multiplier(state, data, options, TradeDirection.LONG)
|
||||
state.vars.prescribedTrades.append(Trade(
|
||||
account = account_long,
|
||||
id=uuid4(),
|
||||
last_update=datetime.fromtimestamp(state.time).astimezone(zoneNY),
|
||||
status=TradeStatus.READY,
|
||||
@ -83,9 +99,12 @@ def execute_signal_generator(state, data, name):
|
||||
direction=TradeDirection.LONG,
|
||||
entry_price=None,
|
||||
stoploss_value = None))
|
||||
elif short_enabled and go_conditions_met(state, data, signalname=name, direction=TradeDirection.SHORT):
|
||||
trade_made = account_long
|
||||
#pri multiaccountu muzeme udelat v jedne iteraci vice tradu avsak vzdy na ruznych accountech
|
||||
if (trade_made is None or trade_made != account_short) and state.account_variables[account_short].pending is None and state.account_variables[account_short].activeTrade is None and short_enabled and go_conditions_met(state, data, signalname=name, direction=TradeDirection.SHORT):
|
||||
multiplier = get_multiplier(state, data, options, TradeDirection.SHORT)
|
||||
state.vars.prescribedTrades.append(Trade(
|
||||
account=account_short,
|
||||
id=uuid4(),
|
||||
last_update=datetime.fromtimestamp(state.time).astimezone(zoneNY),
|
||||
status=TradeStatus.READY,
|
||||
@ -95,5 +114,5 @@ def execute_signal_generator(state, data, name):
|
||||
direction=TradeDirection.SHORT,
|
||||
entry_price=None,
|
||||
stoploss_value = None))
|
||||
else:
|
||||
return
|
||||
state.ilog(lvl=0,e=f"{name} NO SIGNAL")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
|
||||
from v2realbot.common.model import Trade, TradeDirection, TradeStatus
|
||||
import v2realbot.utils.utils as utls
|
||||
from v2realbot.config import KW
|
||||
from uuid import uuid4
|
||||
@ -99,7 +99,8 @@ def get_multiplier(state: StrategyState, data, signaloptions: dict, direction: T
|
||||
|
||||
if probe_enabled:
|
||||
#zatim pouze probe number 1 natvrdo, tzn. nesmi byt trade pro aktivace
|
||||
if state.vars.last_in_index is None:
|
||||
#zatim funguje pouze pro primarni
|
||||
if state.account_variables[state.account].last_entry_index is None:
|
||||
#probe_number = utls.safe_get(options, "probe_number",1)
|
||||
probe_size = float(utls.safe_get(options, "probe_size", 0.1))
|
||||
state.ilog(lvl=1,e=f"SIZER - PROBE - setting multiplier to {probe_size}", options=options)
|
||||
|
||||
@ -12,7 +12,7 @@ from enum import Enum
|
||||
import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
#from rich import print
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
|
||||
@ -11,7 +11,7 @@ import numpy as np
|
||||
import v2realbot.controller.services as cs
|
||||
from rich import print as richprint
|
||||
from v2realbot.common.model import AnalyzerInputs
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||
from pathlib import Path
|
||||
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||
@ -23,7 +23,7 @@ from collections import defaultdict
|
||||
from scipy.stats import zscore
|
||||
from io import BytesIO
|
||||
from typing import Tuple, Optional, List
|
||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||
from collections import Counter
|
||||
import vectorbtpro as vbt
|
||||
|
||||
|
||||
@ -9,8 +9,8 @@ import decimal
|
||||
from v2realbot.enums.enums import RecordType, Mode, StartBarAlign
|
||||
import pickle
|
||||
import os
|
||||
from v2realbot.common.model import StrategyInstance, Runner, RunArchive, RunArchiveDetail, Intervals, SLHistory, InstantIndicator
|
||||
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType
|
||||
from v2realbot.common.model import StrategyInstance, Runner, RunArchive, RunArchiveDetail, Intervals, SLHistory, InstantIndicator, AccountVariables
|
||||
from v2realbot.common.model import Trade, TradeDirection, TradeStatus, TradeStoplossType
|
||||
from typing import List
|
||||
import tomli
|
||||
from v2realbot.config import DATA_DIR, ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY
|
||||
@ -36,6 +36,220 @@ import shutil
|
||||
from filelock import FileLock
|
||||
import v2realbot.utils.config_handler as cfh
|
||||
import pandas_market_calendars as mcal
|
||||
from typing import Dict, Any, Callable, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
def get_attribute(obj: Any, attr: str) -> Any:
|
||||
"""
|
||||
Returns the value of given attribute from the object being it dict or BaseModel
|
||||
"""
|
||||
if isinstance(obj, dict):
|
||||
return obj.get(attr)
|
||||
if isinstance(obj, BaseModel):
|
||||
return getattr(obj, attr, None)
|
||||
return None
|
||||
|
||||
def gaka(
|
||||
account_variables: Dict[str, Any],
|
||||
name_of_attribute: str,
|
||||
transform_function: Optional[Callable[[Any], Any]] = None,
|
||||
condition_function: Optional[Callable[[Any], bool]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Gets Account Keyed Attribute
|
||||
Extracts the specified attribute from each account variable in the given dictionary.
|
||||
It also contains transformation function and condition function.
|
||||
|
||||
```
|
||||
avgps = gaka(account_variables, "avgp",
|
||||
transform_function=lambda x: round(x, 3),
|
||||
condition_function=lambda x: x > 3)
|
||||
|
||||
returns:
|
||||
{
|
||||
'account2': 5000.654,
|
||||
'account3': 3000.789,
|
||||
'account4': 8000.235
|
||||
}
|
||||
```
|
||||
|
||||
Args:
|
||||
account_variables (Dict[str, BaseModel]): A dictionary of account variables.
|
||||
name_of_attribute (str): The name of the attribute to extract.
|
||||
transform_function (Optional[Callable[[Any], Any]]): Optional function to transform the attribute value.
|
||||
condition_function (Optional[Callable[[Any], bool]]): Optional function to filter the results.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: A dictionary containing the extracted attribute for each account that meets the condition.
|
||||
"""
|
||||
result = {}
|
||||
for account_str, acc_vars in account_variables.items():
|
||||
value = get_attribute(acc_vars, name_of_attribute)
|
||||
|
||||
if value is None and not hasattr(acc_vars, name_of_attribute):
|
||||
continue
|
||||
|
||||
transformed_value = transform_function(value) if transform_function else value
|
||||
|
||||
if condition_function is None or condition_function(transformed_value):
|
||||
result[account_str] = transformed_value
|
||||
|
||||
return result
|
||||
|
||||
def gaka_unoptimized(
|
||||
account_variables: Dict[str, Any],
|
||||
name_of_attribute: str,
|
||||
transform_function: Optional[Callable[[Any], Any]] = None,
|
||||
condition_function: Optional[Callable[[Any], bool]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Gets Account Keyed Attribute
|
||||
Extracts the specified attribute from each account variable in the given dictionary.
|
||||
It also contains transformation function and condition function.
|
||||
|
||||
```
|
||||
avgps = gaka(account_variables, "avgp",
|
||||
transform_function=lambda x: round(x, 3),
|
||||
condition_function=lambda x: x > 3)
|
||||
|
||||
returns:
|
||||
{
|
||||
'account2': 5000.654,
|
||||
'account3': 3000.789,
|
||||
'account4': 8000.235
|
||||
}
|
||||
```
|
||||
|
||||
Args:
|
||||
account_variables (Dict[str, BaseModel]): A dictionary of account variables.
|
||||
name_of_attribute (str): The name of the attribute to extract.
|
||||
transform_function (Optional[Callable[[Any], Any]]): Optional function to transform the attribute value.
|
||||
condition_function (Optional[Callable[[Any], bool]]): Optional function to filter the results.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: A dictionary containing the extracted attribute for each account that meets the condition.
|
||||
"""
|
||||
result = {}
|
||||
for account, acc_vars in account_variables.items():
|
||||
if isinstance(acc_vars, BaseModel):
|
||||
value = getattr(acc_vars, name_of_attribute, None)
|
||||
elif isinstance(acc_vars, dict):
|
||||
value = acc_vars.get(name_of_attribute, None)
|
||||
else:
|
||||
continue # Skip if acc_vars is neither BaseModel nor dict
|
||||
|
||||
if value is None and not hasattr(acc_vars, name_of_attribute):
|
||||
continue # Skip if attribute doesn't exist
|
||||
|
||||
transformed_value = transform_function(value) if transform_function else value
|
||||
|
||||
if condition_function is None or condition_function(transformed_value):
|
||||
result[account] = transformed_value
|
||||
|
||||
return result
|
||||
|
||||
def gaka_old_with_comprehesion(
|
||||
account_variables: Dict[str, Any],
|
||||
name_of_attribute: str,
|
||||
transform_function: Optional[Callable[[Any], Any]] = None,
|
||||
condition_function: Optional[Callable[[Any], bool]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Gets Account Keyed Attribute
|
||||
Extracts the specified attribute from each account variable in the given dictionary.
|
||||
|
||||
It also contains transformation function and condition function.
|
||||
|
||||
```
|
||||
avgps = gaka(account_variables, "avgp",
|
||||
transform_function=lambda x: round(x, 3),
|
||||
condition_function=lambda x: x > 3)
|
||||
|
||||
returns:
|
||||
{
|
||||
'account2': 5000.654,
|
||||
'account3': 3000.789,
|
||||
'account4': 8000.235
|
||||
}
|
||||
```
|
||||
|
||||
Args:
|
||||
account_variables (Dict[str, BaseModel]): A dictionary of account variables.
|
||||
name_of_attribute (str): The name of the attribute to extract.
|
||||
transform_function (Optional[Callable[[Any], Any]]): Optional function to transform the attribute value.
|
||||
condition_function (Optional[Callable[[Any], bool]]): Optional function to filter the results.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: A dictionary containing the extracted attribute for each account that meets the condition.
|
||||
"""
|
||||
return {
|
||||
account: transformed_value
|
||||
for account, acc_vars in account_variables.items()
|
||||
if (value := (
|
||||
getattr(acc_vars, name_of_attribute, None)
|
||||
if isinstance(acc_vars, BaseModel)
|
||||
else acc_vars.get(name_of_attribute, None)
|
||||
if isinstance(acc_vars, dict)
|
||||
else None
|
||||
)) is not None or name_of_attribute in acc_vars.__dict__
|
||||
and (transformed_value := (
|
||||
transform_function(value) if transform_function else value
|
||||
)) is not None
|
||||
and (not condition_function or condition_function(transformed_value))
|
||||
}
|
||||
|
||||
def gaka_old(account_variables: Dict[str, Any], name_of_attribute: str, transform_function: Optional[Callable[[Any], Any]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Gets Account Keyed Attribute
|
||||
Extracts the specified attribute from each account variable in the given dictionary.
|
||||
|
||||
It also contain transformation function.
|
||||
|
||||
```
|
||||
avgps = extract_attribute(account_variables, "avgp", lambda x: round(x, 3))
|
||||
|
||||
returns:
|
||||
{
|
||||
'account1': 1000.123,
|
||||
'account2': 5000.654,
|
||||
'account3': 3000.789,
|
||||
'account4': 8000.235
|
||||
}
|
||||
```
|
||||
|
||||
Args:
|
||||
account_variables (Dict[str, BaseModel]): A dictionary of account variables.
|
||||
name_of_attribute (str): The name of the attribute to extract.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: A dictionary containing the extracted attribute for each account.
|
||||
"""
|
||||
#return {account: getattr(acc_vars, name_of_attribute) for account, acc_vars in account_variables.items()}
|
||||
return {
|
||||
account: (
|
||||
transform_function(value) if transform_function and value is not None else value
|
||||
)
|
||||
for account, acc_vars in account_variables.items()
|
||||
if (value := (
|
||||
getattr(acc_vars, name_of_attribute, None)
|
||||
if isinstance(acc_vars, BaseModel)
|
||||
else acc_vars.get(name_of_attribute, None)
|
||||
if isinstance(acc_vars, dict)
|
||||
else None
|
||||
)) is not None
|
||||
}
|
||||
|
||||
def empty_lists_in_dict(d: dict):
|
||||
"""
|
||||
Assumes all values of dict are list. Returns true if all lists are empty.
|
||||
|
||||
Args:
|
||||
d (dict): The dictionary to check.
|
||||
|
||||
Returns:
|
||||
bool: True if all lists in the dictionary are empty, False otherwise.
|
||||
"""
|
||||
return all(len(v) == 0 for v in d.values())
|
||||
|
||||
def validate_and_format_time(time_string):
|
||||
"""
|
||||
@ -675,6 +889,7 @@ def json_serial(obj):
|
||||
SLHistory: lambda obj: obj.__dict__,
|
||||
InstantIndicator: lambda obj: obj.__dict__,
|
||||
StrategyInstance: lambda obj: obj.__dict__,
|
||||
AccountVariables: lambda obj: obj.__dict__
|
||||
}
|
||||
|
||||
serializer = type_map.get(type(obj))
|
||||
|
||||
255
venv1_packages.txt
Normal file
255
venv1_packages.txt
Normal file
@ -0,0 +1,255 @@
|
||||
absl-py==2.0.0
|
||||
alpaca==1.0.0
|
||||
alpaca-py==0.18.1
|
||||
altair==4.2.2
|
||||
annotated-types==0.6.0
|
||||
anyio==3.6.2
|
||||
appdirs==1.4.4
|
||||
appnope==0.1.3
|
||||
APScheduler==3.10.4
|
||||
argon2-cffi==23.1.0
|
||||
argon2-cffi-bindings==21.2.0
|
||||
arrow==1.3.0
|
||||
asttokens==2.2.1
|
||||
astunparse==1.6.3
|
||||
async-lru==2.0.4
|
||||
attrs==22.2.0
|
||||
Babel==2.15.0
|
||||
beautifulsoup4==4.12.3
|
||||
better-exceptions==0.3.3
|
||||
bleach==6.0.0
|
||||
blinker==1.5
|
||||
bottle==0.12.25
|
||||
cachetools==5.3.0
|
||||
CD==1.1.0
|
||||
certifi==2022.12.7
|
||||
cffi==1.16.0
|
||||
chardet==5.1.0
|
||||
charset-normalizer==3.0.1
|
||||
click==8.1.3
|
||||
colorama==0.4.6
|
||||
comm==0.1.4
|
||||
contourpy==1.0.7
|
||||
cycler==0.11.0
|
||||
dash==2.9.1
|
||||
dash-bootstrap-components==1.4.1
|
||||
dash-core-components==2.0.0
|
||||
dash-html-components==2.0.0
|
||||
dash-table==5.0.0
|
||||
dateparser==1.1.8
|
||||
debugpy==1.8.1
|
||||
decorator==5.1.1
|
||||
defusedxml==0.7.1
|
||||
dill==0.3.7
|
||||
dm-tree==0.1.8
|
||||
entrypoints==0.4
|
||||
exceptiongroup==1.1.3
|
||||
exchange_calendars==4.5.5
|
||||
executing==1.2.0
|
||||
fastapi==0.109.2
|
||||
fastjsonschema==2.19.1
|
||||
filelock==3.13.1
|
||||
Flask==2.2.3
|
||||
flatbuffers==23.5.26
|
||||
fonttools==4.39.0
|
||||
fpdf2==2.7.6
|
||||
fqdn==1.5.1
|
||||
gast==0.4.0
|
||||
gitdb==4.0.10
|
||||
GitPython==3.1.31
|
||||
google-auth==2.23.0
|
||||
google-auth-oauthlib==1.0.0
|
||||
google-pasta==0.2.0
|
||||
greenlet==3.0.3
|
||||
grpcio==1.58.0
|
||||
h11==0.14.0
|
||||
h5py==3.10.0
|
||||
html2text==2024.2.26
|
||||
httpcore==1.0.5
|
||||
httpx==0.27.0
|
||||
humanize==4.9.0
|
||||
icecream==2.1.3
|
||||
idna==3.4
|
||||
imageio==2.31.6
|
||||
importlib-metadata==6.1.0
|
||||
ipykernel==6.29.4
|
||||
ipython==8.17.2
|
||||
ipywidgets==8.1.1
|
||||
isoduration==20.11.0
|
||||
itables==2.0.1
|
||||
itsdangerous==2.1.2
|
||||
jax==0.4.23
|
||||
jaxlib==0.4.23
|
||||
jedi==0.19.1
|
||||
Jinja2==3.1.2
|
||||
joblib==1.3.2
|
||||
json5==0.9.25
|
||||
jsonpointer==2.4
|
||||
jsonschema==4.22.0
|
||||
jsonschema-specifications==2023.12.1
|
||||
jupyter-events==0.10.0
|
||||
jupyter-lsp==2.2.5
|
||||
jupyter_client==8.6.1
|
||||
jupyter_core==5.7.2
|
||||
jupyter_server==2.14.0
|
||||
jupyter_server_terminals==0.5.3
|
||||
jupyterlab==4.1.8
|
||||
jupyterlab-widgets==3.0.9
|
||||
jupyterlab_pygments==0.3.0
|
||||
jupyterlab_server==2.27.1
|
||||
kaleido==0.2.1
|
||||
keras==3.0.2
|
||||
keras-core==0.1.7
|
||||
keras-nightly==3.0.3.dev2024010203
|
||||
keras-nlp-nightly==0.7.0.dev2024010203
|
||||
keras-tcn @ git+https://github.com/drew2323/keras-tcn.git@4bddb17a02cb2f31c9fe2e8f616b357b1ddb0e11
|
||||
kiwisolver==1.4.4
|
||||
korean-lunar-calendar==0.3.1
|
||||
libclang==16.0.6
|
||||
lightweight-charts @ git+https://github.com/drew2323/lightweight-charts-python.git@2b9f238a4242d958bc863b6209bf6444786477c5
|
||||
llvmlite==0.39.1
|
||||
Markdown==3.4.3
|
||||
markdown-it-py==2.2.0
|
||||
MarkupSafe==2.1.2
|
||||
matplotlib==3.8.2
|
||||
matplotlib-inline==0.1.6
|
||||
mdurl==0.1.2
|
||||
mistune==3.0.2
|
||||
ml-dtypes==0.3.1
|
||||
mlroom @ git+https://github.com/drew2323/mlroom.git@692900e274c4e0542d945d231645c270fc508437
|
||||
mplfinance==0.12.10b0
|
||||
msgpack==1.0.4
|
||||
mypy-extensions==1.0.0
|
||||
namex==0.0.7
|
||||
nbclient==0.10.0
|
||||
nbconvert==7.16.4
|
||||
nbformat==5.10.4
|
||||
nest-asyncio==1.6.0
|
||||
newtulipy==0.4.6
|
||||
notebook_shim==0.2.4
|
||||
numba==0.56.4
|
||||
numpy==1.23.5
|
||||
oauthlib==3.2.2
|
||||
opt-einsum==3.3.0
|
||||
orjson==3.9.10
|
||||
overrides==7.7.0
|
||||
packaging==23.0
|
||||
pandas==2.2.1
|
||||
pandas_market_calendars==4.4.1
|
||||
pandocfilters==1.5.1
|
||||
param==1.13.0
|
||||
parso==0.8.3
|
||||
patsy==0.5.6
|
||||
pexpect==4.8.0
|
||||
Pillow==9.4.0
|
||||
platformdirs==4.2.0
|
||||
plotly==5.22.0
|
||||
prometheus_client==0.20.0
|
||||
prompt-toolkit==3.0.39
|
||||
proto-plus==1.22.2
|
||||
protobuf==3.20.3
|
||||
proxy-tools==0.1.0
|
||||
psutil==5.9.8
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
pyarrow==11.0.0
|
||||
pyasn1==0.4.8
|
||||
pyasn1-modules==0.2.8
|
||||
pycparser==2.22
|
||||
pyct==0.5.0
|
||||
pydantic==2.6.4
|
||||
pydantic_core==2.16.3
|
||||
pydeck==0.8.0
|
||||
Pygments==2.14.0
|
||||
pyinstrument==4.5.3
|
||||
pyluach==2.2.0
|
||||
Pympler==1.0.1
|
||||
pyobjc-core==10.3
|
||||
pyobjc-framework-Cocoa==10.3
|
||||
pyobjc-framework-Security==10.3
|
||||
pyobjc-framework-WebKit==10.3
|
||||
pyparsing==3.0.9
|
||||
pyrsistent==0.19.3
|
||||
pysos==1.3.0
|
||||
python-call-graph==2.1.2
|
||||
python-dateutil==2.8.2
|
||||
python-dotenv==1.0.0
|
||||
python-json-logger==2.0.7
|
||||
python-multipart==0.0.6
|
||||
pytz==2022.7.1
|
||||
pytz-deprecation-shim==0.1.0.post0
|
||||
pyviz-comms==2.2.1
|
||||
PyWavelets==1.5.0
|
||||
pywebview==5.1
|
||||
PyYAML==6.0
|
||||
pyzmq==25.1.2
|
||||
referencing==0.35.1
|
||||
regex==2023.10.3
|
||||
requests==2.31.0
|
||||
requests-oauthlib==1.3.1
|
||||
rfc3339-validator==0.1.4
|
||||
rfc3986-validator==0.1.1
|
||||
rich==13.3.1
|
||||
rpds-py==0.18.0
|
||||
rsa==4.9
|
||||
schedule==1.2.1
|
||||
scikit-learn==1.3.2
|
||||
scipy==1.11.2
|
||||
seaborn==0.12.2
|
||||
semver==2.13.0
|
||||
Send2Trash==1.8.3
|
||||
six==1.16.0
|
||||
smmap==5.0.0
|
||||
sniffio==1.3.0
|
||||
soupsieve==2.5
|
||||
SQLAlchemy==2.0.27
|
||||
sseclient-py==1.7.2
|
||||
stack-data==0.6.3
|
||||
starlette==0.36.3
|
||||
statsmodels==0.14.1
|
||||
streamlit==1.20.0
|
||||
structlog==23.1.0
|
||||
TA-Lib==0.4.28
|
||||
tb-nightly==2.16.0a20240102
|
||||
tenacity==8.2.2
|
||||
tensorboard==2.15.1
|
||||
tensorboard-data-server==0.7.1
|
||||
tensorflow-addons==0.23.0
|
||||
tensorflow-estimator==2.15.0
|
||||
tensorflow-io-gcs-filesystem==0.34.0
|
||||
termcolor==2.3.0
|
||||
terminado==0.18.1
|
||||
tf-estimator-nightly==2.14.0.dev2023080308
|
||||
tf-nightly==2.16.0.dev20240101
|
||||
tf_keras-nightly==2.16.0.dev2023123010
|
||||
threadpoolctl==3.2.0
|
||||
tinycss2==1.3.0
|
||||
tinydb==4.7.1
|
||||
tinydb-serialization==2.1.0
|
||||
tinyflux==0.4.0
|
||||
toml==0.10.2
|
||||
tomli==2.0.1
|
||||
toolz==0.12.0
|
||||
tornado==6.2
|
||||
tqdm==4.65.0
|
||||
traitlets==5.13.0
|
||||
typeguard==2.13.3
|
||||
types-python-dateutil==2.9.0.20240316
|
||||
typing_extensions==4.9.0
|
||||
tzdata==2023.2
|
||||
tzlocal==4.3
|
||||
uri-template==1.3.0
|
||||
urllib3==1.26.14
|
||||
uvicorn==0.21.1
|
||||
-e git+https://github.com/drew2323/v2trading.git@7a283a127e8b11914cb2ba0ad5ef551f5a398f62#egg=v2realbot
|
||||
validators==0.20.0
|
||||
vectorbtpro @ file:///Users/davidbrazda/Downloads/vectorbt.pro-develop
|
||||
wcwidth==0.2.9
|
||||
webcolors==1.13
|
||||
webencodings==0.5.1
|
||||
websocket-client==1.7.0
|
||||
websockets==11.0.3
|
||||
Werkzeug==2.2.3
|
||||
widgetsnbextension==4.0.9
|
||||
wrapt==1.14.1
|
||||
zipp==3.15.0
|
||||
Reference in New Issue
Block a user