clboss/Boss/Mod/PeerTrackRecord.hpp
Ken Sedgwick a83489ab03
PeerTrackRecord: judge channel candidates by past earnings
New module that judges the earnings track record of nodes we had
channels with before, so channel-open candidate selection can prefer
proven earners.  The metric is TRAL (annualized net return on
liquidity, in basis points), the same metric as
contrib/clboss-forwarding-stats: net earnings from the EarningsTracker
daily buckets, divided by the average balance and the observed
operational days from the FeeMonitor records, annualized.  Both
sources persist after a channel closes, which is exactly the case this
serves: candidates whose previous channel with us is gone.

Operational days count only in-window records (roughly one per hour
while a channel exists), so a channel that overlapped the window
partially is annualized over its actual operating time.  Averaging the
balance per-record rather than sample-and-hold over wall-clock time
keeps a mid-window close from diluting the average.

Verdicts: keeper (TRAL at or above threshold), no-record (no or
insufficient history), underperformer (history below threshold).

Three dynamic options (runtime-settable via setconfig):
clboss-candidate-record-window-days (180),
clboss-candidate-keeper-tral-bps (50),
clboss-candidate-min-record-days (7).

A clboss-track-record nodeid command shows the verdict and its inputs
for one node, plus the current option values, for tuning at runtime.
2026-08-04 11:02:14 -07:00

87 lines
2.5 KiB
C++

#ifndef BOSS_MOD_PEERTRACKRECORD_HPP
#define BOSS_MOD_PEERTRACKRECORD_HPP
#include"Boss/Msg/TrackRecord.hpp"
#include"Ev/Io.hpp"
#include"Ev/now.hpp"
#include"Ln/CommandId.hpp"
#include"Sqlite3.hpp"
#include<cstdint>
#include<functional>
#include<string>
#include<vector>
namespace Boss { namespace Msg { struct Option; }}
namespace Ln { class NodeId; }
namespace S { class Bus; }
namespace Boss { namespace Mod {
/** class Boss::Mod::PeerTrackRecord
*
* @brief Judges the earnings track record of nodes we had
* channels with before, so channel-open candidate selection
* can prefer proven earners ("keepers") and defer known
* underperformers.
*
* @desc Responds to `Boss::Msg::RequestPeerTrackRecord` with
* `Boss::Msg::ResponsePeerTrackRecord`.
*
* The judgment is TRAL (annualized net return on liquidity,
* in basis points), the same metric as the
* `contrib/clboss-forwarding-stats` TRAL column: net earnings
* from the `EarningsTracker` daily buckets, divided by the
* average channel balance and the observed operational days
* from the `FeeMonitor` records, annualized. Both data
* sources persist after a channel closes, which is exactly
* the case this module serves: candidates whose previous
* channel with us is gone.
*
* Operational days count only in-window records (~one per
* hour while a channel exists), so a channel that overlapped
* the window partially, or closed mid-window, is annualized
* over its actual operating time and not diluted by the gap.
*/
class PeerTrackRecord {
private:
S::Bus& bus;
std::function<double()> get_now;
Sqlite3::Db db;
/* Options; see the manifest descriptions in the .cpp. */
std::int64_t window_days;
std::int64_t keeper_tral_bps;
std::int64_t min_record_days;
void start();
Ev::Io<void> on_option(Msg::Option const& o);
Ev::Io<void> handle_int_option( Msg::Option const& o
, std::int64_t& target
, char const* name
, std::int64_t minimum
);
Ev::Io<void> on_request( void* requester
, std::vector<Ln::NodeId> nodes
);
Ev::Io<void> on_command(Ln::CommandId id, std::string nodeid);
Ev::Io<Sqlite3::Tx> db_transact();
bool have_tables(Sqlite3::Tx& tx);
Msg::TrackRecord compute( Sqlite3::Tx& tx
, std::string const& node
, double now
);
public:
PeerTrackRecord() =delete;
PeerTrackRecord(PeerTrackRecord&&) =delete;
PeerTrackRecord(PeerTrackRecord const&) =delete;
explicit
PeerTrackRecord( S::Bus& bus_
, std::function<double()> get_now_ = &Ev::now
);
};
}}
#endif /* !defined(BOSS_MOD_PEERTRACKRECORD_HPP) */