Skip to content
This repository has been archived by the owner on Dec 2, 2024. It is now read-only.

Commit

Permalink
save
Browse files Browse the repository at this point in the history
  • Loading branch information
byn9826 committed Oct 23, 2023
1 parent eb77b60 commit fb7e607
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 156 deletions.
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "melody-app",
"name": "melody-invest",
"private": true,
"workspaces": [
"server",
Expand All @@ -11,7 +11,6 @@
"version": "0.0.2",
"description": "",
"author": "Baozier",
"license": "UNLICENSED",
"engines": {
"node": "^18.12.1",
"npm": "^9.1.2",
Expand All @@ -20,7 +19,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/ValueMelody/melody-app"
"url": "git+https://github.com/ValueMelody/melody-invest"
},
"scripts": {
"shared": "npm run build --workspace=constants && npm run build --workspace=helpers",
Expand Down
80 changes: 30 additions & 50 deletions server/logics/evaluation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ export const isIndicatorFitPatternBehaviors = (
movementBehaviors: interfaces.traderPatternModel.IndicatorMovementBehavior[],
compareBehaviors: interfaces.traderPatternModel.indicatorCompareBehavior[],
): boolean => {
if (!indicatorInfo) return false

// Make sure every movement behaviors in one trader pattern fit current indicator
const movementFit = movementBehaviors.every((behavior) => {
const indicatorKey = IndicatorMovementTriggers[behavior]
Expand Down Expand Up @@ -446,27 +448,32 @@ export const getTickerWithSellEvaluation = (
export const getTickerWithBuyEvaluation = (
tickerId: number,
pattern: interfaces.traderPatternModel.Record,
dailyTicker: interfaces.dailyTickersModel.TickerInfo | null,
tickerInfo: interfaces.dailyTickersModel.TickerInfo | null,
): TickerWithEvaluation | null => {
if (!dailyTicker) return null
return null
// When no available info for this tickerId, then do not buy
if (!tickerInfo) return null

// const preferValue = getTickerPreferValue(
// pattern.buyPreference, dailyTicker.daily, dailyTicker.quarterly, dailyTicker.yearly,
// )
// if (!preferValue && preferValue !== 0) return null
// Check current patterns buy preference if tickers are equal weight
const preferValue = getTickerEqualWeightPreferValue(
pattern.buyPreference, tickerInfo,
)
if (preferValue === null) return null

// const weight = getTickerMovementWeights(
// pattern,
// dailyTicker.info,
// constants.Behavior.TickerMovementBuyBehaviors,
// )
// Get current tickers buy weight
const weight = getTickerWeights(
pattern,
tickerInfo,
constants.Behavior.TickerMovementBuyBehaviors,
constants.Behavior.TickerCompareBuyBehaviors,
)

// if (!weight) return null
if (!weight) return null

// return {
// tickerId, preferValue, weight,
// }
return {
tickerId,
preferValue,
weight,
}
}

export const getOrderedTickerEvaluations = (
Expand All @@ -485,51 +492,24 @@ export const getOrderedTickerEvaluations = (
})
}

export const getTickerBuyEaluations = (
export const getTickerBuyEvaluations = (
tickerIds: number[],
pattern: interfaces.traderPatternModel.Record,
dailyTickers: interfaces.dailyTickersModel.TickerInfos,
tickerInfos: interfaces.dailyTickersModel.TickerInfos,
) => {
const emptyEvaluations: TickerWithEvaluation[] = []
const tickerEvaluations = tickerIds.reduce((evaluations, tickerId) => {
const evaluation = getTickerWithBuyEvaluation(
tickerId, pattern, dailyTickers[tickerId],
tickerId, pattern, tickerInfos[tickerId],
)
if (evaluation) evaluations.push(evaluation)
return evaluations
}, emptyEvaluations)
}, [] as TickerWithEvaluation[])
// Order tickerEvaluations by weight and prefer value
const orderedEvaluations = getOrderedTickerEvaluations(tickerEvaluations, pattern.buyPreference)
return orderedEvaluations
}

export const getIndicatorBuyMatches = (
pattern: interfaces.traderPatternModel.Record,
indicatorInfo: interfaces.dailyIndicatorsModel.IndicatorInfo,
): boolean => {
const shouldBuy = getIndicatorMovementAndCompareMatches(
pattern,
indicatorInfo,
constants.Behavior.IndicatorMovementBuyBehaviors,
constants.Behavior.IndicatorCompareBuyBehaviors,
)
return shouldBuy
}

export const shouldSellBasedOnIndicator = (
pattern: interfaces.traderPatternModel.Record,
indicatorInfo: interfaces.dailyIndicatorsModel.IndicatorInfo,
): boolean => {
// Should sell if indicator matches all pattern behaviors
const shouldSell = isIndicatorFitPatternBehaviors(
pattern,
indicatorInfo,
constants.Behavior.IndicatorMovementSellBehaviors,
constants.Behavior.IndicatorCompareSellBehaviors,
)
return shouldSell
}

export const getTickerSellEvalutions = (
export const getTickerSellEvaluations = (
tickerIds: number[],
pattern: interfaces.traderPatternModel.Record,
tickerInfos: interfaces.dailyTickersModel.TickerInfos,
Expand All @@ -542,7 +522,7 @@ export const getTickerSellEvalutions = (
if (evaluation) evaluations.push(evaluation)
return evaluations
}, [] as TickerWithEvaluation[])
// Order tickerEvalutions by weight and prefer value
// Order tickerEvaluations by weight and prefer value
const orderedEvaluations = getOrderedTickerEvaluations(tickerEvaluations, pattern.sellPreference)
return orderedEvaluations
}
170 changes: 94 additions & 76 deletions server/logics/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,37 +191,46 @@ export const rebalanceHoldingDetail = (
}
}

export const sellForHoldingPercent = (
export const sellItemFromHolding = (
holdingDetail: interfaces.traderHoldingModel.Detail,
itemForSell: interfaces.traderHoldingModel.Item,
tickerDaily: interfaces.tickerDailyModel.Record,
tickerInfo: interfaces.dailyTickersModel.TickerInfo,
holdingSellPercent: number,
tickerMinPercent: number,
maxCashValue: number,
cashMaxPercent: number,
): interfaces.traderHoldingModel.Detail | null => {
const sharesSold = Math.floor(itemForSell.shares * tickerDaily.splitMultiplier * holdingSellPercent / 100)
const baseSharesShold = holdingSellPercent === 100 ? itemForSell.shares : sharesSold / tickerDaily.splitMultiplier
// Get how many shares and base shares should be sold
const sharesSold = Math.floor(itemForSell.shares * tickerInfo.splitMultiplier * holdingSellPercent)
const baseSharesShold = holdingSellPercent === 1 ? itemForSell.shares : sharesSold / tickerInfo.splitMultiplier
if (itemForSell.shares < baseSharesShold) return null

const valueSold = getItemHoldingValue(baseSharesShold, 0, tickerDaily)
const valueSold = sharesSold * tickerInfo.closePrice

// If hold percentage is less than min required hold percentage, then do not sell
const percentAfterSell = (itemForSell.value - valueSold) / holdingDetail.totalValue
if (percentAfterSell * 100 < tickerMinPercent) return null
if (percentAfterSell < tickerMinPercent) return null

// If case after sell is larger than maxmium allow cash, then do not sell
const cashAfterSell = holdingDetail.totalCash + valueSold
if (cashAfterSell > maxCashValue) return null
const cashMaxValue = holdingDetail.totalValue * cashMaxPercent
if (cashAfterSell > cashMaxValue) return null

const sharesAfterSell = itemForSell.shares - baseSharesShold
const valueAfterSell = sharesAfterSell * tickerDaily.closePrice * tickerDaily.splitMultiplier
const itemDetail = {
tickerId: itemForSell.tickerId,
shares: sharesAfterSell,
value: valueAfterSell,
splitMultiplier: tickerDaily.splitMultiplier,
}

const items = sharesAfterSell
? holdingDetail.items.map((item) => item.tickerId === itemForSell.tickerId ? itemDetail : item)
: holdingDetail.items.filter((item) => item.tickerId !== itemForSell.tickerId)
let items
// If no shares left, then remove from holdings
if (sharesAfterSell) {
const valueAfterSell = sharesAfterSell * tickerInfo.closePrice * tickerInfo.splitMultiplier
const itemDetail = {
tickerId: itemForSell.tickerId,
shares: sharesAfterSell,
value: valueAfterSell,
splitMultiplier: tickerInfo.splitMultiplier,
}
items = holdingDetail.items.map((item) => item.tickerId === itemForSell.tickerId ? itemDetail : item)
} else {
items = holdingDetail.items.filter((item) => item.tickerId !== itemForSell.tickerId)
}

return {
date: '',
Expand All @@ -231,76 +240,81 @@ export const sellForHoldingPercent = (
}
}

export const detailAfterSell = (
export const getHoldingDetailAfterSell = (
currentDetail: interfaces.traderHoldingModel.Detail,
sellTickerIds: number[],
dailyTickers: interfaces.dailyTickersModel.TickerInfos,
tickerInfos: interfaces.dailyTickersModel.TickerInfos,
holdingSellPercent: number,
tickerMinPercent: number,
maxCashValue: number,
cashMaxPercent: number,
): DetailAndTransaction => {
// let hasTransaction = false
let hasTransaction = false
const holdingDetail = sellTickerIds.reduce((details, tickerId) => {
return details
const tickerInfo = tickerInfos[tickerId]
const item = details.items.find((item) => item.tickerId === tickerId)
// Make sure holding and ticker info exists
if (!tickerInfo || !item) return details

const refreshedDetails = sellItemFromHolding(
details,
item,
tickerInfo,
holdingSellPercent,
tickerMinPercent,
cashMaxPercent,
)
// If nothing sold, keep details the same
if (!refreshedDetails) return details

// const matchedDaily = dailyTickers[tickerId]?.daily
// const item = details.items.find((item) => item.tickerId === tickerId)
// if (!matchedDaily || !item) return details

// const refreshed = sellForHoldingPercent(
// details,
// item,
// matchedDaily,
// holdingSellPercent,
// tickerMinPercent,
// maxCashValue,
// )
// if (!refreshed) return details

// hasTransaction = true
// return refreshed
hasTransaction = true
return refreshedDetails
}, currentDetail)

return {
hasTransaction: false,
hasTransaction,
holdingDetail,
}
}

export const buyForHoldingPercent = (
export const buyItemToHolding = (
holdingDetail: interfaces.traderHoldingModel.Detail,
itemForBuy: interfaces.traderHoldingModel.Item,
tickerDaily: interfaces.tickerDailyModel.Record,
maxBuyAmount: number,
tickerInfo: interfaces.dailyTickersModel.TickerInfo,
holdingBuyPercent: number,
tickerMaxPercent: number,
): interfaces.traderHoldingModel.Detail | null => {
const isNewHolding = !itemForBuy.shares
const maxBuyAmount = holdingDetail.totalValue & holdingBuyPercent

// Use maximum allowed cash or use total cash left if less than maximun allowed
const maxCashToUse = holdingDetail.totalCash < maxBuyAmount
? holdingDetail.totalCash
: maxBuyAmount
const sharesBought = Math.floor(maxCashToUse / tickerDaily.closePrice)
if (!sharesBought) return null

const baseSharesBought = sharesBought / tickerDaily.splitMultiplier
const adjustedPrice = tickerDaily.closePrice * tickerDaily.splitMultiplier
const valueBought = baseSharesBought * adjustedPrice
const sharesBought = Math.floor(maxCashToUse / tickerInfo.closePrice)
if (!sharesBought) return null

const baseSharesBought = sharesBought / tickerInfo.splitMultiplier
const valueBought = sharesBought * tickerInfo.closePrice
const sharesAfterBuy = baseSharesBought + itemForBuy.shares
const valueAfterBuy = sharesAfterBuy * adjustedPrice
const percentAfterBuy = (valueAfterBuy / holdingDetail.totalValue) * 100
const valueAfterBuy = sharesAfterBuy * tickerInfo.closePrice * tickerInfo.splitMultiplier

// If hold more than maxmum allowed percentage after buy, then do not buy
const percentAfterBuy = valueAfterBuy / holdingDetail.totalValue
const isLessThanMaxPercent = percentAfterBuy <= tickerMaxPercent
if (!isLessThanMaxPercent) return null

const itemDetail = {
tickerId: itemForBuy.tickerId,
shares: sharesAfterBuy,
value: valueAfterBuy,
splitMultiplier: tickerDaily.splitMultiplier,
splitMultiplier: tickerInfo.splitMultiplier,
}

const isNewHolding = !itemForBuy.shares
const items = isNewHolding
? [...holdingDetail.items, itemDetail]
: holdingDetail.items.map((item) => item.tickerId === itemForBuy.tickerId ? itemDetail : item)

return {
totalValue: holdingDetail.totalValue,
totalCash: holdingDetail.totalCash - valueBought,
Expand All @@ -309,37 +323,41 @@ export const buyForHoldingPercent = (
}
}

export const detailAfterBuy = (
export const getHoldingDetailAfterBuy = (
currentDetail: interfaces.traderHoldingModel.Detail,
buyTickerIds: number[],
dailyTickers: interfaces.dailyTickersModel.TickerInfos,
maxBuyAmount: number,
tickerInfos: interfaces.dailyTickersModel.TickerInfos,
holdingBuyPercent: number,
tickerMaxPercent: number,
): DetailAndTransaction => {
// let hasTransaction = false
const holdingDetail = buyTickerIds.reduce((detail, tickerId) => {
return detail
// const matchedDaily = dailyTickers[tickerId]?.daily
// if (!matchedDaily) return detail

// const item = currentDetail.items.find((item) => item.tickerId === tickerId) ||
// { tickerId, shares: 0, splitMultiplier: 0, value: 0 }

// const refreshed = buyForHoldingPercent(
// detail,
// item,
// matchedDaily,
// maxBuyAmount,
// tickerMaxPercent,
// )
// if (!refreshed) return detail

// hasTransaction = true
// return refreshed
let hasTransaction = false
const holdingDetail = buyTickerIds.reduce((details, tickerId) => {
const tickerInfo = tickerInfos[tickerId]
// Make sure holding and ticker info exists
if (!tickerInfo) return details

// Find existing holding item or initial an empty one
const item = details.items.find((item) => item.tickerId === tickerId) || {
tickerId, shares: 0, splitMultiplier: 0, value: 0
}

const refreshedDetails = buyItemToHolding(
details,
item,
tickerInfo,
holdingBuyPercent,
tickerMaxPercent,
)

// If nothing bought, keep details the same
if (!refreshedDetails) return details

hasTransaction = true
return refreshedDetails
}, currentDetail)

return {
holdingDetail,
hasTransaction: false,
hasTransaction,
}
}
Loading

0 comments on commit fb7e607

Please sign in to comment.