mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-28 17:57:45 +00:00
f10baa6dfb
- Add docs/license.md with Apache 2.0 rationale and patent protection analysis - Add docs/python.md and docs/pinescript.md platform guides - Expand README license section with disclosure and link to rationale - Update docs/api.md and docs/architecture.md - Update Python bindings: helpers, all indicator modules, pyproject.toml - Add Python tests for Arrow and Polars integration - Update TValue core type and documentation - Add fix_length_to_period tooling script
160 lines
5.1 KiB
C#
160 lines
5.1 KiB
C#
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace QuanTAlib;
|
|
|
|
/// <summary>
|
|
/// A lightweight struct representing a time-value pair.
|
|
/// Pure data type: 16 bytes (long + double).
|
|
/// Implements ISpanFormattable for allocation-free formatting.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
[StructLayout(LayoutKind.Auto)]
|
|
public readonly record struct TValue(long Time, double Value) : ISpanFormattable
|
|
{
|
|
public DateTime AsDateTime => new(Time, DateTimeKind.Utc);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public TValue(DateTime time, double value)
|
|
: this(time.Kind == DateTimeKind.Utc ? time.Ticks : time.ToUniversalTime().Ticks, value)
|
|
{
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static explicit operator double(TValue tv) => tv.Value;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static implicit operator DateTime(TValue tv) => new(tv.Time, DateTimeKind.Utc);
|
|
|
|
/// <summary>
|
|
/// Implicitly converts a raw double to a TValue with DateTime.UtcNow timestamp.
|
|
/// Enables ergonomic APIs like <c>sma.Update(110.4)</c>.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static implicit operator TValue(double value) => new(DateTime.UtcNow, value);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override string ToString()
|
|
{
|
|
string valueStr = Value switch
|
|
{
|
|
double.PositiveInfinity => ((char)0x221E).ToString(),
|
|
double.NegativeInfinity => "-" + (char)0x221E,
|
|
_ when double.IsNaN(Value) => "NaN",
|
|
_ => Value.ToString("F2"),
|
|
};
|
|
return $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}, {valueStr}]";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Formats the TValue using the specified format string.
|
|
/// Note: Custom format and formatProvider are not supported by TValue.
|
|
/// If a non-null/non-empty format is provided, a NotSupportedException is thrown.
|
|
/// </summary>
|
|
/// <param name="format">Must be null or empty; custom formats are not supported.</param>
|
|
/// <param name="formatProvider">Ignored; TValue uses its own fixed format.</param>
|
|
/// <returns>The string representation of this TValue.</returns>
|
|
/// <exception cref="NotSupportedException">Thrown when a non-null/non-empty format is provided.</exception>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public string ToString(string? format, IFormatProvider? formatProvider)
|
|
{
|
|
if (!string.IsNullOrEmpty(format))
|
|
{
|
|
throw new NotSupportedException($"Custom format '{format}' is not supported by TValue. Use ToString() for the default format.");
|
|
}
|
|
|
|
return ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Formats the TValue into the provided span without heap allocation.
|
|
/// Format: "[yyyy-MM-dd HH:mm:ss, value]"
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
|
|
{
|
|
charsWritten = 0;
|
|
|
|
// Early reject for buffers too small to hold even the timestamp portion.
|
|
// This is a heuristic check; actual buffer-overflow protection is performed
|
|
// by the explicit length checks that guard each write operation below.
|
|
if (destination.Length < 24)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Write opening bracket
|
|
destination[0] = '[';
|
|
int pos = 1;
|
|
|
|
// Format datetime: yyyy-MM-dd HH:mm:ss (19 chars)
|
|
if (!AsDateTime.TryFormat(destination.Slice(pos), out int dtChars, "yyyy-MM-dd HH:mm:ss", provider))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pos += dtChars;
|
|
|
|
// Write separator
|
|
if (pos + 2 > destination.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
destination[pos++] = ',';
|
|
destination[pos++] = ' ';
|
|
|
|
// Format value
|
|
if (double.IsPositiveInfinity(Value))
|
|
{
|
|
if (pos + 1 > destination.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
destination[pos++] = (char)0x221E; //
|
|
}
|
|
else if (double.IsNegativeInfinity(Value))
|
|
{
|
|
if (pos + 2 > destination.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
destination[pos++] = '-';
|
|
destination[pos++] = (char)0x221E; // -
|
|
}
|
|
else if (double.IsNaN(Value))
|
|
{
|
|
if (pos + 3 > destination.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
destination[pos++] = 'N';
|
|
destination[pos++] = 'a';
|
|
destination[pos++] = 'N';
|
|
}
|
|
else
|
|
{
|
|
if (!Value.TryFormat(destination.Slice(pos), out int valueChars, "F2", provider))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pos += valueChars;
|
|
}
|
|
|
|
// Write closing bracket
|
|
if (pos + 1 > destination.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
destination[pos++] = ']';
|
|
|
|
charsWritten = pos;
|
|
return true;
|
|
}
|
|
}
|