Adds Market command
This commit is contained in:
parent
b805c5f004
commit
9c8151bdb7
|
@ -3,6 +3,7 @@
|
||||||
public class GiveRewardsCommand
|
public class GiveRewardsCommand
|
||||||
{
|
{
|
||||||
public RewardUsersCommand[] Rewards { get; set; } = Array.Empty<RewardUsersCommand>();
|
public RewardUsersCommand[] Rewards { get; set; } = Array.Empty<RewardUsersCommand>();
|
||||||
|
public MarketAverage[] Averages { get; set; } = Array.Empty<MarketAverage>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RewardUsersCommand
|
public class RewardUsersCommand
|
||||||
|
@ -10,4 +11,15 @@
|
||||||
public ulong RewardId { get; set; }
|
public ulong RewardId { get; set; }
|
||||||
public string[] UserAddresses { get; set; } = Array.Empty<string>();
|
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 BiblioTech.Rewards;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
using DiscordRewards;
|
||||||
using Logging;
|
using Logging;
|
||||||
|
|
||||||
namespace BiblioTech
|
namespace BiblioTech
|
||||||
|
@ -16,6 +17,7 @@ namespace BiblioTech
|
||||||
public static AdminChecker AdminChecker { get; private set; } = null!;
|
public static AdminChecker AdminChecker { get; private set; } = null!;
|
||||||
public static IDiscordRoleDriver RoleDriver { get; set; } = null!;
|
public static IDiscordRoleDriver RoleDriver { get; set; } = null!;
|
||||||
public static ILog Log { get; private 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)
|
public static Task Main(string[] args)
|
||||||
{
|
{
|
||||||
|
@ -49,7 +51,8 @@ namespace BiblioTech
|
||||||
sprCommand,
|
sprCommand,
|
||||||
associateCommand,
|
associateCommand,
|
||||||
notifyCommand,
|
notifyCommand,
|
||||||
new AdminCommand(sprCommand)
|
new AdminCommand(sprCommand),
|
||||||
|
new MarketCommand()
|
||||||
);
|
);
|
||||||
|
|
||||||
await client.LoginAsync(TokenType.Bot, Config.ApplicationToken);
|
await client.LoginAsync(TokenType.Bot, Config.ApplicationToken);
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace BiblioTech.Rewards
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Program.Averages = cmd.Averages;
|
||||||
await Program.RoleDriver.GiveRewards(cmd);
|
await Program.RoleDriver.GiveRewards(cmd);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace TestNetRewarder
|
||||||
historicState.UpdateStorageRequests(contracts);
|
historicState.UpdateStorageRequests(contracts);
|
||||||
|
|
||||||
StartedRequests = historicState.StorageRequests.Where(r => r.RecentlyStarted).ToArray();
|
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);
|
RequestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange);
|
||||||
RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange);
|
RequestCancelledEvents = contracts.GetRequestCancelledEvents(blockRange);
|
||||||
SlotFilledEvents = contracts.GetSlotFilledEvents(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'.")]
|
[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;
|
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
|
public string LogPath
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -26,5 +29,13 @@ namespace TestNetRewarder
|
||||||
return Path.Combine(DataPath, "logs");
|
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 EthAddress[] Hosts { get; private set; }
|
||||||
public RequestState State { get; private set; }
|
public RequestState State { get; private set; }
|
||||||
public bool RecentlyStarted { 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)
|
public void Update(ICodexContracts contracts)
|
||||||
{
|
{
|
||||||
|
@ -45,7 +45,7 @@ namespace TestNetRewarder
|
||||||
State == RequestState.New &&
|
State == RequestState.New &&
|
||||||
newState == RequestState.Started;
|
newState == RequestState.Started;
|
||||||
|
|
||||||
RecentlyFininshed =
|
RecentlyFinished =
|
||||||
State == RequestState.Started &&
|
State == RequestState.Started &&
|
||||||
newState == RequestState.Finished;
|
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 HistoricState historicState = new HistoricState();
|
||||||
private static readonly RewardRepo rewardRepo = new RewardRepo();
|
private static readonly RewardRepo rewardRepo = new RewardRepo();
|
||||||
|
private static readonly MarketTracker marketTracker = new MarketTracker();
|
||||||
private readonly ILog log;
|
private readonly ILog log;
|
||||||
private BlockInterval? lastBlockRange;
|
private BlockInterval? lastBlockRange;
|
||||||
|
|
||||||
|
@ -63,21 +64,30 @@ namespace TestNetRewarder
|
||||||
ProcessReward(outgoingRewards, reward, chainState);
|
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 (outgoingRewards.Any())
|
||||||
{
|
{
|
||||||
if (!await SendRewardsCommand(outgoingRewards))
|
if (!await SendRewardsCommand(outgoingRewards, marketAverages))
|
||||||
{
|
{
|
||||||
log.Error("Failed to send reward command.");
|
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
|
var cmd = new GiveRewardsCommand
|
||||||
{
|
{
|
||||||
Rewards = outgoingRewards.ToArray()
|
Rewards = outgoingRewards.ToArray(),
|
||||||
|
Averages = marketAverages.ToArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd));
|
log.Debug("Sending rewards: " + JsonConvert.SerializeObject(cmd));
|
||||||
|
|
|
@ -13,10 +13,10 @@ namespace TestNetRewarder
|
||||||
{
|
{
|
||||||
this.log = log;
|
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.");
|
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;
|
start = DateTimeOffset.FromUnixTimeSeconds(configuration.CheckHistoryTimestamp).UtcDateTime;
|
||||||
|
|
||||||
log.Log("Starting time segments at " + start);
|
log.Log("Starting time segments at " + start);
|
||||||
|
|
Loading…
Reference in New Issue