fix: replace evaluate.c cascade with paper-trade simulation from executor
The evaluation now runs the exact same paper-mode simulation code that the executor uses, instead of a separate cascade with different formulas. predicted_bps comes from the simulation PnL, matching effective_bps.
This commit is contained in:
parent
174b7570fa
commit
728f41679a
252
src/evaluate.c
252
src/evaluate.c
|
|
@ -18,6 +18,7 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
static inline int64_t now_ms(void) {
|
static inline int64_t now_ms(void) {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
|
|
@ -25,6 +26,18 @@ static inline int64_t now_ms(void) {
|
||||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Copied from executor.c for paper-trade simulation ── */
|
||||||
|
static double apply_fee_hold(double vol, double fee_rate, bool next_is_buy) {
|
||||||
|
if (next_is_buy && fee_rate > 0)
|
||||||
|
vol /= (1.0 + fee_rate);
|
||||||
|
return vol;
|
||||||
|
}
|
||||||
|
static double apply_increment_floor(double vol, double inc) {
|
||||||
|
if (inc > 0)
|
||||||
|
vol = floor(vol / inc - 1e-12) * inc;
|
||||||
|
return vol;
|
||||||
|
}
|
||||||
|
|
||||||
void evaluator_init(evaluator_t *ev, const triangle_set_t *triangles,
|
void evaluator_init(evaluator_t *ev, const triangle_set_t *triangles,
|
||||||
const order_book_t *books, const config_t *cfg,
|
const order_book_t *books, const config_t *cfg,
|
||||||
spsc_queue_t *queue, bool kcs_discount) {
|
spsc_queue_t *queue, bool kcs_discount) {
|
||||||
|
|
@ -37,7 +50,6 @@ void evaluator_init(evaluator_t *ev, const triangle_set_t *triangles,
|
||||||
ev->stats.best_net_bps = -1e18;
|
ev->stats.best_net_bps = -1e18;
|
||||||
ev->stats.worst_net_bps = 1e18;
|
ev->stats.worst_net_bps = 1e18;
|
||||||
ev->stats.best_triangle_key[0] = '\0';
|
ev->stats.best_triangle_key[0] = '\0';
|
||||||
memset(ev->last_signal_ts_ms, 0, sizeof(ev->last_signal_ts_ms));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -54,8 +66,10 @@ void evaluator_init(evaluator_t *ev, const triangle_set_t *triangles,
|
||||||
* (converted back to starting quote via inverse cumulative product)
|
* (converted back to starting quote via inverse cumulative product)
|
||||||
* 6. Apply exchange precision rounding:
|
* 6. Apply exchange precision rounding:
|
||||||
* - floor() for quantities (base size)
|
* - floor() for quantities (base size)
|
||||||
* - ceil() for quote costs (must cover the order)
|
* - ceil() for quote costs (must cover the full cost)
|
||||||
* - Adjust by 1e-12 epsilon to avoid floating-point boundary errors
|
* - Adjust by 1e-12 epsilon to avoid floating-point boundary errors
|
||||||
|
* e.g. ceil(value / qi - 1e-12) ensures that 0.10000000000000001 doesn't
|
||||||
|
* round up to 0.10000001 when qi = 0.01
|
||||||
* 7. Check base_min_size constraints in live mode
|
* 7. Check base_min_size constraints in live mode
|
||||||
* 8. Push signal to queue with full leg/order params
|
* 8. Push signal to queue with full leg/order params
|
||||||
*
|
*
|
||||||
|
|
@ -122,12 +136,10 @@ bool evaluate_symbol(evaluator_t *ev, uint16_t symbol_idx, int64_t t_sock_arrive
|
||||||
double rate;
|
double rate;
|
||||||
double max_input;
|
double max_input;
|
||||||
if (use_bid) {
|
if (use_bid) {
|
||||||
// Sell: we receive base, sell at bid -> output = bid_price
|
|
||||||
if (bk->bid_count == 0) { valid = false; break; }
|
if (bk->bid_count == 0) { valid = false; break; }
|
||||||
rate = bk->bids[0][0];
|
rate = bk->bids[0][0];
|
||||||
max_input = bk->bids[0][1];
|
max_input = bk->bids[0][1];
|
||||||
} else {
|
} else {
|
||||||
// Buy: we input quote, buy at ask -> rate = 1/ask_price (quote-to-base conversion)
|
|
||||||
if (bk->ask_count == 0) { valid = false; break; }
|
if (bk->ask_count == 0) { valid = false; break; }
|
||||||
double ask_price = bk->asks[0][0];
|
double ask_price = bk->asks[0][0];
|
||||||
if (ask_price <= 0.0) { valid = false; break; }
|
if (ask_price <= 0.0) { valid = false; break; }
|
||||||
|
|
@ -141,9 +153,6 @@ bool evaluate_symbol(evaluator_t *ev, uint16_t symbol_idx, int64_t t_sock_arrive
|
||||||
double leg_mult = rate * ff;
|
double leg_mult = rate * ff;
|
||||||
cumulative *= leg_mult;
|
cumulative *= leg_mult;
|
||||||
|
|
||||||
// max_v0_list[leg]: how much starting quote can pass through this leg
|
|
||||||
// Divide by cumulative_mult (product of prior leg multipliers) to
|
|
||||||
// convert this leg's max_input back to starting-quote-equivalent volume
|
|
||||||
if (cumulative_mult > 0) {
|
if (cumulative_mult > 0) {
|
||||||
max_v0_list[leg] = max_input / cumulative_mult;
|
max_v0_list[leg] = max_input / cumulative_mult;
|
||||||
}
|
}
|
||||||
|
|
@ -250,81 +259,177 @@ bool evaluate_symbol(evaluator_t *ev, uint16_t symbol_idx, int64_t t_sock_arrive
|
||||||
signal_entry_t sig;
|
signal_entry_t sig;
|
||||||
memset(&sig, 0, sizeof(sig));
|
memset(&sig, 0, sizeof(sig));
|
||||||
|
|
||||||
/*
|
/* Set leg 0 order_param from max_volume (used by the paper simulation) */
|
||||||
* Compute per-leg order parameters at max_volume scale with exchange precision.
|
{
|
||||||
*
|
double fi0 = tri->funds_increment[0];
|
||||||
* Precision rounding strategy:
|
double qi0 = tri->quote_increment[0];
|
||||||
* - Base size: floor(base_increment) — we cannot trade a fraction of a step
|
double bi0 = tri->base_increment[0];
|
||||||
* - Quote cost: ceil(quote_increment) — must cover the full cost
|
double ff0 = tri->fee_factor[0];
|
||||||
* - 1e-12 epsilon guards against floating-point truncation at increment boundaries
|
if (fi0 <= 0) fi0 = qi0;
|
||||||
* e.g. ceil(value / qi - 1e-12) ensures that 0.10000000000000001 doesn't
|
bool is_buy0 = !tri->use_bid[0];
|
||||||
* round up to 0.10000001 when qi = 0.01
|
double price0 = is_buy0 ? books_arr[0]->asks[0][0] : books_arr[0]->bids[0][0];
|
||||||
*
|
|
||||||
* Buy leg: input = quote, output = base
|
|
||||||
* quote_cost = ceil(leg_input / qi - eps) * qi
|
|
||||||
* net = quote_cost * ff
|
|
||||||
* base = floor(net / price / bi + eps) * bi
|
|
||||||
* final_quote = ceil(base * price / qi - eps) * qi (re-check)
|
|
||||||
*
|
|
||||||
* Sell leg: input = base, output = quote
|
|
||||||
* base = floor(leg_input / bi + eps) * bi
|
|
||||||
* gross = ceil(base * price / qi - eps) * qi
|
|
||||||
* net = gross * ff
|
|
||||||
*/
|
|
||||||
double leg_input = max_volume;
|
|
||||||
double leg_quote_vol[3] = {0};
|
|
||||||
double leg_base_size[3] = {0};
|
|
||||||
for (int leg = 0; leg < 3; leg++) {
|
|
||||||
const order_book_t *bk = books_arr[leg];
|
|
||||||
double bi = tri->base_increment[leg];
|
|
||||||
double qi = tri->quote_increment[leg];
|
|
||||||
double ff = tri->fee_factor[leg];
|
|
||||||
bool is_buy = !tri->use_bid[leg];
|
|
||||||
double price = is_buy ? bk->asks[0][0] : bk->bids[0][0];
|
|
||||||
double leg_output;
|
|
||||||
|
|
||||||
if (is_buy) {
|
if (is_buy0) {
|
||||||
double fi = tri->funds_increment[leg];
|
double quote_input = floor(max_volume / qi0 - 1e-12) * qi0;
|
||||||
if (fi <= 0) fi = qi;
|
double base = floor(quote_input * ff0 / price0 / bi0 - 1e-12) * bi0;
|
||||||
double quote_input = (qi > 0) ? floor(leg_input / qi - 1e-12) * qi : leg_input;
|
double quote_cost = floor(base * price0 / fi0 - 1e-12) * fi0;
|
||||||
double net = quote_input * ff;
|
sig.legs.legs[0].quote_volume = quote_cost;
|
||||||
double base = (bi > 0) ? floor(net / price / bi + 1e-12) * bi : (net / price);
|
|
||||||
double quote_cost = (fi > 0) ? floor(base * price / fi - 1e-12) * fi : (base * price);
|
|
||||||
leg_quote_vol[leg] = quote_cost;
|
|
||||||
sig.legs.legs[leg].quote_volume = quote_cost;
|
|
||||||
leg_base_size[leg] = base;
|
|
||||||
leg_output = base;
|
|
||||||
} else {
|
} else {
|
||||||
double base = (bi > 0) ? floor(leg_input / bi + 1e-12) * bi : leg_input;
|
double base = floor(max_volume / bi0 - 1e-12) * bi0;
|
||||||
double gross = (qi > 0) ? floor(base * price / qi - 1e-12) * qi : (base * price);
|
sig.legs.legs[0].quote_volume = floor(base * price0 / qi0 - 1e-12) * qi0;
|
||||||
leg_quote_vol[leg] = gross;
|
|
||||||
sig.legs.legs[leg].quote_volume = gross;
|
|
||||||
leg_base_size[leg] = base;
|
|
||||||
leg_output = gross * ff;
|
|
||||||
}
|
}
|
||||||
|
sig.legs.legs[0].base_increment = bi0;
|
||||||
|
sig.legs.legs[0].quote_increment = qi0;
|
||||||
|
sig.legs.legs[0].funds_increment = fi0;
|
||||||
|
sig.legs.legs[0].fee_rate = 1.0 - ff0;
|
||||||
|
strncpy(sig.legs.legs[0].symbol, tri->symbol_names[0], SYMBOL_NAME_LEN);
|
||||||
|
strncpy(sig.legs.legs[0].fee_currency, tri->fee_currency[0], CURRENCY_NAME_LEN);
|
||||||
|
snprintf(sig.legs.legs[0].order_param, sizeof(sig.legs.legs[0].order_param), "%.8g",
|
||||||
|
sig.legs.legs[0].quote_volume);
|
||||||
|
if (!tri->use_bid[0]) strncpy(sig.legs.legs[0].side, "buy", 5);
|
||||||
|
else strncpy(sig.legs.legs[0].side, "sell", 5);
|
||||||
|
}
|
||||||
|
/* Set legs 1-2 data needed for the simulation */
|
||||||
|
for (int l = 1; l < 3; l++) {
|
||||||
|
strncpy(sig.legs.legs[l].symbol, tri->symbol_names[l], SYMBOL_NAME_LEN);
|
||||||
|
strncpy(sig.legs.legs[l].fee_currency, tri->fee_currency[l], CURRENCY_NAME_LEN);
|
||||||
|
sig.legs.legs[l].base_increment = tri->base_increment[l];
|
||||||
|
sig.legs.legs[l].quote_increment = tri->quote_increment[l];
|
||||||
|
sig.legs.legs[l].funds_increment = tri->funds_increment[l];
|
||||||
|
sig.legs.legs[l].fee_rate = 1.0 - tri->fee_factor[l];
|
||||||
|
if (!tri->use_bid[l]) strncpy(sig.legs.legs[l].side, "buy", 5);
|
||||||
|
else strncpy(sig.legs.legs[l].side, "sell", 5);
|
||||||
|
}
|
||||||
|
|
||||||
/* Floor leg_output to the next leg's input increment so the
|
/* Paper-trade simulation: exact copy of executor.c */
|
||||||
cascade always produces valid order parameters downstream.
|
double leg_output[3] = {0};
|
||||||
For the last leg (leg 2) there is no next leg to constrain. */
|
double fills[3][6] = {{0}};
|
||||||
if (leg < 2) {
|
bool sim_ok = true;
|
||||||
bool next_buy = !tri->use_bid[leg + 1];
|
{
|
||||||
double next_incr = next_buy
|
for (int leg = 0; leg < 3; leg++) {
|
||||||
? tri->quote_increment[leg + 1]
|
signal_leg_t *sl = &sig.legs.legs[leg];
|
||||||
: tri->base_increment[leg + 1];
|
bool is_buy = (strcmp(sl->side, "buy") == 0);
|
||||||
if (next_incr > 0) {
|
|
||||||
leg_output = floor(leg_output / next_incr - 1e-12) * next_incr;
|
double input_vol;
|
||||||
|
if (leg == 0) {
|
||||||
|
input_vol = atof(sl->order_param);
|
||||||
|
input_vol = apply_increment_floor(input_vol,
|
||||||
|
is_buy ? (sl->funds_increment > 0 ? sl->funds_increment : sl->quote_increment) : sl->base_increment);
|
||||||
|
} else {
|
||||||
|
input_vol = leg_output[leg - 1];
|
||||||
|
input_vol = apply_fee_hold(input_vol, sl->fee_rate, is_buy);
|
||||||
|
input_vol = apply_increment_floor(input_vol,
|
||||||
|
is_buy ? (sl->funds_increment > 0 ? sl->funds_increment : sl->quote_increment) : sl->base_increment);
|
||||||
|
}
|
||||||
|
fills[leg][4] = input_vol;
|
||||||
|
|
||||||
|
double total_size = 0, total_funds = 0, avg_price = 0, total_fee = 0;
|
||||||
|
{
|
||||||
|
const order_book_t *bk = books_arr[leg];
|
||||||
|
if (is_buy && bk->ask_count > 0) {
|
||||||
|
double ask_price = bk->asks[0][0];
|
||||||
|
total_size = input_vol / ask_price;
|
||||||
|
if (sl->base_increment > 0)
|
||||||
|
total_size = floor(total_size / sl->base_increment - 1e-12) * sl->base_increment;
|
||||||
|
if (sl->quote_increment > 0)
|
||||||
|
total_funds = floor(total_size * ask_price / sl->quote_increment - 1e-12) * sl->quote_increment;
|
||||||
|
else
|
||||||
|
total_funds = total_size * ask_price;
|
||||||
|
avg_price = ask_price;
|
||||||
|
} else if (!is_buy && bk->bid_count > 0) {
|
||||||
|
double bid_price = bk->bids[0][0];
|
||||||
|
total_size = input_vol;
|
||||||
|
if (sl->base_increment > 0)
|
||||||
|
total_size = floor(total_size / sl->base_increment - 1e-12) * sl->base_increment;
|
||||||
|
if (sl->quote_increment > 0)
|
||||||
|
total_funds = floor(total_size * bid_price / sl->quote_increment - 1e-12) * sl->quote_increment;
|
||||||
|
else
|
||||||
|
total_funds = total_size * bid_price;
|
||||||
|
avg_price = bid_price;
|
||||||
|
} else {
|
||||||
|
ev->stats.triangles_skipped++;
|
||||||
|
sim_ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (sl->fee_rate > 0) {
|
||||||
|
double output_amt = is_buy ? total_size : total_funds;
|
||||||
|
total_fee = output_amt * sl->fee_rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leg_output[leg] = is_buy ? total_size : total_funds;
|
||||||
|
fills[leg][0] = leg_output[leg];
|
||||||
|
fills[leg][1] = avg_price;
|
||||||
|
fills[leg][2] = total_fee;
|
||||||
|
fills[leg][3] = total_funds;
|
||||||
|
|
||||||
|
if (leg < 2) {
|
||||||
|
signal_leg_t *nsl = &sig.legs.legs[leg + 1];
|
||||||
|
bool nxt_buy = (strcmp(nsl->side, "buy") == 0);
|
||||||
|
leg_output[leg] = apply_fee_hold(leg_output[leg], nsl->fee_rate, nxt_buy);
|
||||||
|
leg_output[leg] = apply_increment_floor(leg_output[leg],
|
||||||
|
nxt_buy ? (nsl->funds_increment > 0 ? nsl->funds_increment : nsl->quote_increment) : nsl->base_increment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leg_input = leg_output;
|
|
||||||
}
|
}
|
||||||
sig.starting_volume = leg_quote_vol[0];
|
if (!sim_ok) continue;
|
||||||
|
|
||||||
|
/* PnL: exact copy of executor.c */
|
||||||
|
double profit = 0;
|
||||||
|
net_bps = 0;
|
||||||
|
if (fills[2][0] > 0) {
|
||||||
|
double leg0_in = fills[0][4];
|
||||||
|
double leg2_out = fills[2][0];
|
||||||
|
{
|
||||||
|
const signal_leg_t *sl2 = &sig.legs.legs[2];
|
||||||
|
bool leg2_buy = (strcmp(sl2->side, "buy") == 0);
|
||||||
|
const char *pair = sl2->symbol;
|
||||||
|
const char *dash = strchr(pair, '-');
|
||||||
|
char base_ccy[16] = {0}, quote_ccy[16] = {0};
|
||||||
|
if (dash) {
|
||||||
|
size_t blen = (size_t)(dash - pair);
|
||||||
|
if (blen > 15) blen = 15;
|
||||||
|
memcpy(base_ccy, pair, blen);
|
||||||
|
strncpy(quote_ccy, dash + 1, 15);
|
||||||
|
}
|
||||||
|
const char *out_ccy = leg2_buy ? base_ccy : quote_ccy;
|
||||||
|
if (out_ccy[0] && strcmp(sl2->fee_currency, out_ccy) == 0) {
|
||||||
|
leg2_out -= fills[2][2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profit = leg2_out - leg0_in;
|
||||||
|
if (leg0_in > 0)
|
||||||
|
net_bps = (profit / leg0_in) * 10000.0;
|
||||||
|
}
|
||||||
|
sig.predicted_bps = net_bps;
|
||||||
|
|
||||||
|
if (net_bps > ev->stats.best_net_bps) {
|
||||||
|
ev->stats.best_net_bps = net_bps;
|
||||||
|
snprintf(ev->stats.best_triangle_key, sizeof(ev->stats.best_triangle_key),
|
||||||
|
"%s/%s/%s", tri->base, tri->mid, tri->quote);
|
||||||
|
}
|
||||||
|
if (net_bps < ev->stats.worst_net_bps) ev->stats.worst_net_bps = net_bps;
|
||||||
|
if (last_status_ms == 0 || now - last_status_ms >= 30000) {
|
||||||
|
last_status_ms = now;
|
||||||
|
log_write_screen("[STATUS] evals=%lu signals=%lu "
|
||||||
|
"best=%.2f bps (%s) | %u triangles\n",
|
||||||
|
(unsigned long)ev->stats.triangles_evaluated,
|
||||||
|
(unsigned long)ev->stats.signals_fired,
|
||||||
|
ev->stats.best_net_bps, ev->stats.best_triangle_key,
|
||||||
|
tris->triangle_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (net_bps <= cfg->signal_threshold_bps) {
|
||||||
|
ev->stats.triangles_skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.starting_volume = fills[0][3];
|
||||||
sig.live = cfg->live_mode;
|
sig.live = cfg->live_mode;
|
||||||
|
|
||||||
snprintf(sig.triangle_key, sizeof(sig.triangle_key), "%s/%s/%s",
|
snprintf(sig.triangle_key, sizeof(sig.triangle_key), "%s/%s/%s",
|
||||||
tri->base, tri->mid, tri->quote);
|
tri->base, tri->mid, tri->quote);
|
||||||
strncpy(sig.primary_quote, tri->base, CURRENCY_NAME_LEN);
|
strncpy(sig.primary_quote, tri->base, CURRENCY_NAME_LEN);
|
||||||
sig.predicted_bps = net_bps;
|
|
||||||
snprintf(sig.max_volume, sizeof(sig.max_volume), "%.8g", max_volume);
|
snprintf(sig.max_volume, sizeof(sig.max_volume), "%.8g", max_volume);
|
||||||
sig.ts_ms = now;
|
sig.ts_ms = now;
|
||||||
sig.book_ts_ms = book_ts_ms;
|
sig.book_ts_ms = book_ts_ms;
|
||||||
|
|
@ -367,12 +472,11 @@ bool evaluate_symbol(evaluator_t *ev, uint16_t symbol_idx, int64_t t_sock_arrive
|
||||||
|
|
||||||
bool use_bid = tri->use_bid[leg];
|
bool use_bid = tri->use_bid[leg];
|
||||||
bool is_buy = !use_bid;
|
bool is_buy = !use_bid;
|
||||||
// order_param: for buys the param is quote volume, for sells it's base size
|
|
||||||
if (is_buy) {
|
if (is_buy) {
|
||||||
snprintf(sl->order_param, sizeof(sl->order_param), "%.8g",
|
snprintf(sl->order_param, sizeof(sl->order_param), "%.8g",
|
||||||
sig.legs.legs[leg].quote_volume);
|
sig.legs.legs[leg].quote_volume);
|
||||||
} else {
|
} else {
|
||||||
snprintf(sl->order_param, sizeof(sl->order_param), "%.8g", leg_base_size[leg]);
|
snprintf(sl->order_param, sizeof(sl->order_param), "%.8g", fills[leg][4]);
|
||||||
}
|
}
|
||||||
sl->base_increment = tri->base_increment[leg];
|
sl->base_increment = tri->base_increment[leg];
|
||||||
sl->quote_increment = tri->quote_increment[leg];
|
sl->quote_increment = tri->quote_increment[leg];
|
||||||
|
|
@ -380,12 +484,10 @@ bool evaluate_symbol(evaluator_t *ev, uint16_t symbol_idx, int64_t t_sock_arrive
|
||||||
sl->base_min_size = tri->base_min_size[leg];
|
sl->base_min_size = tri->base_min_size[leg];
|
||||||
|
|
||||||
if (use_bid) {
|
if (use_bid) {
|
||||||
// Hit the bid: we sell base, receive quote
|
|
||||||
strncpy(sl->input_currency, base_cur, CURRENCY_NAME_LEN);
|
strncpy(sl->input_currency, base_cur, CURRENCY_NAME_LEN);
|
||||||
strncpy(sl->output_currency, quote_cur, CURRENCY_NAME_LEN);
|
strncpy(sl->output_currency, quote_cur, CURRENCY_NAME_LEN);
|
||||||
strncpy(sl->side, "sell", 5);
|
strncpy(sl->side, "sell", 5);
|
||||||
} else {
|
} else {
|
||||||
// Hit the ask: we buy base, pay quote
|
|
||||||
strncpy(sl->input_currency, quote_cur, CURRENCY_NAME_LEN);
|
strncpy(sl->input_currency, quote_cur, CURRENCY_NAME_LEN);
|
||||||
strncpy(sl->output_currency, base_cur, CURRENCY_NAME_LEN);
|
strncpy(sl->output_currency, base_cur, CURRENCY_NAME_LEN);
|
||||||
strncpy(sl->side, "buy", 5);
|
strncpy(sl->side, "buy", 5);
|
||||||
|
|
@ -399,7 +501,7 @@ bool evaluate_symbol(evaluator_t *ev, uint16_t symbol_idx, int64_t t_sock_arrive
|
||||||
ev->stats.signals_fired++;
|
ev->stats.signals_fired++;
|
||||||
ev->stats.last_eval_ts_ms = now;
|
ev->stats.last_eval_ts_ms = now;
|
||||||
log_write("[SIGNAL] %.4f bps vol=%s | %s (%s, %s, %s)\n",
|
log_write("[SIGNAL] %.4f bps vol=%s | %s (%s, %s, %s)\n",
|
||||||
net_bps, sig.max_volume, sig.triangle_key,
|
sig.predicted_bps, sig.max_volume, sig.triangle_key,
|
||||||
sig.legs.legs[0].symbol, sig.legs.legs[1].symbol, sig.legs.legs[2].symbol);
|
sig.legs.legs[0].symbol, sig.legs.legs[1].symbol, sig.legs.legs[2].symbol);
|
||||||
fired_any = true;
|
fired_any = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue