Add Ticker24h support: hourly market snapshots with USD-normalized volume filtering

This commit is contained in:
2026-04-26 18:39:52 -04:00
parent 85fcbe1330
commit 0178b5d29d
45 changed files with 1995 additions and 170 deletions

View File

@@ -2,6 +2,8 @@
import { ref } from 'vue'
import DetailsEditDialog from './DetailsEditDialog.vue'
import ResearchViewDialog from './ResearchViewDialog.vue'
import { useIndicatorStore } from '../stores/indicators'
import { useIndicatorTypesStore } from '../stores/indicatorTypes'
const props = defineProps<{
category: 'indicator' | 'strategy' | 'research'
@@ -14,6 +16,9 @@ const editingName = ref('')
const viewDialogVisible = ref(false)
const viewingName = ref('')
const indicatorStore = useIndicatorStore()
const indicatorTypesStore = useIndicatorTypesStore()
function openEdit(name: string) {
editingName.value = name
dialogVisible.value = true
@@ -24,8 +29,28 @@ function openView(name: string) {
viewDialogVisible.value = true
}
function addToChart(pandasTaName: string, displayName: string) {
const type = indicatorTypesStore.types[pandasTaName]
if (!type) return
const defaultParams: Record<string, any> = {}
for (const [k, p] of Object.entries(type.metadata.parameters)) {
defaultParams[k] = p.default
}
const now = Math.floor(Date.now() / 1000)
indicatorStore.addIndicator({
id: `${pandasTaName}_${Date.now()}`,
pandas_ta_name: pandasTaName,
instance_name: displayName,
parameters: defaultParams,
visible: true,
pane: type.metadata.pane,
custom_metadata: type.metadata,
created_at: now,
modified_at: now,
})
}
function onUpdated(_payload: { category: string; name: string; success: boolean; error?: string }) {
// Hook for handling the details_updated response — add logic here as needed
}
</script>
@@ -35,8 +60,9 @@ function onUpdated(_payload: { category: string; name: string; success: boolean;
<div v-for="row in rows" :key="row.id" class="item-row">
<span class="item-name">{{ row.display_name }}</span>
<span class="item-desc">{{ row.description ?? '' }}</span>
<button v-if="category === 'research'" class="view-btn" @click="openView(row.display_name)">View</button>
<button class="edit-btn" @click="openEdit(row.display_name)">Edit</button>
<button class="edit-btn" @click="openEdit(row.display_name)">Spec</button>
<button v-if="category === 'research'" class="view-btn" @click="openView(row.display_name)">Result</button>
<button v-if="category === 'indicator'" class="use-btn" @click="addToChart(row.id, row.display_name)">Use</button>
</div>
</div>
@@ -138,4 +164,21 @@ function onUpdated(_payload: { category: string; name: string; success: boolean;
border-color: #089981;
color: #089981;
}
.use-btn {
flex-shrink: 0;
background: none;
border: 1px solid #3d3d3d;
color: #888;
cursor: pointer;
font-size: 11px;
padding: 2px 8px;
border-radius: 3px;
line-height: 18px;
}
.use-btn:hover {
border-color: #4a9eca;
color: #4a9eca;
}
</style>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch, computed, onUnmounted } from 'vue'
import { ref, watch, computed, onUnmounted, nextTick } from 'vue'
import Dialog from 'primevue/dialog'
import Button from 'primevue/button'
import { useEditor, EditorContent } from '@tiptap/vue-3'
@@ -54,12 +54,13 @@ watch(() => props.visible, (v) => {
}
})
const messageHandler = (msg: WebSocketMessage) => {
const messageHandler = async (msg: WebSocketMessage) => {
if (msg.category !== props.category || msg.name !== props.name) return
if (msg.type === 'details_data') {
originalContent.value = msg.details ?? ''
editor.value?.commands.setContent(msg.details ?? '')
await nextTick()
originalContent.value = (editor.value?.storage as any).markdown.getMarkdown() ?? ''
loadState.value = 'ready'
} else if (msg.type === 'details_error') {
loadError.value = msg.error ?? 'Failed to load details'