Merge branch 'feature/bot-upgrade'
This commit is contained in:
commit
8db84bfe7d
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace BiblioTech.Rewards
|
|||
{
|
||||
try
|
||||
{
|
||||
Program.Averages = cmd.Averages;
|
||||
await Program.RoleDriver.GiveRewards(cmd);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue