The Psychological Line (PSY) is a sentiment indicator that measures the percentage of periods within a lookback window that closed higher than the previous period. It reflects the buying vs. selling pressure on a percentage scale from 0 to 100 and is often used to identify overbought and oversold market conditions driven by trader psychology.
| Parameter | Type | Default | Description |
|---|---|---|---|
period |
int | 12 | The number of periods used to count up-days |
upperThreshold |
float64 | 75 | Threshold above which the market is considered overbought |
lowerThreshold |
float64 | 25 | Threshold below which the market is considered oversold |
[period, upperThreshold?, lowerThreshold?]
Examples:
[12] // Just period, uses default thresholds (75, 25)
[12, 75, 25] // Period with standard thresholds
[24, 80, 20] // Longer period with tighter thresholds
The signal’s RawValue field contains a JSON object with the following keys:
| Key | Type | Description |
|---|---|---|
psy |
float64 | The calculated PSY value (0-100 scale, percent of up days) |
Example:
{"psy": 58.33}
| Condition | Signal Type | Description |
|---|---|---|
| PSY < lower threshold | SIGNAL_TYPE_BUY_LONG |
Market is oversold, potential buy opportunity |
| PSY > upper threshold | SIGNAL_TYPE_SELL_SHORT |
Market is overbought, potential sell opportunity |
| Otherwise | SIGNAL_TYPE_NO_ACTION |
PSY is in neutral zone |
For a lookback period:
period + 1 closing prices.func (s *MyStrategy) Initialize(_ context.Context, req *strategy.InitializeRequest) (*emptypb.Empty, error) {
api := strategy.NewStrategyApi()
_, err := api.ConfigureIndicator(context.Background(), &strategy.ConfigureRequest{
IndicatorType: strategy.IndicatorType_INDICATOR_PSY,
Config: `[12, 75, 25]`,
})
if err != nil {
return nil, fmt.Errorf("failed to configure PSY: %w", err)
}
return &emptypb.Empty{}, nil
}
func (s *MyStrategy) ProcessData(ctx context.Context, req *strategy.ProcessDataRequest) (*emptypb.Empty, error) {
data := req.Data
api := strategy.NewStrategyApi()
signal, err := api.GetSignal(ctx, &strategy.GetSignalRequest{
IndicatorType: strategy.IndicatorType_INDICATOR_PSY,
MarketData: data,
})
if err != nil {
return nil, fmt.Errorf("failed to get PSY signal: %w", err)
}
var raw struct {
PSY float64 `json:"psy"`
}
if err := json.Unmarshal([]byte(signal.RawValue), &raw); err != nil {
return nil, fmt.Errorf("failed to parse PSY value: %w", err)
}
switch signal.Type {
case strategy.SignalType_SIGNAL_TYPE_BUY_LONG:
fmt.Printf("PSY oversold: %.2f\n", raw.PSY)
case strategy.SignalType_SIGNAL_TYPE_SELL_SHORT:
fmt.Printf("PSY overbought: %.2f\n", raw.PSY)
default:
fmt.Printf("PSY neutral: %.2f\n", raw.PSY)
}
return &emptypb.Empty{}, nil
}
| PSY Range | Interpretation |
|---|---|
| 0-25 | Oversold - sustained selling pressure |
| 25-50 | Bearish sentiment, but not oversold |
| 50 | Balanced sentiment |
| 50-75 | Bullish sentiment, but not overbought |
| 75-100 | Overbought - sustained buying pressure |