обновление алгоритма принятия решений
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,
UptrendEnd = 32,
UptrendStart = 64,
HorisontTrend = 128,
}
}

View File

@ -7,7 +7,7 @@ namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
public string Figi { get; }
public int Length { get; }
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);
/// <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 Bound;
@ -11,7 +11,7 @@
public readonly TimeSpan PeriodStart;
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)
{
Success = success;

View File

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

View File

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

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.Math.Declisions.Dtos;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
@ -11,199 +9,48 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
/// </summary>
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 data = unit.GetData().Result;
var times = data.timestamps;
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);
}
var res = TradingEvent.None;
res |= CheckUptrendStart(times, prices, firstPeriod, secondPeriod, meanfullDiff, boundIndex);
res |= CheckUptrendEnd(times, prices, firstPeriod, secondPeriod, meanfullDiff, boundIndex);
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);
return data.Success && System.Math.Abs(data.PeriodDiff) < 1.5m * meanfullDiff && System.Math.Abs(data.PeriodMax - data.PeriodMin) < 2 * meanfullDiff;
var periodStat = GetTwoPeriodsProcessingData(times, prices, firstPeriod, secondPeriod, boundIndex, 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;
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;
var res = new TwoLocalTrendsResultDto(success: false, 0, 0, 0, 0, 0, TimeSpan.Zero, TimeSpan.Zero);
int count = -1;
var lastTime = time[time.Length - 1];
var lastTime = times[times.Length - 1];
var bound = -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;
shift = lastTime - time[i];
lastPeriod = lastTime - times[i];
}
if (lastTime - time[i] >= shift + firstPeriod)
if (lastTime - times[i] >= lastPeriod + firstPeriod)
{
start = i;
@ -217,105 +64,37 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
var diff1 = prices[bound] - prices[start];
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;
}
public static bool CheckLongClose(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int pointsStart, int pointsEnd)
internal static bool CheckLongOpen(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex)
{
var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, pointsStart, pointsEnd, firstPeriod, 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 periodStat = GetTwoPeriodsProcessingData(data.timestamps, data.prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff);
var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff;
var isEndOk = periodStat.Success && periodStat.DiffEnd >= 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;
var res = TradingEvent.None;
//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 uptrendStarts2 = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), meanfullDiff, 15, 2);
var downtrendEnds = data.CheckLongOpen(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), meanfullDiff, 15, 5);
var uptrendStarts = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(7), meanfullDiff, 3);
var uptrendStarts2 = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), meanfullDiff, 2);
var downtrendEnds = data.CheckLongOpen(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), meanfullDiff, 5);
uptrendStarts |= downtrendEnds;
uptrendStarts |= uptrendStarts2;
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 uptrendEnds = data.CheckLongClose(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), meanfullDiff * 1.5m, 8, 8);
var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 15, 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, 8);
uptrendEnds |= uptrendEnds2;
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
{

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Exchange.Models
namespace KLHZ.Trader.Core.Exchange.Models.Assets
{
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
{

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Exchange.Models
namespace KLHZ.Trader.Core.Exchange.Models.Assets
{
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 Tinkoff.InvestApi;
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
{
@ -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
@ -126,7 +126,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
price = position.AveragePositionPrice;
}
#pragma warning disable CS0612 // Тип или член устарел
var asset = new Models.Asset()
var asset = new Models.Assets.Asset()
{
AccountId = AccountId,
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.Exchange.Extentions;
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.Utils;
using Microsoft.EntityFrameworkCore;
@ -20,7 +21,7 @@ using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.Threading.Channels;
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
{
@ -31,6 +32,8 @@ namespace KLHZ.Trader.Core.Exchange.Services
private readonly IDataBus _dataBus;
private readonly BotModeSwitcher _botModeSwitcher;
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, ManagedAccount> Accounts = new();
private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new();
@ -125,7 +128,37 @@ namespace KLHZ.Trader.Core.Exchange.Services
{
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 (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)
{
var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(3);
OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo);
LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuyShortTime, message);
//OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo);
//LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuyShortTime, message);
}
var resultLongOpen = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, 100, 45, 180, 2.5m);
var resultLongClose = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, 100, 15, 120, 2.5m);
var res = TradingEvent.None;
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, smallWindowProcessor, resultLongOpen.smallWindowAv);
LogPrice(processedPrices, message, bigWindowProcessor, resultMoveAvFull.bigWindowAv);
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);
OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo);
//LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuy, message);
}
if ((resultLongOpen.events & TradingEvent.UptrendStart) == TradingEvent.UptrendStart
&& !OpeningStops.TryGetValue(message.Figi, out _))
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart
&& !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