Create API for price data.

This commit is contained in:
caspar 2026-03-17 20:08:31 -04:00
parent f5e2d70f57
commit 08825894f4
7 changed files with 263 additions and 103 deletions

43
Backend.cs Normal file
View File

@ -0,0 +1,43 @@
using Backend.ServiceHander;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<MarketDataService>();
builder.Services.AddCors(options =>
{
options.AddPolicy("Frontend", policy =>
{
policy
// ---- DEVELOPMENT ----
.AllowAnyOrigin()
// ---- PRODUCTION ----
// .WithOrigins("https://app.dongfeng-systems.org")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseCors("Frontend");
app.MapControllers();
app.Run();

View File

@ -11,4 +11,9 @@
<ProjectReference Include="../twsapi/CSharpAPI.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.5" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,89 @@
using Backend.ServiceHander;
using Backend.MarketDataRequest;
using Microsoft.AspNetCore.Mvc;
using IBApi;
namespace BackendApi.Controller
{
[ApiController]
[Route("api/market")]
public class MarketController : ControllerBase
{
private readonly MarketDataService _marketDataService;
public MarketController(MarketDataService marketDataService)
{
_marketDataService = marketDataService;
}
[HttpGet("live/{symbol}")]
public IActionResult GetLive(string symbol)
{
try
{
var data = _marketDataService.GetLiveQuote(symbol.ToUpperInvariant());
return Ok(data);
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.ToString() });
}
}
[HttpGet("historical")]
public IActionResult GetHistorical(
[FromQuery] string symbol,
[FromQuery] string endDateTime,
[FromQuery] string duration,
[FromQuery] HistoricalBarSize barSize,
[FromQuery] HistoricalWhatToShow whatToShow = HistoricalWhatToShow.Trades,
[FromQuery] bool useRth = true)
{
try
{
var data = _marketDataService.GetHistorical(
symbol.ToUpperInvariant(),
endDateTime,
duration,
barSize,
whatToShow,
useRth
);
return Ok(data);
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.ToString() });
}
}
[HttpGet("historical-line")]
public IActionResult GetHistoricalLine(
[FromQuery] string symbol,
[FromQuery] string endDateTime,
[FromQuery] string duration,
[FromQuery] HistoricalBarSize barSize,
[FromQuery] HistoricalWhatToShow whatToShow = HistoricalWhatToShow.Trades,
[FromQuery] bool useRth = true)
{
try
{
var data = _marketDataService.GetHistoricalLine(
symbol.ToUpperInvariant(),
endDateTime,
duration,
barSize,
whatToShow,
useRth
);
return Ok(data);
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.ToString() });
}
}
}
}

View File

@ -6,7 +6,9 @@ namespace Backend.MarketDataRequest
FiveMins,
FifteenMins,
OneHour,
OneDay
OneDay,
FiveDays,
OneMonth
}
public enum HistoricalWhatToShow
@ -17,6 +19,12 @@ namespace Backend.MarketDataRequest
Midpoint
}
public sealed class HistoricalLinePoint
{
public string Timestamp { get; init; } = string.Empty;
public double Price { get; init; }
}
public sealed class HistoricalEquityRequest
{
public int RequestId { get; init; }
@ -62,6 +70,8 @@ namespace Backend.MarketDataRequest
HistoricalBarSize.FifteenMins => "15 mins",
HistoricalBarSize.OneHour => "1 hour",
HistoricalBarSize.OneDay => "1 day",
HistoricalBarSize.FiveDays => "5 days",
HistoricalBarSize.OneMonth => "1 month",
_ => throw new ArgumentOutOfRangeException(nameof(BarSize))
};

View File

@ -1,100 +0,0 @@
using Backend.MarketDataRequest;
using IBApi;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Backend
{
public static class Program
{
public static void Main(string[] args)
{
var Gateway = new IBGatewayControl();
try
{
Gateway.Start("127.0.0.1", 4002, 1);
}
catch
{
Console.WriteLine("Invalid connection to IB Gateway. Please ensure gateway is running and connected.");
Environment.Exit(1);
}
while (!Gateway.IsApiReady)
{
Thread.Sleep(50);
}
// ---- TEST HISTORICAL DATA REQUEST ----
var HistoricalRequest = new HistoricalEquityRequest
{
RequestId = 1,
Symbol = "NVDA",
EndDateTime = "20260311 11:00:00 US/Eastern",
Duration = "3600 S",
BarSize = HistoricalBarSize.FiveMins,
WhatToShow = HistoricalWhatToShow.Trades,
UseRth = true,
KeepUpToDate = false,
FormatDate = 1
};
List<Bar> Bars = Gateway.RequestHistoricalBlocking(HistoricalRequest, TimeSpan.FromSeconds(15));
Console.WriteLine();
Console.WriteLine($"Received {Bars.Count} bars. Continue program here.");
// ---- TEST LIVE DATA REQUEST ----
var liveRequest = new LiveEquityRequest
{
RequestId = 2,
Symbol = "NVDA"
};
var live = Gateway.RequestLiveEquityBlocking(
liveRequest,
TimeSpan.FromSeconds(15));
Console.WriteLine();
Console.WriteLine("=== Live Quote ===");
Console.WriteLine($"Symbol: {live.Symbol}");
Console.WriteLine($"Last Price: {live.LastPrice}");
Console.WriteLine($"Volume: {live.Volume}");
Console.WriteLine($"Previous Close: {live.PreviousClose}");
Console.WriteLine($"Open: {live.Open}");
// ---- FUNDAMENTALS DATA REQUEST ----
// DOES NOT WORK, NEEDS EXTRA DATA SUBSCRIPTION
/*
var fundamentalsRequest = new FundamentalsEquityRequest
{
RequestId = 3,
Symbol = "NVDA"
};
try
{
var fundamentals = Gateway.RequestFundamentalsBlocking(
fundamentalsRequest,
TimeSpan.FromSeconds(15));
Console.WriteLine();
Console.WriteLine("=== Fundamentals ===");
Console.WriteLine($"Symbol: {fundamentals.Symbol}");
Console.WriteLine($"Market Cap: {fundamentals.MarketCap}");
Console.WriteLine($"P/E Ratio: {fundamentals.PERatio}");
Console.WriteLine($"Dividend Yield: {fundamentals.DividendYield}");
}
catch (Exception ex)
{
Console.WriteLine($"Fundamentals request failed.");
}
*/
Gateway.Disconnect();
}
}
}

View File

@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5246",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@ -14,7 +14,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7039;http://localhost:5246",
"applicationUrl": "https://localhost:7039;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -0,0 +1,113 @@
using Backend.MarketDataRequest;
using IBApi;
namespace Backend.ServiceHander
{
public sealed class MarketDataService
{
private readonly IBGatewayControl Gateway;
private int NextRequestId = 1;
private readonly object Lock = new();
public MarketDataService()
{
Gateway = new IBGatewayControl();
try
{
Gateway.Start("127.0.0.1", 4002, 1);
}
catch
{
Console.WriteLine("Invalid connection to IB Gateway. Please ensure gateway is running and connected.");
Environment.Exit(1);
}
}
private int GetNextRequestId()
{
lock (Lock)
{
return NextRequestId++;
}
}
public LiveEquitySnapshot GetLiveQuote(string Symbol) //
{
var Request = new LiveEquityRequest
{
RequestId = GetNextRequestId(),
Symbol = Symbol,
SecType = "STK",
Exchange = "SMART",
PrimaryExchange = "NASDAQ",
Currency = "USD",
RegulatorySnapshot = false
};
return Gateway.RequestLiveEquityBlocking(Request, TimeSpan.FromSeconds(10));
}
public List<object> GetHistorical(string symbol, string endDateTime, string duration, HistoricalBarSize barSize, HistoricalWhatToShow whatToShow = HistoricalWhatToShow.Trades, bool useRth = true)
{
var Request = new HistoricalEquityRequest
{
RequestId = GetNextRequestId(),
Symbol = symbol,
EndDateTime = endDateTime,
Duration = duration,
BarSize = barSize,
WhatToShow = whatToShow,
UseRth = useRth,
KeepUpToDate = false,
FormatDate = 1
};
var Bars = Gateway.RequestHistoricalBlocking(Request, TimeSpan.FromSeconds(20));
return Bars.Select(b => new
{
time = b.Time,
open = b.Open,
high = b.High,
low = b.Low,
close = b.Close,
volume = b.Volume
}).Cast<object>().ToList();
}
public List<object> GetHistoricalLine(string symbol, string endDateTime, string duration, HistoricalBarSize barSize, HistoricalWhatToShow whatToShow = HistoricalWhatToShow.Trades, bool useRth = true)
{
var Request = new HistoricalEquityRequest
{
RequestId = GetNextRequestId(),
Symbol = symbol,
EndDateTime = endDateTime,
Duration = duration,
BarSize = barSize,
WhatToShow = whatToShow,
UseRth = useRth,
KeepUpToDate = false,
FormatDate = 1
};
var Bars = Gateway.RequestHistoricalBlocking(Request, TimeSpan.FromSeconds(20));
return Bars.Select(b => new
{
timestamp = b.Time,
price = b.Close,
volume = b.Volume
}).Cast<object>().ToList();
}
public void Dispose()
{
Gateway.Dispose();
}
public void Disconnect()
{
Gateway.Disconnect();
}
}
}