Update indicators

This commit is contained in:
2025-06-18 09:40:10 +07:00
parent 0f7df04813
commit 0aa1f40f53
11 changed files with 607 additions and 210 deletions

View File

@@ -171,7 +171,8 @@ public class BacktestController : BaseController
FlipPosition = request.Config.FlipPosition, FlipPosition = request.Config.FlipPosition,
Name = request.Config.Name ?? Name = request.Config.Name ??
$"Backtest-{request.Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}", $"Backtest-{request.Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}",
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable,
Scenario = scenario,
}; };
switch (request.Config.BotType) switch (request.Config.BotType)

View File

@@ -1084,8 +1084,8 @@ namespace Managing.Application.Tests
var scenarioName = string.Join("_", var scenarioName = string.Join("_",
paramCombo.Select(p => $"{p.strategyConfig.Name}_{p.parameterSet.Name}")); paramCombo.Select(p => $"{p.strategyConfig.Name}_{p.parameterSet.Name}"));
var scenario = BuildScenario(scenarioName, paramCombo); var scenario = BuildScenario(scenarioName, paramCombo);
scenarios.Add(scenario);
scenario.LoopbackPeriod = 15; scenario.LoopbackPeriod = 15;
scenarios.Add(scenario);
} }
} }

View File

@@ -1,14 +1,14 @@
import React, {useEffect} from 'react' import React, {useEffect} from 'react'
import {SubmitHandler, useForm} from 'react-hook-form' import {SubmitHandler, useForm} from 'react-hook-form'
import {Modal} from '../../mollecules' import {Modal} from '../../mollecules'
import type {Scenario, Strategy} from '../../../generated/ManagingApi' import type {Indicator, Scenario} from '../../../generated/ManagingApi'
import type {IScenarioFormInput} from '../../../global/type' import type {IScenarioFormInput} from '../../../global/type'
interface ScenarioModalProps { interface ScenarioModalProps {
showModal: boolean showModal: boolean
onClose: () => void onClose: () => void
onSubmit: (data: IScenarioFormInput) => Promise<void> onSubmit: (data: IScenarioFormInput) => Promise<void>
strategies: Strategy[] indicators: Indicator[]
scenario?: Scenario | null // For update mode scenario?: Scenario | null // For update mode
isUpdate?: boolean isUpdate?: boolean
} }
@@ -17,7 +17,7 @@ const ScenarioModal: React.FC<ScenarioModalProps> = ({
showModal, showModal,
onClose, onClose,
onSubmit, onSubmit,
strategies, indicators,
scenario = null, scenario = null,
isUpdate = false isUpdate = false
}) => { }) => {
@@ -30,7 +30,7 @@ const ScenarioModal: React.FC<ScenarioModalProps> = ({
// Pre-populate form for update // Pre-populate form for update
setValue('name', scenario.name || '') setValue('name', scenario.name || '')
setValue('loopbackPeriod', scenario.loopbackPeriod || 0) setValue('loopbackPeriod', scenario.loopbackPeriod || 0)
setValue('strategies', scenario.strategies?.map(s => s.name || '') || []) setValue('indicators', scenario.indicators?.map(s => s.name || '') || [])
} else { } else {
// Reset form for create // Reset form for create
reset() reset()
@@ -68,15 +68,15 @@ const ScenarioModal: React.FC<ScenarioModalProps> = ({
<div className="form-control"> <div className="form-control">
<div className="input-group"> <div className="input-group">
<label htmlFor="strategies" className="label mr-6"> <label htmlFor="indicators" className="label mr-6">
Strategies Indicators
</label> </label>
<select <select
multiple multiple
className="select select-bordered h-28 w-full max-w-xs" className="select select-bordered h-28 w-full max-w-xs"
{...register('strategies')} {...register('indicators')}
> >
{strategies.map((item) => ( {indicators?.map((item) => (
<option key={item.name} value={item.name || ''}> <option key={item.name} value={item.name || ''}>
{item.signalType} - {item.name} {item.signalType} - {item.name}
</option> </option>

View File

@@ -1600,8 +1600,8 @@ export class ScenarioClient extends AuthorizedApiBase {
return Promise.resolve<FileResponse>(null as any); return Promise.resolve<FileResponse>(null as any);
} }
scenario_GetStrategies(): Promise<Strategy[]> { scenario_GetIndicators(): Promise<Indicator[]> {
let url_ = this.baseUrl + "/Scenario/strategy"; let url_ = this.baseUrl + "/Scenario/indicator";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = { let options_: RequestInit = {
@@ -1614,17 +1614,17 @@ export class ScenarioClient extends AuthorizedApiBase {
return this.transformOptions(options_).then(transformedOptions_ => { return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_); return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => { }).then((_response: Response) => {
return this.processScenario_GetStrategies(_response); return this.processScenario_GetIndicators(_response);
}); });
} }
protected processScenario_GetStrategies(response: Response): Promise<Strategy[]> { protected processScenario_GetIndicators(response: Response): Promise<Indicator[]> {
const status = response.status; const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) { if (status === 200) {
return response.text().then((_responseText) => { return response.text().then((_responseText) => {
let result200: any = null; let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Strategy[]; result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Indicator[];
return result200; return result200;
}); });
} else if (status !== 200 && status !== 204) { } else if (status !== 200 && status !== 204) {
@@ -1632,15 +1632,15 @@ export class ScenarioClient extends AuthorizedApiBase {
return throwException("An unexpected server error occurred.", status, _responseText, _headers); return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}); });
} }
return Promise.resolve<Strategy[]>(null as any); return Promise.resolve<Indicator[]>(null as any);
} }
scenario_CreateStrategy(strategyType: StrategyType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise<Strategy> { scenario_CreateIndicator(indicatorType: IndicatorType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise<Indicator> {
let url_ = this.baseUrl + "/Scenario/strategy?"; let url_ = this.baseUrl + "/Scenario/indicator?";
if (strategyType === null) if (indicatorType === null)
throw new Error("The parameter 'strategyType' cannot be null."); throw new Error("The parameter 'indicatorType' cannot be null.");
else if (strategyType !== undefined) else if (indicatorType !== undefined)
url_ += "strategyType=" + encodeURIComponent("" + strategyType) + "&"; url_ += "indicatorType=" + encodeURIComponent("" + indicatorType) + "&";
if (name !== undefined && name !== null) if (name !== undefined && name !== null)
url_ += "name=" + encodeURIComponent("" + name) + "&"; url_ += "name=" + encodeURIComponent("" + name) + "&";
if (period !== undefined && period !== null) if (period !== undefined && period !== null)
@@ -1671,17 +1671,17 @@ export class ScenarioClient extends AuthorizedApiBase {
return this.transformOptions(options_).then(transformedOptions_ => { return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_); return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => { }).then((_response: Response) => {
return this.processScenario_CreateStrategy(_response); return this.processScenario_CreateIndicator(_response);
}); });
} }
protected processScenario_CreateStrategy(response: Response): Promise<Strategy> { protected processScenario_CreateIndicator(response: Response): Promise<Indicator> {
const status = response.status; const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) { if (status === 200) {
return response.text().then((_responseText) => { return response.text().then((_responseText) => {
let result200: any = null; let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Strategy; result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Indicator;
return result200; return result200;
}); });
} else if (status !== 200 && status !== 204) { } else if (status !== 200 && status !== 204) {
@@ -1689,11 +1689,11 @@ export class ScenarioClient extends AuthorizedApiBase {
return throwException("An unexpected server error occurred.", status, _responseText, _headers); return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}); });
} }
return Promise.resolve<Strategy>(null as any); return Promise.resolve<Indicator>(null as any);
} }
scenario_DeleteStrategy(name: string | null | undefined): Promise<FileResponse> { scenario_DeleteIndicator(name: string | null | undefined): Promise<FileResponse> {
let url_ = this.baseUrl + "/Scenario/strategy?"; let url_ = this.baseUrl + "/Scenario/indicator?";
if (name !== undefined && name !== null) if (name !== undefined && name !== null)
url_ += "name=" + encodeURIComponent("" + name) + "&"; url_ += "name=" + encodeURIComponent("" + name) + "&";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
@@ -1708,11 +1708,11 @@ export class ScenarioClient extends AuthorizedApiBase {
return this.transformOptions(options_).then(transformedOptions_ => { return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_); return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => { }).then((_response: Response) => {
return this.processScenario_DeleteStrategy(_response); return this.processScenario_DeleteIndicator(_response);
}); });
} }
protected processScenario_DeleteStrategy(response: Response): Promise<FileResponse> { protected processScenario_DeleteIndicator(response: Response): Promise<FileResponse> {
const status = response.status; const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) { if (status === 200 || status === 206) {
@@ -1734,12 +1734,12 @@ export class ScenarioClient extends AuthorizedApiBase {
return Promise.resolve<FileResponse>(null as any); return Promise.resolve<FileResponse>(null as any);
} }
scenario_UpdateStrategy(strategyType: StrategyType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise<FileResponse> { scenario_Updateindicator(indicatorType: IndicatorType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise<FileResponse> {
let url_ = this.baseUrl + "/Scenario/strategy?"; let url_ = this.baseUrl + "/Scenario/indicator?";
if (strategyType === null) if (indicatorType === null)
throw new Error("The parameter 'strategyType' cannot be null."); throw new Error("The parameter 'indicatorType' cannot be null.");
else if (strategyType !== undefined) else if (indicatorType !== undefined)
url_ += "strategyType=" + encodeURIComponent("" + strategyType) + "&"; url_ += "indicatorType=" + encodeURIComponent("" + indicatorType) + "&";
if (name !== undefined && name !== null) if (name !== undefined && name !== null)
url_ += "name=" + encodeURIComponent("" + name) + "&"; url_ += "name=" + encodeURIComponent("" + name) + "&";
if (period !== undefined && period !== null) if (period !== undefined && period !== null)
@@ -1770,11 +1770,11 @@ export class ScenarioClient extends AuthorizedApiBase {
return this.transformOptions(options_).then(transformedOptions_ => { return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_); return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => { }).then((_response: Response) => {
return this.processScenario_UpdateStrategy(_response); return this.processScenario_Updateindicator(_response);
}); });
} }
protected processScenario_UpdateStrategy(response: Response): Promise<FileResponse> { protected processScenario_Updateindicator(response: Response): Promise<FileResponse> {
const status = response.status; const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) { if (status === 200 || status === 206) {
@@ -2745,46 +2745,23 @@ export interface GmxClaimableSummary {
claimableFundingFees?: FundingFeesData | null; claimableFundingFees?: FundingFeesData | null;
claimableUiFees?: UiFeesData | null; claimableUiFees?: UiFeesData | null;
rebateStats?: RebateStatsData | null; rebateStats?: RebateStatsData | null;
summary?: SummaryData | null;
} }
export interface FundingFeesData { export interface FundingFeesData {
totalLongUsdc?: number;
totalShortUsdc?: number;
totalUsdc?: number; totalUsdc?: number;
markets?: { [key: string]: FundingFeesMarketData; } | null;
}
export interface FundingFeesMarketData {
claimableFundingAmountLong?: number;
claimableFundingAmountShort?: number;
} }
export interface UiFeesData { export interface UiFeesData {
totalUsdc?: number; totalUsdc?: number;
markets?: { [key: string]: UiFeesMarketData; } | null;
}
export interface UiFeesMarketData {
claimableUiFeeAmount?: number;
} }
export interface RebateStatsData { export interface RebateStatsData {
totalRebateUsdc?: number; totalRebateUsdc?: number;
discountUsdc?: number; discountUsdc?: number;
volume?: number;
tier?: number;
rebateFactor?: number; rebateFactor?: number;
discountFactor?: number; discountFactor?: number;
} }
export interface SummaryData {
totalClaimableUsdc?: number;
hasFundingFees?: boolean;
hasUiFees?: boolean;
hasRebates?: boolean;
}
export interface Backtest { export interface Backtest {
id: string; id: string;
finalPnl: number; finalPnl: number;
@@ -2802,7 +2779,7 @@ export interface Backtest {
walletBalances: KeyValuePairOfDateTimeAndDecimal[]; walletBalances: KeyValuePairOfDateTimeAndDecimal[];
optimizedMoneyManagement: MoneyManagement; optimizedMoneyManagement: MoneyManagement;
user: User; user: User;
strategiesValues: { [key in keyof typeof StrategyType]?: StrategiesResultBase; }; strategiesValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
score: number; score: number;
} }
@@ -2810,7 +2787,6 @@ export interface TradingBotConfig {
accountName: string; accountName: string;
moneyManagement: MoneyManagement; moneyManagement: MoneyManagement;
ticker: Ticker; ticker: Ticker;
scenarioName: string;
timeframe: Timeframe; timeframe: Timeframe;
isForWatchingOnly: boolean; isForWatchingOnly: boolean;
botTradingBalance: number; botTradingBalance: number;
@@ -2820,6 +2796,8 @@ export interface TradingBotConfig {
maxLossStreak: number; maxLossStreak: number;
flipPosition: boolean; flipPosition: boolean;
name: string; name: string;
scenario?: Scenario | null;
scenarioName?: string | null;
maxPositionTimeHours?: number | null; maxPositionTimeHours?: number | null;
closeEarlyWhenProfitable?: boolean; closeEarlyWhenProfitable?: boolean;
flipOnlyWhenInProfit: boolean; flipOnlyWhenInProfit: boolean;
@@ -2959,6 +2937,53 @@ export enum BotType {
FlippingBot = "FlippingBot", FlippingBot = "FlippingBot",
} }
export interface Scenario {
name?: string | null;
indicators?: Indicator[] | null;
loopbackPeriod?: number | null;
user?: User | null;
}
export interface Indicator {
name?: string | null;
type?: IndicatorType;
signalType?: SignalType;
minimumHistory?: number;
period?: number | null;
fastPeriods?: number | null;
slowPeriods?: number | null;
signalPeriods?: number | null;
multiplier?: number | null;
smoothPeriods?: number | null;
stochPeriods?: number | null;
cyclePeriods?: number | null;
user?: User | null;
}
export enum IndicatorType {
RsiDivergence = "RsiDivergence",
RsiDivergenceConfirm = "RsiDivergenceConfirm",
MacdCross = "MacdCross",
EmaCross = "EmaCross",
ThreeWhiteSoldiers = "ThreeWhiteSoldiers",
SuperTrend = "SuperTrend",
ChandelierExit = "ChandelierExit",
EmaTrend = "EmaTrend",
Composite = "Composite",
StochRsiTrend = "StochRsiTrend",
Stc = "Stc",
StDev = "StDev",
LaggingStc = "LaggingStc",
SuperTrendCrossEma = "SuperTrendCrossEma",
DualEmaCross = "DualEmaCross",
}
export enum SignalType {
Signal = "Signal",
Trend = "Trend",
Context = "Context",
}
export interface Position { export interface Position {
accountName: string; accountName: string;
date: Date; date: Date;
@@ -3058,7 +3083,7 @@ export interface Signal extends ValueObject {
identifier: string; identifier: string;
ticker: Ticker; ticker: Ticker;
exchange: TradingExchanges; exchange: TradingExchanges;
strategyType: StrategyType; indicatorType: IndicatorType;
signalType: SignalType; signalType: SignalType;
user?: User | null; user?: User | null;
} }
@@ -3094,30 +3119,6 @@ export interface Candle {
timeframe: Timeframe; timeframe: Timeframe;
} }
export enum StrategyType {
RsiDivergence = "RsiDivergence",
RsiDivergenceConfirm = "RsiDivergenceConfirm",
MacdCross = "MacdCross",
EmaCross = "EmaCross",
ThreeWhiteSoldiers = "ThreeWhiteSoldiers",
SuperTrend = "SuperTrend",
ChandelierExit = "ChandelierExit",
EmaTrend = "EmaTrend",
Composite = "Composite",
StochRsiTrend = "StochRsiTrend",
Stc = "Stc",
StDev = "StDev",
LaggingStc = "LaggingStc",
SuperTrendCrossEma = "SuperTrendCrossEma",
DualEmaCross = "DualEmaCross",
}
export enum SignalType {
Signal = "Signal",
Trend = "Trend",
Context = "Context",
}
export interface PerformanceMetrics { export interface PerformanceMetrics {
count?: number; count?: number;
sharpeRatio?: number; sharpeRatio?: number;
@@ -3134,7 +3135,7 @@ export interface KeyValuePairOfDateTimeAndDecimal {
value?: number; value?: number;
} }
export interface StrategiesResultBase { export interface IndicatorsResultBase {
ema?: EmaResult[] | null; ema?: EmaResult[] | null;
fastEma?: EmaResult[] | null; fastEma?: EmaResult[] | null;
slowEma?: EmaResult[] | null; slowEma?: EmaResult[] | null;
@@ -3285,29 +3286,6 @@ export interface Spotlight {
tickerSignals: TickerSignal[]; tickerSignals: TickerSignal[];
} }
export interface Scenario {
name?: string | null;
strategies?: Strategy[] | null;
loopbackPeriod?: number | null;
user?: User | null;
}
export interface Strategy {
name?: string | null;
type?: StrategyType;
signalType?: SignalType;
minimumHistory?: number;
period?: number | null;
fastPeriods?: number | null;
slowPeriods?: number | null;
signalPeriods?: number | null;
multiplier?: number | null;
smoothPeriods?: number | null;
stochPeriods?: number | null;
cyclePeriods?: number | null;
user?: User | null;
}
export interface TickerSignal { export interface TickerSignal {
ticker: Ticker; ticker: Ticker;
fiveMinutes: Signal[]; fiveMinutes: Signal[];

View File

@@ -7,20 +7,15 @@ import type {
Backtest, Backtest,
Balance, Balance,
BotType, BotType,
Candle,
FlowOutput, FlowOutput,
FlowType, FlowType,
IFlow, IFlow,
KeyValuePairOfDateTimeAndDecimal, Indicator,
MoneyManagement, MoneyManagement,
PerformanceMetrics,
Position, Position,
RiskLevel, RiskLevel,
Scenario, Scenario,
Signal, Signal,
StrategiesResultBase,
Strategy,
StrategyType,
Ticker, Ticker,
Timeframe, Timeframe,
TradeDirection, TradeDirection,
@@ -150,16 +145,6 @@ export type IMoneyManagementModalProps = {
moneyManagement?: MoneyManagement moneyManagement?: MoneyManagement
disableInputs?: boolean disableInputs?: boolean
} }
export type IBotRowDetails = {
candles: Candle[]
positions: Position[]
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
strategiesValues?: { [key in keyof typeof StrategyType]?: StrategiesResultBase; } | null;
signals: Signal[]
optimizedMoneyManagement: MoneyManagement
statistics: PerformanceMetrics
moneyManagement: MoneyManagement
}
export type IBacktestFormInput = { export type IBacktestFormInput = {
accountName: string accountName: string
@@ -186,14 +171,14 @@ export type IOpenPositionFormInput = {
export type IBotList = { export type IBotList = {
list: TradingBotResponse[] list: TradingBotResponse[]
} }
export type IScenarioFormInput = { export type IScenarioFormInput = {
name: string name: string
strategies: string[] indicators: string[]
loopbackPeriod: number | undefined loopbackPeriod: number | undefined
} }
export type IScenarioList = { export type IScenarioList = {
list: Scenario[] list: Scenario[]
strategies?: Strategy[] indicators?: Indicator[]
setScenarios?: React.Dispatch<React.SetStateAction<Scenario[]>> setScenarios?: React.Dispatch<React.SetStateAction<Scenario[]>>
} }

View File

@@ -0,0 +1,433 @@
import React, {useEffect, useState} from 'react'
import type {SubmitHandler} from 'react-hook-form'
import {useForm} from 'react-hook-form'
import 'react-toastify/dist/ReactToastify.css'
import useApiUrlStore from '../../app/store/apiStore'
import {Toast} from '../../components/mollecules'
import type {Indicator} from '../../generated/ManagingApi'
import {IndicatorType, ScenarioClient, Timeframe,} from '../../generated/ManagingApi'
import IndicatorTable from './indicatorTable'
interface IIndicatorFormInput {
type: IndicatorType
timeframe: Timeframe
name: string
period: number
fastPeriods: number
slowPeriods: number
signalPeriods: number
multiplier: number
stochPeriods: number
smoothPeriods: number
cyclePeriods: number
}
const IndicatorList: React.FC = () => {
const [indicatorType, setIndicatorType] = useState<IndicatorType>(
IndicatorType.RsiDivergence
)
const [indicators, setIndicators] = useState<Indicator[]>([])
const [showModal, setShowModal] = useState(false)
const { register, handleSubmit } = useForm<IIndicatorFormInput>()
const { apiUrl } = useApiUrlStore()
const scenarioClient = new ScenarioClient({}, apiUrl)
async function createIndicator(form: IIndicatorFormInput) {
const t = new Toast('Creating indicator')
await scenarioClient
.scenario_CreateIndicator(
form.type,
form.name,
form.period,
form.fastPeriods,
form.slowPeriods,
form.signalPeriods,
form.multiplier,
form.stochPeriods,
form.smoothPeriods,
form.cyclePeriods
)
.then((indicator: Indicator) => {
t.update('success', 'Indicator created')
setIndicators((arr) => [...arr, indicator])
})
.catch((err: any) => {
t.update('error', err)
})
}
function setIndicatorTypeEvent(e: any) {
setIndicatorType(e.target.value)
}
const onSubmit: SubmitHandler<IIndicatorFormInput> = async (form) => {
closeModal()
await createIndicator(form)
}
useEffect(() => {
scenarioClient.scenario_GetIndicators().then((data: Indicator[]) => {
setIndicators(data)
})
}, [])
function openModal() {
setShowModal(true)
}
function closeModal() {
setShowModal(false)
}
return (
<div>
<div className="container mx-auto">
<button className="btn" onClick={openModal}>
Create indicator
</button>
<IndicatorTable list={indicators} />
{showModal ? (
<>
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="modal modal-bottom sm:modal-middle modal-open">
<div className="modal-box">
<button
onClick={closeModal}
className="btn btn-sm btn-circle right-2 top-2 absolute"
>
</button>
<div className="text-primary mb-3 text-lg">
Indicator builder
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="name" className="label mr-6">
Name
</label>
<input
className="bg-inherit w-full max-w-xs"
{...register('name')}
></input>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="type" className="label mr-6">
Type
</label>
<select
className="select w-full max-w-xs"
{...register('type', {
onChange: (e) => {
setIndicatorTypeEvent(e)
},
})}
>
{Object.keys(IndicatorType).map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="timeframe" className="label mr-6">
Timeframe
</label>
<select
className="select w-full max-w-xs"
{...register('timeframe')}
>
{Object.keys(Timeframe).map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</div>
</div>
{indicatorType == IndicatorType.EmaTrend ||
indicatorType == IndicatorType.RsiDivergence ||
indicatorType == IndicatorType.StDev ||
indicatorType == IndicatorType.EmaCross ||
indicatorType == IndicatorType.RsiDivergenceConfirm ? (
<>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Period
</label>
<label className="input-group">
<input
type="number"
placeholder="5"
className="input"
{...register('period')}
/>
</label>
</div>
</div>
</>
) : null}
{indicatorType == IndicatorType.MacdCross ? (
<>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Fast Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="12"
className="input"
{...register('fastPeriods')}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Slow Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="26"
className="input"
{...register('slowPeriods')}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Signal Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="9"
className="input"
{...register('signalPeriods')}
/>
</label>
</div>
</div>
</>
) : null}
{indicatorType == IndicatorType.DualEmaCross ? (
<>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Fast Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="9"
className="input"
{...register('fastPeriods')}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Slow Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="21"
className="input"
{...register('slowPeriods')}
/>
</label>
</div>
</div>
</>
) : null}
{indicatorType == IndicatorType.Stc || indicatorType == IndicatorType.LaggingStc ? (
<>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Fast Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="23"
className="input"
{...register('fastPeriods')}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Slow Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="50"
className="input"
{...register('slowPeriods')}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Cycle Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="10"
className="input"
{...register('cyclePeriods')}
/>
</label>
</div>
</div>
</>
) : null}
{indicatorType == IndicatorType.SuperTrend ||
indicatorType == IndicatorType.SuperTrendCrossEma ||
indicatorType == IndicatorType.ChandelierExit ? (
<>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Period
</label>
<label className="input-group">
<input
type="number"
placeholder="10"
className="input"
{...register('period')}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="multiplier" className="label mr-6">
Multiplier
</label>
<label className="input-group">
<input
type="number"
placeholder="3"
className="input"
step="any"
{...register('multiplier')}
/>
</label>
</div>
</div>
</>
) : null}
{indicatorType == IndicatorType.StochRsiTrend ? (
<>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="14"
className="input"
{...register('period')}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Stoch Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="14"
className="input"
{...register('stochPeriods')}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Signal Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="3"
className="input"
{...register('signalPeriods')}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="period" className="label mr-6">
Smooth Periods
</label>
<label className="input-group">
<input
type="number"
placeholder="1"
className="input"
{...register('smoothPeriods')}
/>
</label>
</div>
</div>
</>
) : null}
<div className="modal-action">
<button type="submit" className="btn">
Build
</button>
</div>
</div>
</div>
</form>
</div>
</>
) : null}
</div>
</div>
)
}
export default IndicatorList

View File

@@ -1,27 +1,27 @@
import { TrashIcon } from '@heroicons/react/solid' import {TrashIcon} from '@heroicons/react/solid'
import React, { useEffect, useState } from 'react' import React, {useEffect, useState} from 'react'
import useApiUrlStore from '../../app/store/apiStore' import useApiUrlStore from '../../app/store/apiStore'
import { SelectColumnFilter, Table, Toast } from '../../components/mollecules' import {SelectColumnFilter, Table, Toast} from '../../components/mollecules'
import type { Strategy } from '../../generated/ManagingApi' import type {Indicator} from '../../generated/ManagingApi'
import { ScenarioClient } from '../../generated/ManagingApi' import {ScenarioClient} from '../../generated/ManagingApi'
interface IStrategyList { interface IIndicatorList {
list: Strategy[] list: Indicator[]
} }
const StrategyTable: React.FC<IStrategyList> = ({ list }) => { const IndicatorTable: React.FC<IIndicatorList> = ({ list }) => {
const [rows, setRows] = useState<Strategy[]>([]) const [rows, setRows] = useState<Indicator[]>([])
const { apiUrl } = useApiUrlStore() const { apiUrl } = useApiUrlStore()
async function deleteBacktest(id: string) { async function deleteIndicator(name: string) {
const t = new Toast('Deleting strategy') const t = new Toast('Deleting indicator')
const client = new ScenarioClient({}, apiUrl) const client = new ScenarioClient({}, apiUrl)
await client await client
.scenario_DeleteStrategy(id) .scenario_DeleteIndicator(name)
.then(() => { .then(() => {
t.update('info', 'Strategy deleted') t.update('info', 'Indicator deleted')
}) })
.catch((err) => { .catch((err) => {
t.update('error', err) t.update('error', err)
@@ -62,10 +62,10 @@ const StrategyTable: React.FC<IStrategyList> = ({ list }) => {
{ {
Cell: ({ cell }: any) => ( Cell: ({ cell }: any) => (
<> <>
<div className="tooltip" data-tip="Delete strategy"> <div className="tooltip" data-tip="Delete indicator">
<button <button
data-value={cell.row.values.name} data-value={cell.row.values.name}
onClick={() => deleteBacktest(cell.row.values.name)} onClick={() => deleteIndicator(cell.row.values.name)}
> >
<TrashIcon className="text-accent w-4"></TrashIcon> <TrashIcon className="text-accent w-4"></TrashIcon>
</button> </button>
@@ -91,4 +91,4 @@ const StrategyTable: React.FC<IStrategyList> = ({ list }) => {
) )
} }
export default StrategyTable export default IndicatorTable

View File

@@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react' import React, {useEffect, useState} from 'react'
import 'react-toastify/dist/ReactToastify.css' import 'react-toastify/dist/ReactToastify.css'
import { Tabs } from '../../components/mollecules' import {Tabs} from '../../components/mollecules'
import type { TabsType } from '../../global/type' import type {TabsType} from '../../global/type'
import ScenarioList from './scenarioList' import ScenarioList from './scenarioList'
import StrategyList from './strategyList' import IndicatorList from './indicatorList'
// Tabs Array // Tabs Array
const tabs: TabsType = [ const tabs: TabsType = [
@@ -15,7 +15,7 @@ const tabs: TabsType = [
label: 'Scenarios', label: 'Scenarios',
}, },
{ {
Component: StrategyList, Component: IndicatorList,
index: 2, index: 2,
label: 'Strategies', label: 'Strategies',
}, },

View File

@@ -4,14 +4,14 @@ import 'react-toastify/dist/ReactToastify.css'
import useApiUrlStore from '../../app/store/apiStore' import useApiUrlStore from '../../app/store/apiStore'
import {Toast} from '../../components/mollecules' import {Toast} from '../../components/mollecules'
import {ScenarioModal} from '../../components/organism' import {ScenarioModal} from '../../components/organism'
import type {Scenario, Strategy} from '../../generated/ManagingApi' import type {Indicator, Scenario} from '../../generated/ManagingApi'
import {ScenarioClient} from '../../generated/ManagingApi' import {ScenarioClient} from '../../generated/ManagingApi'
import type {IScenarioFormInput} from '../../global/type' import type {IScenarioFormInput} from '../../global/type'
import ScenarioTable from './scenarioTable' import ScenarioTable from './scenarioTable'
const ScenarioList: React.FC = () => { const ScenarioList: React.FC = () => {
const [strategies, setStrategies] = useState<Strategy[]>([]) const [indicators, setIndicators] = useState<Indicator[]>([])
const [scenarios, setScenarios] = useState<Scenario[]>([]) const [scenarios, setScenarios] = useState<Scenario[]>([])
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const { apiUrl } = useApiUrlStore() const { apiUrl } = useApiUrlStore()
@@ -20,7 +20,7 @@ const ScenarioList: React.FC = () => {
async function createScenario(form: IScenarioFormInput) { async function createScenario(form: IScenarioFormInput) {
const t = new Toast('Creating scenario') const t = new Toast('Creating scenario')
await client await client
.scenario_CreateScenario(form.name, form.loopbackPeriod, form.strategies) .scenario_CreateScenario(form.name, form.loopbackPeriod, form.indicators)
.then((data: Scenario) => { .then((data: Scenario) => {
t.update('success', 'Scenario created') t.update('success', 'Scenario created')
setScenarios((arr) => [...arr, data]) setScenarios((arr) => [...arr, data])
@@ -38,8 +38,8 @@ const ScenarioList: React.FC = () => {
client.scenario_GetScenarios().then((data) => { client.scenario_GetScenarios().then((data) => {
setScenarios(data) setScenarios(data)
}) })
client.scenario_GetStrategies().then((data) => { client.scenario_GetIndicators().then((data) => {
setStrategies(data) setIndicators(data)
}) })
}, []) }, [])
@@ -56,12 +56,12 @@ const ScenarioList: React.FC = () => {
<button className="btn" onClick={openModal}> <button className="btn" onClick={openModal}>
Create new scenario Create new scenario
</button> </button>
<ScenarioTable list={scenarios} strategies={strategies} setScenarios={setScenarios} /> <ScenarioTable list={scenarios} indicators={indicators} setScenarios={setScenarios} />
<ScenarioModal <ScenarioModal
showModal={showModal} showModal={showModal}
onClose={closeModal} onClose={closeModal}
onSubmit={handleSubmit} onSubmit={handleSubmit}
strategies={strategies} indicators={indicators}
isUpdate={false} isUpdate={false}
/> />
</div> </div>

View File

@@ -4,11 +4,11 @@ import React, {useEffect, useState} from 'react'
import useApiUrlStore from '../../app/store/apiStore' import useApiUrlStore from '../../app/store/apiStore'
import {Table, Toast} from '../../components/mollecules' import {Table, Toast} from '../../components/mollecules'
import {ScenarioModal} from '../../components/organism' import {ScenarioModal} from '../../components/organism'
import type {Scenario, Strategy} from '../../generated/ManagingApi' import type {Indicator, Scenario} from '../../generated/ManagingApi'
import {ScenarioClient} from '../../generated/ManagingApi' import {ScenarioClient} from '../../generated/ManagingApi'
import type {IScenarioFormInput, IScenarioList} from '../../global/type' import type {IScenarioFormInput, IScenarioList} from '../../global/type'
const ScenarioTable: React.FC<IScenarioList> = ({ list, strategies = [], setScenarios }) => { const ScenarioTable: React.FC<IScenarioList> = ({ list, indicators = [], setScenarios }) => {
const [rows, setRows] = useState<Scenario[]>([]) const [rows, setRows] = useState<Scenario[]>([])
const [showUpdateModal, setShowUpdateModal] = useState(false) const [showUpdateModal, setShowUpdateModal] = useState(false)
const [selectedScenario, setSelectedScenario] = useState<Scenario | null>(null) const [selectedScenario, setSelectedScenario] = useState<Scenario | null>(null)
@@ -38,7 +38,7 @@ const ScenarioTable: React.FC<IScenarioList> = ({ list, strategies = [], setScen
const t = new Toast('Updating scenario') const t = new Toast('Updating scenario')
await client await client
.scenario_UpdateScenario(form.name, form.loopbackPeriod, form.strategies) .scenario_UpdateScenario(form.name, form.loopbackPeriod, form.indicators)
.then(() => { .then(() => {
t.update('success', 'Scenario updated') t.update('success', 'Scenario updated')
// Refetch scenarios after update since the API returns FileResponse // Refetch scenarios after update since the API returns FileResponse
@@ -77,18 +77,18 @@ const ScenarioTable: React.FC<IScenarioList> = ({ list, strategies = [], setScen
{ {
Cell: ({ cell }: any) => ( Cell: ({ cell }: any) => (
<> <>
{cell.row.values.strategies.map((strategy: Strategy) => ( {cell.row.values.indicators.map((indicator: Indicator) => (
<div <div
key={strategy.name} key={indicator.name}
className="badge badge-primary badge-outline" className="badge badge-primary badge-outline"
> >
{strategy.name} {indicator.name}
</div> </div>
))} ))}
</> </>
), ),
Header: 'Strategies', Header: 'Indicators',
accessor: 'strategies', accessor: 'indicators',
disableFilters: true, disableFilters: true,
}, },
{ {
@@ -135,7 +135,7 @@ const ScenarioTable: React.FC<IScenarioList> = ({ list, strategies = [], setScen
showModal={showUpdateModal} showModal={showUpdateModal}
onClose={closeUpdateModal} onClose={closeUpdateModal}
onSubmit={handleUpdateSubmit} onSubmit={handleUpdateSubmit}
strategies={strategies} indicators={indicators}
scenario={selectedScenario} scenario={selectedScenario}
isUpdate={true} isUpdate={true}
/> />

View File

@@ -5,13 +5,13 @@ import 'react-toastify/dist/ReactToastify.css'
import useApiUrlStore from '../../app/store/apiStore' import useApiUrlStore from '../../app/store/apiStore'
import {Toast} from '../../components/mollecules' import {Toast} from '../../components/mollecules'
import type {Strategy} from '../../generated/ManagingApi' import type {Indicator} from '../../generated/ManagingApi'
import {ScenarioClient, StrategyType, Timeframe,} from '../../generated/ManagingApi' import {IndicatorType, ScenarioClient, Timeframe,} from '../../generated/ManagingApi'
import StrategyTable from './strategyTable' import IndicatorTable from './indicatorTable'
interface IStrategyFormInput { interface IIndicatorFormInput {
type: StrategyType type: IndicatorType
timeframe: Timeframe timeframe: Timeframe
name: string name: string
period: number period: number
@@ -24,20 +24,20 @@ interface IStrategyFormInput {
cyclePeriods: number cyclePeriods: number
} }
const StrategyList: React.FC = () => { const IndicatorList: React.FC = () => {
const [strategyType, setStrategyType] = useState<StrategyType>( const [indicatorType, setIndicatorType] = useState<IndicatorType>(
StrategyType.RsiDivergence IndicatorType.RsiDivergence
) )
const [strategies, setStrategies] = useState<Strategy[]>([]) const [indicators, setIndicators] = useState<Indicator[]>([])
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const { register, handleSubmit } = useForm<IStrategyFormInput>() const { register, handleSubmit } = useForm<IIndicatorFormInput>()
const { apiUrl } = useApiUrlStore() const { apiUrl } = useApiUrlStore()
const scenarioClient = new ScenarioClient({}, apiUrl) const scenarioClient = new ScenarioClient({}, apiUrl)
async function createStrategy(form: IStrategyFormInput) { async function createIndicator(form: IIndicatorFormInput) {
const t = new Toast('Creating strategy') const t = new Toast('Creating indicator')
await scenarioClient await scenarioClient
.scenario_CreateStrategy( .scenario_CreateIndicator(
form.type, form.type,
form.name, form.name,
form.period, form.period,
@@ -49,27 +49,27 @@ const StrategyList: React.FC = () => {
form.smoothPeriods, form.smoothPeriods,
form.cyclePeriods form.cyclePeriods
) )
.then((strategy: Strategy) => { .then((indicator: Indicator) => {
t.update('success', 'Strategy created') t.update('success', 'Indicator created')
setStrategies((arr) => [...arr, strategy]) setIndicators((arr) => [...arr, indicator])
}) })
.catch((err) => { .catch((err: any) => {
t.update('error', err) t.update('error', err)
}) })
} }
function setStrategyTypeEvent(e: any) { function setIndicatorTypeEvent(e: any) {
setStrategyType(e.target.value) setIndicatorType(e.target.value)
} }
const onSubmit: SubmitHandler<IStrategyFormInput> = async (form) => { const onSubmit: SubmitHandler<IIndicatorFormInput> = async (form) => {
closeModal() closeModal()
await createStrategy(form) await createIndicator(form)
} }
useEffect(() => { useEffect(() => {
scenarioClient.scenario_GetStrategies().then((data) => { scenarioClient.scenario_GetIndicators().then((data: Indicator[]) => {
setStrategies(data) setIndicators(data)
}) })
}, []) }, [])
@@ -85,9 +85,9 @@ const StrategyList: React.FC = () => {
<div> <div>
<div className="container mx-auto"> <div className="container mx-auto">
<button className="btn" onClick={openModal}> <button className="btn" onClick={openModal}>
Create strategy Create indicator
</button> </button>
<StrategyTable list={strategies} /> <IndicatorTable list={indicators} />
{showModal ? ( {showModal ? (
<> <>
<div> <div>
@@ -101,7 +101,7 @@ const StrategyList: React.FC = () => {
</button> </button>
<div className="text-primary mb-3 text-lg"> <div className="text-primary mb-3 text-lg">
Strategy builder Indicator builder
</div> </div>
<div className="form-control"> <div className="form-control">
<div className="input-group"> <div className="input-group">
@@ -123,11 +123,11 @@ const StrategyList: React.FC = () => {
className="select w-full max-w-xs" className="select w-full max-w-xs"
{...register('type', { {...register('type', {
onChange: (e) => { onChange: (e) => {
setStrategyTypeEvent(e) setIndicatorTypeEvent(e)
}, },
})} })}
> >
{Object.keys(StrategyType).map((item) => ( {Object.keys(IndicatorType).map((item) => (
<option key={item} value={item}> <option key={item} value={item}>
{item} {item}
</option> </option>
@@ -153,11 +153,11 @@ const StrategyList: React.FC = () => {
</div> </div>
</div> </div>
{strategyType == StrategyType.EmaTrend || {indicatorType == IndicatorType.EmaTrend ||
strategyType == StrategyType.RsiDivergence || indicatorType == IndicatorType.RsiDivergence ||
strategyType == StrategyType.StDev || indicatorType == IndicatorType.StDev ||
strategyType == StrategyType.EmaCross || indicatorType == IndicatorType.EmaCross ||
strategyType == StrategyType.RsiDivergenceConfirm ? ( indicatorType == IndicatorType.RsiDivergenceConfirm ? (
<> <>
<div className="form-control"> <div className="form-control">
<div className="input-group"> <div className="input-group">
@@ -177,7 +177,7 @@ const StrategyList: React.FC = () => {
</> </>
) : null} ) : null}
{strategyType == StrategyType.MacdCross ? ( {indicatorType == IndicatorType.MacdCross ? (
<> <>
<div className="form-control"> <div className="form-control">
<div className="input-group"> <div className="input-group">
@@ -227,7 +227,7 @@ const StrategyList: React.FC = () => {
</> </>
) : null} ) : null}
{strategyType == StrategyType.DualEmaCross ? ( {indicatorType == IndicatorType.DualEmaCross ? (
<> <>
<div className="form-control"> <div className="form-control">
<div className="input-group"> <div className="input-group">
@@ -262,7 +262,7 @@ const StrategyList: React.FC = () => {
</> </>
) : null} ) : null}
{strategyType == StrategyType.Stc || strategyType == StrategyType.LaggingStc ? ( {indicatorType == IndicatorType.Stc || indicatorType == IndicatorType.LaggingStc ? (
<> <>
<div className="form-control"> <div className="form-control">
<div className="input-group"> <div className="input-group">
@@ -312,9 +312,9 @@ const StrategyList: React.FC = () => {
</> </>
) : null} ) : null}
{strategyType == StrategyType.SuperTrend || {indicatorType == IndicatorType.SuperTrend ||
strategyType == StrategyType.SuperTrendCrossEma || indicatorType == IndicatorType.SuperTrendCrossEma ||
strategyType == StrategyType.ChandelierExit ? ( indicatorType == IndicatorType.ChandelierExit ? (
<> <>
<div className="form-control"> <div className="form-control">
<div className="input-group"> <div className="input-group">
@@ -349,7 +349,7 @@ const StrategyList: React.FC = () => {
</> </>
) : null} ) : null}
{strategyType == StrategyType.StochRsiTrend ? ( {indicatorType == IndicatorType.StochRsiTrend ? (
<> <>
<div className="form-control"> <div className="form-control">
<div className="input-group"> <div className="input-group">
@@ -429,4 +429,4 @@ const StrategyList: React.FC = () => {
) )
} }
export default StrategyList export default IndicatorList