Add A share to display
This commit is contained in:
+465
-164
@@ -250,7 +250,14 @@
|
||||
|
||||
<div class="controls">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-2">
|
||||
<label for="dataSource" class="form-label">数据源:</label>
|
||||
<select id="dataSource" class="form-select">
|
||||
<option value="crypto" selected>加密货币</option>
|
||||
<option value="a_stock">A股</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3" id="cryptoSymbolContainer">
|
||||
<label for="symbol" class="form-label">交易对:</label>
|
||||
<select id="symbol" class="form-select">
|
||||
{% for symbol in symbols %}
|
||||
@@ -258,6 +265,14 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3" id="astockSymbolContainer" style="display:none;">
|
||||
<label for="astockSymbol" class="form-label">A股股票:</label>
|
||||
<select id="astockSymbol" class="form-select">
|
||||
{% for stock in a_stock_symbols %}
|
||||
<option value="{{ stock.symbol }}" {% if stock.symbol == '000001' %}selected{% endif %}>{{ stock.symbol }} - {{ stock.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="timeframe" class="form-label">时间周期:</label>
|
||||
<select id="timeframe" class="form-select">
|
||||
@@ -277,11 +292,11 @@
|
||||
<option value="Asia/Tokyo">Asia/Tokyo (UTC+9)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-1">
|
||||
<label for="start_time" class="form-label">开始时间:</label>
|
||||
<input type="datetime-local" id="start_time" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-1">
|
||||
<label for="end_time" class="form-label">结束时间:</label>
|
||||
<input type="datetime-local" id="end_time" class="form-control">
|
||||
</div>
|
||||
@@ -1094,17 +1109,25 @@
|
||||
$('#refreshLoadingSpinner').show();
|
||||
|
||||
// 获取参数
|
||||
const symbol = $('#symbol').val() || 'BTC/USDT:USDT';
|
||||
const dataSource = $('#dataSource').val() || 'crypto';
|
||||
let symbol;
|
||||
if (dataSource === 'crypto') {
|
||||
symbol = $('#symbol').val() || 'BTC/USDT:USDT';
|
||||
} else {
|
||||
symbol = $('#astockSymbol').val() || '000001';
|
||||
}
|
||||
|
||||
const timeframe = $('#timeframe').val() || '5m';
|
||||
const timezone = $('#timezone').val() || 'Asia/Shanghai';
|
||||
const elementTimeframe = $('#elementTimeframe').val() || '1m';
|
||||
|
||||
// 确保时区参数有效
|
||||
console.log('更新图表使用时区:', timezone);
|
||||
console.log('数据源:', dataSource, '交易对/股票:', symbol);
|
||||
|
||||
// 如果symbol为空,不发送请求
|
||||
if (!symbol) {
|
||||
console.error('交易对不能为空');
|
||||
console.error('交易对/股票代码不能为空');
|
||||
$('#refreshLoadingSpinner').hide();
|
||||
return;
|
||||
}
|
||||
@@ -1162,7 +1185,75 @@
|
||||
// 初始化图表
|
||||
function initTradingView(symbol, timeframe) {
|
||||
try {
|
||||
console.log('初始化图表:', symbol, timeframe);
|
||||
console.log('初始化TradingView图表:', symbol, timeframe);
|
||||
|
||||
// 获取当前交易对的配置
|
||||
const symbolConfig = getSymbolConfig(symbol);
|
||||
console.log('交易对配置:', symbolConfig);
|
||||
|
||||
// 检查数据是否存在
|
||||
if (!currentData || !currentData.kline_data) {
|
||||
console.error('数据加载失败或不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否使用小周期K线数据
|
||||
const useElementPeriod = $('#useElementPeriod').is(':checked') &&
|
||||
currentData.element_kline_data &&
|
||||
Array.isArray(currentData.element_kline_data);
|
||||
|
||||
// 输出K线周期选择状态
|
||||
console.log('K线周期选择:', useElementPeriod ? '小周期' : '主周期');
|
||||
console.log('当前选择时区:', $('#timezone').val());
|
||||
console.log('交易对类型:', symbolConfig.type);
|
||||
|
||||
let candles = [];
|
||||
|
||||
if (useElementPeriod) {
|
||||
// 使用小周期K线数据
|
||||
candles = currentData.element_kline_data.map((kline) => {
|
||||
// 使用原始日期字符串创建Date对象
|
||||
const date = new Date(kline.date);
|
||||
// 获取时间戳(秒)- 不手动调整时区
|
||||
const timestamp = date.getTime() / 1000;
|
||||
|
||||
return {
|
||||
time: timestamp,
|
||||
open: parseFloat(kline.open),
|
||||
high: parseFloat(kline.high),
|
||||
low: parseFloat(kline.low),
|
||||
close: parseFloat(kline.close),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// 使用主周期K线数据 - 检查数据是否存在
|
||||
if (!currentData.kline_data || !Array.isArray(currentData.kline_data)) {
|
||||
console.error('主周期K线数据不存在或不是数组:', currentData.kline_data);
|
||||
return;
|
||||
}
|
||||
candles = currentData.kline_data.map((kline) => {
|
||||
// 使用原始日期字符串创建Date对象
|
||||
const date = new Date(kline.date);
|
||||
// 获取时间戳(秒)- 不手动调整时区
|
||||
const timestamp = date.getTime() / 1000;
|
||||
|
||||
return {
|
||||
time: timestamp,
|
||||
open: parseFloat(kline.open),
|
||||
high: parseFloat(kline.high),
|
||||
low: parseFloat(kline.low),
|
||||
close: parseFloat(kline.close),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 根据交易对类型过滤数据(仅用于显示优化)
|
||||
if (symbolConfig.type === 'a_stock' && timeframe.includes('m')) {
|
||||
// 对于A股分钟级数据,过滤非交易时间
|
||||
const originalLength = candles.length;
|
||||
candles = filterTradingHours(candles, symbolConfig);
|
||||
console.log(`A股数据过滤: ${originalLength} -> ${candles.length} 条记录`);
|
||||
}
|
||||
|
||||
// 清除图表容器
|
||||
document.getElementById('tradingview_chart').innerHTML = '';
|
||||
@@ -1253,96 +1344,129 @@
|
||||
let syncInProgress = false;
|
||||
|
||||
// 创建统一的图表选项
|
||||
const createChartOptions = (showTimeScale = true) => ({
|
||||
width: mainChartContainer.clientWidth,
|
||||
height: mainChartContainer.clientHeight,
|
||||
layout: {
|
||||
background: { color: '#ffffff' },
|
||||
textColor: '#333',
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: '#f0f0f0' },
|
||||
horzLines: { color: '#f0f0f0' },
|
||||
},
|
||||
crosshair: {
|
||||
mode: LightweightCharts.CrosshairMode.Normal,
|
||||
// 添加十字线工具提示本地化配置
|
||||
horzLine: {
|
||||
labelVisible: true,
|
||||
const createChartOptions = (showTimeScale = true) => {
|
||||
const baseOptions = {
|
||||
width: mainChartContainer.clientWidth,
|
||||
height: mainChartContainer.clientHeight,
|
||||
layout: {
|
||||
background: { color: '#ffffff' },
|
||||
textColor: '#333',
|
||||
},
|
||||
vertLine: {
|
||||
labelVisible: true,
|
||||
// 自定义时间格式化
|
||||
labelFormatter: (time) => {
|
||||
grid: {
|
||||
vertLines: { color: '#f0f0f0' },
|
||||
horzLines: { color: '#f0f0f0' },
|
||||
},
|
||||
crosshair: {
|
||||
mode: LightweightCharts.CrosshairMode.Normal,
|
||||
// 添加十字线工具提示本地化配置
|
||||
horzLine: {
|
||||
labelVisible: true,
|
||||
},
|
||||
vertLine: {
|
||||
labelVisible: true,
|
||||
// 自定义时间格式化
|
||||
labelFormatter: (time) => {
|
||||
const selectedTimezone = $('#timezone').val();
|
||||
try {
|
||||
const date = new Date(time * 1000);
|
||||
if (symbolConfig.type === 'a_stock') {
|
||||
// A股使用中国时区格式
|
||||
return date.toLocaleString('zh-CN', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
} else {
|
||||
return date.toLocaleString('zh-CN', {
|
||||
timeZone: selectedTimezone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('十字线时间格式化错误:', e);
|
||||
return new Date(time * 1000).toLocaleString();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor: '#ddd',
|
||||
},
|
||||
// 添加本地化选项,确保所有时间显示都使用选定的时区
|
||||
localization: {
|
||||
timeFormatter: (time) => {
|
||||
const selectedTimezone = $('#timezone').val();
|
||||
try {
|
||||
return new Date(time * 1000).toLocaleString('zh-CN', {
|
||||
const date = new Date(time * 1000);
|
||||
if (symbolConfig.type === 'a_stock') {
|
||||
// A股使用中国时区格式
|
||||
return date.toLocaleString('zh-CN', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
} else {
|
||||
return date.toLocaleString('zh-CN', {
|
||||
timeZone: selectedTimezone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('全局时间格式化错误:', e);
|
||||
return new Date(time * 1000).toLocaleString();
|
||||
}
|
||||
}
|
||||
},
|
||||
timeScale: {
|
||||
timeVisible: true,
|
||||
secondsVisible: false,
|
||||
visible: showTimeScale,
|
||||
borderColor: '#ddd',
|
||||
barSpacing: symbolConfig.type === 'a_stock' ? 6 : 10,
|
||||
tickMarkFormatter: (time) => {
|
||||
const selectedTimezone = symbolConfig.type === 'a_stock' ? 'Asia/Shanghai' : $('#timezone').val();
|
||||
try {
|
||||
// 使用完整的配置确保时区正确应用
|
||||
const date = new Date(time * 1000);
|
||||
console.log('格式化时间:', time, '转换为:', date.toISOString(), '时区:', selectedTimezone);
|
||||
|
||||
return date.toLocaleString('zh-CN', {
|
||||
timeZone: selectedTimezone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('十字线时间格式化错误:', e);
|
||||
console.error('时间格式化错误:', e);
|
||||
// 如果时区格式化失败,返回简单格式
|
||||
return new Date(time * 1000).toLocaleString();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor: '#ddd',
|
||||
},
|
||||
// 添加本地化选项,确保所有时间显示都使用选定的时区
|
||||
localization: {
|
||||
timeFormatter: (time) => {
|
||||
const selectedTimezone = $('#timezone').val();
|
||||
try {
|
||||
return new Date(time * 1000).toLocaleString('zh-CN', {
|
||||
timeZone: selectedTimezone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('全局时间格式化错误:', e);
|
||||
return new Date(time * 1000).toLocaleString();
|
||||
}
|
||||
}
|
||||
},
|
||||
timeScale: {
|
||||
timeVisible: true,
|
||||
secondsVisible: false,
|
||||
visible: showTimeScale,
|
||||
borderColor: '#ddd',
|
||||
barSpacing: 10,
|
||||
tickMarkFormatter: (time) => {
|
||||
const selectedTimezone = $('#timezone').val();
|
||||
try {
|
||||
// 使用完整的配置确保时区正确应用
|
||||
const date = new Date(time * 1000);
|
||||
console.log('格式化时间:', time, '转换为:', date.toISOString(), '时区:', selectedTimezone);
|
||||
|
||||
return date.toLocaleString('zh-CN', {
|
||||
timeZone: selectedTimezone,
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('时间格式化错误:', e);
|
||||
// 如果时区格式化失败,返回简单格式
|
||||
return new Date(time * 1000).toLocaleString();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 根据交易对类型调整配置
|
||||
return adjustChartForSymbolType(baseOptions, symbolConfig);
|
||||
};
|
||||
|
||||
// 创建主图表
|
||||
const mainChart = LightweightCharts.createChart(mainChartContainer, createChartOptions(true));
|
||||
@@ -1356,50 +1480,6 @@
|
||||
macdChart = LightweightCharts.createChart(macdChartContainer, createChartOptions(false));
|
||||
}
|
||||
|
||||
// 转换K线数据 - 根据选中的周期使用主周期或小周期数据
|
||||
let candles;
|
||||
const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
|
||||
currentData.element_timeframe &&
|
||||
currentData.element_kline_data;
|
||||
|
||||
// 输出K线周期选择状态
|
||||
console.log('K线周期选择:', useElementPeriod ? '小周期' : '主周期');
|
||||
console.log('当前选择时区:', $('#timezone').val());
|
||||
|
||||
if (useElementPeriod) {
|
||||
// 使用小周期K线数据
|
||||
candles = currentData.element_kline_data.map((kline) => {
|
||||
// 使用原始日期字符串创建Date对象
|
||||
const date = new Date(kline.date);
|
||||
// 获取时间戳(秒)- 不手动调整时区
|
||||
const timestamp = date.getTime() / 1000;
|
||||
|
||||
return {
|
||||
time: timestamp,
|
||||
open: parseFloat(kline.open),
|
||||
high: parseFloat(kline.high),
|
||||
low: parseFloat(kline.low),
|
||||
close: parseFloat(kline.close),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// 使用主周期K线数据
|
||||
candles = currentData.kline_data.map((kline) => {
|
||||
// 使用原始日期字符串创建Date对象
|
||||
const date = new Date(kline.date);
|
||||
// 获取时间戳(秒)- 不手动调整时区
|
||||
const timestamp = date.getTime() / 1000;
|
||||
|
||||
return {
|
||||
time: timestamp,
|
||||
open: parseFloat(kline.open),
|
||||
high: parseFloat(kline.high),
|
||||
low: parseFloat(kline.low),
|
||||
close: parseFloat(kline.close),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 创建蜡烛图系列并设置数据
|
||||
if (showOriginalKline) {
|
||||
const candleSeries = mainChart.addCandlestickSeries({
|
||||
@@ -1432,17 +1512,9 @@
|
||||
}
|
||||
|
||||
// 转换成交量数据 - 始终使用主K线周期数据
|
||||
const volumes = useElementPeriod ?
|
||||
currentData.element_kline_data.map(kline => {
|
||||
// 创建日期对象并获取时间戳,不手动调整时区
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
return {
|
||||
time: timestamp,
|
||||
value: parseFloat(kline.volume),
|
||||
color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(220, 53, 69, 0.5)' : 'rgba(40, 167, 69, 0.5)',
|
||||
};
|
||||
}) :
|
||||
currentData.kline_data.map(kline => {
|
||||
let volumes = [];
|
||||
if (useElementPeriod && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) {
|
||||
volumes = currentData.element_kline_data.map(kline => {
|
||||
// 创建日期对象并获取时间戳,不手动调整时区
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
return {
|
||||
@@ -1451,6 +1523,17 @@
|
||||
color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(220, 53, 69, 0.5)' : 'rgba(40, 167, 69, 0.5)',
|
||||
};
|
||||
});
|
||||
} else if (currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
volumes = currentData.kline_data.map(kline => {
|
||||
// 创建日期对象并获取时间戳,不手动调整时区
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
return {
|
||||
time: timestamp,
|
||||
value: parseFloat(kline.volume),
|
||||
color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(220, 53, 69, 0.5)' : 'rgba(40, 167, 69, 0.5)',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 添加成交量图表
|
||||
const volumeSeries = volumeChart.addHistogramSeries({
|
||||
@@ -1464,7 +1547,7 @@
|
||||
tvWidget.series.volumeSeries = volumeSeries;
|
||||
|
||||
// 添加MACD图表 - 始终使用主K线周期的MACD数据
|
||||
if (showMacd && currentData.macd) {
|
||||
if (showMacd && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
// 创建MACD线
|
||||
const macdLineSeries = macdChart.addLineSeries({
|
||||
color: '#2962FF',
|
||||
@@ -2945,6 +3028,12 @@
|
||||
try {
|
||||
console.log('增量更新图表数据');
|
||||
|
||||
// 检查 currentData 是否存在
|
||||
if (!currentData) {
|
||||
console.error('currentData为空,无法更新图表');
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存当前的可视范围
|
||||
if (tvWidget.mainChart) {
|
||||
tvWidget.state.visibleRange = tvWidget.mainChart.timeScale().getVisibleRange();
|
||||
@@ -2957,10 +3046,11 @@
|
||||
// 检查是否使用小周期数据
|
||||
const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
|
||||
currentData.element_timeframe &&
|
||||
currentData.element_kline_data;
|
||||
currentData.element_kline_data &&
|
||||
Array.isArray(currentData.element_kline_data);
|
||||
|
||||
// 转换K线数据
|
||||
let candles;
|
||||
let candles = [];
|
||||
if (useElementPeriod) {
|
||||
console.log('使用小周期K线数据');
|
||||
candles = currentData.element_kline_data.map((kline) => {
|
||||
@@ -2974,7 +3064,7 @@
|
||||
close: parseFloat(kline.close),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
} else if (currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
console.log('使用主周期K线数据');
|
||||
candles = currentData.kline_data.map((kline) => {
|
||||
const date = new Date(kline.date);
|
||||
@@ -3003,16 +3093,9 @@
|
||||
}
|
||||
|
||||
// 更新成交量数据
|
||||
const volumes = useElementPeriod ?
|
||||
currentData.element_kline_data.map(kline => {
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
return {
|
||||
time: timestamp,
|
||||
value: parseFloat(kline.volume),
|
||||
color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(220, 53, 69, 0.5)' : 'rgba(40, 167, 69, 0.5)',
|
||||
};
|
||||
}) :
|
||||
currentData.kline_data.map(kline => {
|
||||
let volumes = [];
|
||||
if (useElementPeriod && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) {
|
||||
volumes = currentData.element_kline_data.map(kline => {
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
return {
|
||||
time: timestamp,
|
||||
@@ -3020,13 +3103,23 @@
|
||||
color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(220, 53, 69, 0.5)' : 'rgba(40, 167, 69, 0.5)',
|
||||
};
|
||||
});
|
||||
} else if (currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
volumes = currentData.kline_data.map(kline => {
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
return {
|
||||
time: timestamp,
|
||||
value: parseFloat(kline.volume),
|
||||
color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(220, 53, 69, 0.5)' : 'rgba(40, 167, 69, 0.5)',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (tvWidget.series.volumeSeries) {
|
||||
tvWidget.series.volumeSeries.setData(volumes);
|
||||
}
|
||||
|
||||
// 更新MACD数据
|
||||
if ($('#showMacd').is(':checked') && currentData.macd && tvWidget.series.macdLineSeries) {
|
||||
if ($('#showMacd').is(':checked') && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data) && tvWidget.series.macdLineSeries) {
|
||||
// 提取MACD数据
|
||||
const macdData = [];
|
||||
const signalData = [];
|
||||
@@ -3521,8 +3614,11 @@
|
||||
tables.kline.clear().destroy();
|
||||
}
|
||||
|
||||
// 检查数据是否存在且为数组
|
||||
const klineData = (data.kline_data && Array.isArray(data.kline_data)) ? data.kline_data : [];
|
||||
|
||||
tables.kline = $('#klineTable').DataTable({
|
||||
data: data.kline_data,
|
||||
data: klineData,
|
||||
order: [[0, 'desc']],
|
||||
pageLength: 25,
|
||||
columns: [
|
||||
@@ -3659,15 +3755,18 @@
|
||||
tables.macd.clear().destroy();
|
||||
}
|
||||
|
||||
const macdData = data.kline_data.map((item, index) => {
|
||||
return {
|
||||
time: item.date,
|
||||
close: item.close,
|
||||
macd: data.macd.macd[index],
|
||||
signal: data.macd.signal[index],
|
||||
histogram: data.macd.histogram[index]
|
||||
};
|
||||
});
|
||||
let macdData = [];
|
||||
if (data.kline_data && Array.isArray(data.kline_data) && data.macd && data.macd.macd) {
|
||||
macdData = data.kline_data.map((item, index) => {
|
||||
return {
|
||||
time: item.date,
|
||||
close: item.close,
|
||||
macd: data.macd.macd[index],
|
||||
signal: data.macd.signal[index],
|
||||
histogram: data.macd.histogram[index]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
tables.macd = $('#macdTable').DataTable({
|
||||
data: macdData,
|
||||
@@ -3786,8 +3885,36 @@
|
||||
console.log('从本地存储恢复时区设置:', savedTimezone);
|
||||
}
|
||||
|
||||
// 初始化数据源切换
|
||||
$('#dataSource').on('change', function() {
|
||||
const dataSource = $(this).val();
|
||||
if (dataSource === 'crypto') {
|
||||
$('#cryptoSymbolContainer').show();
|
||||
$('#astockSymbolContainer').hide();
|
||||
// 停止A股状态更新器
|
||||
if (window.astockStatusInterval) {
|
||||
clearInterval(window.astockStatusInterval);
|
||||
window.astockStatusInterval = null;
|
||||
}
|
||||
} else if (dataSource === 'a_stock') {
|
||||
$('#cryptoSymbolContainer').hide();
|
||||
$('#astockSymbolContainer').show();
|
||||
// 加载A股数据时,如果还没有加载股票列表,可以在这里触发加载
|
||||
loadAStockSymbols();
|
||||
// 启动A股交易时间状态更新器
|
||||
startAStockStatusUpdater();
|
||||
}
|
||||
});
|
||||
|
||||
// 检查初始数据源设置
|
||||
const initialDataSource = $('#dataSource').val();
|
||||
if (initialDataSource === 'a_stock') {
|
||||
startAStockStatusUpdater();
|
||||
}
|
||||
|
||||
// 初始化交易对下拉菜单
|
||||
$('#symbol').val('BTC/USDT:USDT');
|
||||
$('#astockSymbol').val('000001');
|
||||
$('#timeframe').val('5m');
|
||||
$('#elementTimeframe').val('1m');
|
||||
|
||||
@@ -4802,6 +4929,180 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取A股股票列表
|
||||
function loadAStockSymbols() {
|
||||
$.get('/api/popular_a_stocks', function(data) {
|
||||
if (Array.isArray(data)) {
|
||||
const $select = $('#astockSymbol');
|
||||
const currentSymbol = $select.val(); // 保存当前选中的值
|
||||
$select.empty();
|
||||
|
||||
data.forEach(function(stock) {
|
||||
$select.append($('<option>', {
|
||||
value: stock.symbol,
|
||||
text: stock.symbol + ' - ' + stock.name
|
||||
}));
|
||||
});
|
||||
|
||||
// 如果有保存的选中值,恢复它
|
||||
if (currentSymbol && data.some(stock => stock.symbol === currentSymbol)) {
|
||||
$select.val(currentSymbol);
|
||||
} else {
|
||||
// 设置默认值为平安银行
|
||||
$select.val('000001');
|
||||
}
|
||||
}
|
||||
}).fail(function() {
|
||||
console.error('加载A股股票列表失败');
|
||||
});
|
||||
}
|
||||
|
||||
// 检测交易对类型并返回相应的配置
|
||||
function getSymbolConfig(symbol) {
|
||||
const isAStock = symbol && symbol.length === 6 && /^\d+$/.test(symbol);
|
||||
|
||||
if (isAStock) {
|
||||
return {
|
||||
type: 'a_stock',
|
||||
displayName: symbol,
|
||||
tradingSessions: [
|
||||
// A股交易时间配置
|
||||
{ start: '09:30', end: '11:30' }, // 上午
|
||||
{ start: '13:00', end: '15:00' } // 下午
|
||||
],
|
||||
timezone: 'Asia/Shanghai',
|
||||
// A股的交易日配置(周一到周五,除节假日)
|
||||
tradingDays: [1, 2, 3, 4, 5] // 1=周一, 7=周日
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'crypto',
|
||||
displayName: symbol,
|
||||
tradingSessions: [
|
||||
{ start: '00:00', end: '23:59' } // 24小时交易
|
||||
],
|
||||
timezone: 'UTC',
|
||||
tradingDays: [1, 2, 3, 4, 5, 6, 7] // 7天交易
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 根据交易对类型调整图表配置
|
||||
function adjustChartForSymbolType(chartOptions, symbolConfig) {
|
||||
if (symbolConfig.type === 'a_stock') {
|
||||
// A股特殊配置
|
||||
chartOptions.timeScale = {
|
||||
...chartOptions.timeScale,
|
||||
// 禁用非交易时间的显示
|
||||
borderVisible: true,
|
||||
borderColor: '#ddd',
|
||||
// 自定义时间格式化,只显示交易时间
|
||||
timeVisible: true,
|
||||
// 添加A股特定的时间范围限制
|
||||
rightOffset: 12,
|
||||
barSpacing: 6,
|
||||
minBarSpacing: 3,
|
||||
};
|
||||
|
||||
// 添加A股交易时间提示
|
||||
chartOptions.layout = {
|
||||
...chartOptions.layout,
|
||||
fontSize: 12,
|
||||
fontFamily: 'Arial, sans-serif'
|
||||
};
|
||||
}
|
||||
|
||||
return chartOptions;
|
||||
}
|
||||
|
||||
// 过滤非交易时间的数据(仅用于显示优化)
|
||||
function filterTradingHours(data, symbolConfig) {
|
||||
if (symbolConfig.type !== 'a_stock') {
|
||||
return data; // 非A股数据不需要过滤
|
||||
}
|
||||
|
||||
return data.filter(item => {
|
||||
const date = new Date(item.time * 1000);
|
||||
const hour = date.getHours();
|
||||
const minute = date.getMinutes();
|
||||
const timeStr = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
||||
|
||||
// 检查是否在交易时间内
|
||||
return symbolConfig.tradingSessions.some(session => {
|
||||
return timeStr >= session.start && timeStr <= session.end;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 更新A股交易时间状态
|
||||
function updateAStockTradingStatus() {
|
||||
const now = new Date();
|
||||
const chinaTime = new Date(now.toLocaleString("en-US", {timeZone: "Asia/Shanghai"}));
|
||||
const hour = chinaTime.getHours();
|
||||
const minute = chinaTime.getMinutes();
|
||||
const dayOfWeek = chinaTime.getDay(); // 0=周日, 1=周一, ..., 6=周六
|
||||
|
||||
const statusElement = document.getElementById('tradingTimeStatus');
|
||||
if (!statusElement) return;
|
||||
|
||||
// 检查是否为交易日(周一到周五)
|
||||
const isTradingDay = dayOfWeek >= 1 && dayOfWeek <= 5;
|
||||
|
||||
if (!isTradingDay) {
|
||||
statusElement.className = 'badge bg-secondary';
|
||||
statusElement.textContent = '非交易日';
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否在交易时间内
|
||||
const currentTime = hour * 60 + minute; // 转换为分钟
|
||||
const morningStart = 9 * 60 + 30; // 09:30
|
||||
const morningEnd = 11 * 60 + 30; // 11:30
|
||||
const afternoonStart = 13 * 60; // 13:00
|
||||
const afternoonEnd = 15 * 60; // 15:00
|
||||
|
||||
let status = '';
|
||||
let className = '';
|
||||
|
||||
if (currentTime >= morningStart && currentTime <= morningEnd) {
|
||||
status = '上午交易中';
|
||||
className = 'badge bg-success';
|
||||
} else if (currentTime >= afternoonStart && currentTime <= afternoonEnd) {
|
||||
status = '下午交易中';
|
||||
className = 'badge bg-success';
|
||||
} else if (currentTime > morningEnd && currentTime < afternoonStart) {
|
||||
status = '午间休市';
|
||||
className = 'badge bg-warning';
|
||||
} else if (currentTime < morningStart) {
|
||||
status = '开盘前';
|
||||
className = 'badge bg-info';
|
||||
} else if (currentTime > afternoonEnd) {
|
||||
status = '收盘后';
|
||||
className = 'badge bg-dark';
|
||||
} else {
|
||||
status = '非交易时间';
|
||||
className = 'badge bg-secondary';
|
||||
}
|
||||
|
||||
statusElement.className = className;
|
||||
statusElement.textContent = status;
|
||||
}
|
||||
|
||||
// 启动A股交易时间状态更新
|
||||
function startAStockStatusUpdater() {
|
||||
// 如果已经有定时器在运行,先清除
|
||||
if (window.astockStatusInterval) {
|
||||
clearInterval(window.astockStatusInterval);
|
||||
}
|
||||
|
||||
// 立即更新一次
|
||||
updateAStockTradingStatus();
|
||||
|
||||
// 每30秒更新一次
|
||||
window.astockStatusInterval = setInterval(updateAStockTradingStatus, 30000);
|
||||
console.log('A股交易时间状态更新器已启动');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user