Adds Market command

This commit is contained in:
benbierens 2024-04-07 14:04:31 +02:00
parent b805c5f004
commit 9c8151bdb7
No known key found for this signature in database
GPG Key ID: 877D2C2E09A22F3A
10 changed files with 226 additions and 10 deletions

View File

@ -3,6 +3,7 @@
public class GiveRewardsCommand
{
public RewardUsersCommand[] Rewards { get; set; } = Array.Empty<RewardUsersCommand>();
public MarketAverage[] Averages { get; set; } = Array.Empty<MarketAverage>();
}
public class RewardUsersCommand
@ -10,4 +11,15 @@
public ulong RewardId { get; set; }
public string[] UserAddresses { get; set; } = Array.Empty<string>();
}
public class MarketAverage
{
public int NumberOfFinished { get; set; }
public TimeSpan TimeRange { get; set; }
public float Price { get; set; }
public float Size { get; set; }
public float Duration { get; set; }
public float Collateral { get; set; }
public float ProofProbability { get; set; }
}
}

View File

@ -0,0 +1,57 @@
using BiblioTech.Options;
using DiscordRewards;
using System.Globalization;
using Utils;
namespace BiblioTech.Commands
{
public class MarketCommand : BaseCommand
{
public override string Name => "market";
public override string StartingMessage => RandomBusyMessage.Get();
public override string Description => "Fetch some insights about current market conditions.";
protected override async Task Invoke(CommandContext context)
{
await context.Followup(GetInsights());
}
private string[] GetInsights()
{
var result = Program.Averages.SelectMany(GetInsight).ToArray();
if (result.Length > 0)
{
result = new[]
{
"No market insights available."
};
}
return result;
}
private string[] GetInsight(MarketAverage avg)
{
var headerLine = $"[Last {Time.FormatDuration(avg.TimeRange)}] ({avg.NumberOfFinished} Contracts finished)";
if (avg.NumberOfFinished == 0)
{
return new[] { headerLine };
}
return new[]
{
headerLine,
$"Price: {Format(avg.Price)}",
$"Size: {Format(avg.Size)}",
$"Duration: {Format(avg.Duration)}",
$"Collateral: {Format(avg.Collateral)}",
$"ProofProbability: {Format(avg.ProofProbability)}"
};
}
private string Format(float f)
{
return f.ToString("F3", CultureInfo.InvariantCulture);
}
}
}

View File

@ -3,6 +3,7 @@ using BiblioTech.Commands;
using BiblioTech.Rewards;
using Discord;
using Discord.WebSocket;
using DiscordRewards;
using Logging;
namespace BiblioTech
@ -16,6 +17,7 @@ namespace BiblioTech
public static AdminChecker AdminChecker { get; private set; } = null!;
public static IDiscordRoleDriver RoleDriver { get; set; } = null!;
public static ILog Log { get; private set; } = null!;
public static MarketAverage[] Averages { get; set; } = Array.Empty<MarketAverage>();
public static Task Main(string[] args)
{
@ -49,7 +51,8 @@ namespace BiblioTech
sprCommand,
associateCommand,
notifyCommand,
new AdminCommand(sprCommand)
new AdminCommand(sprCommand),
new MarketCommand()
);
await client.LoginAsync(TokenType.Bot, Config.ApplicationToken);

View File

@ -23,6 +23,7 @@ namespace BiblioTech.Rewards
{
try
{
Program.Averages = cmd.Averages;
await Program.RoleDriver.GiveRewards(cmd);
}
catch (Exception ex)

View File

@ -15,7 +15,7 @@ namespace TestNetRewarder
historicState.UpdateStorageRequests(contracts);
StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray();
FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFininshed).ToArray();
FinishedRequests = historicState.StorageRequests.Where(r => r.RecentlyFinished).ToArray();
RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange);
RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange);
SlotFilledEvents = contracts.GetSlotFilledEvents(blockRange);

View File

@ -19,6 +19,9 @@ namespace TestNetRewarder
[Uniform("check-history", "ch", "CHECKHISTORY", true, "Unix epoc timestamp of a moment in history on which processing begins. Required for hosting rewards. Should be 'launch of the testnet'.")]
public int CheckHistoryTimestamp { get; set; } = 0;
[Uniform("market-insights", "mi", "MARKETINSIGHTS", false, "Semi-colon separated integers. Each represents a multiple of intervals, for which a market insights average will be generated.")]
public string MarketInsights { get; set; } = "1;96";
public string LogPath
{
get
@ -26,5 +29,13 @@ namespace TestNetRewarder
return Path.Combine(DataPath, "logs");
}
}
public TimeSpan Interval
{
get
{
return TimeSpan.FromMinutes(IntervalMinutes);
}
}
}
}

View File

@ -33,7 +33,7 @@ namespace TestNetRewarder
public EthAddress[] Hosts { get; private set; }
public RequestState State { get; private set; }
public bool RecentlyStarted { get; private set; }
public bool RecentlyFininshed { get; private set; }
public bool RecentlyFinished { get; private set; }
public void Update(ICodexContracts contracts)
{
@ -45,7 +45,7 @@ namespace TestNetRewarder
State == RequestState.New &&
newState == RequestState.Started;
RecentlyFininshed =
RecentlyFinished =
State == RequestState.Started &&
newState == RequestState.Finished;

View File

@ -0,0 +1,122 @@
using CodexContractsPlugin.Marketplace;
using DiscordRewards;
using System.Numerics;
namespace TestNetRewarder
{
public class MarketTracker
{
private readonly List<ChainState> buffer = new List<ChainState>();
public MarketAverage[] ProcessChainState(ChainState chainState)
{
var intervalCounts = GetInsightCounts();
if (!intervalCounts.Any()) return Array.Empty<MarketAverage>();
UpdateBuffer(chainState, intervalCounts.Max());
var result = intervalCounts
.Select(GenerateMarketAverage)
.Where(a => a != null)
.Cast<MarketAverage>()
.ToArray();
if (!result.Any()) result = Array.Empty<MarketAverage>();
return result;
}
private void UpdateBuffer(ChainState chainState, int maxNumberOfIntervals)
{
buffer.Add(chainState);
while (buffer.Count > maxNumberOfIntervals)
{
buffer.RemoveAt(0);
}
}
private MarketAverage? GenerateMarketAverage(int numberOfIntervals)
{
var states = SelectStates(numberOfIntervals);
return CreateAverage(states);
}
private ChainState[] SelectStates(int numberOfIntervals)
{
if (numberOfIntervals < 1) return Array.Empty<ChainState>();
return buffer.TakeLast(numberOfIntervals).ToArray();
}
private MarketAverage? CreateAverage(ChainState[] states)
{
try
{
return new MarketAverage
{
NumberOfFinished = CountNumberOfFinishedRequests(states),
TimeRange = GetTotalTimeRange(states),
Price = Average(states, s => s.Request.Ask.Reward),
Duration = Average(states, s => s.Request.Ask.Duration),
Size = Average(states, s => GetTotalSize(s.Request.Ask)),
Collateral = Average(states, s => s.Request.Ask.Collateral),
ProofProbability = Average(states, s => s.Request.Ask.ProofProbability)
};
}
catch (Exception ex)
{
Program.Log.Error($"Exception in CreateAverage: {ex}");
return null;
}
}
private int GetTotalSize(Ask ask)
{
var nSlots = Convert.ToInt32(ask.Slots);
var slotSize = Convert.ToInt32(ask.SlotSize);
return nSlots * slotSize;
}
private float Average(ChainState[] states, Func<StorageRequest, BigInteger> getValue)
{
return Average(states, s => Convert.ToInt32(getValue(s)));
}
private float Average(ChainState[] states, Func<StorageRequest, int> getValue)
{
var sum = 0.0f;
var count = 0.0f;
foreach (var state in states)
{
foreach (var finishedRequest in state.FinishedRequests)
{
sum += getValue(finishedRequest);
count++;
}
}
return sum / count;
}
private TimeSpan GetTotalTimeRange(ChainState[] states)
{
return Program.Config.Interval * states.Length;
}
private int CountNumberOfFinishedRequests(ChainState[] states)
{
return states.Sum(s => s.FinishedRequests.Length);
}
private int[] GetInsightCounts()
{
try
{
var tokens = Program.Config.MarketInsights.Split(';').ToArray();
return tokens.Select(t => Convert.ToInt32(t)).ToArray();
}
catch (Exception ex)
{
Program.Log.Error($"Exception when parsing MarketInsights config parameters: {ex}");
}
return Array.Empty<int>();
}
}
}

View File

@ -10,6 +10,7 @@ namespace TestNetRewarder
{
private static readonly HistoricState historicState = new HistoricState();
private static readonly RewardRepo rewardRepo = new RewardRepo();
private static readonly MarketTracker marketTracker = new MarketTracker();
private readonly ILog log;
private BlockInterval? lastBlockRange;
@ -63,21 +64,30 @@ namespace TestNetRewarder
ProcessReward(outgoingRewards, reward, chainState);
}
log.Log($"Found {outgoingRewards.Count} rewards to send.");
var marketAverages = GetMarketAverages(chainState);
log.Log($"Found {outgoingRewards.Count} rewards to send. Found {marketAverages.Length} market averages.");
if (outgoingRewards.Any())
{
if (!await SendRewardsCommand(outgoingRewards))
if (!await SendRewardsCommand(outgoingRewards, marketAverages))
{
log.Error("Failed to send reward command.");
}
}
}
private async Task<bool> SendRewardsCommand(List<RewardUsersCommand> outgoingRewards)
private MarketAverage[] GetMarketAverages(ChainState chainState)
{
return marketTracker.ProcessChainState(chainState);
}
private async Task<bool> SendRewardsCommand(List<RewardUsersCommand> outgoingRewards, MarketAverage[] marketAverages)
{
var cmd = new GiveRewardsCommand
{
Rewards = outgoingRewards.ToArray()
Rewards = outgoingRewards.ToArray(),
Averages = marketAverages.ToArray()
};
log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd));

View File

@ -13,10 +13,10 @@ namespace TestNetRewarder
{
this.log = log;
if (configuration.IntervalMinutes < 0) configuration.IntervalMinutes = 15;
if (configuration.IntervalMinutes < 0) configuration.IntervalMinutes = 1;
if (configuration.CheckHistoryTimestamp == 0) throw new Exception("'check-history' unix timestamp is required. Set it to the start/launch moment of the testnet.");
segmentSize = TimeSpan.FromMinutes(configuration.IntervalMinutes);
segmentSize = configuration.Interval;
start = DateTimeOffset.FromUnixTimeSeconds(configuration.CheckHistoryTimestamp).UtcDateTime;
log.Log("Starting time segments at " + start);