Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff65acba05 | ||
|
|
dc08945719 | ||
|
|
a739ca9a75 | ||
|
|
ed7b187876 | ||
|
|
53e1e92a6a | ||
|
|
eee7af494a | ||
|
|
91a7c35edf | ||
|
|
3eaef1a1b6 | ||
|
|
4a70b4910d | ||
|
|
7697f73ada | ||
|
|
029875e0a0 |
No files matched your search
@@ -0,0 +1,6 @@
|
||||
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/bybit_algo_test
|
||||
SPARKD__RUNTIME_CONF_DIR=/home/bts/8000/sparkdata/conf
|
||||
SPARKD__PYTHON_REQUIREMENTS=src/app_sparkd/configurations/alphaworx_zen_hk_live-preview/requirements.txt
|
||||
SPARKD__SHM_SIZE=8gb
|
||||
SPARKD__MEMORY=8gb
|
||||
SPARKD__IMAGE=sparkd_symbols
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"currencies": [
|
||||
"USDT"
|
||||
],
|
||||
"futures": [
|
||||
"MOVE",
|
||||
"DUCK",
|
||||
"ETH",
|
||||
"BTC",
|
||||
"SOL",
|
||||
"KAITO"
|
||||
],
|
||||
"use_testnet": false,
|
||||
"logging": {
|
||||
"console_level": "WARNING",
|
||||
"file_level": "INFO"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
certifi==2025.4.26
|
||||
charset-normalizer==3.4.2
|
||||
dotenv==0.9.9
|
||||
idna==3.10
|
||||
pybit==5.11.0
|
||||
pycryptodome==3.23.0
|
||||
python-dotenv==1.1.0
|
||||
requests==2.32.4
|
||||
urllib3==2.4.0
|
||||
websocket-client==1.8.0
|
||||
@@ -0,0 +1,58 @@
|
||||
QuickbusCfg {
|
||||
value {
|
||||
node {
|
||||
reconnect_period_secs: 1
|
||||
hb_period_msecs: 15000
|
||||
max_missed_hbs: 5
|
||||
parent_host: ""
|
||||
parent_port: 0
|
||||
my_port: 0
|
||||
}
|
||||
client {
|
||||
reconnect_period_secs: 1
|
||||
hb_period_msecs: 15000
|
||||
max_missed_hbs: 5
|
||||
parent_host: @SystemConfig.config_ip
|
||||
parent_port: @Ports.qbus_port
|
||||
my_port: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JetFIX {
|
||||
value {
|
||||
connection {
|
||||
name: "bybit_1"
|
||||
venue: VENUE_BYBIT
|
||||
max_order_msgs_per_window: 300
|
||||
window_duration_msecs: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MarketDataConfig {
|
||||
value {
|
||||
python_md_providers {
|
||||
venue: VENUE_BYBIT
|
||||
module_path: "python_interop_plugins"
|
||||
provider_type: "bybit_md_provider.BybitMdProvider"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logging {
|
||||
value {
|
||||
initial_queue_size: 120001
|
||||
max_queue_growth: 4
|
||||
file {
|
||||
default_level: "Info"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SoundNotifications {
|
||||
value {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
SymbolLists {
|
||||
value {
|
||||
list {
|
||||
key: "BybitSpots"
|
||||
symbol: "MOVE"
|
||||
symbol: "DUCK"
|
||||
symbol: "ETH"
|
||||
symbol: "BTC"
|
||||
symbol: "SOL"
|
||||
symbol: "KAITO"
|
||||
}
|
||||
list {
|
||||
key: "BybitFutures"
|
||||
symbol: "MOVE"
|
||||
symbol: "DUCK"
|
||||
symbol: "ETH"
|
||||
symbol: "BTC"
|
||||
symbol: "SOL"
|
||||
symbol: "KAITO"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Symbols {
|
||||
value {
|
||||
venues {
|
||||
name: "VENUE_BYBIT"
|
||||
spot: @SymbolLists.list[BybitSpots].symbol
|
||||
future: @SymbolLists.list[BybitFutures].symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,8 +141,8 @@ namespace bts {
|
||||
|
||||
(TickDisplaySettingsImmutable? Price, TickDisplaySettingsImmutable? Quantity) GetDisplaySettings(SecurityImmutable sec) {
|
||||
var meta = _security_metadata.get(sec);
|
||||
var priceDisplay = meta?.PriceDisplay ?? Security.Defaults.price_display_settingsi;
|
||||
var qtyDisplay = meta?.QuantityDisplay ?? Security.Defaults.quantity_display_settingsi;
|
||||
var priceDisplay = meta.HasValue && meta.Value.HasPriceDisplay ? meta.Value.PriceDisplay : Security.Defaults.price_display_settingsi;
|
||||
var qtyDisplay = meta.HasValue && meta.Value.HasQuantityDisplay ? meta.Value.QuantityDisplay : Security.Defaults.quantity_display_settingsi;
|
||||
if (sec.HasContractSpec) {
|
||||
var pd = priceDisplay.CloneToMutable();
|
||||
pd.TickFactors = TickUtils.get_tick(sec, 0).to_api_tick_factors();
|
||||
|
||||
@@ -103,9 +103,9 @@ namespace mbts.persist {
|
||||
var re = new Regex(pattern);
|
||||
return _persist
|
||||
.subscribe_ns(_user_ns(ns))
|
||||
.Filter(r => re.IsMatch(r.Key))
|
||||
.Select(update =>
|
||||
update.added
|
||||
.Where(item => re.IsMatch(item.Key))
|
||||
update.all
|
||||
.Select(item => new PersistResult<T>() {
|
||||
user = _user,
|
||||
ns = ns,
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace mbts.spark {
|
||||
class FillsColumn<TColFilter> : DIYGrid.RenderTextColumn<FillsRowVM, ColKey>, IFillsColumn, IFilterableDIYGridColumn<FillTuple, ColKey, FillsFilterStates> {
|
||||
readonly Func<FillsFilterStates, TColFilter> _get_col_filter;
|
||||
readonly Action<FillsFilterStates, TColFilter> _set_col_filter;
|
||||
readonly Func<FillsRowVM, string> getText;
|
||||
|
||||
public FillsColumn(
|
||||
ColKey key,
|
||||
@@ -57,16 +58,20 @@ namespace mbts.spark {
|
||||
_get_col_filter = get_col_filter;
|
||||
_set_col_filter = set_col_filter;
|
||||
this.filter_model = filter_model;
|
||||
getText = get_text;
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<FillTuple, TColFilter> filter_model { get; }
|
||||
|
||||
IDataGridColumnFilter<FillTuple> IFilterableDIYGridColumn<FillTuple, ColKey>.filter_model => filter_model;
|
||||
IDataGridColumnFilter <FillTuple> IFilterableDIYGridColumn<FillTuple, ColKey>.filter_model => filter_model;
|
||||
IDataGridColumnFilter IFilterableDIYGridColumn.filter_model => filter_model;
|
||||
ColKey IDIYGridColumn<ColKey>.id => key;
|
||||
|
||||
DIYGrid.ColumnBase IFillsColumn.column_view => this;
|
||||
|
||||
public string GetText(FillsRowVM row) =>
|
||||
getText(row);
|
||||
|
||||
public FillsFilterStates assign_filter_settings(FillsFilterStates filter_settings) {
|
||||
_set_col_filter(filter_settings, filter_model.get_state());
|
||||
return filter_settings;
|
||||
|
||||
@@ -17,9 +17,11 @@ namespace mbts.spark {
|
||||
class FillsOrderTypeColumn<TFilter> : DIYGrid.OrderTypeColumn<ColKey>, IFillsColumn {
|
||||
readonly Func<FillsFilterStates, TFilter> _get_col_filter;
|
||||
readonly Action<FillsFilterStates, TFilter> _set_col_filter;
|
||||
readonly Func<FillsRowVM, string> getText;
|
||||
|
||||
public FillsOrderTypeColumn(
|
||||
ColKey key,
|
||||
Func<FillsRowVM, string> getText,
|
||||
BindingBase badge_fill_binding,
|
||||
Binding display_name_binding,
|
||||
IDataGridColumnPersistentFilter<FillTuple, TFilter> filter_model,
|
||||
@@ -27,6 +29,7 @@ namespace mbts.spark {
|
||||
Action<FillsFilterStates, TFilter> set_col_filter,
|
||||
string header = "Order Type")
|
||||
: base(key, badge_fill_binding, display_name_binding, filter_model, header) {
|
||||
this.getText = getText;
|
||||
this.filter_model = filter_model;
|
||||
_get_col_filter = get_col_filter;
|
||||
_set_col_filter = set_col_filter;
|
||||
@@ -40,6 +43,9 @@ namespace mbts.spark {
|
||||
IDataGridColumnFilter IFilterableDIYGridColumn.filter_model => filter_model;
|
||||
ColKey IDIYGridColumn<ColKey>.id => key;
|
||||
|
||||
public string GetText(FillsRowVM r) =>
|
||||
getText(r);
|
||||
|
||||
public FillsFilterStates assign_filter_settings(FillsFilterStates filter_settings) {
|
||||
_set_col_filter(filter_settings, filter_model.get_state());
|
||||
return filter_settings;
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace mbts.spark {
|
||||
|
||||
FillsView.Columns IDIYGridColumn<FillsView.Columns>.id => key;
|
||||
|
||||
public string GetText(FillsRowVM row) =>
|
||||
row.side.ToString();
|
||||
|
||||
public FillsFilterStates assign_filter_settings(FillsFilterStates filter_settings) {
|
||||
filter_settings.side = filter_model.get_state();
|
||||
return filter_settings;
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace mbts.spark {
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -138,6 +139,7 @@ namespace mbts.spark {
|
||||
_columns.Add(
|
||||
new FillsOrderTypeColumn<OrderType[]>(
|
||||
Columns.OrderType,
|
||||
r => r.order_type.ToString(),
|
||||
new Binding(nameof(FillsRowVM.badge_fill)),
|
||||
new Binding(nameof(FillsRowVM.display_name)),
|
||||
order_type_filter,
|
||||
@@ -161,6 +163,7 @@ namespace mbts.spark {
|
||||
_columns.Add(
|
||||
new FillsOrderTypeColumn<AlgoID[]>(
|
||||
Columns.RootAlgoType,
|
||||
r => r.root_algo_order_type.ToString(),
|
||||
new Binding(nameof(FillsRowVM.root_algo_badge_color)) {
|
||||
Converter = new ui.converters.ColorToBrushConverter()
|
||||
},
|
||||
@@ -474,6 +477,33 @@ namespace mbts.spark {
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_rows_model.on_sorting(e);
|
||||
|
||||
public IEnumerable<string> CsvRows(bool includeHeaderRow) {
|
||||
var cols = _columns.AsCollection().ToArray();
|
||||
if (cols.Length == 0) {
|
||||
yield break;
|
||||
}
|
||||
var sb = new StringBuilder();
|
||||
if (includeHeaderRow) {
|
||||
for (int i = 0; i < cols.Length; i++) {
|
||||
if (i > 0) {
|
||||
sb.Append(',');
|
||||
}
|
||||
sb.Append(cols[i].id.ToString());
|
||||
}
|
||||
yield return sb.ToString();
|
||||
}
|
||||
foreach (var r in rows) {
|
||||
sb.Clear();
|
||||
for (int i = 0; i < cols.Length; i++) {
|
||||
if (i > 0) {
|
||||
sb.Append(',');
|
||||
}
|
||||
sb.Append(cols[i].GetText(r));
|
||||
}
|
||||
yield return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(
|
||||
OrdersModel model, FillsSettings state) {
|
||||
var orders_hash = state.orders?.ToHashSet();
|
||||
|
||||
@@ -17,6 +17,7 @@ using Spark.Core.Disposables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
@@ -91,6 +92,7 @@ namespace mbts.spark {
|
||||
has_selected_rows_src,
|
||||
_ => _,
|
||||
_ => _vm.selected_rows.Select(r => r.security_key).ToImmutableHashSet()),
|
||||
new SparkCommandCommand("Export to CSV...", Icons.Disk, FileDialogCommand.ExportToCsv(ExportToCsv)),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
@@ -128,6 +130,14 @@ namespace mbts.spark {
|
||||
// show_ladder_cmd,
|
||||
// SparkCommands.KeyGestures.ShowLadder);
|
||||
// }
|
||||
|
||||
void ExportToCsv(string filePath) {
|
||||
using var fs = File.OpenWrite(filePath);
|
||||
using var sw = new StreamWriter(fs);
|
||||
foreach (var row in _vm.CsvRows(true)) {
|
||||
sw.WriteLine(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace mbts.spark {
|
||||
|
||||
interface IFillsColumn : IFilterableDIYGridColumn<FillTuple, FillsView.Columns, FillsFilterStates>, ISortableDIYGridColumn<FillsView.Columns> {
|
||||
DIYGrid.ColumnBase column_view { get; }
|
||||
string GetText(FillsRowVM row);
|
||||
}
|
||||
|
||||
static class FillsColumnExtensions {
|
||||
|
||||
@@ -28,7 +28,9 @@ namespace bts.shell.pnl {
|
||||
foreach (var t in _triggers) {
|
||||
foreach (var kv in t.trigger(data)) {
|
||||
if (!bd.TryGetValue(kv.Key, out var app)) {
|
||||
app = _defaults[kv.Key];
|
||||
if (!_defaults.TryGetValue(kv.Key, out app)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
app = kv.Value.merge(app);
|
||||
bd[kv.Key] = app;
|
||||
|
||||
@@ -15,6 +15,19 @@ namespace mbts.ui {
|
||||
public static IDialogCommand open_file(Action<string> execute, Func<bool> can_execute, string initial_dir, params (string name, string[] extensions)[] extensions) =>
|
||||
new AnonymousFileDialogCommand<object>(_ => _create_open_file_dialog(null, extensions), (_, file) => execute(file), _ => can_execute());
|
||||
|
||||
public static IDialogCommand ExportToCsv(Action<string> execute, Func<bool> canExecute = null, string initialDir = null, string initialName = null) {
|
||||
return new AnonymousFileDialogCommand<object>(DialogFactory, Execute, CanExecute);
|
||||
|
||||
SaveFileDialog DialogFactory(object _) =>
|
||||
_create_save_file_dialog(initialDir, initialName, new[] { ("Excel", new[] { "csv" }) });
|
||||
|
||||
void Execute(object _, string file) =>
|
||||
execute(file);
|
||||
|
||||
bool CanExecute(object _) =>
|
||||
canExecute?.Invoke() ?? true;
|
||||
}
|
||||
|
||||
public static IDialogCommand export_proto<TProto>(
|
||||
Func<TProto> get_proto,
|
||||
Func<bool> can_execute = null,
|
||||
@@ -66,7 +79,7 @@ namespace mbts.ui {
|
||||
}
|
||||
}, can_execute == null ? null : new Func<object, bool>(_ => can_execute()));
|
||||
|
||||
static TDialog _create_file_dialog<TDialog>(string initial_dir = null, IReadOnlyList<(string name, string[] extensions)> filter_items = null) where TDialog : FileDialog, new() {
|
||||
public static TDialog CreateFileDialog<TDialog>(string initial_dir = null, IReadOnlyList<(string name, string[] extensions)> filter_items = null) where TDialog : FileDialog, new() {
|
||||
var rv = new TDialog();
|
||||
if (!string.IsNullOrEmpty(initial_dir))
|
||||
rv.InitialDirectory = initial_dir;
|
||||
@@ -78,7 +91,7 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
static SaveFileDialog _create_save_file_dialog(string initial_dir = null, string initial_name = null, IReadOnlyList<(string name, string[] extensions)> filter_items = null) {
|
||||
var rv = _create_file_dialog<SaveFileDialog>(initial_dir, filter_items);
|
||||
var rv = CreateFileDialog<SaveFileDialog>(initial_dir, filter_items);
|
||||
rv.OverwritePrompt = true;
|
||||
rv.AddExtension = true;
|
||||
if (!string.IsNullOrEmpty(initial_name))
|
||||
@@ -87,7 +100,7 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
static OpenFileDialog _create_open_file_dialog(string initial_dir = null, IReadOnlyList<(string name, string[] extensions)> filter_items = null) =>
|
||||
_create_file_dialog<OpenFileDialog>(initial_dir, filter_items);
|
||||
CreateFileDialog<OpenFileDialog>(initial_dir, filter_items);
|
||||
|
||||
static string _build_filter(IEnumerable<(string name, string[] extensions)> items) =>
|
||||
string.Join("|", items.Select(i => {
|
||||
|
||||
@@ -240,16 +240,18 @@ namespace mbts.ui.utility {
|
||||
wrapped_models.InsertSorted(table.state.Values, comp);
|
||||
return;
|
||||
} else {
|
||||
var view_item = update(target[pos], ch.New);
|
||||
int new_pos = CollectionHelper.binary_search(wrapped_models, ch.New, comp);
|
||||
var view_item_state = update(target[pos], ch.New);
|
||||
if (new_pos < 0)
|
||||
new_pos = ~new_pos;
|
||||
var view_item = update(target[pos], ch.New);
|
||||
if (Math.Abs(new_pos - pos) > 1) {
|
||||
// If the abs diff between old & new positions is 1 (or 0),
|
||||
// the item does not need to be moved because its position wrt the other elements is unchanged.
|
||||
var state = on_moving == null ? default : on_moving(view_item);
|
||||
target.RemoveAt(pos);
|
||||
if (new_pos > pos) {
|
||||
new_pos--;
|
||||
}
|
||||
target.Insert(new_pos, view_item);
|
||||
on_moved?.Invoke(view_item, state);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ namespace mbts.co2 {
|
||||
using System.Threading;
|
||||
|
||||
struct TimeStamp {
|
||||
#pragma warning disable CS0649
|
||||
long _value;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public static ulong invalid_value => ulong.MaxValue;
|
||||
public ulong time => (ulong)Interlocked.Read(ref _value);
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "PingpongAlgo.h"
|
||||
|
||||
#include "spark/domain_model/Security.h"
|
||||
#include "spark/orderman/order_factory/IOrderFactory.h"
|
||||
#include "spark/orderman/orderman_utils/Book.h"
|
||||
#include "spark/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "spark/orderman/orders/algo/IAlgoServices.h"
|
||||
#include "spark/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
#include "spark/os/os.h"
|
||||
#include "spark/utils/PBUtils.h"
|
||||
#include "spark/utils/datetime.h"
|
||||
#include <stdint.h>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
|
||||
enum class PingpongAlgo::Status { WAITING_FOR_SIGNAL, WORKING, DONE };
|
||||
|
||||
struct PingpongAlgo::PImpl {
|
||||
Status state = Status::WAITING_FOR_SIGNAL;
|
||||
IAlgoServices *algo_services = nullptr;
|
||||
SecurityKey sec_key = SecurityKey(0);
|
||||
//PingpongAlgo::PingpongAlgoParams algo_params;
|
||||
};
|
||||
|
||||
PingpongAlgo::PingpongAlgo() : me(new PImpl()) {}
|
||||
PingpongAlgo::~PingpongAlgo() {}
|
||||
// initialization /////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
OrderOpResult PingpongAlgo::do_before_launch(
|
||||
IBeforeLaunchAlgoServices & /*serv*/) {
|
||||
return {OrderError::ORDER_ERROR_OK, std::string("OK")};
|
||||
}
|
||||
void PingpongAlgo::do_launch(IAlgoServices &algo_services) {
|
||||
me->algo_services = &algo_services;
|
||||
_algo().subscribe_status(me->sec_key);
|
||||
//_algo().subscribe_timer();
|
||||
}
|
||||
void PingpongAlgo::do_launch_attached(
|
||||
IAlgoServices &algo_services, OrderKey, ChildState) {
|
||||
me->algo_services = &algo_services;
|
||||
_cxl_and_end(true, "TAS Algo can not be attached to working orders");
|
||||
}
|
||||
// updates ////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void PingpongAlgo::do_update(const api::orderman::Update &) {
|
||||
LOG(OrderMan, Warn,
|
||||
"PingpongAlgo::do_update order:{} state:{} update not allowed ",
|
||||
_algo().order_key().value(), static_cast<int>(me->state));
|
||||
}
|
||||
// callbacks //////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void PingpongAlgo::on_book(SecurityKey, const Book &) {
|
||||
// find the mean and std dev. for std dev, we'll most likely have to keep a sliding window of the
|
||||
// bids and asks, perhaps a std::vector<std::pair<double, double>> and we recompute it everytime.
|
||||
|
||||
// we put buy and sell open pos orders at μ + ol.σ and μ + os.σ respectively
|
||||
// (ol will be < 0, for example when ol = -2, our buy order will go at μ - 2σ).
|
||||
// when the open long pos order gets filled, we send the close long position order at μ + cl.σ.
|
||||
// when the buy open pos order gets filled, we send the close long position order at μ + cl.σ.
|
||||
// similiarly for short.
|
||||
}
|
||||
void PingpongAlgo::on_child_change(OrderKey, ChildState const &) {}
|
||||
void PingpongAlgo::on_status(SecurityKey, co2::TradingStatus) {}
|
||||
void PingpongAlgo::on_timer() {}
|
||||
IAlgoServices &PingpongAlgo::_algo() const { return *me->algo_services; }
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ChildTemplate *PingpongAlgo::_create_order_template(
|
||||
IBeforeLaunchAlgoServices &, SecurityKey, api::Side) {
|
||||
return nullptr;
|
||||
}
|
||||
void PingpongAlgo::_switch_state(PingpongAlgo::Status new_state) {
|
||||
switch (new_state) {
|
||||
case (PingpongAlgo::Status::WAITING_FOR_SIGNAL):
|
||||
bts_assert(false);
|
||||
break;
|
||||
case (PingpongAlgo::Status::WORKING):
|
||||
bts_assert(me->state == PingpongAlgo::Status::WAITING_FOR_SIGNAL);
|
||||
break;
|
||||
case (PingpongAlgo::Status::DONE):
|
||||
bts_assert(me->state != PingpongAlgo::Status::DONE);
|
||||
break;
|
||||
}
|
||||
LOG(OrderMan, Info, "PingpongAlgo::_switch_state key:{} {}->{} ",
|
||||
_algo().order_key(), static_cast<int>(me->state),
|
||||
static_cast<int>(new_state));
|
||||
me->state = new_state;
|
||||
}
|
||||
|
||||
void PingpongAlgo::_cxl_and_end(bool is_error, const std::string &err_msg) {
|
||||
if (is_error)
|
||||
_algo().terminate_error(err_msg);
|
||||
else
|
||||
_algo().complete();
|
||||
_switch_state(PingpongAlgo::Status::DONE);
|
||||
}
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "spark/orderman/orders/algo/IAlgoOrder.h"
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
class ChildTemplate;
|
||||
|
||||
class PingpongAlgo : public IAlgoOrder {
|
||||
public:
|
||||
PingpongAlgo();
|
||||
~PingpongAlgo();
|
||||
|
||||
public:
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
void on_status(SecurityKey, co2::TradingStatus) override;
|
||||
void on_timer() override;
|
||||
|
||||
private:
|
||||
enum class Status;
|
||||
struct PImpl;
|
||||
PImpl *me;
|
||||
//std::unique_ptr<PImpl> me;
|
||||
|
||||
private:
|
||||
IAlgoServices &_algo() const;
|
||||
static ChildTemplate *_create_order_template(
|
||||
IBeforeLaunchAlgoServices &, SecurityKey, api::Side);
|
||||
// acts on md, cognizant of the state
|
||||
void _switch_state(Status new_status);
|
||||
void _cxl_and_end(bool is_error, const std::string &err_msg);
|
||||
};
|
||||
} // namespace bts::orderman
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
#include "DormanAlgo.h"
|
||||
#include "OnSignalAlgo.h"
|
||||
#include "PyramidAlgo.h"
|
||||
@@ -11,6 +10,7 @@
|
||||
#include "spark/config/protos/toggles.pb.h"
|
||||
#include "spark/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "spark/orderman/orders/algo/algo_orders/LimitCoverAlgo.h"
|
||||
#include "spark/orderman/orders/algo/algo_orders/SprayAndSkewAlgo.h"
|
||||
#include "spark/orderman/orders/algo/algo_orders/TaskingEyeAlgo.h"
|
||||
#include "spark/orderman/orders/algo/algo_orders/VolAlgo.h"
|
||||
#include "spark/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
@@ -238,11 +238,11 @@ extern "C" void init_quoting_algo_plugin(bts::SparkServices &srv) {
|
||||
quoting_algo.set_dynamic_order_type("QuotingAlgo");
|
||||
*quoting_algo.add_param_desc() = param_int32("n_ticks");
|
||||
*quoting_algo.add_param_desc() = param_order("child_order");
|
||||
srv.register_dynamic_algo_order(
|
||||
quoting_algo, []() { return std::make_unique<orderman::QuotingAlgo>(); });
|
||||
srv.register_dynamic_algo_order(quoting_algo, []() {
|
||||
return std::make_unique<orderman::QuotingAlgo>();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
extern "C" void init_on_signal_plugin(bts::SparkServices &srv) {
|
||||
DynamicOrderDescriptor on_signal;
|
||||
on_signal.set_dynamic_order_type("OnSignal");
|
||||
@@ -307,6 +307,25 @@ extern "C" void init_execution_tester_plugin(bts::SparkServices &srv) {
|
||||
return std::make_unique<orderman::ExecutionTesterAlgo>();
|
||||
});
|
||||
}
|
||||
extern "C" void init_spray_and_skew_plugin(bts::SparkServices &srv) {
|
||||
DynamicOrderDescriptor spray_and_skew;
|
||||
spray_and_skew.set_dynamic_order_type("Spray and Skew");
|
||||
// contracts
|
||||
*spray_and_skew.add_param_desc() = param_double("Half Spread");
|
||||
*spray_and_skew.add_param_desc() = param_double("Skew");
|
||||
*spray_and_skew.add_param_desc() = param_double("C1");
|
||||
*spray_and_skew.add_param_desc() = param_uint32("Looking Depth Levels");
|
||||
*spray_and_skew.add_param_desc() = param_uint32("Window");
|
||||
*spray_and_skew.add_param_desc() = param_double("Interval Sec");
|
||||
*spray_and_skew.add_param_desc() = param_uint32("Grid Num");
|
||||
*spray_and_skew.add_param_desc() = param_double("Grid Interval");
|
||||
*spray_and_skew.add_param_desc() = param_double("Order Size");
|
||||
*spray_and_skew.add_param_desc() = param_double("Max Position Dollar");
|
||||
|
||||
srv.register_dynamic_algo_order(spray_and_skew, []() {
|
||||
return std::make_unique<orderman::SprayAndSkewAlgo>();
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" void init_task_eye_plugin(bts::SparkServices &srv) {
|
||||
DynamicOrderDescriptor tasking_eye_algo;
|
||||
@@ -350,6 +369,22 @@ extern "C" void init_vol_algo_plugin(bts::SparkServices &srv) {
|
||||
vol_algo, []() { return std::make_unique<orderman::VolAlgo>(); });
|
||||
}
|
||||
|
||||
extern "C" void init_pingpong_plugin(bts::SparkServices &srv) {
|
||||
DynamicOrderDescriptor pingpong_algo;
|
||||
pingpong_algo.set_dynamic_order_type("Pingpong");
|
||||
// μ + ol.σ
|
||||
*pingpong_algo.add_param_desc() = param_double("Open Long Pos");
|
||||
//μ + cl.σ
|
||||
*pingpong_algo.add_param_desc() = param_double("Close Long Pos");
|
||||
// μ + os.σ
|
||||
*pingpong_algo.add_param_desc() = param_double("Open Short Pos");
|
||||
// μ + cs.σ
|
||||
*pingpong_algo.add_param_desc() = param_double("Close Short Pos");
|
||||
*pingpong_algo.add_param_desc() = param_uint32("Sliding Window Seconds");
|
||||
srv.register_dynamic_algo_order(
|
||||
pingpong_algo, []() { return std::make_unique<orderman::VolAlgo>(); });
|
||||
}
|
||||
|
||||
extern "C" void init_spark_plugin(bts::SparkServices &srv) {
|
||||
auto &dyn_algo_config = config::get<config::toggles::DynamicAlgos>();
|
||||
if (dyn_algo_config.enable_dorman())
|
||||
@@ -369,9 +404,11 @@ extern "C" void init_spark_plugin(bts::SparkServices &srv) {
|
||||
if (dyn_algo_config.enable_execution_tester())
|
||||
init_execution_tester_plugin(srv);
|
||||
// init_sniper_plugin(srv);
|
||||
init_spray_and_skew_plugin(srv);
|
||||
init_task_eye_plugin(srv);
|
||||
init_vol_algo_plugin(srv);
|
||||
init_quoting_algo_plugin(srv);
|
||||
init_pingpong_plugin(srv);
|
||||
}
|
||||
|
||||
} // namespace dorman
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "spark/domain_model/SecurityKey.h"
|
||||
#include "spark/domain_model/common.h"
|
||||
#include "spark/orderman/orders/algo/IAlgoOrder.h"
|
||||
#include <openssl/ossl_typ.h>
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
/*
|
||||
Spray tiny limit orders on both sides of the book every 500 ms (here via obi_mm), inside a very tight half-spread (≈1 bp).
|
||||
Lean (skew) the quotes away from your inventory—if buys fill and you're long, pull bids a tick lower and lift offers a tick higher—so the position mean-reverts instead of snowballing.
|
||||
Because fills come to you passively, each trade earns the maker rebate (-0.005 %), and the micro-edge from skewing usually caps adverse selection; over thousands of fills the rebates > tiny pnl bleed, so the net is positive even if price action is noise.
|
||||
|
||||
The minimal loop looks like:
|
||||
while hbt.elapse(0.5e9) == 0: # every 0.5 s
|
||||
mid = (best_bid + best_ask)/2
|
||||
fair = mid + c1 * alpha # alpha = std-score of OBI std score should be normalized between 1 and -1, c1 would be something lik 1 or 10
|
||||
rp = fair - skew * (pos/qty) # inventory-adjusted reservation price, skew 1 and 10, dividing by quanity to make sure in ticks
|
||||
bid = max(np.floor(rp - half_spread), best_bid) #half_spreads 50 or 100 ticks number of ticks
|
||||
ask = min(np.ceil (rp + half_spread), best_ask)
|
||||
make_grid(bid, ask, qty) # post / cancel as in obi_mm
|
||||
max_position_dollar # set as parameter, max position you can hold in terms of dollars and if this is met hold everything on that side keep algo running, algo max position in terms of contracts
|
||||
cancel and update:
|
||||
on update
|
||||
|
||||
0.005%
|
||||
|
||||
That's all: churn volume, stay flat, clip rebates. Tweak half_spread, skew, and order_qty until the "rebate pnl" outweighs fees and slippage.*/
|
||||
struct custom_state;
|
||||
class SprayAndSkewAlgo : public IAlgoOrder {
|
||||
public:
|
||||
SprayAndSkewAlgo();
|
||||
~SprayAndSkewAlgo();
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
void on_status(SecurityKey, co2::TradingStatus) override;
|
||||
void on_timer() override;
|
||||
void on_position(PositionUpdate const &) override;
|
||||
double get_rp(
|
||||
double stddev, double imbalance, double mean, double mid);
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
|
||||
private:
|
||||
void _terminate_algo(bool is_error, const std::string &err_msg);
|
||||
void update_custom_state(const custom_state &cs);
|
||||
void timed_handler();
|
||||
};
|
||||
|
||||
} // namespace bts::orderman
|
||||
@@ -22,6 +22,7 @@ void init_plugins(SparkServices &services) {
|
||||
boost::filesystem::path(), ""))>
|
||||
keep_alive;
|
||||
|
||||
init_spark_plugin(services);
|
||||
auto plugin_path = os::sparkd_wd() + "/plugins/";
|
||||
if (!boost::filesystem::is_directory(plugin_path)) {
|
||||
LOG(Plugin, Info,
|
||||
@@ -31,7 +32,6 @@ void init_plugins(SparkServices &services) {
|
||||
}
|
||||
|
||||
// initialize the plugins in libspark.so
|
||||
init_spark_plugin(services);
|
||||
|
||||
boost::filesystem::directory_iterator end;
|
||||
for (auto iter = boost::filesystem::directory_iterator(plugin_path);
|
||||
|
||||
Reference in new issue
Block a user