обновление алгоритма принятия решений
test / deploy_trader_prod (push) Successful in 2m0s Details

main
vlad zverzhkhovskiy 2025-09-04 22:43:01 +03:00
parent 21833412d8
commit 76bde7c853
16 changed files with 166 additions and 597 deletions

View File

@ -11,5 +11,6 @@
ShortOpen = 16, ShortOpen = 16,
UptrendEnd = 32, UptrendEnd = 32,
UptrendStart = 64, UptrendStart = 64,
HorisontTrend = 128,
} }
} }

View File

@ -7,7 +7,7 @@ namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
public string Figi { get; } public string Figi { get; }
public int Length { get; } public int Length { get; }
public ValueTask AddData(INewPrice priceChange); public ValueTask AddData(INewPrice priceChange);
public ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData(); public ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData(int? length = null);
public ValueTask AddOrderbook(IOrderbook orderbook); public ValueTask AddOrderbook(IOrderbook orderbook);
/// <summary> /// <summary>

View File

@ -1,28 +0,0 @@
namespace KLHZ.Trader.Core.Math.Declisions.Dtos
{
internal readonly struct PeriodPricesInfoDto
{
public readonly int Start;
public readonly int End;
public readonly decimal LastPrice;
public readonly decimal FirstPrice;
public readonly decimal PeriodDiff;
public readonly decimal PeriodMax;
public readonly decimal PeriodMin;
public readonly bool Success;
public readonly TimeSpan Period;
public PeriodPricesInfoDto(bool success, decimal firstPrice, decimal lastPrice, decimal periodDiff, decimal periodMin, decimal periodMax, TimeSpan period, int start, int end)
{
Success = success;
LastPrice = lastPrice;
FirstPrice = firstPrice;
PeriodDiff = periodDiff;
PeriodMax = periodMax;
PeriodMin = periodMin;
Period = period;
Start = start;
End = end;
}
}
}

View File

@ -1,6 +1,6 @@
namespace KLHZ.Trader.Core.Contracts.Declisions.Dtos namespace KLHZ.Trader.Core.Math.Declisions.Dtos
{ {
public readonly struct TwoPeriodsResultDto internal readonly struct TwoLocalTrendsResultDto
{ {
public readonly int Start; public readonly int Start;
public readonly int Bound; public readonly int Bound;
@ -11,7 +11,7 @@
public readonly TimeSpan PeriodStart; public readonly TimeSpan PeriodStart;
public readonly TimeSpan PeriodEnd; public readonly TimeSpan PeriodEnd;
public TwoPeriodsResultDto(bool success, decimal diffStart, decimal diffEnd, int start, int bound, int end, public TwoLocalTrendsResultDto(bool success, decimal diffStart, decimal diffEnd, int start, int bound, int end,
TimeSpan periodStart, TimeSpan periodEnd) TimeSpan periodStart, TimeSpan periodEnd)
{ {
Success = success; Success = success;

View File

@ -47,7 +47,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
public ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData() public ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData(int? length = null)
{ {
lock (_locker) lock (_locker)
{ {

View File

@ -76,7 +76,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
public ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData() public ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData(int? length = null)
{ {
lock (_locker) lock (_locker)
{ {
@ -86,10 +86,12 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
} }
else else
{ {
var prices = new decimal[_length]; var dataLength = length.HasValue ? System.Math.Min(length.Value, _length) : _length;
var timestamps = new DateTime[_length]; var prices = new decimal[dataLength];
Array.Copy(Prices, 1 + _pointer - _length, prices, 0, prices.Length); var timestamps = new DateTime[dataLength];
Array.Copy(Timestamps, 1 + _pointer - _length, timestamps, 0, timestamps.Length); var index = 1 + _pointer - dataLength;
Array.Copy(Prices, index, prices, 0, prices.Length);
Array.Copy(Timestamps, index, timestamps, 0, timestamps.Length);
return ValueTask.FromResult((timestamps, prices)); return ValueTask.FromResult((timestamps, prices));
} }
} }

View File

@ -1,8 +1,6 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos; using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Math.Declisions.Dtos; using KLHZ.Trader.Core.Math.Declisions.Dtos;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
namespace KLHZ.Trader.Core.Math.Declisions.Utils namespace KLHZ.Trader.Core.Math.Declisions.Utils
{ {
@ -11,199 +9,48 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
/// </summary> /// </summary>
public static class LocalTrends public static class LocalTrends
{ {
internal static PeriodPricesInfoDto GetPriceDiffForTimeSpan(this IPriceHistoryCacheUnit unit, TimeSpan timeShift, TimeSpan timeSpan, int? pointsShift = null) public static TradingEvent CheckByLocalTrends(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
{ {
var res = new PeriodPricesInfoDto(false, 0, 0, 0, 0, 0, timeSpan, 0, 0); var res = TradingEvent.None;
var data = unit.GetData().Result; res |= CheckUptrendStart(times, prices, firstPeriod, secondPeriod, meanfullDiff, boundIndex);
var times = data.timestamps; res |= CheckUptrendEnd(times, prices, firstPeriod, secondPeriod, meanfullDiff, boundIndex);
var prices = data.prices;
if (times.Length < 2) return res;
var lastPriceTime = times[times.Length - 1];
var intervalEnd = lastPriceTime - timeShift;
var intervalStart = intervalEnd - timeSpan;
var max = decimal.MinValue;
var min = decimal.MaxValue;
var intervaEndIndex = -1;
var intervaStartIndex = -1;
int count = 0;
for (int i = times.Length - 1; i > -1; i--)
{
if ((times[i] <= intervalEnd || pointsShift.HasValue && count < pointsShift.Value) && intervaEndIndex < 0)
{
intervaEndIndex = i;
}
if (prices[i] > max && intervaEndIndex >= 0)
{
max = prices[i];
}
if (prices[i] < min && intervaEndIndex >= 0)
{
min = prices[i];
}
if (times[i] <= intervalStart && intervaStartIndex < 0)
{
intervaStartIndex = i;
if (intervaStartIndex != intervaEndIndex && intervaEndIndex >= 0)
break;
}
count++;
}
if (intervaStartIndex >= 0 && intervaEndIndex >= 0)
{
res = new PeriodPricesInfoDto(
true,
prices[intervaStartIndex],
prices[intervaEndIndex],
prices[intervaEndIndex] - prices[intervaStartIndex],
min,
max,
timeSpan,
intervaStartIndex,
intervaEndIndex);
}
return res; return res;
} }
internal static bool CheckStable(this PeriodPricesInfoDto data, decimal meanfullDiff) internal static TradingEvent CheckUptrendStart(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
{ {
meanfullDiff = System.Math.Abs(meanfullDiff); var periodStat = GetTwoPeriodsProcessingData(times, prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
return data.Success && System.Math.Abs(data.PeriodDiff) < 1.5m * meanfullDiff && System.Math.Abs(data.PeriodMax - data.PeriodMin) < 2 * meanfullDiff; var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd >= meanfullDiff;
return isStartOk && isEndOk && prices[periodStat.Start] - prices[periodStat.End] >= meanfullDiff ? TradingEvent.UptrendStart : TradingEvent.None;
} }
internal static bool CheckGrowing(this PeriodPricesInfoDto data, decimal meanfullDiff) internal static TradingEvent CheckUptrendEnd(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
{ {
return meanfullDiff > 0 && data.Success && data.PeriodDiff > meanfullDiff && System.Math.Abs(data.PeriodMax - data.PeriodMin) < 3 * System.Math.Abs(data.PeriodDiff); var periodStat = GetTwoPeriodsProcessingData(times, prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
var isStartOk = periodStat.Success && periodStat.DiffStart > 0 && periodStat.DiffStart > 1.5m * meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd < meanfullDiff;
return isStartOk && isEndOk && prices[periodStat.End] - prices[periodStat.Start] >= meanfullDiff ? TradingEvent.UptrendEnd : TradingEvent.None; ;
} }
internal static bool CheckFalling(this PeriodPricesInfoDto data, decimal meanfullDiff) internal static TwoLocalTrendsResultDto GetTwoPeriodsProcessingData(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan lastPeriod, int boundIndex, decimal meanfullDiff)
{ {
meanfullDiff = -meanfullDiff; var res = new TwoLocalTrendsResultDto(success: false, 0, 0, 0, 0, 0, TimeSpan.Zero, TimeSpan.Zero);
return meanfullDiff < 0 && data.Success && data.PeriodDiff < meanfullDiff && System.Math.Abs(data.PeriodMax - data.PeriodMin) < 3 * System.Math.Abs(data.PeriodDiff);
}
internal static float CalcTrendRelationAbs(PeriodPricesInfoDto first, PeriodPricesInfoDto second)
{
var k1 = System.Math.Abs(first.PeriodDiff) / System.Math.Abs((decimal)first.Period.TotalSeconds);
var k2 = System.Math.Abs(second.PeriodDiff) / System.Math.Abs((decimal)second.Period.TotalSeconds);
if (k2 == 0 && k1 != 0) return 1000;
return (float)(k1 / k2);
}
internal static float CalcTrendRelationAbs(TwoPeriodsResultDto data)
{
var k1 = System.Math.Abs(data.DiffStart) / System.Math.Abs((decimal)data.PeriodStart.TotalSeconds);
var k2 = System.Math.Abs(data.DiffEnd) / System.Math.Abs((decimal)data.PeriodEnd.TotalSeconds);
if (k2 == 0 && k1 != 0) return 1000;
return (float)(k1 / k2);
}
internal static bool CheckDowntrendEnding(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff)
{
var totalDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, firstPeriod + secondPeriod);
var startDiff = unit.GetPriceDiffForTimeSpan(secondPeriod, firstPeriod);
var endDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, secondPeriod);
var isEndStable = endDiff.CheckStable(meanfullDiff);
var isEndGrown = endDiff.CheckGrowing(meanfullDiff);
var isStartFalls = startDiff.CheckFalling(meanfullDiff);
var isTotalFalls = totalDiff.CheckFalling(meanfullDiff);
var trendRelation = CalcTrendRelationAbs(startDiff, endDiff);
var res = totalDiff.Success && isStartFalls && (isEndStable || isEndGrown) && trendRelation >= 2;
if (startDiff.Success)
{
}
return res;
}
internal static bool CheckUptrendEnding(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff)
{
var totalDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, firstPeriod + secondPeriod);
var startDiff = unit.GetPriceDiffForTimeSpan(secondPeriod, firstPeriod);
var endDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, secondPeriod);
var isEndStable = endDiff.CheckStable(meanfullDiff);
var isEndFalls = endDiff.CheckFalling(meanfullDiff);
var isStartGrows = startDiff.CheckGrowing(meanfullDiff);
var trendRelation = CalcTrendRelationAbs(startDiff, endDiff);
var isEndLocal = endDiff.PeriodDiff == 0;
var res = totalDiff.Success && isStartGrows && (isEndStable || isEndFalls) && trendRelation >= 2 && !isEndLocal;
if (res)
{
}
return res;
}
internal static bool CheckDowntrendStarting(this PriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff)
{
var totalDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, firstPeriod + secondPeriod);
var startDiff = unit.GetPriceDiffForTimeSpan(secondPeriod, firstPeriod);
var endDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, secondPeriod);
var isEndFalls = endDiff.CheckFalling(meanfullDiff);
var isStartStable = startDiff.CheckStable(meanfullDiff);
var isStartGrows = startDiff.CheckGrowing(meanfullDiff);
var trendRelation = CalcTrendRelationAbs(endDiff, startDiff);
return totalDiff.Success && (isStartStable || isStartGrows) && isEndFalls && trendRelation >= 2;
}
internal static bool CheckUptrendStarting(this PriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff)
{
var totalDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, firstPeriod + secondPeriod);
var startDiff = unit.GetPriceDiffForTimeSpan(secondPeriod, firstPeriod);
var endDiff = unit.GetPriceDiffForTimeSpan(TimeSpan.Zero, secondPeriod);
var isEndGrows = endDiff.CheckGrowing(meanfullDiff);
var isStartStable = startDiff.CheckStable(meanfullDiff);
var isStartFalls = startDiff.CheckStable(meanfullDiff);
var trendRelation = CalcTrendRelationAbs(endDiff, startDiff);
var res = totalDiff.Success && (isStartStable || isStartFalls) && isEndGrows && endDiff.PeriodDiff > meanfullDiff;
if (isStartStable)
{
res &= trendRelation >= 2;
}
else
{
}
if (res)
{
}
return res;
}
internal static TwoPeriodsResultDto GetTwoPeriodsProcessingData(this (DateTime[] timestamps, decimal[] prices) data, TimeSpan shift, int shiftPointsStart, int shiftPointsEnd, TimeSpan firstPeriod, decimal meanfullDiff)
{
var res = new TwoPeriodsResultDto(success: false, 0, 0, 0, 0, 0, TimeSpan.Zero, TimeSpan.Zero);
var time = data.timestamps;
var prices = data.prices;
int count = -1; int count = -1;
var lastTime = time[time.Length - 1]; var lastTime = times[times.Length - 1];
var bound = -1; var bound = -1;
var start = -1; var start = -1;
var end = time.Length - 1; var end = times.Length - 1;
for (int i = time.Length - 1; i > -1; i--) for (int i = times.Length - 1; i > -1; i--)
{ {
if (count > 0 && bound < 0 && (count == shiftPointsEnd || lastTime - time[i] >= shift)) if (count > 0 && bound < 0 && (count == boundIndex || lastTime - times[i] >= lastPeriod))
{ {
bound = i; bound = i;
shift = lastTime - time[i]; lastPeriod = lastTime - times[i];
} }
if (lastTime - time[i] >= shift + firstPeriod) if (lastTime - times[i] >= lastPeriod + firstPeriod)
{ {
start = i; start = i;
@ -217,105 +64,37 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
{ {
var diff1 = prices[bound] - prices[start]; var diff1 = prices[bound] - prices[start];
var diff2 = prices[end] - prices[bound]; var diff2 = prices[end] - prices[bound];
res = new TwoPeriodsResultDto(true, diff1, diff2, start, bound, end, time[bound] - time[start], time[end] - time[bound]); res = new TwoLocalTrendsResultDto(true, diff1, diff2, start, bound, end, times[bound] - times[start], times[end] - times[bound]);
} }
return res; return res;
} }
internal static bool CheckLongOpen(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
public static bool CheckLongClose(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int pointsStart, int pointsEnd)
{ {
var data = unit.GetData().Result; var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, pointsStart, pointsEnd, firstPeriod, meanfullDiff); var periodStat = GetTwoPeriodsProcessingData(data.timestamps, data.prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
var trendRelation = CalcTrendRelationAbs(periodStat);
var isStartOk = periodStat.Success && periodStat.DiffStart > 0 && periodStat.DiffStart > 1.5m * meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd < meanfullDiff;
if (isEndOk)
{
}
if (isStartOk)
{
}
if (isEndOk && isStartOk)
{
}
return isStartOk && isEndOk && data.prices[periodStat.End] - data.prices[periodStat.Start] >= meanfullDiff;
}
internal static bool CheckUptrendStarting2(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff)
{
var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, 15, 2, firstPeriod, meanfullDiff);
var trendRelation = CalcTrendRelationAbs(periodStat);
var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd >= meanfullDiff;
if (isEndOk)
{
}
if (isStartOk)
{
}
if (isEndOk && isStartOk)
{
}
return isStartOk && isEndOk;
}
internal static bool _CheckUptrendStarting2(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff)
{
var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, 15, 1, firstPeriod, meanfullDiff);
var trendRelation = CalcTrendRelationAbs(periodStat);
var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd >= meanfullDiff;
if (isEndOk)
{
}
if (isStartOk)
{
}
if (isEndOk && isStartOk)
{
}
return isStartOk && isEndOk;
}
public static bool CheckLongOpen(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int pointsStart, int pointsEnd)
{
var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, pointsStart, pointsEnd, firstPeriod, meanfullDiff);
var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff; var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd >= meanfullDiff; var isEndOk = periodStat.Success && periodStat.DiffEnd >= meanfullDiff;
return isStartOk && isEndOk && data.prices[periodStat.Start] - data.prices[periodStat.End] >= meanfullDiff; return isStartOk && isEndOk && data.prices[periodStat.Start] - data.prices[periodStat.End] >= meanfullDiff;
} }
public static TradingEvent Detect(IPriceHistoryCacheUnit data) internal static bool CheckLongClose(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
{
var data = unit.GetData().Result;
var periodStat = GetTwoPeriodsProcessingData(data.timestamps, data.prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
var isStartOk = periodStat.Success && periodStat.DiffStart > 0 && periodStat.DiffStart > 1.5m * meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd < meanfullDiff;
return isStartOk && isEndOk && data.prices[periodStat.End] - data.prices[periodStat.Start] >= meanfullDiff;
}
internal static TradingEvent Detect(IPriceHistoryCacheUnit data)
{ {
decimal meanfullDiff = 1m; decimal meanfullDiff = 1m;
var res = TradingEvent.None; var res = TradingEvent.None;
//var downtrendStarts = data.CheckDowntrendStarting(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(7), meanfullDiff); //var downtrendStarts = data.CheckDowntrendStarting(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(7), meanfullDiff);
var uptrendStarts = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(7), meanfullDiff, 8, 3); var uptrendStarts = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(7), meanfullDiff, 3);
var uptrendStarts2 = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), meanfullDiff, 15, 2); var uptrendStarts2 = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), meanfullDiff, 2);
var downtrendEnds = data.CheckLongOpen(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), meanfullDiff, 15, 5); var downtrendEnds = data.CheckLongOpen(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), meanfullDiff, 5);
uptrendStarts |= downtrendEnds; uptrendStarts |= downtrendEnds;
uptrendStarts |= uptrendStarts2; uptrendStarts |= uptrendStarts2;
if (uptrendStarts) if (uptrendStarts)
@ -324,8 +103,8 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
} }
//var downtrendEnds = data.CheckDowntrendEnding(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(15), meanfullDiff); //var downtrendEnds = data.CheckDowntrendEnding(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(15), meanfullDiff);
var uptrendEnds = data.CheckLongClose(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), meanfullDiff * 1.5m, 8, 8); var uptrendEnds = data.CheckLongClose(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), meanfullDiff * 1.5m, 8);
var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 15, 8); var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 8);
uptrendEnds |= uptrendEnds2; uptrendEnds |= uptrendEnds2;
if (uptrendEnds) if (uptrendEnds)
{ {

View File

@ -219,5 +219,28 @@ namespace KLHZ.Trader.Core.Tests
} }
} }
} }
[Test]
public void Test10()
{
var length = 77;
var shift = 334;
var count = PriceHistoryCacheUnit.CacheMaxLength + shift;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit2(figi, hist);
var data = cacheUnit.GetData(length).Result;
Assert.That(data.prices.Length == length);
Assert.That(data.timestamps.Length == length);
Assert.That(data.prices.Last() == hist.Last().Value);
Assert.That(data.timestamps.Last() == hist.Last().Time);
for (var i = 1; i <= length; i++)
{
Assert.That(hist[hist.Length - i].Value, Is.EqualTo(data.prices[data.prices.Length - i]));
Assert.That(hist[hist.Length - i].Time, Is.EqualTo(data.timestamps[data.prices.Length - i]));
}
}
} }
} }

View File

@ -1,275 +0,0 @@
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
using KLHZ.Trader.Core.Math.Declisions.Utils;
namespace KLHZ.Trader.Core.Tests
{
public class HistoryProcessingInstrumentsTests
{
private static PriceChange[] GetHistory(int count, string figi, DateTime startDt, decimal startValue, decimal step)
{
var res = new PriceChange[count];
if (count != 0)
{
for (int i = 0; i < count; i++)
{
startValue += step;
startDt = startDt.AddSeconds(1);
res[i] = new PriceChange()
{
Figi = figi,
Ticker = figi + "_ticker",
Id = i,
Time = startDt,
Value = startValue,
};
}
}
return res;
}
[Test]
public void Test0()
{
var figi = "figi";
var startDate = new DateTime(2020, 1, 1, 1, 0, 0, DateTimeKind.Utc);
var count = 100;
var step = 0;
var startValue = 10;
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
var periodLength = 4;
var shift = 0;
var result = LocalTrends.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
var firstPrice = startValue + (step * count) - (step * (shift + periodLength));
var lastPrice = startValue + (step * count) - step * shift;
var diff = firstPrice - lastPrice;
Assert.IsTrue(result.LastPrice == lastPrice);
Assert.IsTrue(result.FirstPrice == firstPrice);
Assert.IsTrue(result.PeriodMax == maxValue);
Assert.IsTrue(result.PeriodMin == minValue);
Assert.IsTrue(result.PeriodDiff == diff);
}
[Test]
public void Test1()
{
var figi = "figi";
var startDate = new DateTime(2020, 1, 1, 1, 0, 0, DateTimeKind.Utc);
var count = 100;
var step = 0.5m;
var startValue = 10;
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
var periodLength = 4;
var shift = 0;
var result = LocalTrends.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
var firstPrice = startValue + (step * count) - (step * (shift + periodLength));
var lastPrice = startValue + (step * count) - step * shift;
var diff = lastPrice - firstPrice;
Assert.IsTrue(result.LastPrice == lastPrice);
Assert.IsTrue(result.FirstPrice == firstPrice);
Assert.IsTrue(result.PeriodMax == maxValue);
Assert.IsTrue(result.PeriodMin == minValue);
Assert.IsTrue(result.PeriodDiff == diff);
}
[Test]
public void Test2()
{
var figi = "figi";
var startDate = new DateTime(2020, 1, 1, 1, 0, 0, DateTimeKind.Utc);
var count = 100;
var step = 0.5m;
var startValue = 10;
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
var periodLength = 4;
var shift = 1;
var result = LocalTrends.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
var firstPrice = startValue + (step * count) - (step * (shift + periodLength));
var lastPrice = startValue + (step * count) - step * shift;
var diff = lastPrice - firstPrice;
Assert.IsTrue(result.LastPrice == lastPrice);
Assert.IsTrue(result.FirstPrice == firstPrice);
Assert.IsTrue(result.PeriodMax == maxValue);
Assert.IsTrue(result.PeriodMin == minValue);
Assert.IsTrue(result.PeriodDiff == diff);
}
[Test]
public void Test3()
{
var figi = "figi";
var startDate = new DateTime(2020, 1, 1, 1, 0, 0, DateTimeKind.Utc);
var count = 100;
var step = -0.5m;
var startValue = 10;
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
var periodLength = 4;
var shift = 0;
var result = LocalTrends.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
var firstPrice = startValue + (step * count) - (step * (shift + periodLength));
var lastPrice = startValue + (step * count) - step * shift;
var diff = lastPrice - firstPrice;
Assert.IsTrue(result.LastPrice == lastPrice);
Assert.IsTrue(result.FirstPrice == firstPrice);
Assert.IsTrue(result.PeriodMax == maxValue);
Assert.IsTrue(result.PeriodMin == minValue);
Assert.IsTrue(result.PeriodDiff == diff);
}
[Test]
public void Test4()
{
var figi = "figi";
var startDate = new DateTime(2020, 1, 1, 1, 0, 0, DateTimeKind.Utc);
var count = 100;
var step = -0.5m;
var startValue = 10;
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
var periodLength = 4;
var shift = 3;
var result = LocalTrends.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
var firstPrice = startValue + (step * count) - (step * (shift + periodLength));
var lastPrice = startValue + (step * count) - step * shift;
var diff = lastPrice - firstPrice;
Assert.IsTrue(result.LastPrice == lastPrice);
Assert.IsTrue(result.FirstPrice == firstPrice);
Assert.IsTrue(result.PeriodMax == maxValue);
Assert.IsTrue(result.PeriodMin == minValue);
Assert.IsTrue(result.PeriodDiff == diff);
}
[Test]
public void Test5()
{
var figi = "figi";
var startDate = new DateTime(2020, 1, 1, 1, 0, 0, DateTimeKind.Utc);
var count = 100;
var step = -0.5m;
var startValue = 10;
var unit = new PriceHistoryCacheUnit(figi, GetHistory(count, figi, startDate, startValue, step));
var data = unit.GetData().Result;
var endDate = startDate.AddSeconds(count);
Assert.IsTrue(data.timestamps.Last() == endDate);
Assert.IsTrue(data.prices.Last() == startValue + step * count);
var periodLength = 4;
var shift = 3;
var result = LocalTrends.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue1 = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue1 = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
var firstPrice1 = startValue + (step * count) - (step * (shift + periodLength));
var lastPrice1 = startValue + (step * count) - step * shift;
var diff1 = lastPrice1 - firstPrice1;
Assert.IsTrue(result.LastPrice == lastPrice1);
Assert.IsTrue(result.FirstPrice == firstPrice1);
Assert.IsTrue(result.PeriodMax == maxValue1);
Assert.IsTrue(result.PeriodMin == minValue1);
Assert.IsTrue(result.PeriodDiff == diff1);
var unit2 = new PriceHistoryCacheUnit(figi);
var data2 = unit.GetData().Result;
for (int i = 0; i < data2.prices.Length; i++)
{
var value = (decimal)data2.prices[i];
if (i == data2.prices.Length - 5)
{
value = 100;
}
else if (i == data2.prices.Length - 6)
{
value = -100;
}
unit2.AddData(new PriceChange()
{
Figi = figi,
Ticker = figi,
Time = data2.timestamps[i],
Value = value
});
}
var result2 = LocalTrends.GetPriceDiffForTimeSpan(unit2, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue2 = 100;
var minValue2 = -100;
Assert.IsTrue(result2.LastPrice == result.LastPrice);
Assert.IsTrue(result2.FirstPrice == result.FirstPrice);
Assert.IsTrue(result2.PeriodMax == maxValue2);
Assert.IsTrue(result2.PeriodMin == minValue2);
Assert.IsTrue(result2.PeriodDiff == result.PeriodDiff);
}
}
}

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Exchange.Models; using KLHZ.Trader.Core.Exchange.Models.Assets;
namespace KLHZ.Trader.Core.Exchange.Extentions namespace KLHZ.Trader.Core.Exchange.Extentions
{ {

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Exchange.Models namespace KLHZ.Trader.Core.Exchange.Models.Assets
{ {
public class Asset public class Asset
{ {

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Exchange.Models namespace KLHZ.Trader.Core.Exchange.Models.Assets
{ {
public enum AssetType public enum AssetType
{ {

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Exchange.Models namespace KLHZ.Trader.Core.Exchange.Models.Assets
{ {
public enum PositionType public enum PositionType
{ {

View File

@ -0,0 +1,9 @@
namespace KLHZ.Trader.Core.Exchange.Models
{
internal class DeferredTrade
{
public required string Figi { get; set; }
public decimal Price { get; set; }
public DateTime Time { get; set; }
}
}

View File

@ -10,7 +10,7 @@ using System.Collections.Concurrent;
using System.Threading.Channels; using System.Threading.Channels;
using Tinkoff.InvestApi; using Tinkoff.InvestApi;
using Tinkoff.InvestApi.V1; using Tinkoff.InvestApi.V1;
using PositionType = KLHZ.Trader.Core.Exchange.Models.PositionType; using PositionType = KLHZ.Trader.Core.Exchange.Models.Assets.PositionType;
namespace KLHZ.Trader.Core.Exchange.Services namespace KLHZ.Trader.Core.Exchange.Services
{ {
@ -58,7 +58,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
} }
} }
internal readonly ConcurrentDictionary<string, Models.Asset> Assets = new(); internal readonly ConcurrentDictionary<string, Models.Assets.Asset> Assets = new();
#endregion #endregion
@ -126,7 +126,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
price = position.AveragePositionPrice; price = position.AveragePositionPrice;
} }
#pragma warning disable CS0612 // Тип или член устарел #pragma warning disable CS0612 // Тип или член устарел
var asset = new Models.Asset() var asset = new Models.Assets.Asset()
{ {
AccountId = AccountId, AccountId = AccountId,
Figi = position.Figi, Figi = position.Figi,

View File

@ -10,6 +10,7 @@ using KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums;
using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Exchange.Extentions; using KLHZ.Trader.Core.Exchange.Extentions;
using KLHZ.Trader.Core.Exchange.Models; using KLHZ.Trader.Core.Exchange.Models;
using KLHZ.Trader.Core.Exchange.Models.Assets;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache; using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
using KLHZ.Trader.Core.Math.Declisions.Utils; using KLHZ.Trader.Core.Math.Declisions.Utils;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -20,7 +21,7 @@ using Microsoft.Extensions.Options;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Channels; using System.Threading.Channels;
using Tinkoff.InvestApi; using Tinkoff.InvestApi;
using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetType; using AssetType = KLHZ.Trader.Core.Exchange.Models.Assets.AssetType;
namespace KLHZ.Trader.Core.Exchange.Services namespace KLHZ.Trader.Core.Exchange.Services
{ {
@ -31,6 +32,8 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly IDataBus _dataBus; private readonly IDataBus _dataBus;
private readonly BotModeSwitcher _botModeSwitcher; private readonly BotModeSwitcher _botModeSwitcher;
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory; private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
private readonly ConcurrentDictionary<string, DeferredTrade> DeferredLongOpens = new();
private readonly ConcurrentDictionary<string, DeferredTrade> DeferredLongCloses = new();
private readonly ConcurrentDictionary<string, DateTime> OpeningStops = new(); private readonly ConcurrentDictionary<string, DateTime> OpeningStops = new();
private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new(); private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new();
private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new(); private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new();
@ -125,7 +128,37 @@ namespace KLHZ.Trader.Core.Exchange.Services
{ {
if (message.Figi == "FUTIMOEXF000") if (message.Figi == "FUTIMOEXF000")
{ {
var data = await unit.GetData();
DeferredTrade? longOpen;
DeferredLongOpens.TryGetValue(message.Figi, out longOpen);
if (longOpen != null)
{
if (longOpen.Time <= (message.IsHistoricalData ? message.Time : DateTime.UtcNow))
{
DeferredLongOpens.TryRemove(message.Figi, out _);
if (message.Value - longOpen.Price < 1)
{
LogDeclision(declisionsForSave, DeclisionTradeAction.OpenLong, message);
}
}
}
DeferredTrade? longClose;
DeferredLongCloses.TryGetValue(message.Figi, out longClose);
if (longClose != null)
{
if (longClose.Time <= (message.IsHistoricalData ? message.Time : DateTime.UtcNow))
{
DeferredLongCloses.TryRemove(message.Figi, out _);
if (longClose.Price - message.Value < 1)
{
LogDeclision(declisionsForSave, DeclisionTradeAction.CloseLong, message);
}
}
}
var windowMaxSize = 100;
var data = await unit.GetData(windowMaxSize);
if (OpeningStops.TryGetValue(message.Figi, out var dt)) if (OpeningStops.TryGetValue(message.Figi, out var dt))
{ {
if (dt < (message.IsHistoricalData ? message.Time : DateTime.UtcNow)) if (dt < (message.IsHistoricalData ? message.Time : DateTime.UtcNow))
@ -137,34 +170,59 @@ namespace KLHZ.Trader.Core.Exchange.Services
if ((unit.BidsCount / unit.AsksCount) < 0.5m || (unit.BidsCount / unit.AsksCount) > 2m) if ((unit.BidsCount / unit.AsksCount) < 0.5m || (unit.BidsCount / unit.AsksCount) > 2m)
{ {
var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(3); var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(3);
OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo); //OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo);
LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuyShortTime, message); //LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuyShortTime, message);
} }
var resultLongOpen = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, 100, 45, 180, 2.5m); var res = TradingEvent.None;
var resultLongClose = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, 100, 15, 120, 2.5m); var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, windowMaxSize, 45, 180, 2.5m);
var resultLongClose = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, windowMaxSize, 15, 120, 2.5m).events;
if (resultLongOpen.bigWindowAv != 0) var uptrendStarts = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(7), 1.5m, 3);
var uptrendStarts2 = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), 1.5m, 2);
var downtrendEnds = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), 1.5m, 5);
res |= uptrendStarts;
res |= uptrendStarts2;
//res |= downtrendEnds;
res |= resultLongClose;
res |= resultMoveAvFull.events;
if (resultMoveAvFull.bigWindowAv != 0)
{ {
LogPrice(processedPrices, message, bigWindowProcessor, resultLongOpen.bigWindowAv); LogPrice(processedPrices, message, bigWindowProcessor, resultMoveAvFull.bigWindowAv);
LogPrice(processedPrices, message, smallWindowProcessor, resultLongOpen.smallWindowAv); LogPrice(processedPrices, message, smallWindowProcessor, resultMoveAvFull.smallWindowAv);
} }
if ((resultLongClose.events & TradingEvent.StopBuy) == TradingEvent.StopBuy) if ((resultLongClose & TradingEvent.StopBuy) == TradingEvent.StopBuy)
{ {
var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(_buyStopLength); var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(_buyStopLength);
OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo); OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo);
//LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuy, message); //LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuy, message);
} }
if ((resultLongOpen.events & TradingEvent.UptrendStart) == TradingEvent.UptrendStart if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart
&& !OpeningStops.TryGetValue(message.Figi, out _)) && !OpeningStops.TryGetValue(message.Figi, out _)
&& data.timestamps.Length > 1
&& (data.timestamps[data.timestamps.Length - 1] - data.timestamps[data.timestamps.Length - 2] < TimeSpan.FromMinutes(1)))
{ {
LogDeclision(declisionsForSave, DeclisionTradeAction.OpenLong, message); var trade = new DeferredTrade()
{
Figi = message.Figi,
Price = message.Value,
Time = message.Time.AddSeconds(15)
};
DeferredLongOpens[message.Figi] = trade;
} }
if ((resultLongClose.events & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd) if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd)
{ {
LogDeclision(declisionsForSave, DeclisionTradeAction.CloseLong, message); var trade = new DeferredTrade()
{
Figi = message.Figi,
Price = message.Value,
Time = message.Time.AddSeconds(15)
};
DeferredLongCloses[message.Figi] = trade;
} }
//if ((resultLongOpen.events & TradingEvent.ShortOpen) == TradingEvent.ShortOpen //if ((resultLongOpen.events & TradingEvent.ShortOpen) == TradingEvent.ShortOpen